From ecf595574f1ca7bac515f9a19e8e48b7f4227586 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 7 Jan 2025 20:42:24 +0200 Subject: [PATCH 1/5] Added Assign Step --- .../hbs/bot_responses_to_messages.handlebars | 14 +++++ .../AssignBuilder/assign-types.tsx | 34 +++++++++++++ .../AssignBuilder/assignElement.tsx | 36 +++++++++++++ .../FlowElementsPopup/AssignBuilder/index.tsx | 45 ++++++++++++++++ .../AssignBuilder/useAssignBuilder.tsx | 51 +++++++++++++++++++ .../FlowElementsPopup/AssignContent.tsx | 22 ++++++++ .../FlowElementsPopup/ConditionContent.tsx | 2 +- .../components/FlowElementsPopup/index.tsx | 2 + GUI/src/i18n/en/common.json | 1 + GUI/src/i18n/et/common.json | 1 + GUI/src/pages/ServiceFlowPage.tsx | 14 +++-- GUI/src/services/service-builder.ts | 9 ++++ GUI/src/store/new-services.store.ts | 7 +++ GUI/src/types/step-type.enum.ts | 1 + 14 files changed, 230 insertions(+), 9 deletions(-) create mode 100644 DSL/DMapper/hbs/bot_responses_to_messages.handlebars create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx create mode 100644 GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx create mode 100644 GUI/src/components/FlowElementsPopup/AssignContent.tsx diff --git a/DSL/DMapper/hbs/bot_responses_to_messages.handlebars b/DSL/DMapper/hbs/bot_responses_to_messages.handlebars new file mode 100644 index 00000000..073abafc --- /dev/null +++ b/DSL/DMapper/hbs/bot_responses_to_messages.handlebars @@ -0,0 +1,14 @@ +[ +{{#each data.botMessages}} + { + "chatId": "{{../data.chatId}}", + "content": "{{{choose text result}}}", + "buttons": "[{{#each buttons}}{\"title\": \"{{{title}}}\",\"payload\": \"{{{payload}}}\"}{{#unless @last}},{{/unless}}{{/each}}]", + "authorTimestamp": "{{../data.authorTimestamp}}", + "authorId": "{{../data.authorId}}", + "authorFirstName": "{{../data.authorFirstName}}", + "authorLastName": "{{../data.authorLastName}}", + "created": "{{../data.created}}" + }{{#unless @last}},{{/unless}} +{{/each}} +] diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx new file mode 100644 index 00000000..5778e862 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx @@ -0,0 +1,34 @@ +import { v4 as uuidv4 } from 'uuid'; + +export interface ElementGroupBuilderProps { + group?: AssignGroup; + onRemove?: (id: string) => void; + onChange: (config: any) => void; + seedGroup?: any; +} + +export interface Assign { + id: string; + key: string; + value: string; +} + +export interface AssignGroup { + id: string; + children: Assign[]; +} + +export const getInitialElement = () => { + return { + id: uuidv4(), + key: '', + value: '', + }; +} + +export const getInitialAssignGroup = () => { + return { + id: uuidv4(), + children: [getInitialElement()], + } as AssignGroup; +}; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx new file mode 100644 index 00000000..0af1520a --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -0,0 +1,36 @@ +import React from 'react'; +import { FormInput, Icon, Track } from 'components'; +import { MdDeleteOutline } from 'react-icons/md'; +import { Assign } from "./assign-types"; + +interface AssignElementProps { + element: Assign; + onRemove: (id: string) => void; + onChange: (element: Assign) => void; +} + +const AssignElement: React.FC = ({ element, onRemove, onChange }) => { + + const handleChange = (e: React.ChangeEvent) => { + change(e.target.name, e.target.value); + } + + const change = (name: string, value?: string) => { + onChange({ ...element, [name]: value }); + } + + return ( + + + + = + + + + + ); +} + +export default AssignElement; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx new file mode 100644 index 00000000..9135de16 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { MdDeleteOutline } from 'react-icons/md'; +import { useTranslation } from 'react-i18next'; +import { Icon, Track } from 'components'; +import { Assign, ElementGroupBuilderProps } from "./assign-types"; +import AssignElement from "./assignElement"; +import { useAssignBuilder } from './useAssignBuilder'; +import '../styles.scss'; + +const AssignBuilder: React.FC = ({ group, onRemove, onChange, seedGroup }) => { + const { t } = useTranslation(); + const { + elements, + addElement, + remove, + changeElement, + } = useAssignBuilder({ + group, + root: !onRemove, + onChange, + seedGroup, + }); + + return ( + + + + + {onRemove && ( + + )} + + + {elements?.map((element) => ( + + ))} + + ); +}; + +export default AssignBuilder; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx new file mode 100644 index 00000000..57752d1a --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx @@ -0,0 +1,51 @@ +import { useEffect, useState } from "react"; +import { Assign, getInitialElement, AssignGroup } from "./assign-types"; + +interface UseAssignBuilderProps { + group?: AssignGroup; + root?: boolean; + onChange: (group: AssignGroup) => void; + seedGroup?: any; +} + +export const useAssignBuilder = (config: UseAssignBuilderProps) => { + const elementsInitialValue = config.root ? (config.seedGroup?.children ?? []) : config.group!.children; + const seedGroup = config.seedGroup?.length > 0 || config.seedGroup?.children?.length ? config.seedGroup : getInitialElement(); + const groupInfoInitialValue = config.root ? seedGroup : config.group! + const [elements, setElements] = useState(elementsInitialValue); + const [groupInfo, setGroupInfo] = useState(groupInfoInitialValue); + + useEffect(() => { + config.onChange({ + ...groupInfo, + children: elements + }) + }, [elements, groupInfo]); + + const addElement = () => { + console.log('addElement'); + setElements([...elements, getInitialElement()]); + } + + const remove = (id: string) => { + setElements(elements.filter(x => x.id !== id)); + } + + const changeElement = (element: Assign) => setElementById(element.id, element); + + const onSubGroupChange = (parentId: string) => (rule: any) => setElementById(parentId, rule); + + const setElementById = (id: string, element: Assign) => { + const newElements = elements.map((x) => (x.id === id ? { ...element } : x)); + setElements(newElements); + }; + + return { + // groupInfo, + elements, + addElement, + remove, + changeElement, + onSubGroupChange, + }; +} diff --git a/GUI/src/components/FlowElementsPopup/AssignContent.tsx b/GUI/src/components/FlowElementsPopup/AssignContent.tsx new file mode 100644 index 00000000..055c0a53 --- /dev/null +++ b/GUI/src/components/FlowElementsPopup/AssignContent.tsx @@ -0,0 +1,22 @@ +import { FC } from "react"; +import Track from "../Track"; +import useServiceStore from "store/new-services.store"; +import PreviousVariables from "./PreviousVariables"; +import AssignBuilder from "./AssignBuilder"; + +type AssignContentProps = { + readonly nodeId: string; +}; + +const AssignContent: FC = ({ nodeId }) => { + const assignElements = useServiceStore((state) => state.assignElements); + + return ( + + + + + ); +}; + +export default AssignContent; diff --git a/GUI/src/components/FlowElementsPopup/ConditionContent.tsx b/GUI/src/components/FlowElementsPopup/ConditionContent.tsx index d30a0364..8cd69e3c 100644 --- a/GUI/src/components/FlowElementsPopup/ConditionContent.tsx +++ b/GUI/src/components/FlowElementsPopup/ConditionContent.tsx @@ -1,4 +1,4 @@ -import { FC, useState } from "react"; +import { FC } from "react"; import Track from "../Track"; import RuleBuilder from "./RuleBuilder"; import useServiceStore from "store/new-services.store"; diff --git a/GUI/src/components/FlowElementsPopup/index.tsx b/GUI/src/components/FlowElementsPopup/index.tsx index a793b25e..0e8168dc 100644 --- a/GUI/src/components/FlowElementsPopup/index.tsx +++ b/GUI/src/components/FlowElementsPopup/index.tsx @@ -22,6 +22,7 @@ import useServiceStore from "store/new-services.store"; import FileSignContent from "./FileSignContent"; import "./styles.scss"; import ConditionContent from "./ConditionContent"; +import AssignContent from "./AssignContent"; const FlowElementsPopup: React.FC = () => { const { t } = useTranslation(); @@ -218,6 +219,7 @@ const FlowElementsPopup: React.FC = () => { {stepType === StepType.FileSign && } {stepType === StepType.FinishingStepEnd && } {stepType === StepType.RasaRules && } + {stepType === StepType.Assign && } {stepType === StepType.Condition && } diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 118c8ee0..5c88bb45 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -241,6 +241,7 @@ "taraAuthentication": "Tara authorization", "textfield": "Send message to client", "clientInput": "Client input", + "assign": "Assign", "condition": "Condition", "openNewWebpage": "Open new webpage", "fileGeneration": "File generation", diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 2997e427..068d0e40 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -241,6 +241,7 @@ "taraAuthentication": "TARA autentimine", "textfield": "Sõnum kliendile", "clientInput": "Kliendi sisend", + "assign": "Suunamine", "condition": "Tingimus", "openNewWebpage": "Uue veebilehe avamine", "fileGeneration": "Faili genereerimine", diff --git a/GUI/src/pages/ServiceFlowPage.tsx b/GUI/src/pages/ServiceFlowPage.tsx index 9a07caa8..add284a8 100644 --- a/GUI/src/pages/ServiceFlowPage.tsx +++ b/GUI/src/pages/ServiceFlowPage.tsx @@ -22,11 +22,12 @@ const ServiceFlowPage: FC = () => { { id: 10, label: t("serviceFlow.element.taraAuthentication"), type: StepType.Auth }, { id: 20, label: t("serviceFlow.element.textfield"), type: StepType.Textfield }, { id: 30, label: t("serviceFlow.element.clientInput"), type: StepType.Input }, - { id: 40, label: t("serviceFlow.element.condition"), type: StepType.Condition }, - { id: 50, label: t("serviceFlow.element.rasaRules"), type: StepType.RasaRules }, - { id: 60, label: t("serviceFlow.element.openNewWebpage"), type: StepType.OpenWebpage }, - { id: 70, label: t("serviceFlow.element.fileGeneration"), type: StepType.FileGenerate }, - { id: 80, label: t("serviceFlow.element.fileSigning"), type: StepType.FileSign }, + { id: 40, label: t("serviceFlow.element.assign"), type: StepType.Assign }, + { id: 50, label: t("serviceFlow.element.condition"), type: StepType.Condition }, + { id: 60, label: t("serviceFlow.element.rasaRules"), type: StepType.RasaRules }, + { id: 70, label: t("serviceFlow.element.openNewWebpage"), type: StepType.OpenWebpage }, + { id: 80, label: t("serviceFlow.element.fileGeneration"), type: StepType.FileGenerate }, + { id: 90, label: t("serviceFlow.element.fileSigning"), type: StepType.FileSign }, { id: 100, label: t("serviceFlow.element.conversationEnd"), type: StepType.FinishingStepEnd }, { id: 110, label: t("serviceFlow.element.redirectConversationToSupport"), type: StepType.FinishingStepRedirect }, ], @@ -53,7 +54,6 @@ const ServiceFlowPage: FC = () => { const nodes = useServiceStore((state) => state.nodes); const setNodes = useServiceStore((state) => state.setNodes); - const availableVariables = useServiceStore((state) => state.availableVariables); const onDragStart = (event: React.DragEvent, step: Step) => { event.dataTransfer.setData("application/reactflow-label", step.label); @@ -68,8 +68,6 @@ const ServiceFlowPage: FC = () => { <> navigate(ROUTES.OVERVIEW_ROUTE)} /> diff --git a/GUI/src/services/service-builder.ts b/GUI/src/services/service-builder.ts index e16ec074..3f339f45 100644 --- a/GUI/src/services/service-builder.ts +++ b/GUI/src/services/service-builder.ts @@ -520,6 +520,15 @@ export const saveFlow = async ({ }); const finishedFlow = new Map(); + + finishedFlow.set("prepare", { + assign: { + chatId: "${incoming.body.chatId}", + authorId: "${incoming.body.authorId}", + }, + next: "get_secrets", + }); + finishedFlow.set("get_secrets", { call: "http.get", args: { diff --git a/GUI/src/store/new-services.store.ts b/GUI/src/store/new-services.store.ts index 72a60917..d60bfad3 100644 --- a/GUI/src/store/new-services.store.ts +++ b/GUI/src/store/new-services.store.ts @@ -17,6 +17,7 @@ import { GroupOrRule } from "components/FlowElementsPopup/RuleBuilder/types"; import useTestServiceStore from "./test-services.store"; import { Chip } from "types/chip"; import { endpointResponseVariables } from "types/endpoint/endpoint-response-variables"; +import { AssignGroup } from "components/FlowElementsPopup/AssignBuilder/assign-types"; interface ServiceStoreState { endpoints: EndpointData[]; @@ -28,10 +29,12 @@ interface ServiceStoreState { nodes: Node[]; isNewService: boolean; serviceState: ServiceState; + assignElements: AssignGroup[]; rules: GroupOrRule[]; isYesNoQuestion: boolean; endpointsResponseVariables: endpointResponseVariables[]; setIsYesNoQuestion: (value: boolean) => void; + changeAssignNode: (assign: AssignGroup[]) => void; changeRulesNode: (rules: GroupOrRule[]) => void; markAsNewService: () => void; unmarkAsNewService: () => void; @@ -69,6 +72,7 @@ interface ServiceStoreState { updateEndpointRawData: (rawData: RequestVariablesTabsRawData, endpointDataId?: string, parentEndpointId?: string) => void; updateEndpointData: (data: RequestVariablesTabsRowsData, endpointDataId?: string, parentEndpointId?: string) => void; resetState: () => void; + resetAssign: () => void; resetRules: () => void; onContinueClick: (navigate: NavigateFunction) => Promise; selectedNode: Node | null; @@ -103,10 +107,12 @@ const useServiceStore = create((set, get, store) => ({ serviceState: ServiceState.Draft, isTestButtonVisible: false, isTestButtonEnabled: true, + assignElements: [], rules: [], isYesNoQuestion: false, endpointsResponseVariables: [], setIsYesNoQuestion: (value: boolean) => set({ isYesNoQuestion: value }), + changeAssignNode: (assign) => set({ assignElements: assign }), changeRulesNode: (rules) => set({ rules }), disableTestButton: () => set({ @@ -261,6 +267,7 @@ const useServiceStore = create((set, get, store) => ({ }); useTestServiceStore.getState().reset(); }, + resetAssign: () => set({ assignElements: [] }), resetRules: () => set({ rules: [], isYesNoQuestion: false }), loadService: async (id) => { get().resetState(); diff --git a/GUI/src/types/step-type.enum.ts b/GUI/src/types/step-type.enum.ts index f2614ad0..2c80ed15 100644 --- a/GUI/src/types/step-type.enum.ts +++ b/GUI/src/types/step-type.enum.ts @@ -2,6 +2,7 @@ export enum StepType { Auth = "auth", Textfield = "textfield", Input = "input", + Assign = "assign", Condition = "condition", RuleDefinition = "rule-definition", OpenWebpage = "open-webpage", From f042203b7ef75f4c699d958c8f083af26dffba44 Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Tue, 7 Jan 2025 23:26:37 +0200 Subject: [PATCH 2/5] Added Assign Element State --- .../AssignBuilder/assign-types.tsx | 14 +------ .../AssignBuilder/assignElement.tsx | 4 +- .../FlowElementsPopup/AssignBuilder/index.tsx | 26 +++++------- .../AssignBuilder/useAssignBuilder.tsx | 25 ++++-------- .../components/FlowElementsPopup/index.tsx | 16 ++++++++ .../components/FlowElementsPopup/styles.scss | 28 +++++++++++++ GUI/src/components/Steps/StepNode.tsx | 13 ++++++ GUI/src/i18n/en/common.json | 1 + GUI/src/i18n/et/common.json | 1 + GUI/src/store/new-services.store.ts | 40 ++++++++++++++----- GUI/src/types/service-flow.ts | 1 + 11 files changed, 110 insertions(+), 59 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx index 5778e862..39fc37e8 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assign-types.tsx @@ -1,7 +1,7 @@ import { v4 as uuidv4 } from 'uuid'; export interface ElementGroupBuilderProps { - group?: AssignGroup; + assignElements?: Assign[]; onRemove?: (id: string) => void; onChange: (config: any) => void; seedGroup?: any; @@ -13,11 +13,6 @@ export interface Assign { value: string; } -export interface AssignGroup { - id: string; - children: Assign[]; -} - export const getInitialElement = () => { return { id: uuidv4(), @@ -25,10 +20,3 @@ export const getInitialElement = () => { value: '', }; } - -export const getInitialAssignGroup = () => { - return { - id: uuidv4(), - children: [getInitialElement()], - } as AssignGroup; -}; diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx index 0af1520a..cd4cbf20 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/assignElement.tsx @@ -23,10 +23,10 @@ const AssignElement: React.FC = ({ element, onRemove, onChan - = + : - diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index 9135de16..28b9dcd1 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -1,42 +1,38 @@ import React from 'react'; -import { MdDeleteOutline } from 'react-icons/md'; import { useTranslation } from 'react-i18next'; -import { Icon, Track } from 'components'; -import { Assign, ElementGroupBuilderProps } from "./assign-types"; +import { Track } from 'components'; +import { ElementGroupBuilderProps } from "./assign-types"; import AssignElement from "./assignElement"; import { useAssignBuilder } from './useAssignBuilder'; import '../styles.scss'; -const AssignBuilder: React.FC = ({ group, onRemove, onChange, seedGroup }) => { +const AssignBuilder: React.FC = ({ assignElements, onRemove, onChange, seedGroup }) => { const { t } = useTranslation(); const { elements, addElement, remove, changeElement, - } = useAssignBuilder({ - group, + } = useAssignBuilder({ + assignElements, root: !onRemove, onChange, seedGroup, }); + + console.log('elements = ', elements); return ( - + - - {onRemove && ( - - )} {elements?.map((element) => ( - + ))} ); diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx index 57752d1a..e047b140 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/useAssignBuilder.tsx @@ -1,29 +1,22 @@ import { useEffect, useState } from "react"; -import { Assign, getInitialElement, AssignGroup } from "./assign-types"; +import { Assign, getInitialElement } from "./assign-types"; interface UseAssignBuilderProps { - group?: AssignGroup; + assignElements?: Assign[]; root?: boolean; - onChange: (group: AssignGroup) => void; + onChange: (group: Assign[]) => void; seedGroup?: any; } export const useAssignBuilder = (config: UseAssignBuilderProps) => { - const elementsInitialValue = config.root ? (config.seedGroup?.children ?? []) : config.group!.children; - const seedGroup = config.seedGroup?.length > 0 || config.seedGroup?.children?.length ? config.seedGroup : getInitialElement(); - const groupInfoInitialValue = config.root ? seedGroup : config.group! - const [elements, setElements] = useState(elementsInitialValue); - const [groupInfo, setGroupInfo] = useState(groupInfoInitialValue); + const elementsInitialValue = config.root ? config.seedGroup ?? [] : config.assignElements!; + const [elements, setElements] = useState(elementsInitialValue ?? []); useEffect(() => { - config.onChange({ - ...groupInfo, - children: elements - }) - }, [elements, groupInfo]); + config.onChange(elements) + }, [elements]); const addElement = () => { - console.log('addElement'); setElements([...elements, getInitialElement()]); } @@ -33,19 +26,15 @@ export const useAssignBuilder = (config: UseAssignBuilderProps) => { const changeElement = (element: Assign) => setElementById(element.id, element); - const onSubGroupChange = (parentId: string) => (rule: any) => setElementById(parentId, rule); - const setElementById = (id: string, element: Assign) => { const newElements = elements.map((x) => (x.id === id ? { ...element } : x)); setElements(newElements); }; return { - // groupInfo, elements, addElement, remove, changeElement, - onSubGroupChange, }; } diff --git a/GUI/src/components/FlowElementsPopup/index.tsx b/GUI/src/components/FlowElementsPopup/index.tsx index 0e8168dc..b3a522d7 100644 --- a/GUI/src/components/FlowElementsPopup/index.tsx +++ b/GUI/src/components/FlowElementsPopup/index.tsx @@ -35,11 +35,16 @@ const FlowElementsPopup: React.FC = () => { const endpoints = useServiceStore((state) => state.endpoints); const rules = useServiceStore((state) => state.rules); + const assignElements = useServiceStore((state) => state.assignElements); useEffect(() => { if (node) node.data.rules = rules; }, [rules]); + useEffect(() => { + if (node) node.data.assignElements = assignElements; + }, [assignElements]); + // StepType.Textfield const [textfieldMessage, setTextfieldMessage] = useState(null); const [textfieldMessagePlaceholders, setTextfieldMessagePlaceholders] = useState<{ [key: string]: string }>({}); @@ -61,6 +66,13 @@ const FlowElementsPopup: React.FC = () => { useServiceStore.getState().changeRulesNode(node.data.rules); }, [stepType === StepType.Input, stepType === StepType.Condition]); + useEffect(() => { + if (stepType !== StepType.Assign) return; + if (!node?.data?.assignElements) return; + + useServiceStore.getState().changeAssignNode(node.data.assignElements); + }, [stepType === StepType.Assign]); + if (!node) return <>; const title = node.data.label; @@ -98,6 +110,10 @@ const FlowElementsPopup: React.FC = () => { updatedNode.data.rules = rules; } + if (stepType === StepType.Assign) { + updatedNode.data.assignElements = assignElements; + } + useServiceStore.getState().handlePopupSave(updatedNode); onClose(); }; diff --git a/GUI/src/components/FlowElementsPopup/styles.scss b/GUI/src/components/FlowElementsPopup/styles.scss index 15e49741..058f39ec 100644 --- a/GUI/src/components/FlowElementsPopup/styles.scss +++ b/GUI/src/components/FlowElementsPopup/styles.scss @@ -131,3 +131,31 @@ div>.small-rule-group-button:last-child { .rule-gray:hover { background: #0002; } + +.assign-action-container { + padding: 10px; + background: rgba(25, 0, 255, 0.067); + border: 1px solid rgba(25, 0, 255, 0.067); +} + +.small-assign-button { + padding: 4px 10px; + margin: 4px; + border-radius: 10px; +} + +.assign-blue { + background: #00f5; +} + +.assign-blue:hover { + background: #00f6; +} + +.assign-red { + background: #f007; +} + +.assign-red:hover { + background: #f008; +} diff --git a/GUI/src/components/Steps/StepNode.tsx b/GUI/src/components/Steps/StepNode.tsx index 52e3973e..df38785d 100644 --- a/GUI/src/components/Steps/StepNode.tsx +++ b/GUI/src/components/Steps/StepNode.tsx @@ -5,6 +5,7 @@ import { ExclamationBadge, CheckBadge, Track } from "../"; import { StepType } from "../../types"; import useServiceStore from "store/new-services.store"; import { Group, Rule } from "components/FlowElementsPopup/RuleBuilder/types"; +import { Assign } from "components/FlowElementsPopup/AssignBuilder/assign-types"; type NodeDataProps = { data: { @@ -28,6 +29,7 @@ type NodeDataProps = { signOption?: { label: string; value: string }; originalDefinedNodeId?: string; rules?: Group; + assignElements?: Assign[]; }; }; @@ -66,6 +68,17 @@ const StepNode: FC = ({ data }) => { if (data.stepType === StepType.OpenWebpage) return !data.link || !data.linkText; if (data.stepType === StepType.FileGenerate) return !data.fileName || !data.fileContent; if (data.stepType === StepType.FileSign) return !data.signOption; + if (data.stepType === StepType.Assign) { + const hasInvalidElements = (elements: any[]): boolean => { + return elements.some((e) => { + const element = e as Assign; + return element.key === "" || element.value === ""; + }); + }; + + const invalidElementsExist = hasInvalidElements(data.assignElements || []); + return data?.assignElements === undefined || invalidElementsExist || data?.assignElements.length === 0; + }; return !(data.readonly || !!data.message?.length); }; diff --git a/GUI/src/i18n/en/common.json b/GUI/src/i18n/en/common.json index 5c88bb45..4c3fc43d 100644 --- a/GUI/src/i18n/en/common.json +++ b/GUI/src/i18n/en/common.json @@ -281,6 +281,7 @@ "or": "OR", "not": "NOT", "addRule": "+ Rule", + "addElement": "+ Element", "addGroup": "+ Group", "remove": "Remove", "signType": "Sign Type" diff --git a/GUI/src/i18n/et/common.json b/GUI/src/i18n/et/common.json index 068d0e40..7c8de958 100644 --- a/GUI/src/i18n/et/common.json +++ b/GUI/src/i18n/et/common.json @@ -281,6 +281,7 @@ "or": "VÕI", "not": "MITTE", "addRule": "+ Reegel", + "addElement": "+ Element", "addGroup": "+ Grupp", "remove": "Eemalda", "signType": "Märgi tüüp" diff --git a/GUI/src/store/new-services.store.ts b/GUI/src/store/new-services.store.ts index d60bfad3..b5d03848 100644 --- a/GUI/src/store/new-services.store.ts +++ b/GUI/src/store/new-services.store.ts @@ -17,7 +17,7 @@ import { GroupOrRule } from "components/FlowElementsPopup/RuleBuilder/types"; import useTestServiceStore from "./test-services.store"; import { Chip } from "types/chip"; import { endpointResponseVariables } from "types/endpoint/endpoint-response-variables"; -import { AssignGroup } from "components/FlowElementsPopup/AssignBuilder/assign-types"; +import { Assign } from "components/FlowElementsPopup/AssignBuilder/assign-types"; interface ServiceStoreState { endpoints: EndpointData[]; @@ -29,12 +29,12 @@ interface ServiceStoreState { nodes: Node[]; isNewService: boolean; serviceState: ServiceState; - assignElements: AssignGroup[]; + assignElements: Assign[]; rules: GroupOrRule[]; isYesNoQuestion: boolean; endpointsResponseVariables: endpointResponseVariables[]; setIsYesNoQuestion: (value: boolean) => void; - changeAssignNode: (assign: AssignGroup[]) => void; + changeAssignNode: (assign: Assign[]) => void; changeRulesNode: (rules: GroupOrRule[]) => void; markAsNewService: () => void; unmarkAsNewService: () => void; @@ -69,7 +69,11 @@ interface ServiceStoreState { selectedTab: EndpointEnv; setSelectedTab: (tab: EndpointEnv) => void; isLive: () => boolean; - updateEndpointRawData: (rawData: RequestVariablesTabsRawData, endpointDataId?: string, parentEndpointId?: string) => void; + updateEndpointRawData: ( + rawData: RequestVariablesTabsRawData, + endpointDataId?: string, + parentEndpointId?: string + ) => void; updateEndpointData: (data: RequestVariablesTabsRowsData, endpointDataId?: string, parentEndpointId?: string) => void; resetState: () => void; resetAssign: () => void; @@ -112,7 +116,7 @@ const useServiceStore = create((set, get, store) => ({ isYesNoQuestion: false, endpointsResponseVariables: [], setIsYesNoQuestion: (value: boolean) => set({ isYesNoQuestion: value }), - changeAssignNode: (assign) => set({ assignElements: assign }), + changeAssignNode: (assignElements) => set({ assignElements: assignElements }), changeRulesNode: (rules) => set({ rules }), disableTestButton: () => set({ @@ -258,6 +262,7 @@ const useServiceStore = create((set, get, store) => ({ edges: [initialEdge], nodes: initialNodes, isTestButtonEnabled: true, + assignElements: [], rules: [], isYesNoQuestion: false, clickedNode: null, @@ -318,7 +323,9 @@ const useServiceStore = create((set, get, store) => ({ await get().loadTaraVariables(); } - const variables = nodes?.filter((node) => node.data.stepType === StepType.Input).map((node) => `{{client_input_${node.data.clientInputId}}}`); + const variables = nodes + ?.filter((node) => node.data.stepType === StepType.Input) + .map((node) => `{{client_input_${node.data.clientInputId}}}`); get().addProductionVariables(variables); }, @@ -363,7 +370,9 @@ const useServiceStore = create((set, get, store) => ({ const response = endpoint?.definedEndpoints.find((x) => x.isSelected)?.response ?? []; const variables = response.map((x) => `{{${newName ?? x.id}.${x.name}}}`); - const oldFilteredVariables = get().availableVariables.prod.filter((v) => v.replace("{{", "").split(".")[0] !== oldName); + const oldFilteredVariables = get().availableVariables.prod.filter( + (v) => v.replace("{{", "").split(".")[0] !== oldName + ); const newEndpoints = get().endpoints.map((x) => { if (x.id !== endpointId) return x; @@ -402,7 +411,8 @@ const useServiceStore = create((set, get, store) => ({ .filter((x) => !!x.selected) .map(({ selected, endpoint }, index) => ({ id: index + 1, - label: endpoint.name.trim().length > 0 ? endpoint.name : `${selected!.methodType.toUpperCase()} ${selected!.url}`, + label: + endpoint.name.trim().length > 0 ? endpoint.name : `${selected!.methodType.toUpperCase()} ${selected!.url}`, type: StepType.UserDefined, data: endpoint, })); @@ -420,7 +430,9 @@ const useServiceStore = create((set, get, store) => ({ const live = get().isLive() ? "value" : "testValue"; const endpoints = JSON.parse(JSON.stringify(get().endpoints)) as EndpointData[]; - const defEndpoint = endpoints.find((x) => x.id === parentEndpointId)?.definedEndpoints.find((x) => x.id === endpointId); + const defEndpoint = endpoints + .find((x) => x.id === parentEndpointId) + ?.definedEndpoints.find((x) => x.id === endpointId); for (const key in data) { if (defEndpoint?.[key as EndpointTab]) { @@ -437,14 +449,20 @@ const useServiceStore = create((set, get, store) => ({ const live = get().isLive() ? "value" : "testValue"; const endpoints = JSON.parse(JSON.stringify(get().endpoints)) as EndpointData[]; - const defEndpoint = endpoints.find((x) => x.id === parentEndpointId)?.definedEndpoints.find((x) => x.id === endpointId); + const defEndpoint = endpoints + .find((x) => x.id === parentEndpointId) + ?.definedEndpoints.find((x) => x.id === endpointId); if (!defEndpoint) return; for (const key in data) { const keyedDefEndpoint = defEndpoint[key as EndpointTab]; for (const row of data[key as EndpointTab] ?? []) { - if (!row.endpointVariableId && row.variable && !keyedDefEndpoint?.variables.map((e) => e.name).includes(row.variable)) { + if ( + !row.endpointVariableId && + row.variable && + !keyedDefEndpoint?.variables.map((e) => e.name).includes(row.variable) + ) { keyedDefEndpoint?.variables.push({ id: uuid(), name: row.variable, diff --git a/GUI/src/types/service-flow.ts b/GUI/src/types/service-flow.ts index cca3fae3..da9f29f6 100644 --- a/GUI/src/types/service-flow.ts +++ b/GUI/src/types/service-flow.ts @@ -43,6 +43,7 @@ export type NodeDataProps = { fileContent?: string; signOption?: any; rules?: any; + assignElements?: any; }; export const initialNodes: Node[] = [ From 94a65f83a0ee9d5ffeaf75b721f5e2675f40606a Mon Sep 17 00:00:00 2001 From: 1AhmedYasser <26207361+1AhmedYasser@users.noreply.github.com> Date: Wed, 8 Jan 2025 00:57:02 +0200 Subject: [PATCH 3/5] Added Assign Step to service flow --- .../FlowElementsPopup/AssignBuilder/index.tsx | 2 - .../FlowElementsPopup/AssignContent.tsx | 5 +- .../FlowElementsPopup/PreviousVariables.tsx | 46 ++++++++++++++++++- GUI/src/components/Steps/StepNode.tsx | 1 + GUI/src/i18n/en/common.json | 6 ++- GUI/src/i18n/et/common.json | 6 ++- GUI/src/services/flow-builder.ts | 31 +++++++++---- GUI/src/services/service-builder.ts | 25 ++++++++++ 8 files changed, 104 insertions(+), 18 deletions(-) diff --git a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx index 28b9dcd1..528d06a6 100644 --- a/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignBuilder/index.tsx @@ -19,8 +19,6 @@ const AssignBuilder: React.FC = ({ assignElements, onR onChange, seedGroup, }); - - console.log('elements = ', elements); return ( diff --git a/GUI/src/components/FlowElementsPopup/AssignContent.tsx b/GUI/src/components/FlowElementsPopup/AssignContent.tsx index 055c0a53..b1182527 100644 --- a/GUI/src/components/FlowElementsPopup/AssignContent.tsx +++ b/GUI/src/components/FlowElementsPopup/AssignContent.tsx @@ -9,11 +9,12 @@ type AssignContentProps = { }; const AssignContent: FC = ({ nodeId }) => { - const assignElements = useServiceStore((state) => state.assignElements); + const nodes = useServiceStore((state) => state.nodes); + const currentNodeElements = nodes.find((node) => node.id === nodeId)?.data?.assignElements ?? []; return ( - + ); diff --git a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx index 2b465794..033b8846 100644 --- a/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx +++ b/GUI/src/components/FlowElementsPopup/PreviousVariables.tsx @@ -3,25 +3,37 @@ import Track from "../Track"; import useServiceStore from "store/new-services.store"; import { endpointResponseVariables } from "types/endpoint/endpoint-response-variables"; import OutputElementBox from "components/OutputElementBox"; +import { StepType } from "types"; +import { Assign } from "./AssignBuilder/assign-types"; +import { useTranslation } from "react-i18next"; type PreviousVariablesProps = { readonly nodeId: string; }; const PreviousVariables: FC = ({ nodeId }) => { + const { t } = useTranslation(); let endpointsVariables = useServiceStore((state) => state.endpointsResponseVariables); const nodes = useServiceStore((state) => state.nodes); const [endpoints, setEndpoints] = useState([]); + const [assignedVariables, setAssignedVariables] = useState([]); useEffect(() => { const previousNodes = nodes.slice( 0, nodes.findIndex((node) => node.id === nodeId) ); - const endpointNodes = previousNodes.filter((node) => node.data.stepType === "user-defined"); + + // Get Endpoints variables + const endpointNodes = previousNodes.filter((node) => node.data.stepType === StepType.UserDefined); const names = endpointNodes.map((node) => node.data.label); endpointsVariables = endpointsVariables.filter((endpoint) => names.includes(endpoint.name)); setEndpoints(endpointsVariables); + + // Get Assign variables + const assignNodes = previousNodes.filter((node) => node.data.stepType === StepType.Assign); + const assignElements = assignNodes.map((node) => node.data.assignElements).flat(); + setAssignedVariables(assignElements); }, [endpointsVariables]); const popupBodyCss: CSSProperties = { @@ -31,8 +43,38 @@ const PreviousVariables: FC = ({ nodeId }) => { return ( + {assignedVariables.length > 0 && ( + + + + {assignedVariables.map((assign) => ( + + ))} + + + )} {endpoints.map((endpoint) => ( - +