diff --git a/.eslintrc.js b/.eslintrc.js index c23d84a7c51..4b52da2fef6 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -72,7 +72,7 @@ const getLicenseHeaderPattern = () => { const LICENSE_HEADER_DEFAULT_PATTERN = [ "*", { - pattern: " Copyright \\(c\\) \\b(2019|202[0-4])(?:-(202[0-4]))?, WSO2 LLC. \\(https://www.wso2.com\\).$", + pattern: " Copyright \\(c\\) \\b(2019|202[0-5])(?:-(202[0-5]))?, WSO2 LLC. \\(https://www.wso2.com\\).$", template: " * Copyright (c) {{year}}, WSO2 LLC. (https://www.wso2.com)." }, " *", diff --git a/features/admin.actions.v1/components/action-config-form.tsx b/features/admin.actions.v1/components/action-config-form.tsx index a0fc7cc2462..031376e5463 100644 --- a/features/admin.actions.v1/components/action-config-form.tsx +++ b/features/admin.actions.v1/components/action-config-form.tsx @@ -28,7 +28,7 @@ import { FeatureAccessConfigInterface, useRequiredScopes } from "@wso2is/access- import { AppState } from "@wso2is/admin.core.v1"; import useGetRulesMeta from "@wso2is/admin.rules.v1/api/use-get-rules-meta"; import RulesComponent from "@wso2is/admin.rules.v1/components/rules-component"; -import { RuleInterface } from "@wso2is/admin.rules.v1/models/rules"; +import { RuleExecuteCollectionInterface } from "@wso2is/admin.rules.v1/models/rules"; import { getRuleInstanceValue } from "@wso2is/admin.rules.v1/providers/rules-provider"; import { AlertLevels, IdentifiableComponentInterface } from "@wso2is/core/models"; import { addAlert } from "@wso2is/core/store"; @@ -111,10 +111,18 @@ const ActionConfigForm: FunctionComponent = ({ } = useGetActionsByType(actionTypeApiPath); const { - data: RulesMeta + data: RuleExpressionsMetaData } = useGetRulesMeta(actionTypeApiPath); - const showRulesComponent: boolean = false; + // TODO: Remove this temporary boolean once the management API is ready. + const showRulesComponent: boolean = true; + + // TODO: Use this function to get the rule value. + /* eslint-disable @typescript-eslint/no-unused-vars */ + const handleGetRuleValue = () => { + const ruleValue: RuleExecuteCollectionInterface = getRuleInstanceValue(); + }; + /* eslint-enable @typescript-eslint/no-unused-vars */ /** * The following useEffect is used to set the current Action Authentication Type. @@ -128,13 +136,6 @@ const ActionConfigForm: FunctionComponent = ({ } }, [ initialValues ]); - // TODO: Use this function to get the rule value. - /* eslint-disable @typescript-eslint/no-unused-vars */ - const handleGetRuleValue = () => { - const ruleValue: RuleInterface[] = getRuleInstanceValue(); - }; - /* eslint-enable @typescript-eslint/no-unused-vars */ - const renderInputAdornmentOfSecret = (showSecret: boolean, onClick: () => void): ReactElement => ( = ({ { t("actions:fields.authentication.label") } { renderAuthenticationSection() } - { (RulesMeta && showRulesComponent) && ( + { (RuleExpressionsMetaData && showRulesComponent) && ( <> - + ) } diff --git a/features/admin.registration-flow-builder.v1/components/element-property-panel/nodes/rules-properties.tsx b/features/admin.registration-flow-builder.v1/components/element-property-panel/nodes/rules-properties.tsx index a5acdad9cad..93d53531b31 100644 --- a/features/admin.registration-flow-builder.v1/components/element-property-panel/nodes/rules-properties.tsx +++ b/features/admin.registration-flow-builder.v1/components/element-property-panel/nodes/rules-properties.tsx @@ -22,7 +22,13 @@ import Stack from "@oxygen-ui/react/Stack"; import Typography from "@oxygen-ui/react/Typography"; import useGetRulesMeta from "@wso2is/admin.rules.v1/api/use-get-rules-meta"; import RulesComponent from "@wso2is/admin.rules.v1/components/rules-component"; -import { sampleExecutionsList } from "@wso2is/admin.rules.v1/data"; +import { + sampleExpressionsMeta, + sampleRuleExecuteInstances, + sampleRuleExecutionMeta +} from "@wso2is/admin.rules.v1/data"; +import { RuleExecuteCollectionInterface } from "@wso2is/admin.rules.v1/models/rules"; +import { getRuleInstanceValue } from "@wso2is/admin.rules.v1/providers/rules-provider"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; import "./rules-properties.scss"; @@ -33,30 +39,51 @@ import "./rules-properties.scss"; export interface RulesPropertiesPropsInterface extends IdentifiableComponentInterface { } /** - * Component to generate the properties for the attribute collector widget. + * Rules properties component. * * @param props - Props injected to the component. - * @returns The RulesProperties component. + * @returns Rules properties component. */ const RulesProperties: FunctionComponent = ({ ["data-componentid"]: componentId = "rules-properties-component" }: RulesPropertiesPropsInterface): ReactElement => { - // TODO: Change the glow value + // TODO: Change to collect dynamic value from the context const { - data: RulesMeta + data: RuleExpressionsMetaData } = useGetRulesMeta("preIssueAccessToken"); + // TODO: Use this function to get the rule value. + /* eslint-disable @typescript-eslint/no-unused-vars */ + const handleGetRuleValue = () => { + const ruleValue: RuleExecuteCollectionInterface = getRuleInstanceValue(); + + // eslint-disable-next-line no-console + console.log(ruleValue); + }; + /* eslint-enable @typescript-eslint/no-unused-vars */ + return ( Define a rule to how conditionally proceed to next steps in the flow - { RulesMeta && - - } + { RuleExpressionsMetaData && ( + + ) } - + ); diff --git a/features/admin.rules.v1/api/use-get-rules-meta.ts b/features/admin.rules.v1/api/use-get-rules-meta.ts index f4a5aa5781e..255683ddd05 100644 --- a/features/admin.rules.v1/api/use-get-rules-meta.ts +++ b/features/admin.rules.v1/api/use-get-rules-meta.ts @@ -23,7 +23,7 @@ import useRequest, { } from "@wso2is/admin.core.v1/hooks/use-request"; import { store } from "@wso2is/admin.core.v1/store"; import { HttpMethods } from "@wso2is/core/models"; -import { RuleComponentMetaDataInterface } from "../models/rules"; +import { ConditionExpressionsMetaDataInterface } from "../models/meta"; /** * Hook to get the rules meta data. @@ -38,7 +38,7 @@ import { RuleComponentMetaDataInterface } from "../models/rules"; * @param shouldFetch - Should fetch the data. * @returns SWR response object containing the data, error, isLoading, isValidating, mutate. */ -const useGetRulesMeta = ( +const useGetRulesMeta = ( flow: string, shouldFetch: boolean = true ): RequestResultInterface => { diff --git a/features/admin.rules.v1/components/rule-conditions.tsx b/features/admin.rules.v1/components/rule-conditions.tsx index 9030e22d742..240f6f3487c 100644 --- a/features/admin.rules.v1/components/rule-conditions.tsx +++ b/features/admin.rules.v1/components/rule-conditions.tsx @@ -31,17 +31,22 @@ import Select from "@oxygen-ui/react/Select"; import TextField from "@oxygen-ui/react/TextField"; import { IdentifiableComponentInterface } from "@wso2is/core/models"; import debounce from "lodash-es/debounce"; -import React, { FunctionComponent, ReactElement, useEffect, useState } from "react"; +import React, { Fragment, FunctionComponent, ReactElement, useEffect, useState } from "react"; import useGetResourcesList from "../api/use-get-resource-list"; import { useRulesContext } from "../hooks/use-rules-context"; import { - ConditionTypes, - ExpressionFieldTypes, + ConditionExpressionMetaInterface, + ExpressionValueInterface, LinkInterface, - ListDataInterface, - RuleComponentExpressionValueInterface, - RuleComponentMetaInterface, - RuleConditionsInterface + ListDataInterface +} from "../models/meta"; +import { + AdjoiningOperatorTypes, + ConditionExpressionInterface, + ExpressionFieldTypes, + RuleConditionInterface, + RuleConditionsInterface, + RuleInterface } from "../models/rules"; import "./rules-component.scss"; @@ -49,35 +54,29 @@ import "./rules-component.scss"; * Value input autocomplete props interface. */ interface ValueInputAutocompleteProps { - metaValue: RuleComponentExpressionValueInterface; + localValue: string; resourceType: string; + initialResourcesLoadUrl: string; + filterBaseResourcesUrl: string; } /** * Condition value input props interface. */ interface ConditionValueInputProps { - metaValue: RuleComponentExpressionValueInterface; + metaValue: ExpressionValueInterface; +} + +interface ResourceListSelectProps { + initialResourcesLoadUrl: string; + filterBaseResourcesUrl: string; } /** * Props interface of {@link RulesComponent} */ export interface RulesComponentPropsInterface extends IdentifiableComponentInterface { - /** - * Conditions to render. - */ - conditions: RuleConditionsInterface[]; - - /** - * Rule id. - */ - ruleId: string; - - /** - * Condition removable flag. - */ - conditionRemovable: boolean; + rule: RuleInterface; } /** @@ -88,57 +87,43 @@ export interface RulesComponentPropsInterface extends IdentifiableComponentInter */ const RuleConditions: FunctionComponent = ({ ["data-componentid"]: componentId = "rules-component", - conditions, - ruleId, - conditionRemovable + rule: ruleInstance }: RulesComponentPropsInterface): ReactElement => { + const ruleConditions: RuleConditionsInterface = ruleInstance.rules; + const { - addNewRuleCondition, + addNewRuleConditionExpression, conditionExpressionsMeta, - updateRuleConditionExpression, - removeRuleCondition + updateConditionExpression, + removeRuleConditionExpression } = useRulesContext(); /** - * Rule expression component. + * Rule expression component to recursive render. * * @param props - Props injected to the component. * @returns Rule expression component. */ const RuleExpression = ({ - condition, + expression, ruleId, - index + conditionId, + index, + isConditionExpressionRemovable }: { + expression: ConditionExpressionInterface; ruleId: string; - condition: RuleConditionsInterface; + conditionId: string; index: number; + isConditionExpressionRemovable: boolean; }) => { - const [ localField, setLocalField ] = useState(condition.expressions[0].field); - const [ localOperator, setLocalOperator ] = useState(condition.expressions[0].operator); - const [ localValue, setLocalValue ] = useState(condition.expressions[0].value); - - const [ metaOperators, setMetaOperators ] = - useState(conditionExpressionsMeta[0]?.operators); - const [ metaValue, setMetaValue ] = useState( - conditionExpressionsMeta[0]?.value + const findMetaValuesAgainst: ConditionExpressionMetaInterface = conditionExpressionsMeta.find( + (expressionMeta: ConditionExpressionMetaInterface) => expressionMeta.field.name === expression.field ); - useEffect(() => { - conditionExpressionsMeta?.map((expressionMeta: RuleComponentMetaInterface) => { - if (expressionMeta.field.name === localField) { - setMetaOperators(expressionMeta.operators); - setMetaValue(expressionMeta.value); - - return; - } - - return; - }); - }, [ localField ]); - /** * Debounced function to handle the change of the condition expression. + * * @param changedValue - Changed value. * @param ruleId - Rule id. * @param conditionId - Condition id. @@ -146,7 +131,7 @@ const RuleConditions: FunctionComponent = ({ * @param fieldName - Field name. * @returns Debounced function. */ - const handleChangeDebounced: ( + const handleExpressionChangeDebounced: ( changedValue: string, ruleId: string, conditionId: string, @@ -160,31 +145,33 @@ const RuleConditions: FunctionComponent = ({ expressionId: string, fieldName: ExpressionFieldTypes ) => { - updateRuleConditionExpression(changedValue, ruleId, conditionId, expressionId, fieldName); + updateConditionExpression(changedValue, ruleId, conditionId, expressionId, fieldName); }, 300 ); /** * Value input autocomplete component. + * * @param metaValue - Meta value. * @param resourceType - Resource type. * @returns Value input autocomplete component. */ const ValueInputAutocomplete: FunctionComponent = ({ - metaValue, - resourceType + localValue, + resourceType, + initialResourcesLoadUrl, + filterBaseResourcesUrl }: ValueInputAutocompleteProps) => { const [ inputValue, setInputValue ] = useState(localValue); const [ options, setOptions ] = useState([]); const [ open, setOpen ] = useState(false); - const initialLoadUrl: string = metaValue?.links.find((link: LinkInterface) => link.rel === "values")?.href; - const filterBaseUrl: string = metaValue?.links.find((link: LinkInterface) => link.rel === "filter")?.href; - const filterUrl: string = inputValue ? filterBaseUrl?.replace("*", inputValue) : null; - - const { data: initialResources = [], isLoading: isInitialLoading } = useGetResourcesList(initialLoadUrl); - + const filterUrl: string = inputValue ? + filterBaseResourcesUrl?.replace("*", inputValue) : initialResourcesLoadUrl; + const { data: initialResources = [], isLoading: isInitialLoading } = useGetResourcesList( + initialResourcesLoadUrl + ); const { data: filteredResources = [], isLoading: isFiltering } = useGetResourcesList(filterUrl); useEffect(() => { @@ -207,15 +194,16 @@ const RuleConditions: FunctionComponent = ({ options={ options || [] } getOptionLabel={ (option: { name: string }) => option.name || "" } loading={ isInitialLoading || isFiltering } - value={ options.find((option: any) => option.name === inputValue) || null } + value={ (options || []).some((option: any) => option.name === inputValue) + ? options.find((option: any) => option.name === inputValue) + : null } onChange={ (event: React.SyntheticEvent, value: { name: string } | null) => { if (value) { - setLocalValue(value.name); - handleChangeDebounced( + handleExpressionChangeDebounced( value.name, ruleId, - condition.id, - condition.expressions[0].id, + conditionId, + expression.id, ExpressionFieldTypes.Value ); } @@ -239,47 +227,92 @@ const RuleConditions: FunctionComponent = ({ } } /> ) } + renderOption={ (props: any, option: { name: string }) => ( +
  • + { option.name } +
  • + ) } /> ); }; /** - * Condition value input component. - * @param metaValue - Meta value. - * @returns Condition value input component. + * Resource list select component. + * + * @returns Resource list select component. */ - const ConditionValueInput: FunctionComponent = (props: any) => { - const { metaValue } = props; + const ResourceListSelect: FunctionComponent = (props: any) => { + const { initialResourcesLoadUrl, filterBaseResourcesUrl } = props; let resourcesList: any = null; let resourceType: string = ""; - // Handle fetching data unconditionally - const resourcesListLink: string = - metaValue?.links?.find((link: LinkInterface) => link.rel === "values")?.href; + const { data: fetchedResourcesList } = useGetResourcesList(initialResourcesLoadUrl); - const { data: fetchedResourcesList } = useGetResourcesList(resourcesListLink || null); + // Determine resourcesList if it's needed + if (findMetaValuesAgainst?.value?.links?.length > 1 && fetchedResourcesList) { + resourcesList = fetchedResourcesList; + } - if (localField === "application") { + // TODO: Handle other resource types once the API is ready + if (expression.field === "application") { resourceType = "applications"; } - // Determine resourcesList if it's needed - if (metaValue?.links?.length > 1 && fetchedResourcesList) { - resourcesList = fetchedResourcesList; + if (resourcesList) { + if (resourcesList.count < 10) { + return ( + + ); + } + + return ( + + ); } + }; + + /** + * Condition value input component. + * + * @param metaValue - Meta value. + * @returns Condition value input component. + */ + const ConditionValueInput: FunctionComponent = (props: any) => { + const { metaValue } = props; - if (metaValue?.inputType === "input") { + if (metaValue?.inputType === "input" || null) { return ( ) => { - setLocalValue(e.target.value); - handleChangeDebounced( + handleExpressionChangeDebounced( e.target.value, ruleId, - condition.id, - condition.expressions[0].id, + conditionId, + expression.id, ExpressionFieldTypes.Value ); } } @@ -291,20 +324,19 @@ const RuleConditions: FunctionComponent = ({ if (metaValue?.values?.length > 1) { return ( { - setLocalValue(e.target.value); - updateRuleConditionExpression( - e.target.value, - ruleId, - condition.id, - condition.expressions[0].id, - ExpressionFieldTypes.Value - ); - } } - > - { resourcesList[resourceType]?.map((item: any, index: number) => ( - - { item.name } - - )) } - - ); + return null; } return null; @@ -355,27 +379,26 @@ const RuleConditions: FunctionComponent = ({ > { - setLocalOperator(e.target.value); - updateRuleConditionExpression( + updateConditionExpression( e.target.value, ruleId, - condition.id, - condition.expressions[0].id, + conditionId, + expression.id, ExpressionFieldTypes.Operator ); } } > - ; - { metaOperators?.map((item: ListDataInterface, index: number) => ( - + { findMetaValuesAgainst?.operators?.map((item: ListDataInterface, index: number) => ( + { item.displayName } )) } - + - { conditionRemovable && ( + { isConditionExpressionRemovable && ( removeRuleCondition(ruleId, condition.id) } + onClick={ () => removeRuleConditionExpression(ruleId, expression.id) } > @@ -434,34 +462,54 @@ const RuleConditions: FunctionComponent = ({ ); }; - const ConditionOrDivider = (props: any) => ( - - - - ); - return ( <> - { conditions?.map((condition: RuleConditionsInterface, index: any) => ( - <> - { condition?.condition === ConditionTypes.Or && conditions[index - 1]?.id && ( - - ) } - - - - - )) } - { conditions?.length > 0 && - } + { ruleConditions?.map( + (condition: RuleConditionInterface, index: number) => + ruleInstance?.condition === AdjoiningOperatorTypes.Or && ( + + { condition.condition === AdjoiningOperatorTypes.And && ( + <> + { condition.expressions?.map( + (expression: ConditionExpressionInterface, exprIndex: number) => ( + + 1 || + ruleInstance.rules.length > 1 + } + /> + + ) + ) } + + ) } + { condition.expressions?.length > 0 && ( + + + + ) } + + ) + ) } ); }; diff --git a/features/admin.rules.v1/components/rules-component.tsx b/features/admin.rules.v1/components/rules-component.tsx index e2b871d1622..e8555b9290b 100644 --- a/features/admin.rules.v1/components/rules-component.tsx +++ b/features/admin.rules.v1/components/rules-component.tsx @@ -18,8 +18,9 @@ import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; -import Rules from "./rules"; -import { RuleComponentMetaDataInterface, RuleExecutionMetaDataInterface, RuleInterface } from "../models/rules"; +import RuleExecutionComponent from "./rules"; +import { ConditionExpressionsMetaDataInterface, RuleExecutionMetaDataInterface } from "../models/meta"; +import { RuleExecuteCollectionInterface, RuleInterface } from "../models/rules"; import { RulesProvider } from "../providers/rules-provider"; import "./rules-component.scss"; @@ -30,43 +31,54 @@ interface RulesComponentPropsInterface extends IdentifiableComponentInterface { /** * Initial data to be passed to the rules component. */ - initialData?: RuleInterface[]; + initialData?: RuleExecuteCollectionInterface | RuleInterface; /** - * Meta data to be passed to the rules component. + * Rule expressions meta data. */ - metaData: RuleComponentMetaDataInterface; + conditionExpressionsMetaData: ConditionExpressionsMetaDataInterface; /** - * Multiple rules flag. + * Is multiple rules flag. */ - multipleRules?: boolean; + isMultipleRules?: boolean; /** - * Rule execution meta data. + * Rule executions meta data. */ - ruleExecutions?: RuleExecutionMetaDataInterface[]; + ruleExecutionMetaData?: RuleExecutionMetaDataInterface; } +/** + * Props interface of {@link RulesComponent} + */ type RulesComponentPropsWithValidation = - | (RulesComponentPropsInterface & { multipleRules: true; ruleExecutions: any }) - | (RulesComponentPropsInterface & { multipleRules?: false; ruleExecutions?: never }); + | (RulesComponentPropsInterface & { isMultipleRules: true; ruleExecutionMetaData: any }) + | (RulesComponentPropsInterface & { isMultipleRules?: false; ruleExecutionMetaData?: never }); /** - * Landing page for the Flows feature. + * Rules component to render. * * @param props - Props injected to the component. - * @returns Flows page component. + * @returns Rule component. + * */ const RulesComponent: FunctionComponent = ({ ["data-componentid"]: componentId = "rules-component", initialData, - metaData, - multipleRules, - ruleExecutions + conditionExpressionsMetaData, + isMultipleRules, + ruleExecutionMetaData }: RulesComponentPropsWithValidation): ReactElement => ( - - + + ); diff --git a/features/admin.rules.v1/components/rules.tsx b/features/admin.rules.v1/components/rules.tsx index a75a4a8d911..0b7b4a9ed02 100644 --- a/features/admin.rules.v1/components/rules.tsx +++ b/features/admin.rules.v1/components/rules.tsx @@ -32,6 +32,7 @@ import { IdentifiableComponentInterface } from "@wso2is/core/models"; import React, { FunctionComponent, ReactElement } from "react"; import RuleConditions from "./rule-conditions"; import { useRulesContext } from "../hooks/use-rules-context"; +import { ListDataInterface } from "../models/meta"; import { RuleInterface } from "../models/rules"; import "./rules.scss"; @@ -40,26 +41,33 @@ import "./rules.scss"; */ export interface RulesPropsInterface extends IdentifiableComponentInterface { /** - * Multiple rules flag. + * Is multiple rules flag. */ - multipleRules: boolean; + isMultipleRules: boolean; } /** - * Rules component to render. + * Rules execution component to render. * * @param props - Props injected to the component. * @returns Rule component. */ -const Rules: FunctionComponent = ({ +const RuleExecutionComponent: FunctionComponent = ({ ["data-componentid"]: componentId = "rules-render-component", - multipleRules = false + isMultipleRules = false }: RulesPropsInterface): ReactElement => { - const { rulesInstance, conditionsMeta, addNewRule, removeRule, updateRuleExecution } = useRulesContext(); + const { + ruleExecuteCollection, + ruleExecutionsMeta, + addNewRule, + removeRule, + updateRulesFallbackExecution, + updateRuleExecution + } = useRulesContext(); return (
    - { multipleRules && ( + { isMultipleRules && ( ) } - { rulesInstance?.map((ruleInstance: RuleInterface, index: number) => ( + { ruleExecuteCollection?.rules?.map( + (rule: RuleInterface, index: number) => ( + + + + Execute + + { ruleExecutionsMeta?.executions ? ( + + + + + + ) : ( +   + ) } + + If + + + + { ruleExecuteCollection?.rules?.length > 1 && ( + removeRule(rule.id) } + > + + + ) } + + ) + ) } + { isMultipleRules && ( - Execute + Else Execute - { ruleInstance.execution ? ( - - - updateRulesFallbackExecution(event) } + > + { ruleExecutionsMeta?.fallbackExecutions?.map( + (item: ListDataInterface, index: number) => ( + { item.displayName } - )) } - - - - ) : ( -   - ) } - - If + ) + ) } + + - 1 } - /> - { rulesInstance?.length > 1 && ( - removeRule(ruleInstance.id) } - > - - - ) } - )) } + ) }
    ); }; -export default Rules; +export default RuleExecutionComponent; diff --git a/features/admin.rules.v1/contexts/rules-context.ts b/features/admin.rules.v1/contexts/rules-context.ts index 07fd748da5c..cdf7cf36df9 100644 --- a/features/admin.rules.v1/contexts/rules-context.ts +++ b/features/admin.rules.v1/contexts/rules-context.ts @@ -18,26 +18,27 @@ import { SelectChangeEvent } from "@mui/material"; import { createContext } from "react"; -import { ConditionTypes, ExpressionFieldTypes, RuleComponentMetaDataInterface, RuleInterface } from "../models/rules"; +import { ConditionExpressionsMetaDataInterface, RuleExecutionMetaDataInterface } from "../models/meta"; +import { AdjoiningOperatorTypes, ExpressionFieldTypes, RuleExecuteCollectionInterface } from "../models/rules"; /** * Interface for the RulesContext. */ export interface RulesContextInterface { /** - * Rules instance. + * Rules execute instances. */ - rulesInstance: RuleInterface[]; + ruleExecuteCollection: RuleExecuteCollectionInterface; /** - * Conditions meta. + * Rule executions meta. */ - conditionsMeta: any; + ruleExecutionsMeta: RuleExecutionMetaDataInterface; /** - * Condition expressions meta. + * Rule condition expressions meta. */ - conditionExpressionsMeta: RuleComponentMetaDataInterface; + conditionExpressionsMeta: ConditionExpressionsMetaDataInterface; /** * Method to add a new rule. @@ -50,27 +51,37 @@ export interface RulesContextInterface { removeRule: (id: string) => void; /** - * Method to add a new rule condition. + * Method to add a new rule condition expression. */ - addNewRuleCondition: (ruleId: string, previousConditionInstanceId: string, conditionType: ConditionTypes) => void; + addNewRuleConditionExpression: ( + ruleId: string, + conditionId: string, + conditionType: AdjoiningOperatorTypes, + previousExpressionId?: string, + ) => void; /** - * Method to remove a rule condition. + * Method to remove a rule condition expression. */ - removeRuleCondition: (ruleId: string, conditionId: string) => void; + removeRuleConditionExpression: (ruleId: string, expressionId: string) => void; /** - * Method to update the rule execution type. + * Method to update the rule execution. */ updateRuleExecution: (event: SelectChangeEvent, id: string) => void; + /** + * Method to update the rules fallback execution. + */ + updateRulesFallbackExecution: (event: SelectChangeEvent) => void; + /** * Method to update the rule condition expression value. */ - updateRuleConditionExpression: ( + updateConditionExpression: ( changedValue: string, ruleId: string, - conidtionId: string, + conditionId: string, expressionId: string, fieldName: ExpressionFieldTypes ) => void; @@ -79,8 +90,9 @@ export interface RulesContextInterface { /** * Create the context */ -const RulesContext: React.Context = - createContext(undefined); +const RulesContext: React.Context = createContext( + undefined +); RulesContext.displayName = "RulesContext"; diff --git a/features/admin.rules.v1/data.ts b/features/admin.rules.v1/data.ts new file mode 100644 index 00000000000..4504bc83c0f --- /dev/null +++ b/features/admin.rules.v1/data.ts @@ -0,0 +1,280 @@ +/** + * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ConditionExpressionsMetaDataInterface, RuleExecutionMetaDataInterface } from "./models/meta"; +import { RuleExecuteCollectionInterface, RuleInterface } from "./models/rules"; + +export const sampleRuleExecuteInstance: RuleInterface = { + condition: "OR", + execution: "totp", + id: "e9a500d4-12dd-4b87-ad7c-9efdacf8a2a5", + rules: [ + { + condition: "AND", + expressions: [ + { + field: "groups", + id: "2cf9e8cc-1f28-4ae5-ac18-06a5c144db6b", + operator: "equal", + order: 1, + value: "employee" + }, + { + field: "roles", + id: "8cbe103e-4c86-4021-91e4-83c37fe893a9", + operator: "equal", + order: 2, + value: "staff" + } + ], + id: "c90293c7-3fc1-465e-a946-adab6170e2dc", + order: 1 + }, + { + condition: "AND", + expressions: [ + { + field: "groups", + id: "2f20d40f-f1a8-4ffc-bd88-6040592495fc", + operator: "equal", + order: 1, + value: "guest" + } + ], + id: "b21cbf6c-985f-47fc-ba88-dfbbed7a38d0", + order: 2 + } + ] +}; + +export const sampleRuleExecuteInstances: RuleExecuteCollectionInterface = { + fallbackExecution: "end", + rules: [ + sampleRuleExecuteInstance, + { + condition: "OR", + execution: "email_otp", + id: "e9a500d4-12dd-4b87-ad7cfr-9efdsdacf8a2a6", + rules: [ + { + condition: "AND", + expressions: [ + { + field: "groups", + id: "2cf9e8cc-1f28-4ae5-ac18-06a5dfc144db6b", + operator: "equal", + order: 1, + value: "employee" + } + ], + id: "c90293c7-3fc1-465e-a946-adab6170fge2dc", + order: 1 + } + ] + } + ] +}; + +export const sampleApplicationList: any = { + applications: [ + { + access: "READ", + accessUrl: "https://localhost:9443/console", + applicationVersion: "v2.0.0", + description: "This is the console application.", + id: "e028224b-51d1-4b26-a0f0-6e41f45472aa", + name: "Console", + self: "/api/server/v1/applications/e028224b-51d1-4b26-a0f0-6e41f45472aa" + }, + { + access: "WRITE", + accessUrl: "https://localhost:9443/myaccount", + applicationVersion: "v2.0.0", + description: "This is the my account application.", + id: "dc2bf156-008e-4cd2-afb1-e31825277429", + name: "My Account", + self: "/api/server/v1/applications/dc2bf156-008e-4cd2-afb1-e31825277429" + } + ], + count: 2, + links: [], + startIndex: 1, + totalResults: 2 +}; + +export const sampleRuleExecutionMeta: RuleExecutionMetaDataInterface = { + executions: [ + { + displayName: "TOTP", + name: "totp" + }, + { + displayName: "Email OTP", + name: "email_otp" + } + ], + fallbackExecutions: [ + { + displayName: "Error", + name: "error" + }, + { + displayName: "End", + name: "end" + } + ] +}; + +export const sampleExpressionsMeta: ConditionExpressionsMetaDataInterface = [ + { + field: { + displayName: "application", + name: "application" + }, + operators: [ + { + displayName: "equal", + name: "equal" + }, + { + displayName: "not equal", + name: "notEqual" + } + ], + value: { + inputType: "options", + links: [ + { + href: "/applications?offset=0&limit=10", + method: "GET", + rel: "values" + }, + { + href: "/applications?filter=name+eq+*&limit=10", + method: "GET", + rel: "filter" + } + ], + valueDisplayAttribute: "name", + valueReferenceAttribute: "id", + valueType: "reference" + } + }, + { + field: { + displayName: "groups", + name: "groups" + }, + operators: [ + { + displayName: "equal", + name: "equal" + }, + { + displayName: "not equal", + name: "notEqual" + } + ], + value: { + inputType: "options", + valueType: "string", + values: [ + { + displayName: "employee", + name: "employee" + }, + { + displayName: "guest", + name: "guest" + } + ] + } + }, + { + field: { + displayName: "roles", + name: "roles" + }, + operators: [ + { + displayName: "equal", + name: "equal" + }, + { + displayName: "not equal", + name: "notEqual" + } + ], + value: { + inputType: "options", + valueType: "string", + values: [ + { + displayName: "staff", + name: "staff" + }, + { + displayName: "admin", + name: "admin" + } + ] + } + }, + { + field: { + displayName: "grant type", + name: "grantType" + }, + operators: [ + { + displayName: "equal", + name: "equal" + }, + { + displayName: "not equal", + name: "notEqual" + } + ], + value: { + inputType: "options", + valueType: "string", + values: [ + { + displayName: "authorization code", + name: "authorization_code" + }, + { + displayName: "password", + name: "password" + }, + { + displayName: "refresh token", + name: "refresh_token" + }, + { + displayName: "client credentials", + name: "client_credentials" + }, + { + displayName: "token exchange", + name: "urn:ietf:params:oauth:grant-type:token-exchange" + } + ] + } + } +]; diff --git a/features/admin.rules.v1/data.tsx b/features/admin.rules.v1/data.tsx deleted file mode 100644 index 7a710123c2b..00000000000 --- a/features/admin.rules.v1/data.tsx +++ /dev/null @@ -1,202 +0,0 @@ -/** - * Copyright (c) 2024, WSO2 LLC. (https://www.wso2.com). - * - * WSO2 LLC. licenses this file to you under the Apache License, - * Version 2.0 (the "License"); you may not use this file except - * in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { RuleComponentMetaDataInterface, RuleInterface } from "./models/rules"; - -export const sampleRules: RuleInterface[] = [ - { - conditions: [ - { - condition: "AND", - expressions: [ - { - field: "application", - id: "8cbe103e-4c86-4021-91e4-83c37effe893a9267753", - operator: "equals", - order: 0, - value: "c90293c7-3fc1-465e-a946" - } - ], - id: "c90293c7-3fc1-465e-a946-adab6170e2fcdc", - order: 0 - }, - { - condition: "AND", - expressions: [ - { - field: "grantType", - id: "8cbe103e-4c86-4021-91e4-83dsfc37fe89ee3a9", - operator: "equals", - order: 0, - value: "authorization_code" - } - ], - id: "c90293c7-3fc1-465e-a946-adab6170e2dc443", - order: 0 - }, - { - condition: "OR", - expressions: [ - { - field: "grantType", - id: "8cbe103e-4c86-4021-91e4-83cdsf37fe893a9", - operator: "notEquals", - order: 0, - value: "client_credentials" - } - ], - id: "c90293c7-3fc1-465e-a946-adab6170e2dc", - order: 0 - }, - { - condition: "OR", - expressions: [ - { - field: "application", - id: "8cbe103e-4c86-4021-91e4-83c3fdsff437fe893a9", - operator: "equals", - order: 0, - value: "8cbe103e-4c86-4021" - } - ], - id: "c90293c7-3fc1-465e-a946-adab61e3270e2dc", - order: 0 - } - ], - execution: "totp", - id: "e9a500d4-12dd-4b87-ad7c-9efdacf8a2a5" - } -]; - -export const sampleApplicationList: any = { - applications: [ - { - access: "READ", - accessUrl: "https://localhost:9443/console", - applicationVersion: "v2.0.0", - description: "This is the console application.", - id: "e028224b-51d1-4b26-a0f0-6e41f45472aa", - name: "Console", - self: "/api/server/v1/applications/e028224b-51d1-4b26-a0f0-6e41f45472aa" - }, - { - access: "WRITE", - accessUrl: "https://localhost:9443/myaccount", - applicationVersion: "v2.0.0", - description: "This is the my account application.", - id: "dc2bf156-008e-4cd2-afb1-e31825277429", - name: "My Account", - self: "/api/server/v1/applications/dc2bf156-008e-4cd2-afb1-e31825277429" - } - ], - count: 2, - links: [], - startIndex: 1, - totalResults: 2 -}; - -// TODO: Replace this with the actual data from the API. -export const sampleExecutionsList: any = [ - { - displayName: "Show TOTP", - value: "dc2bf156-008e-4cdws2-afb1" - }, - { - displayName: "Rule", - value: "dc2bf156-008e-4cd2-afb1sa" - } -]; - -export const sampleExpressionsMeta: RuleComponentMetaDataInterface = [ - { - field: { - displayName: "application", - name: "application" - }, - operators: [ - { - displayName: "= Equals", - name: "equals" - }, - { - displayName: "!= Not equals", - name: "notEquals" - } - ], - value: { - inputType: "options", - links: [ - { - href: "/applications?offset=0&limit=10", - method: "GET", - rel: "values" - }, - { - href: "/applications?filter=name+eq+*&limit=10", - method: "GET", - rel: "filter" - } - ], - valueDisplayAttribute: "name", - valueReferenceAttribute: "id", - valueType: "reference" - } - }, - { - field: { - displayName: "grant type", - name: "grantType" - }, - operators: [ - { - displayName: "= Equals", - name: "equals" - }, - { - displayName: "!= Not equals", - name: "notEquals" - } - ], - value: { - inputType: "options", - valueType: "string", - values: [ - { - displayName: "authorization code", - name: "authorization_code" - }, - { - displayName: "password", - name: "password" - }, - { - displayName: "refresh token", - name: "refresh_token" - }, - { - displayName: "client credentials", - name: "client_credentials" - }, - { - displayName: "token exchange", - name: "urn:ietf:params:oauth:grant-type:token-exchange" - } - ] - } - } -]; diff --git a/features/admin.rules.v1/models/meta.ts b/features/admin.rules.v1/models/meta.ts new file mode 100644 index 00000000000..7f8a81b244a --- /dev/null +++ b/features/admin.rules.v1/models/meta.ts @@ -0,0 +1,68 @@ +/** + * Copyright (c) 2025, WSO2 LLC. (https://www.wso2.com). + * + * WSO2 LLC. licenses this file to you under the Apache License, + * Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Interface to represent the rule execution data. + */ +export interface ListDataInterface { + name: string; + displayName: string; +} + +/** + * Interface to represent the rule execution data. + */ +export interface LinkInterface { + href: string; + method: string; + rel: "values" | "filter"; +} + +/** + * Interface to represent the condition expression meta data value. + */ +export interface ExpressionValueInterface { + inputType: "input" | "options"; + valueType: string; + valueReferenceAttribute?: string; + valueDisplayAttribute?: string; + links?: LinkInterface[]; + values?: ListDataInterface[]; +} + +/** + * Interface to represent the condition expression meta data. + */ +export interface ConditionExpressionMetaInterface { + field: ListDataInterface, + operators: ListDataInterface[], + value: ExpressionValueInterface; +} + +/** + * Interface to represent the condition expressions meta data. + */ +export type ConditionExpressionsMetaDataInterface = ConditionExpressionMetaInterface[]; + +/** + * Interface to represent the rule executions meta data. + */ +export interface RuleExecutionMetaDataInterface { + executions: ListDataInterface[]; + fallbackExecutions: ListDataInterface[]; +} diff --git a/features/admin.rules.v1/models/rules.ts b/features/admin.rules.v1/models/rules.ts index 043f1c13faa..fffcb7e3c6c 100644 --- a/features/admin.rules.v1/models/rules.ts +++ b/features/admin.rules.v1/models/rules.ts @@ -16,50 +16,10 @@ * under the License. */ -/** - * Enum to represent the rule executions. - */ -export enum RuleExecutions { - PreIssueAccessToken = "PreIssueAccessToken", - PrePasswordUpdate = "prePasswordUpdate", - PreProfileUpdate = "preProfileUpdate", - PreLogin = "preLogin", - PostLogin = "postLogin", - InLogin = "inLogin", - PreRegistration = "preRegistration", - InRegistration = "inRegistration", - InPasswordExpiry = "inPasswordExpiry" -} - -/** - * Interface to represent the rule execution data. - */ -export interface ListDataInterface { - name: string; - displayName: string; -} - -/** - * Interface to represent the rule execution data. - */ -export interface LinkInterface { - href: string; - method: string; - rel: string; -} - -/** - * Interface to represent the rule execution meta data. - */ -export interface RuleExecutionMetaDataInterface { - id: string - name: string -} - /** * Enum to represent the rule condition types. */ -export enum ConditionTypes { +export enum AdjoiningOperatorTypes { And = "AND", Or = "OR" } @@ -74,9 +34,9 @@ export enum ExpressionFieldTypes { } /** - * Enum to represent the rule expression operators. + * Interface to represent the condition expression data. */ -export interface ExpressionInterface { +export interface ConditionExpressionInterface { id: string; field: string; operator: string; @@ -85,64 +45,44 @@ export interface ExpressionInterface { } /** - * Interface to represent the rule conditions. + * Interface to represent the condition expressions data. */ -export interface RuleConditionsInterface { +export type ConditionExpressionsInterface = ConditionExpressionInterface[]; + +/** + * Interface to represent the rule condition data. + */ +export interface RuleConditionInterface { id: string; condition: string; order: number; - expressions: ExpressionInterface[]; + expressions: ConditionExpressionsInterface; } /** - * Interface to represent the rule instance. + * Interface to represent the rule conditions data. */ -export interface RuleInterface { - id: string; - execution?: string - conditions: RuleConditionsInterface[]; -} +export type RuleConditionsInterface = RuleConditionInterface[]; /** - * Interface to represent the rule component data. + * Interface to represent the rule data. */ -export interface RuleComponentDataInterface { +export interface RuleInterface { + condition: string; id: string; - type: string; - name: string; - description: string; - status: string, - endpoint: { - uri: string, - authentication: { - type: string - } - }, - rules: RuleInterface[]; + rules: RuleConditionsInterface; + execution?: string; } /** - * Interface to represent the value of a rule component. + * Interface to represent the rules data. */ -export interface RuleComponentExpressionValueInterface { - inputType: "input" | "options"; - valueType: string; - valueReferenceAttribute?: string; - valueDisplayAttribute?: string; - links?: LinkInterface[]; - values?: ListDataInterface[]; -} +export type RulesInterface = RuleInterface[]; /** - * Interface to represent the rule component meta. + * Interface to represent the rules execution data. */ -export interface RuleComponentMetaInterface { - field: ListDataInterface, - operators: ListDataInterface[], - value: RuleComponentExpressionValueInterface; +export interface RuleExecuteCollectionInterface { + fallbackExecution?: string; + rules: RulesInterface; } - -/** - * Interface to represent the rule component meta data. - */ -export type RuleComponentMetaDataInterface = RuleComponentMetaInterface[]; diff --git a/features/admin.rules.v1/providers/rules-provider.tsx b/features/admin.rules.v1/providers/rules-provider.tsx index 3e60daa4e65..0cc0e7fc54d 100644 --- a/features/admin.rules.v1/providers/rules-provider.tsx +++ b/features/admin.rules.v1/providers/rules-provider.tsx @@ -20,104 +20,127 @@ import { SelectChangeEvent } from "@mui/material"; import React, { ReactNode, useEffect, useState } from "react"; import { v4 as uuidv4 } from "uuid"; import RulesContext from "../contexts/rules-context"; +import { ConditionExpressionsMetaDataInterface, RuleExecutionMetaDataInterface } from "../models/meta"; import { - ConditionTypes, + AdjoiningOperatorTypes, + ConditionExpressionInterface, + ConditionExpressionsInterface, ExpressionFieldTypes, - ExpressionInterface, - RuleComponentMetaDataInterface, - RuleConditionsInterface, - RuleExecutionMetaDataInterface, + RuleConditionInterface, + RuleExecuteCollectionInterface, RuleInterface } from "../models/rules"; // Refference to hold the latest context value -const RuleContextRef: { ruleInstance: RuleInterface[] | undefined } = { ruleInstance: undefined }; +const RuleContextRef: { ruleInstance: RuleExecuteCollectionInterface | undefined } = { + ruleInstance: undefined +}; /** * Method to get the context value * * @returns RuleInstanceData */ -export const getRuleInstanceValue = () => RuleContextRef.ruleInstance; +export const getRuleInstanceValue = () => Object.freeze(RuleContextRef.ruleInstance); /** * Provider for the RulesContext * * @param children - ReactNode - * @param metaData - RuleComponentMetaDataInterface - * @param initialData - RuleInterface[] | null + * @param conditionExpressionsMetaData - ConditionExpressionsMetaDataInterface + * @param initialData - RuleExecutionInterface + * @param ruleExecutionsMetaData - RuleExecutionMetaDataInterface * @returns RulesProvider */ export const RulesProvider = ({ children, - metaData, initialData, - ruleExecutions + conditionExpressionsMetaData, + ruleExecutionMetaData }: { children: ReactNode; - metaData: RuleComponentMetaDataInterface; - initialData: RuleInterface[] | []; - ruleExecutions: RuleExecutionMetaDataInterface; + conditionExpressionsMetaData: ConditionExpressionsMetaDataInterface; + initialData: RuleExecuteCollectionInterface | RuleInterface; + ruleExecutionMetaData: RuleExecutionMetaDataInterface; }) => { - const [ rulesInstance, setRuleInstance ] = useState(initialData); + let RuleExecutionData: any = initialData; + + // Check if initialData is a single rule object and if it doesn't have fallbackExecution + // transform it to a collection + if (RuleExecutionData && !RuleExecutionData?.fallbackExecution) { + RuleExecutionData = { + fallbackExecution: "", + rules: [ RuleExecutionData ] + }; + } - const conditionsMeta: RuleExecutionMetaDataInterface | undefined[] = ruleExecutions ?? []; - const conditionExpressionsMeta: RuleComponentMetaDataInterface | undefined[] = metaData ?? []; + const [ ruleComponentInstance, setRuleComponentInstance ] = + useState(RuleExecutionData ?? undefined); - // Update the ref whenever the context value changes - RuleContextRef.ruleInstance = rulesInstance; + const ruleComponentInstanceConditionExpressionsMeta: ConditionExpressionsMetaDataInterface | undefined = + conditionExpressionsMetaData ?? undefined; + const ruleComponentInstanceExecutionsMeta: RuleExecutionMetaDataInterface | undefined = + ruleExecutionMetaData ?? undefined; - const getNewRuleInstanceConditionExpression = (): ExpressionInterface => { - const newRuleConditionExpressionUUID: string = uuidv4(); + // Update the ref whenever the context value changes + RuleContextRef.ruleInstance = ruleComponentInstance; + /** + * Method to get a new rule expression. + * + * @returns Rule Expression + */ + const getNewConditionExpressionInstance = (): ConditionExpressionInterface => { return { - field: metaData?.[0]?.field?.name, - id: newRuleConditionExpressionUUID, - operator: metaData?.[0]?.operators?.[0]?.name, + field: conditionExpressionsMetaData?.[0]?.field?.name, + id: uuidv4(), + operator: conditionExpressionsMetaData?.[0]?.operators?.[0]?.name, order: 0, value: "" }; }; /** - * Method to create a new rule instance condition. + * Method to get a new rule. * - * @param condition - ConditionTypes - * @returns RuleConditionsInterface + * @param condition - AdjoiningOperatorTypes + * @returns Rule */ - const getNewRuleInstanceCondition = ( - condition: ConditionTypes, + const getNewRuleConditionInstance = ( + condition: AdjoiningOperatorTypes, orderIndex: number = 0 - ): RuleConditionsInterface => { - const newRuleConditionUUID: string = uuidv4(); - + ): RuleConditionInterface => { return { condition: condition, - expressions: [ getNewRuleInstanceConditionExpression() ], - id: newRuleConditionUUID, + expressions: [ getNewConditionExpressionInstance() ], + id: uuidv4(), order: orderIndex }; }; /** - * Method to create a new rule instance. + * Method to get a new rule execute instance. * - * @returns RuleInterface + * @returns Rule Execute Instance */ const getNewRuleInstance = (): RuleInterface => { - const newRuleUUID: string = uuidv4(); - return { - conditions: [ getNewRuleInstanceCondition(ConditionTypes.And) ], - execution: conditionsMeta?.[0]?.value, - id: newRuleUUID + condition: AdjoiningOperatorTypes.Or, + id: uuidv4(), + rules: [ getNewRuleConditionInstance(AdjoiningOperatorTypes.And) ] }; }; useEffect(() => { - // If the initial data is not provided, create a new instance. + // If the initial data is not provided, add a new rule execution. if (!initialData) { - setRuleInstance([ getNewRuleInstance() ]); + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + fallbackExecution: ruleExecutionMetaData?.fallbackExecutions?.[0]?.name, + rules: [ getNewRuleInstance() ] + }; + }); } }, []); @@ -127,8 +150,8 @@ export const RulesProvider = ({ const handleAddNewRule = () => { const newRuleInstance: RuleInterface = getNewRuleInstance(); - setRuleInstance((prev: RuleInterface[]) => { - return [ ...prev, newRuleInstance ]; + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { ...prev, rules: [ ...prev.rules, newRuleInstance ] }; }); }; @@ -138,152 +161,207 @@ export const RulesProvider = ({ * @param id - string */ const handleRemoveRule = (id: string) => { - setRuleInstance((prev: RuleInterface[]) => { - return prev.filter((rule: RuleInterface) => rule?.id !== id); + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + rules: prev.rules?.filter( + (ruleExecution: RuleInterface) => ruleExecution?.id !== id) + }; }); }; /** - * Method to remove a rule condition instance. + * Method to update a rule execution. * + * @param event - SelectChangeEvent * @param id - string */ const handleRuleExecutionTypeChange = (event: SelectChangeEvent, id: string) => { const changedValue: string = event.target.value as string; - setRuleInstance((prev: RuleInterface[]) => { - return prev.map((rule: RuleInterface) => { - if (rule.id === id) { - return { ...rule, execution: changedValue }; - } + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + rules: prev.rules?.map((ruleExecution: RuleInterface) => { + if (ruleExecution.id === id) { + return { ...ruleExecution, execution: changedValue }; + } - return rule; - }); + return ruleExecution; + }) + }; + }); + }; + + /** + * Method to update a rules default execution. + * + * @param event - SelectChangeEvent + */ + const handleRulesFallbackExecutionTypeChange = (event: SelectChangeEvent) => { + const changedValue: string = event.target.value as string; + + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { ...prev, fallbackExecution: changedValue }; }); }; /** - * Method to update the rule condition expression value. + * Method to update the rule expression value. * * @param event - SelectChangeEvent | React.ChangeEvent - * @param ruleId - string + * @param ruleExecutionId - string * @param conditionId - string * @param expressionId - string * @param fieldName - ExpressionFieldTypes */ - const handleRuleConditionExpressionValueChange = ( + const handleConditionExpressionValueChange = ( changedValue: string, ruleId: string, conditionId: string, expressionId: string, fieldName: ExpressionFieldTypes ) => { - setRuleInstance((prev: RuleInterface[]) => { - return prev.map((rule: RuleInterface) => { - if (rule.id === ruleId) { - return { - ...rule, - conditions: rule.conditions.map((condition: RuleConditionsInterface) => { - if (condition.id === conditionId) { - return { - ...condition, - expressions: condition.expressions.map((expression: ExpressionInterface) => { - if (expression.id === expressionId) { - return { - ...expression, - [fieldName]: changedValue - }; - } - - return expression; - }) - }; - } - - return condition; - }) - }; - } - - return rule; - }); + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + rules: prev.rules?.map((rule: RuleInterface) => { + if (rule.id === ruleId) { + return { + ...rule, + rules: rule.rules?.map((condition: RuleConditionInterface) => { + // Handle undefined or empty rules + if (!condition) return condition; + + if (condition.id === conditionId) { + + return { + ...condition, + expressions: condition.expressions.map( + (expression: ConditionExpressionInterface) => { + if (expression.id === expressionId) { + return { + ...expression, + [fieldName]: changedValue + }; + } + + return expression; + } + ) + }; + } + + return condition; + }) + }; + } + + return rule; + }) + }; }); }; + /** - * Method to add a new rule condition. + * Method to add a new condition expression. * * @param ruleId - string - * @param previousConditionId - string - * @param conditionType - ConditionTypes + * @param conditionId - string + * @param expressionType - AdjoiningOperatorTypes + * @param previousExpressionId - string */ - const handleAddRuleCondition = (ruleId: string, previousConditionId: string, conditionType: ConditionTypes) => { - setRuleInstance((prev: RuleInterface[]) => { - return prev.map((rule: RuleInterface) => { - if (rule.id === ruleId) { - // Clone conditions to avoid mutating the original state - const updatedConditions: RuleConditionsInterface[] = [ ...rule.conditions ]; - - // Find the index of the item with the matching id - const index: number = updatedConditions.findIndex( - (condition: RuleConditionsInterface) => condition.id === previousConditionId - ); - - if (index === -1) { - return rule; + const handleAddConditionExpression = ( + ruleId: string, + conditionId: string, + expressionType: AdjoiningOperatorTypes, + previousExpressionId?: string + ) => { + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + rules: prev.rules?.map((rule: RuleInterface) => { + if (rule.id === ruleId) { + return { + ...rule, + rules: rule.rules?.flatMap((condition: RuleConditionInterface) => { + if (expressionType === AdjoiningOperatorTypes.Or) { + if (condition.id === conditionId) { + // Insert new condition after the matched condition + return [ + condition, + getNewRuleConditionInstance(AdjoiningOperatorTypes.And) + ]; + } + + return [ condition ]; + } else { + return { + ...condition, + expressions: condition.expressions.flatMap( + (expression: ConditionExpressionInterface) => { + if (expression.id === previousExpressionId) { + // Insert new expression after the matched expression + return [ + expression, + getNewConditionExpressionInstance() + ]; + } + + return [ expression ]; + } + ) + }; + } + }) + }; } - // Insert the new condition after the matched condition - updatedConditions.splice(index + 1, 0, getNewRuleInstanceCondition(conditionType, index + 1)); - - return { - ...rule, - conditions: updatedConditions - }; - } - - return rule; - }); + return rule; + }) + }; }); }; /** - * Method to remove a rule condition. + * Method to remove a condition expression. * * @param ruleId - string - * @param conditionId - string + * @param expressionId - string */ - const handleRemoveRuleCondition = (ruleId: string, conditionId: string) => { - setRuleInstance((prev: RuleInterface[]) => { - return prev.map((rule: RuleInterface) => { - if (rule.id === ruleId) { - // Clone conditions to avoid mutating the original state - const updatedConditions: RuleConditionsInterface[] = rule.conditions.filter( - (condition: RuleConditionsInterface, index: number) => { - // Skip the condition that is being removed - if (condition.id === conditionId) { - const nextCondition: RuleConditionsInterface = rule.conditions[index + 1]; - - // Handle special case where "OR" is followed by "AND" - if (condition.condition === "OR" && nextCondition?.condition === "AND") { - nextCondition.condition = "OR"; + const handleRemoveConditionExpression = (ruleId: string, expressionId: string) => { + setRuleComponentInstance((prev: RuleExecuteCollectionInterface) => { + return { + ...prev, + rules: prev.rules?.map((rule: RuleInterface) => { + if (rule.id === ruleId) { + return { + ...rule, + rules: rule.rules?.flatMap((condition: RuleConditionInterface) => { + // Remove the expression if it matches + const updatedExpressions: ConditionExpressionsInterface = condition.expressions.filter( + (expression: ConditionExpressionInterface) => expression.id !== expressionId + ); + + // If there are no expressions left, remove the condition + if (updatedExpressions.length === 0) { + return []; } - return false; // Exclude the removed condition - } - - return true; // Keep other conditions - } - ); - - return { - ...rule, - conditions: updatedConditions - }; - } + return [ + { + ...condition, + expressions: updatedExpressions + } + ]; + }) + }; + } - return rule; - }); + return rule; + }) + }; }); }; @@ -291,14 +369,15 @@ export const RulesProvider = ({ { children } diff --git a/scripts/update-version.js b/scripts/update-version.js index de380f817b0..effff405d42 100644 --- a/scripts/update-version.js +++ b/scripts/update-version.js @@ -1,7 +1,7 @@ /** - * Copyright (c) 2019, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. + * Copyright (c) 2019, WSO2 LLC. (https://www.wso2.com). * - * WSO2 Inc. licenses this file to you under the Apache License, + * WSO2 LLC. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at @@ -26,10 +26,12 @@ * pnpm update-version -- jenkins=true build=${BUILD_DISPLAY_NAME} pom=${POM_VERSION/-SNAPSHOT/} */ +/* eslint-disable no-console */ + +const { execSync } = require("child_process"); const path = require("path"); -const fs = require("fs-extra"); const XmlParser = require("fast-xml-parser"); -const { execSync } = require("child_process"); +const fs = require("fs-extra"); const packageJson = path.join(__dirname, "..", "package.json"); const pomXml = path.join(__dirname, "..", "pom.xml"); @@ -38,13 +40,14 @@ const workingDir = path.join(__dirname, ".."); const git = require("simple-git/promise")(workingDir); const getProjectVersion = function() { - const fileContent = fs.readFileSync(pomXml); - const pom = XmlParser.parse(fileContent.toString()); + const fileContent = fs.readFileSync(pomXml); + const pom = XmlParser.parse(fileContent.toString()); - return pom.project.version.replace("-SNAPSHOT", ""); + return pom.project.version.replace("-SNAPSHOT", ""); }; let packageJsonContent = require(packageJson); + packageJsonContent.version = getProjectVersion(); /** @@ -52,9 +55,14 @@ packageJsonContent.version = getProjectVersion(); */ fs.writeFileSync(packageJson, JSON.stringify(packageJsonContent, null, 4) + "\n"); -execSync("pnpm lerna version " + getProjectVersion() + " --yes --no-git-tag-version --force-publish && pnpm install --no-frozen-lockfile", { - cwd: path.join(__dirname, "..") -}); +execSync( + "pnpm lerna version " + + getProjectVersion() + + " --yes --no-git-tag-version --force-publish && pnpm install --no-frozen-lockfile", + { + cwd: path.join(__dirname, "..") + } +); console.log("lerna info update packages version to " + getProjectVersion()); @@ -64,46 +72,43 @@ console.log("lerna info update packages version to " + getProjectVersion()); const processArgs = process.argv.slice(2); let args = {}; -processArgs.map((arg) => { - const argSplit = arg.split("="); - args[argSplit[0]] = argSplit[1]; +processArgs.map(arg => { + const argSplit = arg.split("="); + + args[argSplit[0]] = argSplit[1]; }); -const packageFiles = [ - "package.json", - "lerna.json", - "pnpm-lock.yaml" -]; +const packageFiles = [ "package.json", "lerna.json", "pnpm-lock.yaml" ]; /** * Stage changed files */ if (args.jenkins) { - const BUILD = args.build ? "[Jenkins " + args.build + "] " : ""; - const RELEASE = args.pom ? "[Release " + args.pom + "] " : ""; + const BUILD = args.build ? "[Jenkins " + args.build + "] " : ""; + const RELEASE = args.pom ? "[Release " + args.pom + "] " : ""; - git.status().then((status) => { - if (status.files.length > 0) { - console.log("git info start staging version updated files"); + git.status().then(status => { + if (status.files.length > 0) { + console.log("git info start staging version updated files"); - status.files.map((file) => { - const filePath = file.path; - const fileName = filePath.split("/").slice(-1)[0]; + status.files.map(file => { + const filePath = file.path; + const fileName = filePath.split("/").slice(-1)[0]; - if (packageFiles.includes(fileName)) { - git.add(filePath); - console.log("git info stage " + filePath); - } - }); + if (packageFiles.includes(fileName)) { + git.add(filePath); + console.log("git info stage " + filePath); + } + }); - console.log("git success stage version updated files"); + console.log("git success stage version updated files"); - git.clean("dfx", (error) => { - console.log("git error failed clean: "); - console.log(error); - }); + git.clean("dfx", error => { + console.log("git error failed clean: "); + console.log(error); + }); - git.commit("[WSO2 Release]" + BUILD + " " + RELEASE + " update package versions"); - } - }); + git.commit("[WSO2 Release]" + BUILD + " " + RELEASE + " update package versions"); + } + }); }