diff --git a/docs/inputs/index.md b/docs/inputs/index.md index cd32f4b78..7abceb562 100644 --- a/docs/inputs/index.md +++ b/docs/inputs/index.md @@ -11,17 +11,18 @@ provided, a dropdown field will appear on the Inputs page. In contrast, a button ### Properties -| Property | Type | Description | -| ------------------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| title\* | string | - | -| description | string | It provides a brief summary of an inputs page. | -| [subDescription](../advanced/sub_description.md) | object | It provides broader description of an inputs page. | -| menu | object | This property allows you to enable the [custom menu](../custom_ui_extensions/custom_menu.md) feature. | -| [table](../table.md) | object | It displays input stanzas in a tabular format. | -| groupsMenu | array | This property allows you to enable the [multi-level menu](./multilevel_menu.md) feature. | -| [services](#services-properties)\* | array | It specifies a list of modular inputs. | -| readonlyFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input cannot be edited from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. | -| hideFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input is hidden from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. Check out an example below. | +| Property | Type | Description | +| ------------------------------------------------------------------------- | ------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| title\* | string | - | +| description | string | It provides a brief summary of an inputs page. | +| [subDescription](../advanced/sub_description.md) | object | It provides broader description of an inputs page. | +| menu | object | This property allows you to enable the [custom menu](../custom_ui_extensions/custom_menu.md) feature. | +| [table](../table.md) | object | It displays input stanzas in a tabular format. | +| groupsMenu | array | This property allows you to enable the [multi-level menu](./multilevel_menu.md) feature. | +| [services](#services-properties)\* | array | It specifies a list of modular inputs. | +| readonlyFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input cannot be edited from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. | +| hideFieldId | string | A field of the boolean entity that UCC checks for each input. If the field's value is [truthful](https://docs.splunk.com/Documentation/Splunk/latest/SearchReference/ListOfDataTypes), the corresponding input is hidden from the UI. There is no way to change this from the UI; it is supposed to be changed via REST. Check out an example below. | +| useInputToggleConfirmation | boolean | When true, displays a confirmation modal before toggling an input's status between active and inactive. | ### Services Properties diff --git a/splunk_add_on_ucc_framework/schema/schema.json b/splunk_add_on_ucc_framework/schema/schema.json index a99243630..74271bbe4 100644 --- a/splunk_add_on_ucc_framework/schema/schema.json +++ b/splunk_add_on_ucc_framework/schema/schema.json @@ -3115,6 +3115,9 @@ }, "readonlyFieldId": { "type": "string" + }, + "useInputToggleConfirmation": { + "$ref": "#/definitions/useInputToggleConfirmation" } }, "required": [ @@ -3162,6 +3165,9 @@ "table": { "$ref": "#/definitions/InputsTable" }, + "useInputToggleConfirmation": { + "$ref": "#/definitions/useInputToggleConfirmation" + }, "entity": { "$ref": "#/definitions/AnyOfEntity" }, @@ -3279,6 +3285,11 @@ "type": "string", "description": "Text displayed next to entity field" }, + "useInputToggleConfirmation": { + "type": "boolean", + "description": "When true displays additional confirmation modal when toggling single input status.", + "default": false + }, "IntervalEntity": { "type": "object", "properties": { diff --git a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json index 8002ac8db..b6393c616 100644 --- a/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json +++ b/tests/testdata/test_addons/package_global_config_multi_input/globalConfig.json @@ -387,63 +387,226 @@ "name": "example_input_one", "description": "This is a description for Input One", "title": "Example Input", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [] + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ] }, "warning": { - "create": { - "message": "Warning text for create mode" - }, - "edit": { - "message": "Warning text for edit mode" - }, - "clone": { - "message": "Warning text for clone mode" - }, - "config": { - "message": "Warning text for config mode" - } + "create": { + "message": "Warning text for create mode" + }, + "edit": { + "message": "Warning text for edit mode" + }, + "clone": { + "message": "Warning text for clone mode" + }, + "config": { + "message": "Warning text for config mode" + } } }, { "name": "example_input_two", "description": "This is a description for Input Two", "title": "Example Input Two", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" } - } + }, + "useInputToggleConfirmation": true }, { "name": "example_input_three", "description": "Input hidden for cloud", "title": "Example Input Three Hidden Cloud", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" @@ -455,15 +618,69 @@ "name": "example_input_four", "description": "Input hidden for enterprise", "title": "Example Input Four Hidden Enterprise", - "entity": [], + "entity": [ + { + "type": "text", + "label": "Name", + "validators": [ + { + "type": "regex", + "errorMsg": "Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.", + "pattern": "^[a-zA-Z]\\w*$" + }, + { + "type": "string", + "errorMsg": "Length of input name should be between 1 and 100", + "minLength": 1, + "maxLength": 100 + } + ], + "field": "name", + "help": "A unique name for the data input.", + "required": true + }, + { + "type": "interval", + "field": "interval", + "label": "Interval", + "help": "Time interval of the data input, in seconds.", + "required": true + } + ], "table": { "actions": [ "edit", "delete", "clone" ], - "header": [], - "moreInfo": [], + "header": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], + "moreInfo": [ + { + "label": "Name", + "field": "name" + }, + { + "label": "Interval", + "field": "interval" + }, + { + "label": "Status", + "field": "disabled" + } + ], "customRow": { "type": "external", "src": "custom_row" @@ -477,9 +694,9 @@ "meta": { "name": "Splunk_TA_UCCExample", "restRoot": "splunk_ta_uccexample", - "version": "5.50.1+099cf36c", + "version": "5.52.0+2e44cba94", "displayName": "Splunk UCC test Add-on", - "schemaVersion": "0.0.8", - "_uccVersion": "5.50.1" + "schemaVersion": "0.0.9", + "_uccVersion": "5.52.0" } } diff --git a/ui/src/components/table/CustomTable.tsx b/ui/src/components/table/CustomTable.tsx index 5ac928783..233461fbf 100644 --- a/ui/src/components/table/CustomTable.tsx +++ b/ui/src/components/table/CustomTable.tsx @@ -29,6 +29,7 @@ interface CustomTableProps { sortDir: SortDirection; sortKey?: string; tableConfig: ITableConfig; + useInputToggleConfirmation?: boolean; } interface IEntityModal { @@ -63,6 +64,7 @@ const CustomTable: React.FC = ({ sortDir, sortKey, tableConfig, + useInputToggleConfirmation, }) => { const unifiedConfigs: GlobalConfig = getUnifiedConfigs(); const [entityModal, setEntityModal] = useState({ open: false }); @@ -263,6 +265,7 @@ const CustomTable: React.FC = ({ rowActions={actions} headerMapping={headerMapping} readonly={isReadonlyRow(readonlyFieldId, row)} + useInputToggleConfirmation={useInputToggleConfirmation} {...{ handleEditActionClick, handleCloneActionClick, diff --git a/ui/src/components/table/CustomTableRow.tsx b/ui/src/components/table/CustomTableRow.tsx index bc4511485..daa6a1c5e 100644 --- a/ui/src/components/table/CustomTableRow.tsx +++ b/ui/src/components/table/CustomTableRow.tsx @@ -1,4 +1,4 @@ -import React, { ReactElement, useCallback } from 'react'; +import React, { ReactElement, useCallback, useState } from 'react'; import WaitSpinner from '@splunk/react-ui/WaitSpinner'; import Switch from '@splunk/react-ui/Switch'; @@ -15,6 +15,7 @@ import { _ } from '@splunk/ui-utils/i18n'; import CustomTableControl from './CustomTableControl'; import { ActionButtonComponent } from './CustomTableStyle'; import { getTableCellValue } from './table.utils'; +import AcceptModal from '../AcceptModal/AcceptModal'; import { RowDataFields } from '../../context/TableContext'; const TableCellWrapper = styled(Table.Cell)` @@ -40,6 +41,7 @@ interface CustomTableRowProps { handleEditActionClick: (row: RowDataFields) => void; handleCloneActionClick: (row: RowDataFields) => void; handleDeleteActionClick: (row: RowDataFields) => void; + useInputToggleConfirmation?: boolean; } interface CellHeader { @@ -57,8 +59,11 @@ function CustomTableRow(props: CustomTableRowProps) { handleEditActionClick, handleCloneActionClick, handleDeleteActionClick, + useInputToggleConfirmation, } = props; + const [displayAcceptToggling, setDisplayAcceptToggling] = useState(false); + const getCustomCell = (customRow: RowDataFields, header: CellHeader) => header.customCell?.src && header.customCell?.type && @@ -133,6 +138,17 @@ function CustomTableRow(props: CustomTableRowProps) { [handleEditActionClick, handleCloneActionClick, handleDeleteActionClick] ); + const handleAcceptModal = (accepted: boolean) => { + if (accepted) { + handleToggleActionClick(row); + } + setDisplayAcceptToggling(false); + }; + + const verifyToggleActionClick = () => { + setDisplayAcceptToggling(true); + }; + let statusContent: string | ReactElement = row.disabled ? 'Inactive' : 'Active'; // eslint-disable-next-line no-underscore-dangle if (row.__toggleShowSpinner) { @@ -159,13 +175,25 @@ function CustomTableRow(props: CustomTableRowProps) { ); } else if (header.field === 'disabled') { + const activeText = headerMapping?.disabled?.false + ? headerMapping.disabled.false + : 'Active'; + + const inactiveText = headerMapping?.disabled?.true + ? headerMapping.disabled.true + : 'Inactive'; + cellHTML = ( handleToggleActionClick(row)} + onClick={() => + useInputToggleConfirmation + ? verifyToggleActionClick() + : handleToggleActionClick(row) + } selected={!row.disabled} disabled={ // eslint-disable-next-line no-underscore-dangle @@ -185,6 +213,20 @@ function CustomTableRow(props: CustomTableRowProps) { )} /> {statusContent} + {displayAcceptToggling && ( + + )} ); diff --git a/ui/src/components/table/TableWrapper.tsx b/ui/src/components/table/TableWrapper.tsx index b2f7c39b4..4abb0f880 100644 --- a/ui/src/components/table/TableWrapper.tsx +++ b/ui/src/components/table/TableWrapper.tsx @@ -49,9 +49,9 @@ const getTableConfigAndServices = ( tableConfig: unifiedConfigs.pages.inputs.table, readonlyFieldId: unifiedConfigs.pages.inputs.readonlyFieldId, hideFieldId: unifiedConfigs.pages.inputs.hideFieldId, + useInputToggleConfirmation: unifiedConfigs.pages.inputs.useInputToggleConfirmation, }; } - const serviceWithTable = services?.find((x) => x.name === serviceName); const tableData = serviceWithTable && 'table' in serviceWithTable && serviceWithTable.table; @@ -62,6 +62,10 @@ const getTableConfigAndServices = ( }, readonlyFieldId: undefined, hideFieldId: undefined, + useInputToggleConfirmation: + serviceWithTable && + 'useInputToggleConfirmation' in serviceWithTable && + Boolean(serviceWithTable.useInputToggleConfirmation), }; } @@ -120,10 +124,11 @@ const TableWrapper: React.FC = ({ useTableContext()!; const unifiedConfigs = getUnifiedConfigs(); - const { services, tableConfig, readonlyFieldId, hideFieldId } = useMemo( - () => getTableConfigAndServices(page, unifiedConfigs, serviceName), - [page, unifiedConfigs, serviceName] - ); + const { services, tableConfig, readonlyFieldId, hideFieldId, useInputToggleConfirmation } = + useMemo( + () => getTableConfigAndServices(page, unifiedConfigs, serviceName), + [page, unifiedConfigs, serviceName] + ); const moreInfo = tableConfig && 'moreInfo' in tableConfig ? tableConfig?.moreInfo : null; const headers = tableConfig && 'header' in tableConfig ? tableConfig?.header : null; @@ -135,6 +140,7 @@ const TableWrapper: React.FC = ({ isComponentMounted.current = false; }; }, []); + useEffect(() => { const abortController = new AbortController(); @@ -365,6 +371,7 @@ const TableWrapper: React.FC = ({ sortKey={sortKey} handleOpenPageStyleDialog={handleOpenPageStyleDialog} tableConfig={tableConfig} + useInputToggleConfirmation={useInputToggleConfirmation} /> ); diff --git a/ui/src/components/table/stories/configMockups.ts b/ui/src/components/table/stories/configMockups.ts index 507032372..ad514b95f 100644 --- a/ui/src/components/table/stories/configMockups.ts +++ b/ui/src/components/table/stories/configMockups.ts @@ -376,3 +376,119 @@ export const getSimpleConfigStylePage = () => { const configCp = JSON.parse(JSON.stringify(SIMPLE_TABLE_MOCK_DATA_STYLE_PAGE)); return configCp; }; + +export const SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION = { + pages: { + configuration: { + tabs: [ + { + name: 'account', + table: { + actions: ['edit', 'delete', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'string', + errorMsg: 'Length of ID should be between 1 and 50', + minLength: 1, + maxLength: 50, + }, + { + type: 'regex', + errorMsg: + 'Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + ], + field: 'name', + help: 'Enter a unique name for this account.', + required: true, + }, + ], + title: 'Account', + restHandlerModule: 'splunk_ta_uccexample_validate_account_rh', + restHandlerClass: 'CustomAccountValidator', + }, + ], + title: 'Configuration', + description: 'Set up your add-on', + }, + inputs: { + services: [ + { + name: 'example_input_one', + entity: [ + { + type: 'text', + label: 'Name', + validators: [ + { + type: 'regex', + errorMsg: + 'Input Name must begin with a letter and consist exclusively of alphanumeric characters and underscores.', + pattern: '^[a-zA-Z]\\w*$', + }, + { + type: 'string', + errorMsg: 'Length of input name should be between 1 and 100', + minLength: 1, + maxLength: 100, + }, + ], + field: 'name', + help: 'A unique name for the data input.', + required: true, + }, + { + type: 'checkbox', + label: 'Example Checkbox', + field: 'input_one_checkbox', + help: 'This is an example checkbox for the input one entity', + defaultValue: true, + }, + ], + title: 'Example Input One', + }, + ], + title: 'Inputs', + description: 'Manage your data inputs', + useInputToggleConfirmation: true, + table: { + actions: ['edit', 'delete', 'search', 'clone'], + header: [ + { + label: 'Name', + field: 'name', + }, + { + label: 'Status', + field: 'disabled', + }, + ], + moreInfo: [ + { + label: 'Name', + field: 'name', + }, + ], + }, + }, + }, + meta: { + name: 'Splunk_TA_UCCExample', + restRoot: 'splunk_ta_uccexample', + version: '5.41.0R9c5fbfe0', + displayName: 'Splunk UCC test Add-on', + schemaVersion: '0.0.3', + }, +} satisfies GlobalConfig; diff --git a/ui/src/components/table/stories/rowDataMockup.ts b/ui/src/components/table/stories/rowDataMockup.ts index fa52b2a25..1a811c3c1 100644 --- a/ui/src/components/table/stories/rowDataMockup.ts +++ b/ui/src/components/table/stories/rowDataMockup.ts @@ -503,6 +503,14 @@ export const MockRowDataForStatusCount = { messages: [], }; +export const MockRowDataTogglingResponseDisableTrue = { + entry: [{ content: { disabled: true } }], +}; + +export const MockRowDataTogglingResponseDisableFalse = { + entry: [{ content: { disabled: false } }], +}; + export const ServerHandlers = [ http.get(`/servicesNS/nobody/-/splunk_ta_uccexample_account`, () => HttpResponse.json(MockRowData) diff --git a/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx b/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx new file mode 100644 index 000000000..1d63bd705 --- /dev/null +++ b/ui/src/components/table/tests/CustomTableRowConfirmation.test.tsx @@ -0,0 +1,152 @@ +import { render, screen, within } from '@testing-library/react'; +import React from 'react'; +import userEvent from '@testing-library/user-event'; + +import { BrowserRouter } from 'react-router-dom'; +import { http, HttpResponse } from 'msw'; +import { TableContextProvider } from '../../../context/TableContext'; +import { server } from '../../../mocks/server'; +import { setUnifiedConfig } from '../../../util/util'; +import { SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION } from '../stories/configMockups'; +import { + MockRowData, + MockRowDataTogglingResponseDisableFalse, + MockRowDataTogglingResponseDisableTrue, +} from '../stories/rowDataMockup'; +import TableWrapper, { ITableWrapperProps } from '../TableWrapper'; +import { invariant } from '../../../util/invariant'; + +beforeEach(() => { + const props = { + page: 'inputs', + serviceName: 'example_input_one', + handleRequestModalOpen: jest.fn(), + handleOpenPageStyleDialog: jest.fn(), + displayActionBtnAllRows: false, + } satisfies ITableWrapperProps; + + server.use( + http.get('/servicesNS/nobody/-/splunk_ta_uccexample_example_input_one', () => + HttpResponse.json(MockRowData) + ) + ); + + setUnifiedConfig(SIMPLE_NAME_TABLE_MOCK_DATA_WITH_STATUS_TOGGLE_CONFIRMATION); + + render( + + + , + { wrapper: BrowserRouter } + ); +}); + +const getRowData = (isDisabled: boolean) => { + const active = MockRowData.entry.find( + (entry) => entry.content.disabled === isDisabled // api mocks are created for aaaaaa entity + ); + return active; +}; + +const serverUseDisabledForEntity = (entity: string, isDisabledTrue: boolean) => { + server.use( + http.post(`/servicesNS/nobody/-/splunk_ta_uccexample_example_input_one/${entity}`, () => + HttpResponse.json( + isDisabledTrue + ? MockRowDataTogglingResponseDisableTrue + : MockRowDataTogglingResponseDisableFalse + ) + ) + ); +}; + +const getRowElements = async (isDisabled: boolean) => { + const activeRowData = getRowData(isDisabled); + invariant(activeRowData, 'Active row not found'); + const activeRow = await screen.findByLabelText(`row-${activeRowData?.name}`); + + const statusCell = within(activeRow).getByTestId('status'); + + const statusToggle = within(activeRow).getByRole('switch'); + + return { activeRowData, activeRow, statusCell, statusToggle }; +}; + +it('Status toggling with acceptance model - displayed correctly', async () => { + const { activeRowData, statusToggle } = await getRowElements(false); + + await userEvent.click(statusToggle); + + const acceptModal = await screen.findByRole('dialog', { name: /Make input Inactive?/i }); + + screen.getByText(`Do you want to make ${activeRowData?.name} input Inactive?`); + + screen.getByRole('button', { name: 'Yes' }); + const noBtn = screen.getByRole('button', { name: 'No' }); + + await userEvent.click(noBtn); + + expect(acceptModal).not.toBeInTheDocument(); +}); + +it('Status toggling with acceptance model - toggles state', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(false); + + expect(statusCell).toHaveTextContent('Active'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Inactive?' }); + + const yesBtn = await screen.findByRole('button', { name: 'Yes' }); + await userEvent.click(yesBtn); + + expect(statusCell).toHaveTextContent('Inactive'); + + serverUseDisabledForEntity(activeRowData.name, false); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Active?' }); + + const yesBtn2 = await screen.findByRole('button', { name: 'Yes' }); + await userEvent.click(yesBtn2); + + expect(statusCell).toHaveTextContent('Active'); +}); + +it('Status toggling with acceptance model - decline modal still Active', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(false); + + expect(statusCell).toHaveTextContent('Active'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Inactive?' }); + + const noBtn = await screen.findByRole('button', { name: 'No' }); + await userEvent.click(noBtn); + + expect(statusCell).toHaveTextContent('Active'); +}); + +it('Status toggling with acceptance model - decline modal still Inactive', async () => { + const { activeRowData, statusCell, statusToggle } = await getRowElements(true); + + expect(statusCell).toHaveTextContent('Inactive'); + + serverUseDisabledForEntity(activeRowData.name, true); + + await userEvent.click(statusToggle); + + await screen.findByRole('dialog', { name: 'Make input Active?' }); + + const noBtn = await screen.findByRole('button', { name: 'No' }); + await userEvent.click(noBtn); + + expect(statusCell).toHaveTextContent('Inactive'); +}); diff --git a/ui/src/context/TableContext.tsx b/ui/src/context/TableContext.tsx index 027fa333c..1416aa047 100644 --- a/ui/src/context/TableContext.tsx +++ b/ui/src/context/TableContext.tsx @@ -8,6 +8,7 @@ export type RowDataFields = { disabled?: boolean; id?: string; index?: string; + __toggleShowSpinner?: boolean; } & AcceptableFormRecord; // serviceName > specificRowName > dataForRow diff --git a/ui/src/types/globalConfig/pages.ts b/ui/src/types/globalConfig/pages.ts index b8539d2e1..5fdf3bc84 100644 --- a/ui/src/types/globalConfig/pages.ts +++ b/ui/src/types/globalConfig/pages.ts @@ -96,10 +96,13 @@ export const TableLessServiceSchema = z.object({ inputHelperModule: z.string().optional(), hideForPlatform: z.enum(['cloud', 'enterprise']).optional(), }); + export const TableFullServiceSchema = TableLessServiceSchema.extend({ description: z.string().optional(), table: TableSchema, + useInputToggleConfirmation: z.boolean().optional(), }); + export const InputsPageRegular = z .object({ title: z.string(), @@ -149,6 +152,7 @@ export const InputsPageTableSchema = z services: z.array(TableLessServiceSchema.strict()), hideFieldId: z.string().optional(), readonlyFieldId: z.string().optional(), + useInputToggleConfirmation: z.boolean().optional(), }) .strict();