From bc646d97f1a8632867b86ca45d07954978d3771d Mon Sep 17 00:00:00 2001 From: Abby Date: Mon, 27 May 2024 17:00:04 +0530 Subject: [PATCH] Update of workflows UI for triggers, rules and actions (#699) --- .../Attributes/AttributeEditor/mock-data.tsx | 6 - .../AttributeViewer/AttributeViewer.cy.tsx | 1 - .../component/_pages/users/form/mock-data.ts | 5 - cypress/support/commands.tsx | 11 - src/api.ts | 14 +- src/components/AppRouter.tsx | 30 +- .../index.tsx | 157 ++++++ src/components/ConditionFormFilter/index.tsx | 24 +- src/components/ConditionGroupsList/index.tsx | 51 -- src/components/ConditionsViewer/index.tsx | 276 ---------- .../CustomTable/NewRowWidget/index.tsx | 16 +- src/components/CustomTable/index.tsx | 2 + .../conditionsItemsList.module.scss} | 3 +- .../ConditionsItemsList}/index.tsx | 27 +- .../executionsItemsList.module.scss} | 0 .../ExecutionsItemsList}/index.tsx | 43 +- .../executionConditionItemsList.module.scss} | 0 .../ExecutionConditionItemsList/index.tsx | 88 ++++ src/components/FilterWidget/index.tsx | 13 +- .../FilterWidgetRuleAction/index.tsx | 155 ++---- src/components/Layout/Sidebar/index.tsx | 8 +- src/components/Layout/TabLayout/index.tsx | 2 +- .../_pages/actions/detail/index.tsx | 362 +++++++++++++ .../actions/detail/rulesDetail.module.scss | 7 + .../{action-groups => actions}/form/index.tsx | 99 +++- .../list/actions-list-component/index.tsx | 172 +++++++ .../ruleList.module.scss | 0 .../actionGroupsList.module.scss | 0 .../list/executions-list-component}/index.tsx | 65 +-- src/components/_pages/actions/list/index.tsx | 41 ++ .../form/approval-step-field.tsx | 4 +- .../_pages/approvals/list/index.tsx | 5 - .../details/index.tsx | 67 +-- .../form/index.tsx | 98 ++-- .../_pages/connectors/form/index.tsx | 6 - .../_pages/discoveries/detail/index.tsx | 17 +- .../_pages/discoveries/form/index.tsx | 15 +- .../details/index.tsx | 75 +-- .../_pages/executions/form/index.tsx | 235 +++++++++ .../roles/RolePermissionsEdior/index.tsx | 4 +- src/components/_pages/rules/detail/index.tsx | 134 ++--- src/components/_pages/rules/form/index.tsx | 76 ++- .../conditionList.module.scss} | 0 .../list/conditions-list-component}/index.tsx | 61 ++- src/components/_pages/rules/list/index.tsx | 190 ++----- .../rules/list/rules-list-component/index.tsx | 172 +++++++ .../rules-list-component/ruleList.module.scss | 4 + .../_pages/triggers/details/index.tsx | 149 +++--- src/components/_pages/triggers/form/index.tsx | 158 +++--- src/components/_pages/triggers/list/index.tsx | 33 +- src/ducks/rules-epics.ts | 456 +++++++++++------ src/ducks/rules.ts | 443 +++++++++------- src/ducks/transform/rules.ts | 257 +++++----- src/types/openapi/.openapi-generator/FILES | 42 +- src/types/openapi/apis/RulesManagementApi.ts | 476 ------------------ .../apis/WorkflowActionsManagementApi.ts | 251 +++++++++ .../apis/WorkflowRulesManagementApi.ts | 251 +++++++++ .../apis/WorkflowTriggersManagementApi.ts | 163 ++++++ src/types/openapi/apis/index.ts | 4 +- ...onGroupDetailDto.ts => ActionDetailDto.ts} | 24 +- src/types/openapi/models/ActionDto.ts | 17 +- ...GroupRequestDto.ts => ActionRequestDto.ts} | 23 +- ...itionGroupDetailDto.ts => ConditionDto.ts} | 31 +- ...itionRequestDto.ts => ConditionItemDto.ts} | 18 +- ...itionDto.ts => ConditionItemRequestDto.ts} | 24 +- ...upRequestDto.ts => ConditionRequestDto.ts} | 30 +- src/types/openapi/models/ConditionType.ts | 22 + .../models/DiscoveryHistoryDetailDto.ts | 6 +- ...{RuleActionGroupDto.ts => ExecutionDto.ts} | 31 +- src/types/openapi/models/ExecutionItemDto.ts | 43 ++ .../openapi/models/ExecutionItemRequestDto.ts | 43 ++ .../openapi/models/ExecutionRequestDto.ts | 55 ++ src/types/openapi/models/ExecutionType.ts | 22 + .../openapi/models/FilterConditionOperator.ts | 2 +- src/types/openapi/models/FilterFieldSource.ts | 2 +- src/types/openapi/models/PlatformEnum.ts | 5 +- src/types/openapi/models/Resource.ts | 2 +- src/types/openapi/models/RuleActionDto.ts | 55 -- .../openapi/models/RuleActionRequestDto.ts | 49 -- .../openapi/models/RuleConditionGroupDto.ts | 56 --- src/types/openapi/models/RuleDetailDto.ts | 38 +- src/types/openapi/models/RuleDto.ts | 25 - src/types/openapi/models/RuleRequestDto.ts | 27 +- .../openapi/models/RuleTriggerRequestDto.ts | 85 ---- ...riggerDetailDto.ts => TriggerDetailDto.ts} | 63 ++- .../{RuleTriggerDto.ts => TriggerDto.ts} | 46 +- src/types/openapi/models/TriggerHistoryDto.ts | 77 +++ .../openapi/models/TriggerHistoryRecordDto.ts | 34 ++ src/types/openapi/models/TriggerRequestDto.ts | 84 ++++ src/types/openapi/models/TriggerType.ts | 23 + .../openapi/models/UpdateActionRequestDto.ts | 31 ++ ...estDto.ts => UpdateConditionRequestDto.ts} | 18 +- ...estDto.ts => UpdateExecutionRequestDto.ts} | 18 +- .../openapi/models/UpdateRuleRequestDto.ts | 26 +- .../models/UpdateRuleTriggerRequestDto.ts | 79 --- .../openapi/models/UpdateTriggerRequestDto.ts | 78 +++ src/types/openapi/models/index.ts | 38 +- src/types/rules.ts | 167 +++--- src/utils/rules.ts | 6 +- tsconfig.json | 2 +- 100 files changed, 4229 insertions(+), 2820 deletions(-) create mode 100644 src/components/ConditionAndExecutionItemsViewer/index.tsx delete mode 100644 src/components/ConditionGroupsList/index.tsx delete mode 100644 src/components/ConditionsViewer/index.tsx rename src/components/{ConditionGroupsList/GroupConditionsViewer/groupConditionsViewer.module.scss => ExecutionConditionItemsList/ConditionsItemsList/conditionsItemsList.module.scss} (79%) rename src/components/{ConditionGroupsList/GroupConditionsViewer => ExecutionConditionItemsList/ConditionsItemsList}/index.tsx (79%) rename src/components/{ConditionGroupsList/GroupActionsViewer/groupActionsViewer.module.scss => ExecutionConditionItemsList/ExecutionsItemsList/executionsItemsList.module.scss} (100%) rename src/components/{ConditionGroupsList/GroupActionsViewer => ExecutionConditionItemsList/ExecutionsItemsList}/index.tsx (60%) rename src/components/{ConditionGroupsList/conditionGroupsList.module.scss => ExecutionConditionItemsList/executionConditionItemsList.module.scss} (100%) create mode 100644 src/components/ExecutionConditionItemsList/index.tsx create mode 100644 src/components/_pages/actions/detail/index.tsx create mode 100644 src/components/_pages/actions/detail/rulesDetail.module.scss rename src/components/_pages/{action-groups => actions}/form/index.tsx (67%) create mode 100644 src/components/_pages/actions/list/actions-list-component/index.tsx rename src/components/_pages/{rules/list => actions/list/actions-list-component}/ruleList.module.scss (100%) rename src/components/_pages/{action-groups/list => actions/list/executions-list-component}/actionGroupsList.module.scss (100%) rename src/components/_pages/{action-groups/list => actions/list/executions-list-component}/index.tsx (72%) create mode 100644 src/components/_pages/actions/list/index.tsx rename src/components/_pages/{condition-groups => conditions}/details/index.tsx (78%) rename src/components/_pages/{condition-groups => conditions}/form/index.tsx (72%) rename src/components/_pages/{action-groups => executions}/details/index.tsx (75%) create mode 100644 src/components/_pages/executions/form/index.tsx rename src/components/_pages/{condition-groups/list/conditionGroupsList.module.scss => rules/list/conditions-list-component/conditionList.module.scss} (100%) rename src/components/_pages/{condition-groups/list => rules/list/conditions-list-component}/index.tsx (76%) create mode 100644 src/components/_pages/rules/list/rules-list-component/index.tsx create mode 100644 src/components/_pages/rules/list/rules-list-component/ruleList.module.scss delete mode 100644 src/types/openapi/apis/RulesManagementApi.ts create mode 100644 src/types/openapi/apis/WorkflowActionsManagementApi.ts create mode 100644 src/types/openapi/apis/WorkflowRulesManagementApi.ts create mode 100644 src/types/openapi/apis/WorkflowTriggersManagementApi.ts rename src/types/openapi/models/{RuleActionGroupDetailDto.ts => ActionDetailDto.ts} (59%) rename src/types/openapi/models/{RuleActionGroupRequestDto.ts => ActionRequestDto.ts} (54%) rename src/types/openapi/models/{RuleConditionGroupDetailDto.ts => ConditionDto.ts} (57%) rename src/types/openapi/models/{RuleConditionRequestDto.ts => ConditionItemDto.ts} (67%) rename src/types/openapi/models/{RuleConditionDto.ts => ConditionItemRequestDto.ts} (61%) rename src/types/openapi/models/{RuleConditionGroupRequestDto.ts => ConditionRequestDto.ts} (50%) create mode 100644 src/types/openapi/models/ConditionType.ts rename src/types/openapi/models/{RuleActionGroupDto.ts => ExecutionDto.ts} (58%) create mode 100644 src/types/openapi/models/ExecutionItemDto.ts create mode 100644 src/types/openapi/models/ExecutionItemRequestDto.ts create mode 100644 src/types/openapi/models/ExecutionRequestDto.ts create mode 100644 src/types/openapi/models/ExecutionType.ts delete mode 100644 src/types/openapi/models/RuleActionDto.ts delete mode 100644 src/types/openapi/models/RuleActionRequestDto.ts delete mode 100644 src/types/openapi/models/RuleConditionGroupDto.ts delete mode 100644 src/types/openapi/models/RuleTriggerRequestDto.ts rename src/types/openapi/models/{RuleTriggerDetailDto.ts => TriggerDetailDto.ts} (50%) rename src/types/openapi/models/{RuleTriggerDto.ts => TriggerDto.ts} (50%) create mode 100644 src/types/openapi/models/TriggerHistoryDto.ts create mode 100644 src/types/openapi/models/TriggerHistoryRecordDto.ts create mode 100644 src/types/openapi/models/TriggerRequestDto.ts create mode 100644 src/types/openapi/models/TriggerType.ts create mode 100644 src/types/openapi/models/UpdateActionRequestDto.ts rename src/types/openapi/models/{UpdateRuleActionGroupRequestDto.ts => UpdateConditionRequestDto.ts} (53%) rename src/types/openapi/models/{UpdateRuleConditionGroupRequestDto.ts => UpdateExecutionRequestDto.ts} (51%) delete mode 100644 src/types/openapi/models/UpdateRuleTriggerRequestDto.ts create mode 100644 src/types/openapi/models/UpdateTriggerRequestDto.ts diff --git a/cypress/component/Attributes/AttributeEditor/mock-data.tsx b/cypress/component/Attributes/AttributeEditor/mock-data.tsx index 1899570c0..1414f6af9 100644 --- a/cypress/component/Attributes/AttributeEditor/mock-data.tsx +++ b/cypress/component/Attributes/AttributeEditor/mock-data.tsx @@ -111,7 +111,6 @@ export const customAttributeEditorMockData: AttributeEditorProps = { content: [ { data: { - // code: 'PGgxPk9EUE9WRcSOPC9oMT4KPGRpdj5Qb8SNw610YW1lIGtvxL5rbyBjZXJ0aWZpa8OhdG92IHR1IGplLjwvZGl2PgoKPHVsPgogIDxsaT5TdWJqZWN0OiAke25vdGlmaWNhdGlvbkRhdGEuc3ViamVjdERufTwvbGk+CiAgPGxpPlNlcmlhbCBOdW1iZXI6ICR7bm90aWZpY2F0aW9uRGF0YS5zZXJpYWxOdW1iZXJ9PC9saT4KICA8bGk+SXNzdWVyOiAke25vdGlmaWNhdGlvbkRhdGEuaXNzdWVyRG59PC9saT4KPC91bD4=', code: '', language: ProgrammingLanguageEnum.Html, }, @@ -133,11 +132,6 @@ export const customAttributeEditorMockData: AttributeEditorProps = { list: false, multiSelect: false, }, - // content: [ - // { - // data: '1.1', - // }, - // ], }, { uuid: 'test-uuid-7', diff --git a/cypress/component/Attributes/AttributeViewer/AttributeViewer.cy.tsx b/cypress/component/Attributes/AttributeViewer/AttributeViewer.cy.tsx index 48caa7911..523a20b9c 100644 --- a/cypress/component/Attributes/AttributeViewer/AttributeViewer.cy.tsx +++ b/cypress/component/Attributes/AttributeViewer/AttributeViewer.cy.tsx @@ -151,7 +151,6 @@ describe('AttributeViewer with Metadata', () => { it(`should check if source object table is opened in modal`, () => { cy.get('.fa-caret-down').eq(0).click().wait(clickWait); - // cy.get('.fa-arrow-up').eq(1).click().wait(clickWait); cy.get('.fa-info').eq(0).click().wait(clickWait); cy.get('.modal-content').should('be.visible'); diff --git a/cypress/component/_pages/users/form/mock-data.ts b/cypress/component/_pages/users/form/mock-data.ts index 6e91f279c..13bb33e56 100644 --- a/cypress/component/_pages/users/form/mock-data.ts +++ b/cypress/component/_pages/users/form/mock-data.ts @@ -196,9 +196,7 @@ const userFormMockData = { uuid: 'uuid-1234-text-custom-attribute', name: 'Text Me', label: 'Text Me', - // type: 'custom', type: AttributeType.Custom, - // contentType: 'text', contentType: AttributeContentType.Text, content: [ { @@ -211,7 +209,6 @@ const userFormMockData = { name: 'SomeCustom', label: 'SomeCustom', type: AttributeType.Custom, - // contentType: 'integer', contentType: AttributeContentType.Integer, content: [ @@ -225,7 +222,6 @@ const userFormMockData = { name: 'DepartmentAssociation', label: 'Department', type: AttributeType.Custom, - // contentType: 'string', contentType: AttributeContentType.String, content: [ { @@ -299,7 +295,6 @@ const userFormMockData = { name: 'Test Date', label: 'Test Date', type: AttributeType.Custom, - // contentType: 'date', contentType: AttributeContentType.Date, content: [ { diff --git a/cypress/support/commands.tsx b/cypress/support/commands.tsx index 7819c283d..38ac08c30 100644 --- a/cypress/support/commands.tsx +++ b/cypress/support/commands.tsx @@ -29,14 +29,3 @@ Cypress.Commands.add('mount', (component, options = {}, initialRoute = '/') => { return mount(wrapped, mountOptions); }); - -// Cypress.Commands.add("mount", mount); - -// Cypress.Commands.add('mountWithRouter', (component, route) => { -// // Mount the component with the Router context -// cy.mount(component, {}, route); -// }); - -// Cypress.Commands.add('dataCy', (value) => { -// return cy.get(`[data-cy=${value}]`); -// }); diff --git a/src/api.ts b/src/api.ts index fbf348dbc..be0ba2468 100644 --- a/src/api.ts +++ b/src/api.ts @@ -27,13 +27,15 @@ import { RAProfileManagementApi, ResourceManagementApi, RoleManagementApi, - RulesManagementApi, SCEPProfileManagementApi, ScheduledJobsManagementApi, SettingsApi, StatisticsDashboardApi, TokenProfileManagementApi, UserManagementApi, + WorkflowActionsManagementApi, + WorkflowRulesManagementApi, + WorkflowTriggersManagementApi, } from 'types/openapi'; import { TokenInstanceControllerApi } from 'types/openapi/apis/TokenInstanceControllerApi'; import { @@ -51,7 +53,10 @@ export interface ApiClients { auth: AuthenticationManagementApi; users: UserManagementApi; roles: RoleManagementApi; - rules: RulesManagementApi; + // rules: RulesManagementApi; + actions: WorkflowActionsManagementApi; + rules: WorkflowRulesManagementApi; + triggers: WorkflowTriggersManagementApi; auditLogs: AuditLogApi; raProfiles: RAProfileManagementApi; credentials: CredentialManagementApi; @@ -93,7 +98,10 @@ export const backendClient: ApiClients = { auth: new AuthenticationManagementApi(configuration), users: new UserManagementApi(configuration), roles: new RoleManagementApi(configuration), - rules: new RulesManagementApi(configuration), + // rules: new RulesManagementApi(configuration), + actions: new WorkflowActionsManagementApi(configuration), + rules: new WorkflowRulesManagementApi(configuration), + triggers: new WorkflowTriggersManagementApi(configuration), certificates: new CertificateInventoryApi(configuration), auditLogs: new AuditLogApi(configuration), raProfiles: new RAProfileManagementApi(configuration), diff --git a/src/components/AppRouter.tsx b/src/components/AppRouter.tsx index 7efbe231f..4204101c0 100644 --- a/src/components/AppRouter.tsx +++ b/src/components/AppRouter.tsx @@ -108,12 +108,15 @@ import NotificationInstanceDetail from './_pages/notifications/notification-inst import NotificationInstanceForm from './_pages/notifications/notification-instance-form'; import NotificationsSetting from './_pages/notifications/notifications-setting'; -import ConditionGroupDetails from './_pages/condition-groups/details'; -import ConditionGroupForm from './_pages/condition-groups/form'; -import ConditionGroupsList from './_pages/condition-groups/list'; +import ConditionDetails from './_pages/conditions/details'; +import ConditionForm from './_pages/conditions/form'; -import ActionGroupForm from './_pages/action-groups/form'; -import ActionGroupsList from './_pages/action-groups/list'; +import ExecutionDetails from './_pages/executions/details'; +import ExecutionForm from './_pages/executions/form'; + +import ActionDetails from './_pages/actions/detail'; +import ActionForm from './_pages/actions/form'; +import ActionsList from './_pages/actions/list'; import TriggerDetails from './_pages/triggers/details'; import TriggerForm from './_pages/triggers/form'; @@ -123,7 +126,6 @@ import RuleDetails from './_pages/rules/detail'; import RulesForm from './_pages/rules/form'; import RulesList from './_pages/rules/list'; -import ActionGroupsDetails from './_pages/action-groups/details'; import SchedulerJobDetail from './_pages/scheduler/detail'; import SchedulerJobsList from './_pages/scheduler/list'; import Layout from './Layout'; @@ -333,17 +335,19 @@ export default function AppRouter() { } /> } /> - } /> - } /> - } /> + } /> + } /> } /> - } /> + } /> } /> - } /> - } /> - } /> + } /> + } /> + + } /> + } /> + } /> } /> } /> diff --git a/src/components/ConditionAndExecutionItemsViewer/index.tsx b/src/components/ConditionAndExecutionItemsViewer/index.tsx new file mode 100644 index 000000000..b1813db47 --- /dev/null +++ b/src/components/ConditionAndExecutionItemsViewer/index.tsx @@ -0,0 +1,157 @@ +import { ApiClients } from 'api'; +import FilterWidget from 'components/FilterWidget'; +import FilterWidgetRuleAction from 'components/FilterWidgetRuleAction'; +import { EntityType, actions as filterActions } from 'ducks/filters'; +import { actions as rulesActions, selectors as rulesSelectors } from 'ducks/rules'; +import { useEffect, useMemo, useState } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useParams } from 'react-router-dom'; +import { Resource } from 'types/openapi'; +import { conditionGroupToFilter, filterToConditionItems } from 'utils/rules'; +type FormType = 'condtionItems' | 'executionItems'; + +interface ConditionGroupFormFilterProps { + resource: Resource; + formType: FormType; +} + +const ConditionAndExecutionItemsViewer = ({ resource, formType }: ConditionGroupFormFilterProps) => { + const { id } = useParams(); + const editMode = useMemo(() => !!id, [id]); + + const conditionDetails = useSelector(rulesSelectors.conditionDetails); + const isFetchingConditionDetails = useSelector(rulesSelectors.isFetchingConditionDetails); + const isUpdatingCondition = useSelector(rulesSelectors.isUpdatingCondition); + + const executionDetails = useSelector(rulesSelectors.executionDetails); + const isFetchingExecutionDetails = useSelector(rulesSelectors.isFetchingExecutionDetails); + const isUpdatingExecution = useSelector(rulesSelectors.isUpdatingExecution); + + const [hasEffectRun, setHasEffectRun] = useState(false); + + const dispatch = useDispatch(); + + useEffect(() => { + dispatch(filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS })); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + if (!id) return; + + if (formType === 'condtionItems' && id !== conditionDetails?.uuid) { + dispatch(rulesActions.getCondition({ conditionUuid: id })); + } + + if (formType === 'executionItems' && id !== executionDetails?.uuid) { + dispatch(rulesActions.getExecution({ executionUuid: id })); + } + }, [id, dispatch, formType, conditionDetails?.uuid, executionDetails?.uuid]); + + useEffect(() => { + if (!hasEffectRun && editMode && id) { + if (formType == 'condtionItems') { + if (conditionDetails?.uuid !== id) return; + const currentConditions = conditionDetails?.items || []; + + const currentFilters = conditionGroupToFilter(currentConditions); + setHasEffectRun(true); + dispatch(filterActions.setCurrentFilters({ currentFilters: currentFilters, entity: EntityType.CONDITIONS })); + } + } + }, [editMode, conditionDetails, hasEffectRun, dispatch, formType, id]); + + useEffect(() => { + return () => { + dispatch(filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS })); + }; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + const renderFilterWidgetForConditionItems = useMemo(() => { + if (formType !== 'condtionItems' || !id || !conditionDetails) return null; + const disableBadgeRemove = conditionDetails.items.length === 1 || isFetchingConditionDetails || isUpdatingCondition; + const isBusy = isFetchingConditionDetails || isUpdatingCondition; + return ( +
+ + apiClients.resources.listResourceRuleFilterFields({ + resource, + }) + } + disableBadgeRemove={disableBadgeRemove} + onFilterUpdate={(currentFilters) => { + const currentCondition = filterToConditionItems(currentFilters); + dispatch( + rulesActions.updateCondition({ + conditionUuid: id, + condition: { + items: currentCondition, + description: conditionDetails.description || '', + }, + }), + ); + }} + /> +
+ ); + }, [resource, dispatch, formType, id, conditionDetails, isFetchingConditionDetails, isUpdatingCondition]); + + const renderFilterWidgetForExecutionItems = useMemo(() => { + if (formType !== 'executionItems' || !id || !executionDetails) return null; + const disableBadgeRemove = executionDetails.items.length === 1 || isFetchingExecutionDetails || isUpdatingExecution; + + const isBusy = isFetchingExecutionDetails || isUpdatingExecution; + return ( +
+ { + + apiClients.resources.listResourceRuleFilterFields({ + resource, + settable: true, + }) + } + ExecutionsList={executionDetails.items} + onActionsUpdate={(currentExecutionItems) => { + dispatch( + rulesActions.updateExecution({ + executionUuid: id, + execution: { + items: currentExecutionItems, + description: executionDetails.description, + }, + }), + ); + }} + /> + } +
+ ); + }, [resource, dispatch, formType, id, executionDetails, isFetchingExecutionDetails, isUpdatingExecution]); + + const renderWidgetConditionViewer = useMemo(() => { + switch (formType) { + case 'condtionItems': + return renderFilterWidgetForConditionItems; + + case 'executionItems': + return renderFilterWidgetForExecutionItems; + + default: + return null; + } + }, [formType, renderFilterWidgetForConditionItems, renderFilterWidgetForExecutionItems]); + + return
{renderWidgetConditionViewer}
; +}; + +export default ConditionAndExecutionItemsViewer; diff --git a/src/components/ConditionFormFilter/index.tsx b/src/components/ConditionFormFilter/index.tsx index bccbb08d7..a1920cf5e 100644 --- a/src/components/ConditionFormFilter/index.tsx +++ b/src/components/ConditionFormFilter/index.tsx @@ -2,16 +2,16 @@ import { ApiClients } from 'api'; import cx from 'classnames'; import FilterWidget from 'components/FilterWidget'; import FilterWidgetRuleAction from 'components/FilterWidgetRuleAction'; -import { ActionGroupFormValues } from 'components/_pages/action-groups/form'; +import { ExecutionFormValues } from 'components/_pages/executions/form'; import { EntityType, actions as filterActions } from 'ducks/filters'; import { useEffect, useMemo } from 'react'; import { useForm } from 'react-final-form'; import { useDispatch } from 'react-redux'; import { Resource } from 'types/openapi'; -import { filterToConditionGroup } from 'utils/rules'; -import { ConditionGroupFormValues } from '../_pages/condition-groups/form'; +import { filterToConditionItems } from 'utils/rules'; +import { ConditionFormValues } from '../_pages/conditions/form'; import styles from './conditionGroupForm.module.scss'; -type FormType = 'conditions' | 'actions'; +type FormType = 'conditionItem' | 'cxecutionItem'; interface ConditionGroupFormFilterProps { resource: Resource; formType: FormType; @@ -19,8 +19,8 @@ interface ConditionGroupFormFilterProps { } const ConditionFormFilter = ({ resource, formType, includeIgnoreAction }: ConditionGroupFormFilterProps) => { - const form = useForm(); - const actionGroupForm = useForm(); + const form = useForm(); + const actionGroupForm = useForm(); const dispatch = useDispatch(); @@ -30,11 +30,11 @@ const ConditionFormFilter = ({ resource, formType, includeIgnoreAction }: Condit }; }, [dispatch]); const renderFilterWidget = useMemo(() => { - return formType === 'actions' ? ( + return formType === 'cxecutionItem' ? (
apiClients.resources.listResourceRuleFilterFields({ resource, @@ -43,7 +43,7 @@ const ConditionFormFilter = ({ resource, formType, includeIgnoreAction }: Condit } includeIgnoreAction={includeIgnoreAction} onActionsUpdate={(currentActions) => { - actionGroupForm.change('actions', currentActions); + actionGroupForm.change('items', currentActions); }} />
@@ -51,15 +51,15 @@ const ConditionFormFilter = ({ resource, formType, includeIgnoreAction }: Condit
apiClients.resources.listResourceRuleFilterFields({ resource, }) } onFilterUpdate={(currentFilters) => { - const currentConditionGroups = filterToConditionGroup(currentFilters); - form.change('conditions', currentConditionGroups); + const currentConditionItems = filterToConditionItems(currentFilters); + form.change('items', currentConditionItems); }} />
diff --git a/src/components/ConditionGroupsList/index.tsx b/src/components/ConditionGroupsList/index.tsx deleted file mode 100644 index 44ec02112..000000000 --- a/src/components/ConditionGroupsList/index.tsx +++ /dev/null @@ -1,51 +0,0 @@ -import { EntityType, selectors } from 'ducks/filters'; -import React, { useMemo } from 'react'; -import { useSelector } from 'react-redux'; -import { ActionGroupModel, ConditionRuleGroupModel } from 'types/rules'; -import GroupActionsViewer from './GroupActionsViewer'; -import GroupConditionsViewer from './GroupConditionsViewer'; -import styles from './conditionGroupsList.module.scss'; - -interface ConditionsTableViewerProps { - ruleConditions?: ConditionRuleGroupModel[]; - actionGroups?: ActionGroupModel[]; -} - -const ConditionsGroupsList = ({ ruleConditions, actionGroups }: ConditionsTableViewerProps) => { - const isFetchingAvailableFiltersConditions = useSelector(selectors.isFetchingFilters(EntityType.CONDITIONS)); - const isFetchingAvailableFiltersActions = useSelector(selectors.isFetchingFilters(EntityType.ACTIONS)); - - const renderListData = useMemo(() => { - if (isFetchingAvailableFiltersConditions || isFetchingAvailableFiltersActions) return <>; - - if (ruleConditions?.length) { - return ruleConditions.map((conditionGroup, i) => ( - -
- -
- )); - } else if (actionGroups?.length) { - return actionGroups.map((actionGroup, i) => ( - -
- -
- )); - } else return <>; - }, [ruleConditions, actionGroups, isFetchingAvailableFiltersConditions, isFetchingAvailableFiltersActions]); - - return <>{renderListData}; -}; - -export default ConditionsGroupsList; diff --git a/src/components/ConditionsViewer/index.tsx b/src/components/ConditionsViewer/index.tsx deleted file mode 100644 index f56afad23..000000000 --- a/src/components/ConditionsViewer/index.tsx +++ /dev/null @@ -1,276 +0,0 @@ -import { ApiClients } from 'api'; -import ConditionsGroupsList from 'components/ConditionGroupsList'; -import FilterWidget from 'components/FilterWidget'; -import FilterWidgetRuleAction from 'components/FilterWidgetRuleAction'; -import { EntityType, actions as filterActions } from 'ducks/filters'; -import { actions as rulesActions, selectors as rulesSelectors } from 'ducks/rules'; -import { useEffect, useMemo, useState } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useParams } from 'react-router-dom'; -import { Resource } from 'types/openapi'; -import { conditionGroupToFilter, filterToConditionGroup } from 'utils/rules'; -type FormType = 'rules' | 'conditionGroup' | 'actionGroup' | 'trigger'; - -interface ConditionGroupFormFilterProps { - resource: Resource; - formType: FormType; -} - -const ConditionsViewer = ({ resource, formType }: ConditionGroupFormFilterProps) => { - const { id } = useParams(); - const editMode = useMemo(() => !!id, [id]); - - const conditionGroupsDetails = useSelector(rulesSelectors.conditionGroupDetails); - const isFetchingConditionGroup = useSelector(rulesSelectors.isFetchingConditionGroup); - const isUpdatingConditionGroup = useSelector(rulesSelectors.isUpdatingConditionGroup); - - const ruleDetails = useSelector(rulesSelectors.ruleDetails); - const isFetchingRuleDetail = useSelector(rulesSelectors.isFetchingRuleDetail); - const isUpdatingRule = useSelector(rulesSelectors.isUpdatingRule); - - const actionGroupDetails = useSelector(rulesSelectors.actionGroupDetails); - const isFetchingActionGroup = useSelector(rulesSelectors.isFetchingActionGroup); - const isUpdatingActionGroup = useSelector(rulesSelectors.isUpdatingActionGroup); - - const trigerDetails = useSelector(rulesSelectors.triggerDetails); - const isFetchingTriggerDetail = useSelector(rulesSelectors.isFetchingTriggerDetail); - const isUpdatingTrigger = useSelector(rulesSelectors.isUpdatingTrigger); - - const [hasEffectRun, setHasEffectRun] = useState(false); - - const dispatch = useDispatch(); - - useEffect(() => { - dispatch(filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS })); - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - useEffect(() => { - if (!id) return; - if (formType === 'rules' && id !== ruleDetails?.uuid) { - dispatch(rulesActions.getRule({ ruleUuid: id })); - } - if (formType === 'conditionGroup' && id !== conditionGroupsDetails?.uuid) { - dispatch(rulesActions.getConditionGroup({ conditionGroupUuid: id })); - } - - if (formType === 'actionGroup' && id !== actionGroupDetails?.uuid) { - dispatch(rulesActions.getActionGroup({ actionGroupUuid: id })); - } - - if (formType === 'trigger' && id !== trigerDetails?.uuid) { - dispatch(rulesActions.getTrigger({ triggerUuid: id })); - } - }, [id, dispatch, formType, ruleDetails?.uuid, conditionGroupsDetails?.uuid, actionGroupDetails?.uuid, trigerDetails?.uuid]); - - useEffect(() => { - if (!hasEffectRun && editMode && id) { - let currentConditions = []; - - if (formType === 'rules') { - if (ruleDetails?.uuid !== id) return; - currentConditions = ruleDetails?.conditions || []; - } else { - if (conditionGroupsDetails?.uuid !== id) return; - currentConditions = conditionGroupsDetails?.conditions || []; - } - - const currentFilters = conditionGroupToFilter(currentConditions); - setHasEffectRun(true); - dispatch(filterActions.setCurrentFilters({ currentFilters: currentFilters, entity: EntityType.CONDITIONS })); - } - }, [editMode, ruleDetails, conditionGroupsDetails, hasEffectRun, dispatch, formType, id]); - - useEffect(() => { - return () => { - dispatch(filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS })); - }; - // eslint-disable-next-line react-hooks/exhaustive-deps - }, []); - - const renderAppendContent = useMemo(() => { - if (formType === 'rules' && ruleDetails?.conditionGroups && ruleDetails?.conditionGroups?.length > 0) { - if (isFetchingRuleDetail || isUpdatingRule) return <>; - return ; - } - - if (formType === 'trigger' && trigerDetails?.actionGroups && trigerDetails?.actionGroups?.length > 0) { - if (isFetchingTriggerDetail || isUpdatingTrigger) return <>; - return ; - } else { - return <>; - } - }, [ruleDetails, formType, trigerDetails, isFetchingRuleDetail, isFetchingTriggerDetail, isUpdatingRule, isUpdatingTrigger]); - - const renderFilterWidgetForRules = useMemo(() => { - if (formType !== 'rules' || !id || !ruleDetails) return null; - const disableBadgeRemove = - (ruleDetails.conditions.length === 1 && ruleDetails.conditionGroups.length === 0) || isFetchingRuleDetail || isUpdatingRule; - - const isBusy = isFetchingRuleDetail || isUpdatingRule; - - return ( - - apiClients.resources.listResourceRuleFilterFields({ - resource, - }) - } - disableBadgeRemove={disableBadgeRemove} - onFilterUpdate={(currentFilters) => { - const currentCondition = filterToConditionGroup(currentFilters); - dispatch( - rulesActions.updateRule({ - ruleUuid: id, - rule: { - conditions: currentCondition, - conditionGroupsUuids: ruleDetails.conditionGroups.map((cg) => cg.uuid), - description: ruleDetails.description || '', - }, - }), - ); - }} - /> - ); - }, [resource, dispatch, formType, id, ruleDetails, renderAppendContent, isFetchingRuleDetail, isUpdatingRule]); - - const renderFilterWidgetForConditionGroups = useMemo(() => { - if (formType !== 'conditionGroup' || !id || !conditionGroupsDetails) return null; - const disableBadgeRemove = conditionGroupsDetails.conditions.length === 1 || isFetchingConditionGroup || isUpdatingConditionGroup; - const isBusy = isFetchingConditionGroup || isUpdatingConditionGroup; - return ( -
- - apiClients.resources.listResourceRuleFilterFields({ - resource, - }) - } - disableBadgeRemove={disableBadgeRemove} - onFilterUpdate={(currentFilters) => { - const currentCondition = filterToConditionGroup(currentFilters); - dispatch( - rulesActions.updateConditionGroup({ - conditionGroupUuid: id, - conditionGroup: { - conditions: currentCondition, - }, - }), - ); - }} - /> -
- ); - }, [resource, dispatch, formType, id, conditionGroupsDetails, isFetchingConditionGroup, isUpdatingConditionGroup]); - - const renderFilterWidgetForActionGroup = useMemo(() => { - if (formType !== 'actionGroup' || !id || !actionGroupDetails) return null; - const disableBadgeRemove = actionGroupDetails.actions.length === 1 || isFetchingActionGroup || isUpdatingActionGroup; - - const isBusy = isFetchingActionGroup || isUpdatingActionGroup; - return ( -
- - apiClients.resources.listResourceRuleFilterFields({ - resource, - settable: true, - }) - } - actionsList={actionGroupDetails.actions} - onActionsUpdate={(currentActions) => { - dispatch( - rulesActions.updateActionGroup({ - actionGroupUuid: id, - actionGroup: { - actions: currentActions, - }, - }), - ); - }} - /> -
- ); - }, [resource, dispatch, formType, id, actionGroupDetails, isFetchingActionGroup, isUpdatingActionGroup]); - - const renderFilterWidgetForTrigger = useMemo(() => { - if (formType !== 'trigger' || !id || !trigerDetails) return null; - const isActionOnlyOne = trigerDetails.actions.length === 1; - const isActionGroupEmpty = trigerDetails.actionGroups.length === 0; - - const disableBadgeRemove = isActionOnlyOne && isActionGroupEmpty; - const isBusy = isFetchingTriggerDetail || isUpdatingTrigger; - - return ( -
- - apiClients.resources.listResourceRuleFilterFields({ - resource, - settable: true, - }) - } - actionsList={trigerDetails.actions} - onActionsUpdate={(currentActions) => { - dispatch( - rulesActions.updateTrigger({ - triggerUuid: id, - trigger: { - actions: currentActions, - description: trigerDetails.description, - triggerType: trigerDetails.triggerType, - rulesUuids: trigerDetails.rules.map((r) => r.uuid), - actionGroupsUuids: trigerDetails.actionGroups.map((ag) => ag.uuid), - }, - }), - ); - }} - /> -
- ); - }, [resource, dispatch, formType, id, trigerDetails, renderAppendContent, isFetchingTriggerDetail, isUpdatingTrigger]); - - const renderWidgetConditionViewer = useMemo(() => { - switch (formType) { - case 'conditionGroup': - return renderFilterWidgetForConditionGroups; - case 'rules': - return renderFilterWidgetForRules; - - case 'actionGroup': - return renderFilterWidgetForActionGroup; - - case 'trigger': - return renderFilterWidgetForTrigger; - default: - return null; - } - }, [ - formType, - renderFilterWidgetForConditionGroups, - renderFilterWidgetForRules, - renderFilterWidgetForActionGroup, - renderFilterWidgetForTrigger, - ]); - - return
{renderWidgetConditionViewer}
; -}; - -export default ConditionsViewer; diff --git a/src/components/CustomTable/NewRowWidget/index.tsx b/src/components/CustomTable/NewRowWidget/index.tsx index ee47003e2..a4f09b99d 100644 --- a/src/components/CustomTable/NewRowWidget/index.tsx +++ b/src/components/CustomTable/NewRowWidget/index.tsx @@ -1,5 +1,5 @@ import cx from 'classnames'; -import { useState } from 'react'; +import { useEffect, useState } from 'react'; import Select from 'react-select'; import { Button, ButtonGroup } from 'reactstrap'; import styles from './NewRowWidget.module.scss'; @@ -12,11 +12,20 @@ export interface NewRowWidgetProps { newItemsList: SelectChangeValue[]; isBusy: boolean; onAddClick: (newValues: SelectChangeValue[]) => void; + immidiateAdd?: boolean; + selectHint?: string; } -const NewRowWidget = ({ newItemsList, isBusy, onAddClick }: NewRowWidgetProps) => { +const NewRowWidget = ({ newItemsList, isBusy, onAddClick, immidiateAdd, selectHint }: NewRowWidgetProps) => { const [selectedItems, setSelectedItems] = useState([]); + useEffect(() => { + if (immidiateAdd && selectedItems.length) { + onAddClick(selectedItems); + setSelectedItems([]); + } + }, [immidiateAdd, selectedItems, onAddClick]); + return (
@@ -27,10 +36,11 @@ const NewRowWidget = ({ newItemsList, isBusy, onAddClick }: NewRowWidgetProps) = isMulti value={selectedItems} options={newItemsList} + placeholder={selectHint || 'Select items to add'} />
- {selectedItems?.length ? ( + {selectedItems?.length && !immidiateAdd ? ( + + + ) : ( + + )} +
, + ], + }, + ], + [ + actionDetails, + resourceTypeEnum, + onUpdateDescriptionConfirmed, + updateDescriptionEditEnable, + isUpdatingAction, + updatedDescription, + isFetchingActionDetails, + ], + ); + + const executionDataHeaders = useMemo( + () => [ + { + id: 'name', + content: 'Name', + }, + { + id: 'type', + content: 'Type', + }, + { + id: 'description', + content: 'Description', + }, + { + id: 'actions', + content: 'Actions', + }, + ], + [], + ); + + const executionsData: TableDataRow[] = useMemo(() => { + const isDeleteDisabled = actionDetails?.executions.length === 1 || isFetchingActionDetails || isUpdatingAction; + const conditionGroupData = !actionDetails?.executions.length + ? [] + : actionDetails?.executions.map((conditionGroup) => { + return { + id: conditionGroup.uuid, + columns: [ + {conditionGroup.name} || '', + getEnumLabel(executionTypeEnum, conditionGroup.type) || '', + conditionGroup.description || '', + , + ], + }; + }); + + return conditionGroupData; + }, [actionDetails, isUpdatingAction, onDeleteExecution, isFetchingActionDetails, executionTypeEnum]); + + return ( + + + + + + + + + + + + + + + {/* {actionDetails?.resource && } */} + {actionDetails?.executions.length ? ( + + apiClients.resources.listResourceRuleFilterFields({ + resource: actionDetails.resource, + settable: true, + }) + } + /> + ) : ( + <> + )} + setConfirmDelete(false)} + buttons={[ + { color: 'danger', onClick: onDeleteConfirmed, body: 'Yes, delete' }, + { color: 'secondary', onClick: () => setConfirmDelete(false), body: 'Cancel' }, + ]} + /> + + ); +}; + +export default RuleDetails; diff --git a/src/components/_pages/actions/detail/rulesDetail.module.scss b/src/components/_pages/actions/detail/rulesDetail.module.scss new file mode 100644 index 000000000..e07c91a7f --- /dev/null +++ b/src/components/_pages/actions/detail/rulesDetail.module.scss @@ -0,0 +1,7 @@ +@import 'src/resources/styles/app'; + +.infoIcon { + color: $czertainly-blue; + font-size: small; + font-size: 14px; +} diff --git a/src/components/_pages/action-groups/form/index.tsx b/src/components/_pages/actions/form/index.tsx similarity index 67% rename from src/components/_pages/action-groups/form/index.tsx rename to src/components/_pages/actions/form/index.tsx index 141a45987..89d9a3e0b 100644 --- a/src/components/_pages/action-groups/form/index.tsx +++ b/src/components/_pages/actions/form/index.tsx @@ -1,18 +1,18 @@ import Widget from 'components/Widget'; +import { EntityType, actions as filterActions } from 'ducks/filters'; import { actions as rulesActions, selectors as rulesSelectors } from 'ducks/rules'; -import { useCallback, useMemo } from 'react'; -import { Field, Form } from 'react-final-form'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { useNavigate } from 'react-router-dom'; +import { Field, Form } from 'react-final-form'; + import { Form as BootstrapForm, Button, ButtonGroup, FormFeedback, FormGroup, Input, Label } from 'reactstrap'; import { mutators } from 'utils/attributes/attributeEditorMutators'; -import ConditionFormFilter from 'components/ConditionFormFilter'; import ProgressButton from 'components/ProgressButton'; import Select from 'react-select'; import { Resource } from 'types/openapi'; -import { ActionRuleRequestModel } from 'types/rules'; import { isObjectSame } from 'utils/common-utils'; import { useRuleEvaluatorResourceOptions } from 'utils/rules'; import { composeValidators, validateAlphaNumericWithSpecialChars, validateRequired } from 'utils/validators'; @@ -22,29 +22,51 @@ interface SelectChangeValue { label: string; } -export interface ActionGroupFormValues { +export interface actionFormValues { name: string; selectedResource?: SelectChangeValue; resource: Resource; description?: string; - actions: Array; + executionsUuids: SelectChangeValue[]; } -const ActionGroupForm = () => { +const ActionsForm = () => { const dispatch = useDispatch(); const navigate = useNavigate(); - const title = 'Create Action Group'; - const isCreatingActionGroup = useSelector(rulesSelectors.isCreatingActionGroup); + const title = 'Create Action'; + + const executions = useSelector(rulesSelectors.executions); + const isCreatingAction = useSelector(rulesSelectors.isCreatingAction); + const [selectedResourceState, setSelectedResourceState] = useState(); const { resourceOptionsWithRuleEvaluator, isFetchingResourcesList } = useRuleEvaluatorResourceOptions(); - const isBusy = useMemo(() => isCreatingActionGroup || isFetchingResourcesList, [isCreatingActionGroup, isFetchingResourcesList]); - const defaultValues: ActionGroupFormValues = useMemo(() => { + const isBusy = useMemo(() => isCreatingAction || isFetchingResourcesList, [isCreatingAction, isFetchingResourcesList]); + + const executionsOptions = useMemo(() => { + if (executions === undefined) return []; + return executions.map((execution) => { + return { value: execution.uuid, label: execution.name }; + }); + }, [executions]); + + useEffect(() => { + if (!selectedResourceState) return; + dispatch(rulesActions.listExecutions({ resource: selectedResourceState.value as Resource })); + }, [dispatch, selectedResourceState]); + + useEffect(() => { + return () => { + dispatch(filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS })); + }; + }, [dispatch]); + + const defaultValues: actionFormValues = useMemo(() => { return { name: '', resource: Resource.None, selectedResource: undefined, description: undefined, - actions: [], + executionsUuids: [], }; }, []); @@ -52,19 +74,19 @@ const ActionGroupForm = () => { const inProgressTitle = 'Creating...'; const onCancel = useCallback(() => { - navigate(-1); + navigate('../actions'); }, [navigate]); const onSubmit = useCallback( - (values: ActionGroupFormValues) => { + (values: actionFormValues) => { if (values.resource === Resource.None) return; dispatch( - rulesActions.createActionGroup({ - ruleActionGroupRequest: { - actions: values.actions, - name: values.name, + rulesActions.createAction({ + action: { description: values.description, + name: values.name, resource: values.resource, + executionsUuids: values.executionsUuids.map((execution) => execution.value), }, }), ); @@ -73,7 +95,7 @@ const ActionGroupForm = () => { ); const areDefaultValuesSame = useCallback( - (values: ActionGroupFormValues) => { + (values: actionFormValues) => { const areValuesSame = isObjectSame( values as unknown as Record, defaultValues as unknown as Record, @@ -85,20 +107,20 @@ const ActionGroupForm = () => { return ( -
() }}> + () }}> {({ handleSubmit, pristine, submitting, values, valid, form }) => ( {({ input, meta }) => ( - + {meta.error} @@ -135,12 +157,20 @@ const ActionGroupForm = () => { menuPlacement="auto" options={resourceOptionsWithRuleEvaluator || []} placeholder="Select Resource" + isClearable onChange={(event) => { input.onChange(event); if (event?.value) { form.change('resource', event.value); + setSelectedResourceState(event); + } else { + form.change('resource', undefined); } - form.change('actions', []); + + form.change('executionsUuids', []); + dispatch( + filterActions.setCurrentFilters({ currentFilters: [], entity: EntityType.CONDITIONS }), + ); }} styles={{ control: (provided) => @@ -148,7 +178,6 @@ const ActionGroupForm = () => { ? { ...provided, border: 'solid 1px red', '&:hover': { border: 'solid 1px red' } } : { ...provided }, }} - isClearable />
@@ -158,7 +187,23 @@ const ActionGroupForm = () => { )} - {values?.resource && } + + {({ input, meta }) => ( + + + + { + setSelectedResource(event?.value as Resource); + }} + /> +
+ ), + }, + { + icon: 'plus', + disabled: false, + tooltip: 'Create', + onClick: () => navigate(`../actions/add`), + }, + { + icon: 'trash', + disabled: checkedRows.length === 0, + tooltip: 'Delete', + onClick: () => setConfirmDelete(true), + }, + ], + [checkedRows, resourceOptionsWithRuleEvaluator, navigate], + ); + + return ( + <> + +
+ { + setCheckedRows(checkedRows as string[]); + }} + hasPagination={true} + /> +
+ + setConfirmDelete(false)} + buttons={[ + { color: 'danger', onClick: onDeleteConfirmed, body: 'Yes, delete' }, + { color: 'secondary', onClick: () => setConfirmDelete(false), body: 'Cancel' }, + ]} + /> + + ); +}; + +export default ActionsList; diff --git a/src/components/_pages/rules/list/ruleList.module.scss b/src/components/_pages/actions/list/actions-list-component/ruleList.module.scss similarity index 100% rename from src/components/_pages/rules/list/ruleList.module.scss rename to src/components/_pages/actions/list/actions-list-component/ruleList.module.scss diff --git a/src/components/_pages/action-groups/list/actionGroupsList.module.scss b/src/components/_pages/actions/list/executions-list-component/actionGroupsList.module.scss similarity index 100% rename from src/components/_pages/action-groups/list/actionGroupsList.module.scss rename to src/components/_pages/actions/list/executions-list-component/actionGroupsList.module.scss diff --git a/src/components/_pages/action-groups/list/index.tsx b/src/components/_pages/actions/list/executions-list-component/index.tsx similarity index 72% rename from src/components/_pages/action-groups/list/index.tsx rename to src/components/_pages/actions/list/executions-list-component/index.tsx index d37a43752..f12b1d127 100644 --- a/src/components/_pages/action-groups/list/index.tsx +++ b/src/components/_pages/actions/list/executions-list-component/index.tsx @@ -9,20 +9,20 @@ import { actions as actionGroupsActions, selectors as rulesSelectors } from 'duc import { useCallback, useEffect, useMemo, useState } from 'react'; import { Link, useNavigate } from 'react-router-dom'; import Select from 'react-select'; -import { Container } from 'reactstrap'; import { PlatformEnum, Resource } from 'types/openapi'; import { useRuleEvaluatorResourceOptions } from 'utils/rules'; import styles from './actionGroupsList.module.scss'; -const ActionGroupsList = () => { - const actionGroups = useSelector(rulesSelectors.actionGroups); +const ExecutionsList = () => { + const executions = useSelector(rulesSelectors.executions); const dispatch = useDispatch(); const navigate = useNavigate(); const resourceTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.Resource)); + const executionTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.ExecutionType)); const [selectedResource, setSelectedResource] = useState(); - const isFetchingList = useSelector(rulesSelectors.isFetchingActionGroups); - const isDeleting = useSelector(rulesSelectors.isDeletingActionGroup); + const isFetchingList = useSelector(rulesSelectors.isFetchingExecutions); + const isDeleting = useSelector(rulesSelectors.isDeletingExecution); const [checkedRows, setCheckedRows] = useState([]); const [confirmDelete, setConfirmDelete] = useState(false); @@ -30,13 +30,13 @@ const ActionGroupsList = () => { const isBusy = useMemo(() => isFetchingList || isDeleting, [isFetchingList, isDeleting]); const onDeleteConfirmed = useCallback(() => { - dispatch(actionGroupsActions.deleteActionGroup({ actionGroupUuid: checkedRows[0] })); + dispatch(actionGroupsActions.deleteExecution({ executionUuid: checkedRows[0] })); setConfirmDelete(false); setCheckedRows([]); }, [dispatch, checkedRows]); const getFreshListActionGroups = useCallback(() => { - dispatch(actionGroupsActions.listActionGroups({ resource: selectedResource })); + dispatch(actionGroupsActions.listExecutions({ resource: selectedResource })); }, [dispatch, selectedResource]); useEffect(() => { @@ -45,45 +45,53 @@ const ActionGroupsList = () => { const { resourceOptionsWithRuleEvaluator, isFetchingResourcesList } = useRuleEvaluatorResourceOptions(); - const conditionGroupsRowHeaders: TableHeader[] = useMemo( + const executionsDataHeaders: TableHeader[] = useMemo( () => [ { content: 'Name', align: 'left', id: 'name', - width: '10%', + width: '25%', + sortable: true, + }, + { + content: 'Type', + align: 'left', + id: 'type', + width: '25%', sortable: true, }, { content: 'Resource', align: 'left', id: 'resource', - width: '10%', + width: '25%', sortable: true, }, { content: 'Description', align: 'left', id: 'description', - width: '10%', + width: '25%', }, ], [], ); - const actionGroupList: TableDataRow[] = useMemo( + const executionsData: TableDataRow[] = useMemo( () => - actionGroups.map((actionGroup) => { + executions.map((execution) => { return { - id: actionGroup.uuid, + id: execution.uuid, columns: [ - {actionGroup.name}, - getEnumLabel(resourceTypeEnum, actionGroup.resource), - actionGroup.description || '', + {execution.name}, + getEnumLabel(executionTypeEnum, execution.type), + getEnumLabel(resourceTypeEnum, execution.resource), + execution.description || '', ], }; }), - [actionGroups, resourceTypeEnum], + [executions, resourceTypeEnum, executionTypeEnum], ); const buttons: WidgetButtonProps[] = useMemo( @@ -112,7 +120,7 @@ const ActionGroupsList = () => { icon: 'plus', disabled: false, tooltip: 'Create', - onClick: () => navigate(`./add`), + onClick: () => navigate(`../executions/add`), }, { icon: 'trash', @@ -125,25 +133,26 @@ const ActionGroupsList = () => { ); return ( - + <> +
{ setCheckedRows(checkedRows as string[]); }} @@ -153,16 +162,16 @@ const ActionGroupsList = () => { setConfirmDelete(false)} buttons={[ { color: 'danger', onClick: onDeleteConfirmed, body: 'Yes, delete' }, { color: 'secondary', onClick: () => setConfirmDelete(false), body: 'Cancel' }, ]} /> -
+ ); }; -export default ActionGroupsList; +export default ExecutionsList; diff --git a/src/components/_pages/actions/list/index.tsx b/src/components/_pages/actions/list/index.tsx new file mode 100644 index 000000000..4d32c7346 --- /dev/null +++ b/src/components/_pages/actions/list/index.tsx @@ -0,0 +1,41 @@ +import TabLayout from 'components/Layout/TabLayout'; +import { useEffect, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { Container } from 'reactstrap'; +import ActionsListComponent from './actions-list-component'; +import ExecutionsListComponent from './executions-list-component'; + +const RulesList = () => { + const { tabIndex } = useParams(); + const [activeTab, setActiveTab] = useState(0); + + useEffect(() => { + if (tabIndex && parseInt(tabIndex) <= 1) { + setActiveTab(parseInt(tabIndex)); + } + }, [tabIndex]); + + return ( + + <> + , + onClick: () => setActiveTab(0), + }, + { + title: 'Executions', + content: , + onClick: () => setActiveTab(1), + }, + ]} + /> + + + ); +}; + +export default RulesList; diff --git a/src/components/_pages/approval-profiles/form/approval-step-field.tsx b/src/components/_pages/approval-profiles/form/approval-step-field.tsx index d839328e6..19492a894 100644 --- a/src/components/_pages/approval-profiles/form/approval-step-field.tsx +++ b/src/components/_pages/approval-profiles/form/approval-step-field.tsx @@ -305,7 +305,7 @@ export default function ApprovalStepField({ approvalSteps }: Props) { () => approvalSteps.map((approvalStep, index) => ({ title: ( -
+
setSelectedTab(index)}> Approval Step {index + 1}
@@ -331,7 +331,7 @@ export default function ApprovalStepField({ approvalSteps }: Props) { tabs={[ ...tabs, { - title: , + title: , content: <>, onClick: () => handleAddStepClick(), }, diff --git a/src/components/_pages/approvals/list/index.tsx b/src/components/_pages/approvals/list/index.tsx index 6cbd84926..e84f004a4 100644 --- a/src/components/_pages/approvals/list/index.tsx +++ b/src/components/_pages/approvals/list/index.tsx @@ -3,7 +3,6 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Link, useNavigate } from 'react-router-dom'; import { Button, Container } from 'reactstrap'; -// import { selectors as profileApprovalSelector } from "ducks/approval-profiles"; import CustomTable, { TableDataRow, TableHeader } from 'components/CustomTable'; import Dialog from 'components/Dialog'; @@ -38,10 +37,6 @@ export default function ApprovalsList() { const isBusy = useMemo(() => isFetching, [isFetching]); - useEffect(() => { - // dispatch(rolesActions.getPermissions({ uuid: })); - }, []); - const listUserApprovals = useCallback(() => { dispatch( approvalActions.listUserApprovals({ diff --git a/src/components/_pages/condition-groups/details/index.tsx b/src/components/_pages/conditions/details/index.tsx similarity index 78% rename from src/components/_pages/condition-groups/details/index.tsx rename to src/components/_pages/conditions/details/index.tsx index 654366f6b..5f7f255dd 100644 --- a/src/components/_pages/condition-groups/details/index.tsx +++ b/src/components/_pages/conditions/details/index.tsx @@ -1,4 +1,4 @@ -import ConditionsViewer from 'components/ConditionsViewer'; +import ConditionAndExecutionItemsViewer from 'components/ConditionAndExecutionItemsViewer'; import CustomTable, { TableDataRow, TableHeader } from 'components/CustomTable'; import Dialog from 'components/Dialog'; import Widget from 'components/Widget'; @@ -11,13 +11,14 @@ import { useParams } from 'react-router-dom'; import { Button, ButtonGroup, Col, Container, Input, Row } from 'reactstrap'; import { PlatformEnum } from 'types/openapi'; -const ConditionGroupDetails = () => { +const ConditionDetails = () => { const { id } = useParams(); const dispatch = useDispatch(); const resourceTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.Resource)); - const conditionGroupsDetails = useSelector(rulesSelectors.conditionGroupDetails); - const isFetchingConditionGroup = useSelector(rulesSelectors.isFetchingConditionGroup); - const isUpdatingGroupDetails = useSelector(rulesSelectors.isUpdatingConditionGroup); + const conditionTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.ConditionType)); + const conditionDetails = useSelector(rulesSelectors.conditionDetails); + const isFetchingConditionGroup = useSelector(rulesSelectors.isFetchingConditionDetails); + const isUpdatingGroupDetails = useSelector(rulesSelectors.isUpdatingCondition); const [confirmDelete, setConfirmDelete] = useState(false); const [updateDescriptionEditEnable, setUpdateDescription] = useState(false); @@ -26,13 +27,13 @@ const ConditionGroupDetails = () => { const isBusy = useMemo(() => isFetchingConditionGroup || isUpdatingGroupDetails, [isFetchingConditionGroup, isUpdatingGroupDetails]); useEffect(() => { - if (!conditionGroupsDetails?.description || conditionGroupsDetails.uuid !== id) return; - setUpdatedDescription(conditionGroupsDetails.description); - }, [conditionGroupsDetails, id]); + if (!conditionDetails?.description || conditionDetails.uuid !== id) return; + setUpdatedDescription(conditionDetails.description); + }, [conditionDetails, id]); const getFreshDetails = useCallback(() => { if (!id) return; - dispatch(rulesActions.getConditionGroup({ conditionGroupUuid: id })); + dispatch(rulesActions.getCondition({ conditionUuid: id })); }, [id, dispatch]); useEffect(() => { @@ -41,25 +42,25 @@ const ConditionGroupDetails = () => { const onDeleteConfirmed = useCallback(() => { if (!id) return; - dispatch(rulesActions.deleteConditionGroup({ conditionGroupUuid: id })); + dispatch(rulesActions.deleteCondition({ conditionUuid: id })); setConfirmDelete(false); }, [dispatch, id]); const onUpdateDescriptionConfirmed = useCallback(() => { if (!id || !updateDescriptionEditEnable) return; - if (updatedDescription !== conditionGroupsDetails?.description) { + if (updatedDescription !== conditionDetails?.description) { dispatch( - rulesActions.updateConditionGroup({ - conditionGroupUuid: id, - conditionGroup: { + rulesActions.updateCondition({ + conditionUuid: id, + condition: { description: updatedDescription, - conditions: conditionGroupsDetails?.conditions || [], + items: conditionDetails?.items || [], }, }), ); } setUpdateDescription(false); - }, [dispatch, id, conditionGroupsDetails, updatedDescription, updateDescriptionEditEnable]); + }, [dispatch, id, conditionDetails, updatedDescription, updateDescriptionEditEnable]); const buttons: WidgetButtonProps[] = useMemo( () => [ @@ -85,6 +86,7 @@ const ConditionGroupDetails = () => { { id: 'actions', content: 'Actions', + align: 'center', }, ], [], @@ -92,20 +94,24 @@ const ConditionGroupDetails = () => { const conditionGroupsDetailData: TableDataRow[] = useMemo( () => - !conditionGroupsDetails || isFetchingConditionGroup + !conditionDetails || isFetchingConditionGroup ? [] : [ { id: 'uuid', - columns: ['UUID', conditionGroupsDetails.uuid, ''], + columns: ['UUID', conditionDetails.uuid, ''], }, { id: 'name', - columns: ['Name', conditionGroupsDetails.name, ''], + columns: ['Name', conditionDetails.name, ''], + }, + { + id: 'type', + columns: ['Type', getEnumLabel(conditionTypeEnum, conditionDetails.type), ''], }, { id: 'resource', - columns: ['Resource', getEnumLabel(resourceTypeEnum, conditionGroupsDetails.resource), ''], + columns: ['Resource', getEnumLabel(resourceTypeEnum, conditionDetails.resource), ''], }, { id: 'description', @@ -118,7 +124,7 @@ const ConditionGroupDetails = () => { placeholder="Enter Description" /> ) : ( - conditionGroupsDetails.description || '' + conditionDetails.description || '' ),
{updateDescriptionEditEnable ? ( @@ -131,7 +137,7 @@ const ConditionGroupDetails = () => { onClick={onUpdateDescriptionConfirmed} disabled={ isUpdatingGroupDetails || - updatedDescription === conditionGroupsDetails.description || + updatedDescription === conditionDetails.description || updatedDescription === '' } > @@ -143,7 +149,7 @@ const ConditionGroupDetails = () => { title="Cancel" onClick={() => { setUpdateDescription(false); - setUpdatedDescription(conditionGroupsDetails?.description || ''); + setUpdatedDescription(conditionDetails?.description || ''); }} disabled={isUpdatingGroupDetails} > @@ -169,7 +175,8 @@ const ConditionGroupDetails = () => { }, ], [ - conditionGroupsDetails, + conditionDetails, + conditionTypeEnum, resourceTypeEnum, setUpdateDescription, updateDescriptionEditEnable, @@ -187,7 +194,7 @@ const ConditionGroupDetails = () => { @@ -196,14 +203,14 @@ const ConditionGroupDetails = () => { - {conditionGroupsDetails?.resource && ( - + {conditionDetails?.resource && ( + )} setConfirmDelete(false)} buttons={[ { color: 'danger', onClick: onDeleteConfirmed, body: 'Yes, delete' }, @@ -214,4 +221,4 @@ const ConditionGroupDetails = () => { ); }; -export default ConditionGroupDetails; +export default ConditionDetails; diff --git a/src/components/_pages/condition-groups/form/index.tsx b/src/components/_pages/conditions/form/index.tsx similarity index 72% rename from src/components/_pages/condition-groups/form/index.tsx rename to src/components/_pages/conditions/form/index.tsx index 266e3551a..9da8a40d8 100644 --- a/src/components/_pages/condition-groups/form/index.tsx +++ b/src/components/_pages/conditions/form/index.tsx @@ -1,10 +1,9 @@ import Widget from 'components/Widget'; -import { useCallback, useEffect, useMemo } from 'react'; -import { useDispatch, useSelector } from 'react-redux'; -import { useNavigate, useParams } from 'react-router-dom'; -// import { EntityType, actions as filterActions } from 'ducks/filters'; import { EntityType, actions as filterActions } from 'ducks/filters'; import { actions as rulesActions, selectors as rulesSelectors } from 'ducks/rules'; +import { useCallback, useEffect, useMemo } from 'react'; +import { useDispatch, useSelector } from 'react-redux'; +import { useNavigate } from 'react-router-dom'; import { Field, Form } from 'react-final-form'; @@ -12,9 +11,10 @@ import { Form as BootstrapForm, Button, ButtonGroup, FormFeedback, FormGroup, In import { mutators } from 'utils/attributes/attributeEditorMutators'; import ProgressButton from 'components/ProgressButton'; +import { selectors as enumSelectors, getEnumLabel } from 'ducks/enums'; import Select from 'react-select'; -import { Resource } from 'types/openapi'; -import { RuleConditiontModel } from 'types/rules'; +import { ConditionType, PlatformEnum, Resource } from 'types/openapi'; +import { ConditionItemModel } from 'types/rules'; import { isObjectSame } from 'utils/common-utils'; import { useRuleEvaluatorResourceOptions } from 'utils/rules'; import { composeValidators, validateAlphaNumericWithSpecialChars, validateRequired } from 'utils/validators'; @@ -25,32 +25,25 @@ interface SelectChangeValue { label: string; } -export interface ConditionGroupFormValues { +export interface ConditionFormValues { name: string; selectedResource?: SelectChangeValue; resource: Resource; description?: string; - conditions: RuleConditiontModel[]; + type?: ConditionType; + selectedType?: SelectChangeValue; + items: ConditionItemModel[]; } -const ConditionGroupForm = () => { - const { id } = useParams(); +const ConditionForm = () => { const dispatch = useDispatch(); const navigate = useNavigate(); - const title = 'Create Condition Group'; - const isCreatingConditionGroup = useSelector(rulesSelectors.isCreatingConditionGroup); - const isUpdatingConditionGroup = useSelector(rulesSelectors.isUpdatingConditionGroup); + const title = 'Create Condition'; + const isCreatingCondition = useSelector(rulesSelectors.isCreatingCondition); + const conditionTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.ConditionType)); const { resourceOptionsWithRuleEvaluator, isFetchingResourcesList } = useRuleEvaluatorResourceOptions(); - const isBusy = useMemo( - () => isCreatingConditionGroup || isFetchingResourcesList || isUpdatingConditionGroup, - [isCreatingConditionGroup, isUpdatingConditionGroup, isFetchingResourcesList], - ); - - useEffect(() => { - if (!id) return; - dispatch(rulesActions.getConditionGroup({ conditionGroupUuid: id })); - }, [id, dispatch]); + const isBusy = useMemo(() => isCreatingCondition || isFetchingResourcesList, [isCreatingCondition, isFetchingResourcesList]); useEffect(() => { return () => { @@ -58,30 +51,35 @@ const ConditionGroupForm = () => { }; }, [dispatch]); - const defaultValues: ConditionGroupFormValues = useMemo(() => { + const defaultValues: ConditionFormValues = useMemo(() => { return { name: '', resource: Resource.None, selectedResource: undefined, description: undefined, - conditions: [], + items: [], }; }, []); const submitTitle = 'Create'; const inProgressTitle = 'Creating...'; + const typeOptions = useMemo(() => { + return [{ value: ConditionType.CheckField, label: getEnumLabel(conditionTypeEnum, ConditionType.CheckField) }]; + }, [conditionTypeEnum]); + const onCancel = useCallback(() => { - navigate(-1); + navigate('../rules/1'); }, [navigate]); const onSubmit = useCallback( - (values: ConditionGroupFormValues) => { - if (values.resource === Resource.None) return; + (values: ConditionFormValues) => { + if (values.resource === Resource.None || !values.type) return; dispatch( - rulesActions.createConditionGroup({ - ruleConditionGroupRequest: { - conditions: values.conditions, + rulesActions.createCondition({ + conditionRequestModel: { + items: values.items, + type: values.type, name: values.name, resource: values.resource, description: values.description, @@ -93,7 +91,7 @@ const ConditionGroupForm = () => { ); const areDefaultValuesSame = useCallback( - (values: ConditionGroupFormValues) => { + (values: ConditionFormValues) => { const areValuesSame = isObjectSame( values as unknown as Record, defaultValues as unknown as Record, @@ -103,17 +101,15 @@ const ConditionGroupForm = () => { [defaultValues], ); - if (id) return null; - return ( - () }}> + () }}> {({ handleSubmit, pristine, submitting, values, valid, form }) => ( {({ input, meta }) => ( - + { )} + + {({ input, meta }) => ( + + + + + + {meta.error} + + )} + + + + {({ input, meta }) => ( + + + + + + {meta.error} + + )} + + + + {({ input, meta }) => ( + + + + { + input.onChange(event); + if (event?.value) { + form.change('resource', event.value); + } + form.change('items', []); + }} + styles={{ + control: (provided) => + meta.touched && meta.invalid + ? { ...provided, border: 'solid 1px red', '&:hover': { border: 'solid 1px red' } } + : { ...provided }, + }} + isClearable + /> + +
+ {meta.error} +
+
+ )} +
+ + {values?.resource && } + +
+ + + + + +
+
+ )} + +
+ ); +}; + +export default ExecutionForm; diff --git a/src/components/_pages/roles/RolePermissionsEdior/index.tsx b/src/components/_pages/roles/RolePermissionsEdior/index.tsx index 8f21d492d..17d9cfc2d 100644 --- a/src/components/_pages/roles/RolePermissionsEdior/index.tsx +++ b/src/components/_pages/roles/RolePermissionsEdior/index.tsx @@ -221,7 +221,7 @@ function RolePermissionsEditor({ } onChange={(e) => allowAction(currentResource, action.name, e.target.checked)} /> -    {action.displayName} +    {action.name} ))}
@@ -246,7 +246,7 @@ function RolePermissionsEditor({ (action) => ({ id: action.name, - content: action.displayName, + content: action.name, sortable: false, align: 'center', width: '5em', diff --git a/src/components/_pages/rules/detail/index.tsx b/src/components/_pages/rules/detail/index.tsx index d1a3ba255..29ddb3d83 100644 --- a/src/components/_pages/rules/detail/index.tsx +++ b/src/components/_pages/rules/detail/index.tsx @@ -1,6 +1,7 @@ -import ConditionsViewer from 'components/ConditionsViewer'; +import { ApiClients } from 'api'; import CustomTable, { TableDataRow, TableHeader } from 'components/CustomTable'; import Dialog from 'components/Dialog'; +import ConditionsExecutionsList from 'components/ExecutionConditionItemsList'; import Widget from 'components/Widget'; import { WidgetButtonProps } from 'components/WidgetButtons'; import { selectors as enumSelectors, getEnumLabel } from 'ducks/enums'; @@ -9,7 +10,7 @@ import { useCallback, useEffect, useMemo, useState } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Link, useParams } from 'react-router-dom'; import { Button, ButtonGroup, Col, Container, Input, Row } from 'reactstrap'; -import { PlatformEnum } from 'types/openapi'; +import { PlatformEnum, Resource } from 'types/openapi'; interface SelectChangeValue { value: string; label: string; @@ -19,9 +20,10 @@ const RuleDetails = () => { const dispatch = useDispatch(); const ruleDetails = useSelector(rulesSelectors.ruleDetails); const isUpdatingRule = useSelector(rulesSelectors.isUpdatingRule); - const isFetchingRuleDetail = useSelector(rulesSelectors.isFetchingRuleDetail); + const isFetchingRuleDetails = useSelector(rulesSelectors.isFetchingRuleDetails); const resourceTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.Resource)); - const conditionGroups = useSelector(rulesSelectors.conditionRuleGroups); + const conditions = useSelector(rulesSelectors.conditions); + const conditionTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.ConditionType)); const [confirmDelete, setConfirmDelete] = useState(false); const [updateDescriptionEditEnable, setUpdateDescription] = useState(false); @@ -42,22 +44,19 @@ const RuleDetails = () => { }, [getFreshDetails]); useEffect(() => { - dispatch(rulesActions.listConditionGroups({ resource: ruleDetails?.resource })); + dispatch(rulesActions.listConditions({ resource: ruleDetails?.resource })); }, [ruleDetails, dispatch]); - const isBusy = useMemo(() => isFetchingRuleDetail || isUpdatingRule, [isFetchingRuleDetail, isUpdatingRule]); + const isBusy = useMemo(() => isFetchingRuleDetails || isUpdatingRule, [isFetchingRuleDetails, isUpdatingRule]); - const conditionGroupsOptions = useMemo(() => { - if (conditionGroups === undefined) return []; - return conditionGroups - .map((conditionGroup) => { - return { value: conditionGroup.uuid, label: conditionGroup.name }; + const conditionsOptions = useMemo(() => { + if (conditions === undefined) return []; + return conditions + .map((conditions) => { + return { value: conditions.uuid, label: conditions.name }; }) - .filter( - (conditionGroup) => - !ruleDetails?.conditionGroups?.map((conditionGroup) => conditionGroup.uuid).includes(conditionGroup.value), - ); - }, [conditionGroups, ruleDetails]); + .filter((conditions) => !ruleDetails?.conditions?.map((condition) => condition.uuid).includes(conditions.value)); + }, [conditions, ruleDetails]); const onDeleteConfirmed = useCallback(() => { if (!id) return; @@ -73,10 +72,7 @@ const RuleDetails = () => { ruleUuid: id, rule: { description: updatedDescription, - conditions: ruleDetails?.conditions || [], - conditionGroupsUuids: ruleDetails?.conditionGroups?.length - ? ruleDetails?.conditionGroups.map((conditionGroup) => conditionGroup.uuid) - : [], + conditionsUuids: ruleDetails?.conditions?.length ? ruleDetails?.conditions.map((condition) => condition.uuid) : [], }, }), ); @@ -84,22 +80,21 @@ const RuleDetails = () => { setUpdateDescription(false); }, [dispatch, id, ruleDetails, updatedDescription, updateDescriptionEditEnable]); - const onUpdateConditionGroupsConfirmed = useCallback( + const onUpdateConditionsConfirmed = useCallback( (newValues: SelectChangeValue[]) => { if (!id) return; - const newConditionGroupsUuids = newValues.map((conditionGroup) => conditionGroup.value); + const newConditionsUuids = newValues.map((condition) => condition.value); - const previousAndNewConditionGroupsUuid = ruleDetails?.conditionGroups.map((conditionGroup) => conditionGroup.uuid); - const allConditionGroups = [...(previousAndNewConditionGroupsUuid || []), ...newConditionGroupsUuids]; + const previousAndNewConditionsUuid = ruleDetails?.conditions.map((condition) => condition.uuid); + const allConditions = [...(previousAndNewConditionsUuid || []), ...newConditionsUuids]; dispatch( rulesActions.updateRule({ ruleUuid: id, rule: { description: ruleDetails?.description || '', - conditions: ruleDetails?.conditions || [], - conditionGroupsUuids: allConditionGroups, + conditionsUuids: allConditions, }, }), ); @@ -107,25 +102,24 @@ const RuleDetails = () => { [dispatch, id, ruleDetails], ); - const onDeleteConditionGroup = useCallback( - (conditionGroupUuid: string) => { + const onDeleteCondition = useCallback( + (conditionUuid: string) => { if (!id) return; - const updatedConditionGroupsUuid = ruleDetails?.conditionGroups - .filter((conditionGroup) => conditionGroup.uuid !== conditionGroupUuid) - .map((conditionGroup) => conditionGroup.uuid); + const updatedConditionsUuids = ruleDetails?.conditions + .filter((condition) => condition.uuid !== conditionUuid) + .map((condition) => condition.uuid); dispatch( rulesActions.updateRule({ ruleUuid: id, rule: { - conditions: ruleDetails?.conditions || [], description: ruleDetails?.description || '', - conditionGroupsUuids: updatedConditionGroupsUuid, + conditionsUuids: updatedConditionsUuids?.length ? updatedConditionsUuids : [], }, }), ); }, - [dispatch, id, ruleDetails?.conditionGroups, ruleDetails?.conditions, ruleDetails?.description], + [dispatch, id, ruleDetails?.conditions, ruleDetails?.description], ); const buttons: WidgetButtonProps[] = useMemo( @@ -139,7 +133,7 @@ const RuleDetails = () => { [], ); - const tableHeader: TableHeader[] = useMemo( + const ruleTableHeaders: TableHeader[] = useMemo( () => [ { id: 'property', @@ -152,14 +146,15 @@ const RuleDetails = () => { { id: 'actions', content: 'Actions', + align: 'center', }, ], [], ); - const conditionGroupsDetailData: TableDataRow[] = useMemo( + const ruleDetailsData: TableDataRow[] = useMemo( () => - !ruleDetails || isFetchingRuleDetail + !ruleDetails || isFetchingRuleDetails ? [] : [ { @@ -242,16 +237,20 @@ const RuleDetails = () => { updateDescriptionEditEnable, isUpdatingRule, updatedDescription, - isFetchingRuleDetail, + isFetchingRuleDetails, ], ); - const conditionGroupFieldsDataHeader = useMemo( + const conditionsTableHeader = useMemo( () => [ { id: 'name', content: 'Name', }, + { + id: 'type', + content: 'Type', + }, { id: 'description', content: 'Description', @@ -264,28 +263,28 @@ const RuleDetails = () => { [], ); - const conditionGroupFieldsData: TableDataRow[] = useMemo(() => { - const isDeleteDisabled = - (ruleDetails?.conditions.length === 0 && ruleDetails?.conditionGroups.length === 1) || isFetchingRuleDetail || isUpdatingRule; - const conditionGroupData = !ruleDetails?.conditionGroups.length + const conditionsData: TableDataRow[] = useMemo(() => { + const isDeleteDisabled = ruleDetails?.conditions.length === 1 || isFetchingRuleDetails || isUpdatingRule; + const conditionsData = !ruleDetails?.conditions.length ? [] - : ruleDetails?.conditionGroups.map((conditionGroup) => { + : ruleDetails?.conditions.map((condition) => { return { - id: conditionGroup.uuid, + id: condition.uuid, columns: [ - {conditionGroup.name} || '', - conditionGroup.description || '', + {condition.name} || '', + getEnumLabel(conditionTypeEnum, condition.type), + condition.description || '',
- ), - }, - { - icon: 'plus', - disabled: false, - tooltip: 'Create', - onClick: () => navigate(`./add`), - }, - { - icon: 'trash', - disabled: checkedRows.length === 0, - tooltip: 'Delete', - onClick: () => setConfirmDelete(true), - }, - ], - [checkedRows, resourceOptionsWithRuleEvaluator, navigate], - ); + if (tabIndex && parseInt(tabIndex) <= 1) { + setActiveTab(parseInt(tabIndex)); + } + }, [tabIndex]); return ( - -
- { - setCheckedRows(checkedRows as string[]); - }} - hasPagination={true} + <> + , + onClick: () => setActiveTab(0), + }, + { + title: 'Conditions', + content: , + onClick: () => setActiveTab(1), + }, + ]} /> -
- - setConfirmDelete(false)} - buttons={[ - { color: 'danger', onClick: onDeleteConfirmed, body: 'Yes, delete' }, - { color: 'secondary', onClick: () => setConfirmDelete(false), body: 'Cancel' }, - ]} - /> +
); }; -export default ConditionGroups; +export default RulesList; diff --git a/src/components/_pages/rules/list/rules-list-component/index.tsx b/src/components/_pages/rules/list/rules-list-component/index.tsx new file mode 100644 index 000000000..2f1d27023 --- /dev/null +++ b/src/components/_pages/rules/list/rules-list-component/index.tsx @@ -0,0 +1,172 @@ +import { selectors as enumSelectors, getEnumLabel } from 'ducks/enums'; +import { useDispatch, useSelector } from 'react-redux'; + +import CustomTable, { TableDataRow, TableHeader } from 'components/CustomTable'; +import Dialog from 'components/Dialog'; +import Widget from 'components/Widget'; +import { WidgetButtonProps } from 'components/WidgetButtons'; +import { actions as rulesActions, selectors as rulesSelectors } from 'ducks/rules'; +import { useCallback, useEffect, useMemo, useState } from 'react'; +import { Link, useNavigate } from 'react-router-dom'; +import Select from 'react-select'; +import { PlatformEnum, Resource } from 'types/openapi'; + +import { useRuleEvaluatorResourceOptions } from 'utils/rules'; +import styles from './ruleList.module.scss'; + +const RulesList = () => { + const rules = useSelector(rulesSelectors.rules); + + const dispatch = useDispatch(); + const navigate = useNavigate(); + + const resourceTypeEnum = useSelector(enumSelectors.platformEnum(PlatformEnum.Resource)); + const [selectedResource, setSelectedResource] = useState(); + const isFetchingList = useSelector(rulesSelectors.isFetchingRulesList); + const isDeleting = useSelector(rulesSelectors.isDeletingRule); + + const [checkedRows, setCheckedRows] = useState([]); + const [confirmDelete, setConfirmDelete] = useState(false); + const { resourceOptionsWithRuleEvaluator, isFetchingResourcesList } = useRuleEvaluatorResourceOptions(); + + const isBusy = useMemo( + () => isFetchingList || isDeleting || isFetchingResourcesList, + [isFetchingList, isDeleting, isFetchingResourcesList], + ); + + const onDeleteConfirmed = useCallback(() => { + dispatch(rulesActions.deleteRule({ ruleUuid: checkedRows[0] })); + setConfirmDelete(false); + setCheckedRows([]); + }, [dispatch, checkedRows]); + + const getFreshList = useCallback(() => { + dispatch(rulesActions.listRules({ resource: selectedResource })); + }, [dispatch, selectedResource]); + + useEffect(() => { + getFreshList(); + }, [getFreshList]); + + const rulesHeader: TableHeader[] = useMemo( + () => [ + { + content: 'Name', + align: 'left', + id: 'name', + width: '10%', + sortable: true, + }, + { + content: 'Resource', + align: 'left', + id: 'resource', + width: '10%', + sortable: true, + }, + { + content: 'Description', + align: 'left', + id: 'description', + width: '10%', + }, + ], + [], + ); + + const rulesData: TableDataRow[] = useMemo( + () => + rules.map((rule) => { + return { + id: rule.uuid, + columns: [ + {rule.name}, + getEnumLabel(resourceTypeEnum, rule.resource), + rule.description || '', + ], + }; + }), + [rules, resourceTypeEnum], + ); + + const buttons: WidgetButtonProps[] = useMemo( + () => [ + { + icon: 'search', + disabled: false, + tooltip: 'Select Resource', + onClick: () => {}, + custom: ( +
+ { if (!event?.value) return; input.onChange(event); - form.change('triggerResource', event.value as Resource); + form.change('eventResource', event.value as Resource); - if (values.triggerType === RuleTriggerType.Event) { + if (values.triggerType === TriggerType.Event) { fetchResourceEvents(event.value as Resource); form.change('selectedResource', undefined); form.change('resource', Resource.None); } else { form.change('selectedResource', event); form.change('resource', event.value as Resource); - fetchActionGroups(event.value as Resource); + fetchActions(event.value as Resource); fetchRules(event.value as Resource); } - form.change('eventName', undefined); - form.change('selectedEventName', undefined); - form.change('actions', []); - form.change('actionGroupsUuids', []); + form.change('event', undefined); + form.change('selectedEvent', undefined); + form.change('actionsUuids', []); form.change('rulesUuids', []); }} styles={{ @@ -302,24 +293,24 @@ const ConditionGroupForm = () => { )} - {values?.triggerType === RuleTriggerType.Event && ( - + {values?.triggerType === TriggerType.Event && ( + {({ input, meta }) => ( - + { + if (event.target) { + const isChecked = (event.target as HTMLInputElement).checked; + if (isChecked) { + form.change('actionsUuids', []); + } + } + }} + /> +
+ + )} + + {({ input, meta }) => ( @@ -385,23 +400,22 @@ const ConditionGroupForm = () => { )} - + {({ input, meta }) => ( - +