From f8af243bb0b88c49ac7144d5d011fd1a42167c57 Mon Sep 17 00:00:00 2001 From: Yeser Amer Date: Sat, 19 Oct 2024 09:59:46 +0200 Subject: [PATCH] kie-issues#1463: Test Scenario Editor: Manage `@kie-tools/scesim-editor`'s state with Zustand + Immer (#2628) Co-authored-by: kbowers-ibm Co-authored-by: Jozef Marko --- packages/dmn-editor/package.json | 2 +- .../LoanPreQualification.stories.tsx | 1 - packages/scesim-editor/.storybook/preview.tsx | 8 +- packages/scesim-editor/README.md | 6 +- packages/scesim-editor/package.json | 6 +- .../scesim-editor/src/TestScenarioEditor.tsx | 517 +++++-------- .../src/TestScenarioEditorContext.tsx | 55 ++ .../src/TestScenarioEditorErrorFallback.tsx | 93 +++ .../common/TestScenarioCommonFunctions.tsx | 60 -- .../creation/TestScenarioCreationPanel.tsx | 52 +- .../TestScenarioDrawerCheatSheetPanel.tsx | 57 +- .../TestScenarioDrawerDataSelectorPanel.tsx | 217 ++---- .../src/drawer/TestScenarioDrawerPanel.tsx | 71 +- .../TestScenarioDrawerSettingsPanel.tsx | 59 +- .../src/hook/useEffectAfterFirstRender.ts | 32 + .../src/i18n/TestScenarioEditorI18n.ts | 6 + packages/scesim-editor/src/i18n/locales/en.ts | 6 + .../scesim-editor/src/mutations/addColumn.ts | 183 +++++ .../scesim-editor/src/mutations/addRow.ts | 56 ++ .../src/mutations/deleteColumn.ts | 87 +++ .../scesim-editor/src/mutations/deleteRow.ts | 30 + .../src/mutations/duplicateRow.ts | 41 + .../scesim-editor/src/mutations/updateCell.ts | 52 ++ .../src/mutations/updateColumn.ts | 84 ++ .../src/mutations/updateColumnWidth.ts | 38 + .../src/reactExt/ErrorBoundary.tsx | 62 -- .../src/sidebar/TestScenarioSideBarMenu.tsx | 33 +- .../src/store/ComputedStateCache.ts | 96 +++ .../src/store/TestScenarioEditorStore.ts | 164 ++++ .../src/store/TestScenarioStoreContext.ts | 47 ++ .../computeTestScenarioDataObjects.ts | 80 ++ .../computed/initial.ts} | 13 +- .../src/table/TestScenarioTable.tsx | 722 ++++-------------- .../stories/dev/DevWebApp.stories.tsx | 212 ++--- .../stories/examples/AvailableDMNModels.ts | 9 - .../stories/misc/empty/Empty.stories.tsx | 14 +- .../stories/scesimEditor/SceSimEditor.mdx | 4 +- .../stories/scesimEditorStoriesWrapper.tsx | 60 +- .../useCases/IsOldEnoughRule.stories.tsx | 12 +- .../useCases/TrafficViolationDmn.stories.tsx | 12 +- .../create-a-new-test-scenario.png | Bin 16106 -> 16204 bytes .../create-a-new-test-scenario.png | Bin 16066 -> 14175 bytes .../create-a-new-test-scenario.png | Bin 13961 -> 14048 bytes .../src/schemas/scesim-1_8/SceSim.xsd | 17 + pnpm-lock.yaml | 102 +-- 45 files changed, 1978 insertions(+), 1500 deletions(-) create mode 100644 packages/scesim-editor/src/TestScenarioEditorContext.tsx create mode 100644 packages/scesim-editor/src/TestScenarioEditorErrorFallback.tsx delete mode 100644 packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx create mode 100644 packages/scesim-editor/src/hook/useEffectAfterFirstRender.ts create mode 100644 packages/scesim-editor/src/mutations/addColumn.ts create mode 100644 packages/scesim-editor/src/mutations/addRow.ts create mode 100644 packages/scesim-editor/src/mutations/deleteColumn.ts create mode 100644 packages/scesim-editor/src/mutations/deleteRow.ts create mode 100644 packages/scesim-editor/src/mutations/duplicateRow.ts create mode 100644 packages/scesim-editor/src/mutations/updateCell.ts create mode 100644 packages/scesim-editor/src/mutations/updateColumn.ts create mode 100644 packages/scesim-editor/src/mutations/updateColumnWidth.ts delete mode 100644 packages/scesim-editor/src/reactExt/ErrorBoundary.tsx create mode 100644 packages/scesim-editor/src/store/ComputedStateCache.ts create mode 100644 packages/scesim-editor/src/store/TestScenarioEditorStore.ts create mode 100644 packages/scesim-editor/src/store/TestScenarioStoreContext.ts create mode 100644 packages/scesim-editor/src/store/computed/computeTestScenarioDataObjects.ts rename packages/scesim-editor/src/{common/TestScenarioCommonConstants.ts => store/computed/initial.ts} (76%) diff --git a/packages/dmn-editor/package.json b/packages/dmn-editor/package.json index 27b6bec36ae..3441696ef18 100644 --- a/packages/dmn-editor/package.json +++ b/packages/dmn-editor/package.json @@ -54,7 +54,7 @@ "fast-deep-equal": "^3.1.3", "immer": "^10.0.3", "moment": "^2.29.4", - "react-error-boundary": "^4.0.11", + "react-error-boundary": "^4.0.13", "reactflow": "^11.8.3", "uuid": "^8.3.2", "zustand": "^4.4.2" diff --git a/packages/dmn-editor/stories/useCases/loanPreQualification/LoanPreQualification.stories.tsx b/packages/dmn-editor/stories/useCases/loanPreQualification/LoanPreQualification.stories.tsx index 01285c05d15..131be55e62a 100644 --- a/packages/dmn-editor/stories/useCases/loanPreQualification/LoanPreQualification.stories.tsx +++ b/packages/dmn-editor/stories/useCases/loanPreQualification/LoanPreQualification.stories.tsx @@ -792,7 +792,6 @@ const model = marshaller.parser.parse(); export const LoanPreQualification: Story = { render: Empty.render, args: { - ...Empty.args, model: model, xml: marshaller.builder.build(model), }, diff --git a/packages/scesim-editor/.storybook/preview.tsx b/packages/scesim-editor/.storybook/preview.tsx index 4e7e10ae856..f0300e08ed8 100644 --- a/packages/scesim-editor/.storybook/preview.tsx +++ b/packages/scesim-editor/.storybook/preview.tsx @@ -44,13 +44,7 @@ const preview: Preview = { }, // It should be Story() to be possible to use "preview-api" inside stories; (https://github.com/storybookjs/storybook/issues/22132) - decorators: [ - (Story) => ( -
- {Story()} -
- ), - ], + decorators: [(Story) =>
{Story()}
], }; export default preview; diff --git a/packages/scesim-editor/README.md b/packages/scesim-editor/README.md index fd2b0454047..eedff691504 100644 --- a/packages/scesim-editor/README.md +++ b/packages/scesim-editor/README.md @@ -44,16 +44,16 @@ To build the `scesim-editor` module ONLY, you can use ONE of the below commands: - `pnpm -F @kie-tools/scesim-editor build:dev` This is fast, but not as strict. It skips tests, linters, and some type checks. Recommended for dev purposes. - `pnpm -F @kie-tools/scesim-editor build:prod` The default command to build production-ready packages. This is the recommended build for production purposes -## How to launch the Test Scenario Dev WebApp +## How to launch the Test Scenario Storybook Dev WebApp -After building the project, you can benefit of the Dev Webapp for development or testing scope. +After building the project, you can benefit of the Storybook Dev Webapp for development or testing scope. To launch it, simply type in your terminal the following command: `pnpm -F @kie-tools/scesim-editor start` A web server with a Dev Webapp of Test Scenario editor will be launched, reachable at the following address: -http://localhost:9004/ or http://192.168.1.128:9004/ +http://localhost:9902/ or http://172.20.10.3:9902/ --- diff --git a/packages/scesim-editor/package.json b/packages/scesim-editor/package.json index 3d420430f19..9cd082deee5 100644 --- a/packages/scesim-editor/package.json +++ b/packages/scesim-editor/package.json @@ -26,11 +26,13 @@ "@patternfly/react-core": "^4.276.6", "@patternfly/react-icons": "^4.93.6", "@patternfly/react-styles": "^4.92.6", + "immer": "^10.0.3", "lodash": "^4.17.21", "react": "^17.0.2", "react-dom": "^17.0.2", "react-table": "^7.6.2", - "uuid": "^8.3.2" + "uuid": "^8.3.2", + "zustand": "^4.4.2" }, "devDependencies": { "@babel/core": "^7.16.0", @@ -57,7 +59,9 @@ "@types/uuid": "^8.3.0", "copy-webpack-plugin": "^11.0.0", "cross-env": "^7.0.3", + "deep-object-diff": "^1.1.9", "file-loader": "^6.2.0", + "react-error-boundary": "^4.0.13", "rimraf": "^3.0.2", "run-script-os": "^1.1.6", "storybook": "^7.3.2", diff --git a/packages/scesim-editor/src/TestScenarioEditor.tsx b/packages/scesim-editor/src/TestScenarioEditor.tsx index 62f15be68f3..faf089991e7 100644 --- a/packages/scesim-editor/src/TestScenarioEditor.tsx +++ b/packages/scesim-editor/src/TestScenarioEditor.tsx @@ -20,17 +20,14 @@ import "@patternfly/react-core/dist/styles/base.css"; import * as React from "react"; -import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState } from "react"; +import { useCallback, useImperativeHandle, useMemo, useRef } from "react"; import { I18nDictionariesProvider } from "@kie-tools-core/i18n/dist/react-components"; import { testScenarioEditorDictionaries, TestScenarioEditorI18nContext, testScenarioEditorI18nDefaults } from "./i18n"; -import { getMarshaller, SceSimModel } from "@kie-tools/scesim-marshaller"; -import { - SceSim__FactMappingType, - SceSim__ScenarioSimulationModelType, -} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; +import { SceSimModel } from "@kie-tools/scesim-marshaller"; +import { SceSim__FactMappingType } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; import { Alert } from "@patternfly/react-core/dist/js/components/Alert"; import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye"; @@ -46,16 +43,27 @@ import ErrorIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-ico import TableIcon from "@patternfly/react-icons/dist/esm/icons/table-icon"; import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon"; -import ErrorBoundary from "./reactExt/ErrorBoundary"; +import { ErrorBoundary, ErrorBoundaryPropsWithFallback } from "react-error-boundary"; + +import TestScenarioCreationPanel from "./creation/TestScenarioCreationPanel"; import TestScenarioDrawerPanel from "./drawer/TestScenarioDrawerPanel"; import TestScenarioSideBarMenu from "./sidebar/TestScenarioSideBarMenu"; import TestScenarioTable from "./table/TestScenarioTable"; import { useTestScenarioEditorI18n } from "./i18n"; -import { EMPTY_ONE_EIGHT } from "./resources/EmptyScesimFile"; - import "./TestScenarioEditor.css"; -import TestScenarioCreationPanel from "./creation/TestScenarioCreationPanel"; +import { ComputedStateCache } from "./store/ComputedStateCache"; +import { Computed, createTestScenarioEditorStore, TestScenarioEditorTab } from "./store/TestScenarioEditorStore"; +import { + StoreApiType, + TestScenarioEditorStoreApiContext, + useTestScenarioEditorStore, + useTestScenarioEditorStoreApi, +} from "./store/TestScenarioStoreContext"; +import { TestScenarioEditorErrorFallback } from "./TestScenarioEditorErrorFallback"; +import { TestScenarioEditorContextProvider, useTestScenarioEditor } from "./TestScenarioEditorContext"; +import { useEffectAfterFirstRender } from "./hook/useEffectAfterFirstRender"; +import { INITIAL_COMPUTED_CACHE } from "./store/computed/initial"; /* Constants */ @@ -63,17 +71,6 @@ const CURRENT_SUPPORTED_VERSION = "1.8"; /* Enums */ -export enum TestScenarioEditorDock { - CHEATSHEET, - DATA_OBJECT, - SETTINGS, -} - -enum TestScenarioEditorTab { - EDITOR, - BACKGROUND, -} - enum TestScenarioFileStatus { EMPTY, ERROR, @@ -82,43 +79,33 @@ enum TestScenarioFileStatus { VALID, } -export enum TestScenarioType { - DMN, - RULE, -} - /* Types */ -export type TestScenarioAlert = { - enabled: boolean; - message?: string; - variant: "success" | "danger" | "warning" | "info" | "default"; -}; - -export type TestScenarioDataObject = { - id: string; - children?: TestScenarioDataObject[]; - customBadgeContent?: string; - isSimpleTypeFact?: boolean; - name: string; +export type OnSceSimModelChange = (model: SceSimModel) => void; + +export type TestScenarioEditorProps = { + /** + * A link that will take users to an issue tracker so they can report problems they find on the Test Scenario Editor. + * This is shown on the ErrorBoundary fallback component, when an uncaught error happens. + */ + issueTrackerHref?: string; + /** + * The Test Scenario itself. + */ + model: SceSimModel; + /** + * Called when a change occurs on `model`, so the controlled flow of the component can be done. + */ + onModelChange?: OnSceSimModelChange; + /** + * Notifies the caller when the Test Scenario Editor performs a new edit after the debounce time. + */ + onModelDebounceStateChanged?: (changed: boolean) => void; }; export type TestScenarioEditorRef = { - /* TODO Convert these to Promises */ - getContent(): string; + reset: (mode: SceSimModel) => void; getDiagramSvg: () => Promise; - setContent(pathRelativeToTheWorkspaceRoot: string, content: string): void; -}; - -export type TestScenarioSettings = { - assetType: string; - dmnFilePath?: string; - dmnName?: string; - dmnNamespace?: string; - isStatelessSessionRule?: boolean; - isTestSkipped: boolean; - kieSessionRule?: string; - ruleFlowGroup?: string; }; export type TestScenarioSelectedColumnMetaData = { @@ -127,174 +114,59 @@ export type TestScenarioSelectedColumnMetaData = { isBackground: boolean; }; -function TestScenarioMainPanel({ - fileName, - scesimModel, - updateSettingField, - updateTestScenarioModel, -}: { - fileName: string; - scesimModel: { ScenarioSimulationModel: SceSim__ScenarioSimulationModelType }; - updateSettingField: (field: string, value: string) => void; - updateTestScenarioModel: React.Dispatch>; -}) { +function TestScenarioMainPanel({ fileName }: { fileName: string }) { const { i18n } = useTestScenarioEditorI18n(); - - const [alert, setAlert] = useState({ enabled: false, variant: "info" }); - const [dataObjects, setDataObjects] = useState([]); - const [dockPanel, setDockPanel] = useState({ isOpen: true, selected: TestScenarioEditorDock.DATA_OBJECT }); - const [selectedColumnMetadata, setSelectedColumnMetaData] = useState(null); - const [tab, setTab] = useState(TestScenarioEditorTab.EDITOR); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + const navigation = useTestScenarioEditorStore((s) => s.navigation); + const scesimModel = useTestScenarioEditorStore((s) => s.scesim.model); + const isAlertEnabled = true; // Will be managed in kie-issue#970 + const testScenarioType = scesimModel.ScenarioSimulationModel.settings.type?.__$$text.toUpperCase(); const scenarioTableScrollableElementRef = useRef(null); const backgroundTableScrollableElementRef = useRef(null); - const onTabChanged = useCallback((_event, tab) => { - setSelectedColumnMetaData(null); - setTab(tab); - }, []); - - const closeDockPanel = useCallback(() => { - setDockPanel((prev) => { - return { ...prev, isOpen: false }; - }); - }, []); - - const openDockPanel = useCallback((selected: TestScenarioEditorDock) => { - setDockPanel({ isOpen: true, selected: selected }); - }, []); - - useEffect(() => { - setDockPanel({ isOpen: true, selected: TestScenarioEditorDock.DATA_OBJECT }); - setSelectedColumnMetaData(null); - setTab(TestScenarioEditorTab.EDITOR); - }, [fileName]); - - /** This is TEMPORARY */ - useEffect(() => { - /* To create the Data Object arrays we need an external source, in details: */ - /* DMN Data: Retrieving DMN type from linked DMN file */ - /* Java classes: Retrieving Java classes info from the user projects */ - /* At this time, none of the above are supported */ - /* Therefore, it tries to retrieve these info from the SCESIM file, if are present */ - - /* Retriving Data Object from the scesim file. - That makes sense for previously created scesim files */ - - const factsMappings: SceSim__FactMappingType[] = - scesimModel.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping ?? []; - - const dataObjects: TestScenarioDataObject[] = []; - - /* The first two FactMapping are related to the "Number" and "Description" columns. - If those columns only are present, no Data Objects can be detected in the scesim file */ - for (let i = 2; i < factsMappings.length; i++) { - if (factsMappings[i].className!.__$$text === "java.lang.Void") { - continue; - } - const factID = factsMappings[i].expressionElements!.ExpressionElement![0].step.__$$text; - const dataObject = dataObjects.find((value) => value.id === factID); - const isSimpleTypeFact = factsMappings[i].expressionElements!.ExpressionElement!.length == 1; - const propertyID = isSimpleTypeFact //POTENTIAL BUG - ? factsMappings[i].expressionElements!.ExpressionElement![0].step.__$$text.concat(".") - : factsMappings[i] - .expressionElements!.ExpressionElement!.map((expressionElement) => expressionElement.step.__$$text) - .join("."); - const propertyName = isSimpleTypeFact - ? "value" - : factsMappings[i].expressionElements!.ExpressionElement!.slice(-1)[0].step.__$$text; - if (dataObject) { - if (!dataObject.children?.some((value) => value.id === propertyID)) { - dataObject.children!.push({ - id: propertyID, - customBadgeContent: factsMappings[i].className.__$$text, - isSimpleTypeFact: isSimpleTypeFact, - name: propertyName, - }); - } - } else { - dataObjects.push({ - id: factID, - name: factsMappings[i].factAlias!.__$$text, - customBadgeContent: factsMappings[i].factIdentifier!.className!.__$$text, - children: [ - { - id: propertyID, - name: propertyName, - customBadgeContent: factsMappings[i].className.__$$text, - }, - ], - }); - } - } - - setDataObjects(dataObjects); - }, [scesimModel.ScenarioSimulationModel.settings.type]); - - /** It determines the Alert State */ - useEffect(() => { - const assetType = scesimModel.ScenarioSimulationModel.settings.type!.__$$text; - - let alertEnabled = false; - let alertMessage = ""; - let alertVariant: "default" | "danger" | "warning" | "info" | "success" = "danger"; - - if (dataObjects.length > 0) { - alertMessage = - assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.alerts.dmnDataRetrievedFromScesim - : i18n.alerts.ruleDataRetrievedFromScesim; - alertEnabled = true; - } else { - alertMessage = - assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.alerts.dmnDataNotAvailable - : i18n.alerts.ruleDataNotAvailable; - alertVariant = assetType === TestScenarioType[TestScenarioType.DMN] ? "warning" : "danger"; - alertEnabled = true; - } + const onTabChanged = useCallback( + (_event, tab) => { + testScenarioEditorStoreApi.setState((state) => { + state.navigation.tab = tab; + }); + }, + [testScenarioEditorStoreApi] + ); - setAlert({ enabled: alertEnabled, message: alertMessage, variant: alertVariant }); - }, [dataObjects, i18n, scesimModel.ScenarioSimulationModel.settings.type]); + const showDockPanel = useCallback( + (show: boolean) => { + testScenarioEditorStoreApi.setState((state) => { + state.navigation.dock.isOpen = show; + }); + }, + [testScenarioEditorStoreApi] + ); return ( <>
- + - } + panelContent={ showDockPanel(false)} />} > - {alert.enabled && ( + {isAlertEnabled && (
- +
)}
- + @@ -314,11 +186,8 @@ function TestScenarioMainPanel({ ref={scenarioTableScrollableElementRef} >
@@ -343,11 +212,8 @@ function TestScenarioMainPanel({ ref={backgroundTableScrollableElementRef} >
@@ -357,7 +223,7 @@ function TestScenarioMainPanel({ - + ); } @@ -380,29 +246,82 @@ function TestScenarioParserErrorPanel({ ); } -const TestScenarioEditorInternal = ({ forwardRef }: { forwardRef?: React.Ref }) => { - /** Test Scenario File, Model and Marshaller Management */ +export const TestScenarioEditorInternal = ({ + model, + onModelChange, + onModelDebounceStateChanged, + forwardRef, +}: TestScenarioEditorProps & { forwardRef?: React.Ref }) => { + console.trace("[TestScenarioEditorInternal] Component creation ..."); - const [scesimFile, setScesimFile] = useState({ content: "", path: "" }); + const scesim = useTestScenarioEditorStore((s) => s.scesim); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + const { testScenarioEditorModelBeforeEditingRef, testScenarioEditorRootElementRef } = useTestScenarioEditor(); - const marshaller = useMemo(() => getMarshaller(scesimFile.content.trim()), [scesimFile]); + /** Implementing Editor APIs */ - const scesimLoaded: { ScenarioSimulationModel: SceSim__ScenarioSimulationModelType } = useMemo( - () => marshaller.parser.parse(), - [marshaller.parser] + // Allow imperativelly controlling the Editor. + useImperativeHandle( + forwardRef, + () => ({ + reset: () => { + console.trace("[TestScenarioEditorInternal: Reset called!"); + const state = testScenarioEditorStoreApi.getState(); + state.dispatch(state).scesim.reset(); + }, + getDiagramSvg: async () => undefined, + }), + [testScenarioEditorStoreApi] ); - const [scesimModel, setScesimModel] = useState(scesimLoaded); + // Make sure the Test Scenario Editor reacts to props changing. + useEffectAfterFirstRender(() => { + testScenarioEditorStoreApi.setState((state) => { + // Avoid unecessary state updates + if (model === state.scesim.model) { + console.trace("[TestScenarioEditorInternal]: useEffectAfterFirstRender called, but the models are the same!"); + return; + } + + console.trace("[TestScenarioEditorInternal]: Model updated!"); + + state.scesim.model = model; + testScenarioEditorModelBeforeEditingRef.current = model; + //state.dispatch(state).scesim.reset(); + }); + }, [testScenarioEditorStoreApi, model]); + + // Only notify changes when dragging/resizing operations are not happening. + useEffectAfterFirstRender(() => { + onModelDebounceStateChanged?.(false); + + const timeout = setTimeout(() => { + // Ignore changes made outside... If the controller of the component + // changed its props, it knows it already, we don't need to call "onModelChange" again. + if (model === scesim.model) { + return; + } + + onModelDebounceStateChanged?.(true); + console.trace("[TestScenarioEditorInternal: Debounce State changed!"); + console.trace(scesim.model); + onModelChange?.(scesim.model); + }, 500); + + return () => { + clearTimeout(timeout); + }; + }, [onModelChange, scesim.model]); const scesimFileStatus = useMemo(() => { - if (scesimModel.ScenarioSimulationModel) { - const parserErrorField = "parsererror" as keyof typeof scesimModel.ScenarioSimulationModel; - if (scesimModel.ScenarioSimulationModel[parserErrorField]) { + if (scesim.model.ScenarioSimulationModel) { + const parserErrorField = "parsererror" as keyof typeof scesim.model.ScenarioSimulationModel; + if (scesim.model.ScenarioSimulationModel[parserErrorField]) { return TestScenarioFileStatus.ERROR; } - if (scesimModel.ScenarioSimulationModel["@_version"] != CURRENT_SUPPORTED_VERSION) { + if (scesim.model.ScenarioSimulationModel["@_version"] != CURRENT_SUPPORTED_VERSION) { return TestScenarioFileStatus.UNSUPPORTED; - } else if (scesimModel.ScenarioSimulationModel["settings"]?.["type"]) { + } else if (scesim.model.ScenarioSimulationModel.settings?.type) { return TestScenarioFileStatus.VALID; } else { return TestScenarioFileStatus.NEW; @@ -410,84 +329,12 @@ const TestScenarioEditorInternal = ({ forwardRef }: { forwardRef?: React.Ref { - console.debug("SCESIM Model updated"); - console.debug(scesimLoaded); - setScesimModel(scesimLoaded); - }, [scesimLoaded]); - - /** Implementing Editor APIs */ - - useImperativeHandle( - forwardRef, - () => ({ - getContent: () => marshaller.builder.build(scesimModel), - getDiagramSvg: async () => undefined, - setContent: (normalizedPosixPathRelativeToTheWorkspaceRoot, content) => { - console.debug("SCESIM setContent called"); - console.debug("=== FILE CONTENT ==="); - console.debug(content ? content : "EMPTY FILE"); - console.debug("=== END FILE CONTENT ==="); - - setScesimFile({ content: content || EMPTY_ONE_EIGHT, path: normalizedPosixPathRelativeToTheWorkspaceRoot }); - }, - }), - [marshaller.builder, scesimModel] - ); + }, [scesim]); - /** scesim model update functions */ - - const setInitialSettings = useCallback( - ( - assetType: string, - isStatelessSessionRule: boolean, - isTestSkipped: boolean, - kieSessionRule: string, - ruleFlowGroup: string - ) => - setScesimModel((prevState) => ({ - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - settings: { - ...prevState.ScenarioSimulationModel.settings, - dmnFilePath: - assetType === TestScenarioType[TestScenarioType.DMN] ? { __$$text: "./MockedDMNName.dmn" } : undefined, - dmoSession: - assetType === TestScenarioType[TestScenarioType.RULE] && kieSessionRule - ? { __$$text: kieSessionRule } - : undefined, - ruleFlowGroup: - assetType === TestScenarioType[TestScenarioType.RULE] && ruleFlowGroup - ? { __$$text: ruleFlowGroup } - : undefined, - skipFromBuild: { __$$text: isTestSkipped }, - stateless: - assetType === TestScenarioType[TestScenarioType.RULE] ? { __$$text: isStatelessSessionRule } : undefined, - type: { __$$text: assetType }, - }, - }, - })), - [setScesimModel] - ); - - const updateSettingsField = useCallback( - (fieldName: string, value: string) => - setScesimModel((prevState) => ({ - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - ["settings"]: { - ...prevState.ScenarioSimulationModel["settings"], - [fieldName]: { __$$text: value }, - }, - }, - })), - [setScesimModel] - ); + console.trace("[TestScenarioEditorInternal] File Status: " + TestScenarioFileStatus[scesimFileStatus]); return ( - <> +
{(() => { switch (scesimFileStatus) { case TestScenarioFileStatus.EMPTY: @@ -507,13 +354,13 @@ const TestScenarioEditorInternal = ({ forwardRef }: { forwardRef?: React.Ref ); case TestScenarioFileStatus.NEW: - return ; + return ; case TestScenarioFileStatus.UNSUPPORTED: return ( ); case TestScenarioFileStatus.VALID: - return ( - - ); + return ; } })()} - +
); }; -export const TestScenarioEditor = React.forwardRef((props: {}, ref: React.Ref) => { - const [scesimFileParsingError, setScesimFileParsingError] = useState(null); - - return ( - - - } - setError={setScesimFileParsingError} +export const TestScenarioEditor = React.forwardRef( + (props: TestScenarioEditorProps, ref: React.Ref) => { + console.trace("[TestScenarioEditor] Component creation ... "); + console.trace(props.model); + + const store = useMemo( + () => createTestScenarioEditorStore(props.model, new ComputedStateCache(INITIAL_COMPUTED_CACHE)), + // Purposefully empty. This memoizes the initial value of the store + // eslint-disable-next-line react-hooks/exhaustive-deps + [] + ); + const storeRef = React.useRef(store); + + const resetState: ErrorBoundaryPropsWithFallback["onReset"] = useCallback(({ args }) => { + storeRef.current?.setState((state) => { + state.scesim.model = args[0]; + }); + }, []); + + return ( + - - - - ); -}); + + + + + + + + + ); + } +); diff --git a/packages/scesim-editor/src/TestScenarioEditorContext.tsx b/packages/scesim-editor/src/TestScenarioEditorContext.tsx new file mode 100644 index 00000000000..49ffc97ffd1 --- /dev/null +++ b/packages/scesim-editor/src/TestScenarioEditorContext.tsx @@ -0,0 +1,55 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 * as React from "react"; +import { useContext, useMemo, useRef } from "react"; +import { SceSimModel } from "@kie-tools/scesim-marshaller"; +import { TestScenarioEditorProps } from "./TestScenarioEditor"; + +export type SceSimModelBeforeEditing = SceSimModel; + +export type TestScenarioEditorContextProviderProps = Pick; + +export type TestScenarioEditorContextType = Pick & { + testScenarioEditorModelBeforeEditingRef: React.MutableRefObject; + testScenarioEditorRootElementRef: React.RefObject; +}; + +const TestScenarioEditorContext = React.createContext({} as any); + +export function useTestScenarioEditor() { + return useContext(TestScenarioEditorContext); +} + +export function TestScenarioEditorContextProvider( + props: React.PropsWithChildren +) { + const testScenarioEditorModelBeforeEditingRef = useRef(props.model); + const testScenarioEditorRootElementRef = useRef(null); + + const value = useMemo( + () => ({ + issueTrackerHref: props.issueTrackerHref, + testScenarioEditorModelBeforeEditingRef, + testScenarioEditorRootElementRef, + }), + [props.issueTrackerHref] + ); + return {props.children}; +} diff --git a/packages/scesim-editor/src/TestScenarioEditorErrorFallback.tsx b/packages/scesim-editor/src/TestScenarioEditorErrorFallback.tsx new file mode 100644 index 00000000000..5d532330d10 --- /dev/null +++ b/packages/scesim-editor/src/TestScenarioEditorErrorFallback.tsx @@ -0,0 +1,93 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 * as React from "react"; +import { useCallback, useEffect } from "react"; +import { Button, ButtonVariant } from "@patternfly/react-core/dist/js/components/Button"; +import { + EmptyState, + EmptyStateBody, + EmptyStateIcon, + EmptyStatePrimary, +} from "@patternfly/react-core/dist/js/components/EmptyState"; +import { ExternalLinkAltIcon } from "@patternfly/react-icons/dist/js/icons/external-link-alt-icon"; +import { ClipboardCopy, ClipboardCopyVariant } from "@patternfly/react-core/dist/js/components/ClipboardCopy"; +import { Flex } from "@patternfly/react-core/dist/js/layouts/Flex"; +import { Title } from "@patternfly/react-core/dist/js/components/Title"; +import { FallbackProps } from "react-error-boundary"; +import { useTestScenarioEditorI18n } from "./i18n"; +import { useTestScenarioEditor } from "./TestScenarioEditorContext"; + +export function TestScenarioEditorErrorFallback({ error, resetErrorBoundary }: FallbackProps) { + const { i18n } = useTestScenarioEditorI18n(); + const { testScenarioEditorModelBeforeEditingRef, issueTrackerHref } = useTestScenarioEditor(); + + const resetToLastWorkingState = useCallback(() => { + resetErrorBoundary(testScenarioEditorModelBeforeEditingRef.current); + }, [testScenarioEditorModelBeforeEditingRef, resetErrorBoundary]); + + useEffect(() => { + console.error(error); + }, [error]); + + return ( + + +
😕
} /> + + {i18n.errorFallBack.title} + + {i18n.errorFallBack.body} +
+ + {JSON.stringify( + { + name: error.name, + message: error.message, + cause: error.cause, + stack: error.stack, + }, + null, + 2 + ).replaceAll("\\n", "\n")} + +
+ + + {issueTrackerHref && ( + + + + )} + +
+
+ ); +} diff --git a/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx b/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx deleted file mode 100644 index cd9b95ca376..00000000000 --- a/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 { - SceSim__FactMappingValueType, - SceSim__ScenarioSimulationModelType, - SceSim__expressionIdentifierType, - SceSim__factIdentifierType, -} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; -import * as React from "react"; - -/** - Given a List of FactMappingValues (Row of Cells), it founds the index of the list's element that matches with the - identifiers (factIdentifier and expressionIdentifier) fields. -*/ -export const retrieveFactMappingValueIndexByIdentifiers = ( - factMappingValues: SceSim__FactMappingValueType[], - factIdentifier: SceSim__factIdentifierType, - expressionIdentifier: SceSim__expressionIdentifierType -) => { - return factMappingValues.findIndex( - (factMappingValue) => - factMappingValue.factIdentifier.name?.__$$text == factIdentifier.name?.__$$text && - factMappingValue.factIdentifier.className?.__$$text == factIdentifier.className?.__$$text && - factMappingValue.expressionIdentifier.name?.__$$text == expressionIdentifier.name?.__$$text && - factMappingValue.expressionIdentifier.type?.__$$text == expressionIdentifier.type?.__$$text - ); -}; - -export const retrieveModelDescriptor = (scesimModel: SceSim__ScenarioSimulationModelType, isBackground: boolean) => { - if (isBackground) { - return scesimModel.background.scesimModelDescriptor; - } else { - return scesimModel.simulation.scesimModelDescriptor; - } -}; - -export const retrieveRowsDataFromModel = (scesimModel: SceSim__ScenarioSimulationModelType, isBackground: boolean) => { - if (isBackground) { - return scesimModel.background.scesimData.BackgroundData; - } else { - return scesimModel.simulation.scesimData.Scenario; - } -}; diff --git a/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx index 9dfd3812fca..679b924171b 100644 --- a/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx +++ b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx @@ -18,6 +18,7 @@ */ import * as React from "react"; +import { useCallback } from "react"; import { Button } from "@patternfly/react-core/dist/js/components/Button"; import { Checkbox } from "@patternfly/react-core/dist/js/components/Checkbox"; @@ -33,36 +34,49 @@ import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip"; import AddIcon from "@patternfly/react-icons/dist/esm/icons/add-circle-o-icon"; import CubesIcon from "@patternfly/react-icons/dist/esm/icons/cubes-icon"; -import { TestScenarioType } from "../TestScenarioEditor"; +import { useTestScenarioEditorStoreApi } from "../store/TestScenarioStoreContext"; import { useTestScenarioEditorI18n } from "../i18n"; import "./TestScenarioCreationPanel.css"; -function TestScenarioCreationPanel({ - onCreateScesimButtonClicked, -}: { - onCreateScesimButtonClicked: ( - assetType: string, - isStatelessSessionRule: boolean, - isTestSkipped: boolean, - kieSessionRule: string, - ruleFlowGroup: string - ) => void; -}) { +function TestScenarioCreationPanel() { const { i18n } = useTestScenarioEditorI18n(); const assetsOption = [ { value: "", label: i18n.creationPanel.assetsOption.noChoice, disabled: true }, - { value: TestScenarioType[TestScenarioType.DMN], label: i18n.creationPanel.assetsOption.dmn, disabled: false }, - { value: TestScenarioType[TestScenarioType.RULE], label: i18n.creationPanel.assetsOption.rule, disabled: false }, + { value: "DMN", label: i18n.creationPanel.assetsOption.dmn, disabled: false }, + { value: "RULE", label: i18n.creationPanel.assetsOption.rule, disabled: false }, ]; - const [assetType, setAssetType] = React.useState(""); + const [assetType, setAssetType] = React.useState<"" | "DMN" | "RULE">(""); const [isAutoFillTableEnabled, setAutoFillTableEnabled] = React.useState(true); const [isStatelessSessionRule, setStatelessSessionRule] = React.useState(false); const [isTestSkipped, setTestSkipped] = React.useState(false); const [kieSessionRule, setKieSessionRule] = React.useState(""); const [ruleFlowGroup, setRuleFlowGroup] = React.useState(""); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + + const createTestScenario = useCallback( + ( + assetType: string, + isStatelessSessionRule: boolean, + isTestSkipped: boolean, + kieSessionRule: string, + ruleFlowGroup: string + ) => + testScenarioEditorStoreApi.setState((state) => { + const settings = state.scesim.model.ScenarioSimulationModel.settings; + settings.dmnFilePath = assetType === "DMN" ? { __$$text: "./MockedDMNName.dmn" } : undefined; + settings.dmnName = assetType === "DMN" ? { __$$text: "MockedDMNName.dmn" } : undefined; + settings.dmnNamespace = assetType === "DMN" ? { __$$text: "https:\\kiegroup" } : undefined; + settings.dmoSession = assetType === "RULE" && kieSessionRule ? { __$$text: kieSessionRule } : undefined; + settings.ruleFlowGroup = assetType === "RULE" && ruleFlowGroup ? { __$$text: ruleFlowGroup } : undefined; + settings.skipFromBuild = { __$$text: isTestSkipped }; + settings.stateless = assetType === "RULE" ? { __$$text: isStatelessSessionRule } : undefined; + settings.type = { __$$text: assetType }; + }), + [testScenarioEditorStoreApi] + ); return ( @@ -75,7 +89,7 @@ function TestScenarioCreationPanel({ setAssetType(value)} + onChange={(value: "" | "DMN" | "RULE") => setAssetType(value)} value={assetType} > {assetsOption.map((option, index) => ( @@ -83,7 +97,7 @@ function TestScenarioCreationPanel({ ))} - {assetType === TestScenarioType[TestScenarioType.DMN] && ( + {assetType === "DMN" && ( <> @@ -111,7 +125,7 @@ function TestScenarioCreationPanel({ )} - {assetType === TestScenarioType[TestScenarioType.RULE] && ( + {assetType === "RULE" && ( <> } isDisabled={assetType == ""} onClick={() => - onCreateScesimButtonClicked(assetType, isStatelessSessionRule, isTestSkipped, kieSessionRule, ruleFlowGroup) + createTestScenario(assetType, isStatelessSessionRule, isTestSkipped, kieSessionRule, ruleFlowGroup) } variant="primary" > diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx index c3b14567fdb..982456cf6c4 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx @@ -21,79 +21,58 @@ import * as React from "react"; import { Text, TextContent, TextList, TextListItem } from "@patternfly/react-core/dist/js/components/Text"; -import { TestScenarioType } from "../TestScenarioEditor"; import { useTestScenarioEditorI18n } from "../i18n"; +import { useTestScenarioEditorStore } from "../store/TestScenarioStoreContext"; -function TestScenarioDrawerCheatSheetPanel({ assetType }: { assetType: string }) { +function TestScenarioDrawerCheatSheetPanel() { const { i18n } = useTestScenarioEditorI18n(); + const settingsModel = useTestScenarioEditorStore((state) => state.scesim.model.ScenarioSimulationModel.settings); + const testScenarioType = settingsModel.type?.__$$text.toUpperCase(); return ( {i18n.drawer.cheatSheet.paragraph1} {i18n.drawer.cheatSheet.paragraph2( - assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataSelector.titleDMN - : i18n.drawer.dataSelector.titleRule + testScenarioType === "DMN" ? i18n.drawer.dataSelector.titleDMN : i18n.drawer.dataSelector.titleRule )} {i18n.drawer.cheatSheet.paragraph3(i18n.tab.backgroundTabTitle, i18n.tab.scenarioTabTitle)} {i18n.drawer.cheatSheet.paragraph4} - {assetType === TestScenarioType[TestScenarioType.DMN] && {i18n.drawer.cheatSheet.paragraph5DMN}} + {testScenarioType === "DMN" && {i18n.drawer.cheatSheet.paragraph5DMN}} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.paragraph6DMN - : i18n.drawer.cheatSheet.paragraph6Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.paragraph6DMN : i18n.drawer.cheatSheet.paragraph6Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression1DMN - : i18n.drawer.cheatSheet.expression1Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression1DMN : i18n.drawer.cheatSheet.expression1Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression2DMN - : i18n.drawer.cheatSheet.expression2Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression2DMN : i18n.drawer.cheatSheet.expression2Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression3DMN - : i18n.drawer.cheatSheet.expression3Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression3DMN : i18n.drawer.cheatSheet.expression3Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression4DMN - : i18n.drawer.cheatSheet.expression4Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression4DMN : i18n.drawer.cheatSheet.expression4Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression5DMN - : i18n.drawer.cheatSheet.expression5Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression5DMN : i18n.drawer.cheatSheet.expression5Rule} + T - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression6DMN - : i18n.drawer.cheatSheet.expression6Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression6DMN : i18n.drawer.cheatSheet.expression6Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression7DMN - : i18n.drawer.cheatSheet.expression7Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression7DMN : i18n.drawer.cheatSheet.expression7Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression8DMN - : i18n.drawer.cheatSheet.expression8Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression8DMN : i18n.drawer.cheatSheet.expression8Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.cheatSheet.expression9DMN - : i18n.drawer.cheatSheet.expression9Rule} + {testScenarioType === "DMN" ? i18n.drawer.cheatSheet.expression9DMN : i18n.drawer.cheatSheet.expression9Rule} - {assetType === TestScenarioType[TestScenarioType.DMN] && ( - {i18n.drawer.cheatSheet.expression10DMN} - )} + {testScenarioType === "DMN" && {i18n.drawer.cheatSheet.expression10DMN}} ); diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx index d1a1d554ea7..f6aebaa087b 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx @@ -34,24 +34,14 @@ import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip/"; import { TreeView, TreeViewDataItem, TreeViewSearch } from "@patternfly/react-core/dist/js/components/TreeView/"; import { WarningTriangleIcon } from "@patternfly/react-icons/dist/esm/icons/warning-triangle-icon"; -import { SceSimModel } from "@kie-tools/scesim-marshaller"; -import { - SceSim__FactMappingType, - SceSim__FactMappingValuesTypes, - SceSim__expressionElementsType, -} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; - -import { TestScenarioDataObject, TestScenarioSelectedColumnMetaData, TestScenarioType } from "../TestScenarioEditor"; -import { useTestScenarioEditorI18n } from "../i18n"; +import { SceSim__expressionElementsType } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; -import { EMPTY_TYPE, TEST_SCENARIO_EXPRESSION_TYPE } from "../common/TestScenarioCommonConstants"; -import { - retrieveFactMappingValueIndexByIdentifiers, - retrieveModelDescriptor, - retrieveRowsDataFromModel, -} from "../common/TestScenarioCommonFunctions"; +import { useTestScenarioEditorI18n } from "../i18n"; import "./TestScenarioDrawerDataSelectorPanel.css"; +import { useTestScenarioEditorStore, useTestScenarioEditorStoreApi } from "../store/TestScenarioStoreContext"; +import { TestScenarioDataObject, TestScenarioEditorTab } from "../store/TestScenarioEditorStore"; +import { updateColumn } from "../mutations/updateColumn"; const enum TestScenarioDataSelectorState { DISABLED, // All subcomponents are DISABLED @@ -59,22 +49,19 @@ const enum TestScenarioDataSelectorState { TREEVIEW_ENABLED_ONLY, // TreeView component is enabled only, in a read only mode (when a column is selected) } -function TestScenarioDataSelectorPanel({ - assetType, - dataObjects, - scesimModel, - selectedColumnMetadata, - updateSelectedColumnMetaData, - updateTestScenarioModel, -}: { - assetType: string; - dataObjects: TestScenarioDataObject[]; - scesimModel: SceSimModel; - selectedColumnMetadata: TestScenarioSelectedColumnMetaData | null; - updateSelectedColumnMetaData: React.Dispatch>; - updateTestScenarioModel: React.Dispatch>; -}) { +function TestScenarioDataSelectorPanel() { const { i18n } = useTestScenarioEditorI18n(); + const dataObjects = useTestScenarioEditorStore((state) => state.computed(state).getTestScenarioDataObjects()); + const scesimModel = useTestScenarioEditorStore((state) => state.scesim.model); + const tableStatus = useTestScenarioEditorStore((state) => state.table); + const tabStatus = useTestScenarioEditorStore((state) => state.navigation.tab); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + const testScenarioType = scesimModel.ScenarioSimulationModel.settings.type?.__$$text.toUpperCase(); + + const selectedColumnMetadata = + tabStatus === TestScenarioEditorTab.SIMULATION + ? tableStatus.simulation.selectedColumn + : tableStatus.background.selectedColumn; const [allExpanded, setAllExpanded] = useState(false); const [dataSelectorStatus, setDataSelectorStatus] = useState(TestScenarioDataSelectorState.DISABLED); @@ -171,8 +158,9 @@ function TestScenarioDataSelectorPanel({ /** It filters out all the Data Objects and their Children already assigned in the table */ const filterOutAlreadyAssignedDataObjectsAndChildren = useCallback( (expressionElement: SceSim__expressionElementsType, isBackground: boolean) => { - const testScenarioDescriptor = retrieveModelDescriptor(scesimModel.ScenarioSimulationModel, isBackground); - + const testScenarioDescriptor = isBackground + ? scesimModel.ScenarioSimulationModel.background.scesimModelDescriptor + : scesimModel.ScenarioSimulationModel.simulation.scesimModelDescriptor; const assignedExpressionElements = testScenarioDescriptor.factMappings.FactMapping!.map( (factMapping) => factMapping.expressionElements! ); @@ -233,9 +221,9 @@ function TestScenarioDataSelectorPanel({ * - All the NOT-assigned fields of the selected column instance * - All the NOT-assigned fields of the NOT-ASSIGNED instances, if the selected column doesn't have an instance (1th level header) assigned */ - if (selectedColumnMetadata.factMapping.className.__$$text === EMPTY_TYPE) { + if (selectedColumnMetadata.factMapping.className.__$$text === "java.lang.Void") { const isFactIdentifierAssigned = - selectedColumnMetadata.factMapping.factIdentifier.className!.__$$text !== EMPTY_TYPE; + selectedColumnMetadata.factMapping.factIdentifier.className!.__$$text !== "java.lang.Void"; let filteredDataObjects: TestScenarioDataObject[] = []; if (isFactIdentifierAssigned) { @@ -245,10 +233,9 @@ function TestScenarioDataSelectorPanel({ selectedColumnMetadata.isBackground ); } else { - const testScenarioDescriptor = retrieveModelDescriptor( - scesimModel.ScenarioSimulationModel, - selectedColumnMetadata.isBackground - ); + const testScenarioDescriptor = selectedColumnMetadata.isBackground + ? scesimModel.ScenarioSimulationModel.background.scesimModelDescriptor + : scesimModel.ScenarioSimulationModel.simulation.scesimModelDescriptor; const assignedExpressionElements = testScenarioDescriptor.factMappings.FactMapping!.map( (factMapping) => factMapping.expressionElements! ); @@ -286,12 +273,10 @@ function TestScenarioDataSelectorPanel({ */ const factIdentifier = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text; const filteredDataObjects = dataObjects.filter((dataObject) => filterTypesItems(dataObject, factIdentifier)); - const isExpressionType = - selectedColumnMetadata.factMapping.factMappingValueType!.__$$text === - TEST_SCENARIO_EXPRESSION_TYPE[TEST_SCENARIO_EXPRESSION_TYPE.EXPRESSION]; + const isExpressionType = selectedColumnMetadata.factMapping.factMappingValueType!.__$$text === "EXPRESSION"; const isSimpleTypeFact = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement!.length === 1 && - selectedColumnMetadata.factMapping.className.__$$text !== EMPTY_TYPE; + selectedColumnMetadata.factMapping.className.__$$text !== "java.lang.Void"; let fieldID: string; if (isExpressionType) { fieldID = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text; @@ -303,7 +288,7 @@ function TestScenarioDataSelectorPanel({ .join("."); } - //TODO 1 This not work with multiple level and expressions fields. + //TODO 1 This not work with multiple level and expressions fields. see kie-issues#1514 const treeViewItemToActivate = filteredDataObjects .reduce((acc: TestScenarioDataObject[], item) => { return item.children ? acc.concat(item.children) : acc; @@ -332,25 +317,25 @@ function TestScenarioDataSelectorPanel({ const treeViewEmptyIcon = filteredItems.length === 0 ? WarningTriangleIcon : WarningTriangleIcon; const title = dataObjects.length === 0 - ? assetType === TestScenarioType[TestScenarioType.DMN] + ? testScenarioType === "DMN" ? i18n.drawer.dataSelector.emptyDataObjectsTitleDMN : i18n.drawer.dataSelector.emptyDataObjectsTitleRule : "No more properties"; //TODO CHANGE const description = dataObjects.length === 0 - ? assetType === TestScenarioType[TestScenarioType.DMN] + ? testScenarioType === "DMN" ? i18n.drawer.dataSelector.emptyDataObjectsDescriptionDMN : i18n.drawer.dataSelector.emptyDataObjectsDescriptionRule : "All the properties have been already assigned"; //TODO CHANGE { - assetType === TestScenarioType[TestScenarioType.DMN] + testScenarioType === "DMN" ? i18n.drawer.dataSelector.emptyDataObjectsTitleDMN : i18n.drawer.dataSelector.emptyDataObjectsTitleRule; } return { description: description, enabled: isTreeViewNotEmpty, icon: treeViewEmptyIcon, title: title }; - }, [assetType, dataObjects.length, filteredItems.length, i18n]); + }, [testScenarioType, dataObjects.length, filteredItems.length, i18n]); const insertDataObjectButtonStatus = useMemo(() => { if (!selectedColumnMetadata) { @@ -391,106 +376,49 @@ function TestScenarioDataSelectorPanel({ // CHECK const onInsertDataObjectClick = useCallback( - /** TODO 2 : NEED A POPUP ASKING IF WE WANT TO REPLACE VALUES OR NOT */ - + /** TODO 2 : NEED A POPUP ASKING IF WE WANT TO REPLACE VALUES OR NOT see kie-issues#1514 */ () => { - updateTestScenarioModel((prevState) => { - const isBackground = selectedColumnMetadata!.isBackground; - const factMappings = retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings - .FactMapping!; - const deepClonedFactMappings = JSON.parse(JSON.stringify(factMappings)); - const isRootType = isDataObjectRootParent(dataObjects, treeViewStatus.activeItems[0].id!.toString()); - const rootDataObject = findDataObjectRootParent(dataObjects, treeViewStatus.activeItems[0].id!.toString()); - - const className = treeViewStatus.activeItems[0].customBadgeContent!.toString(); - const expressionAlias = isRootType ? "Expression " : treeViewStatus.activeItems[0].name!.toString(); - const expressionElementsSteps = treeViewStatus.activeItems[0].id!.split(".").filter((step) => !!step.trim()); //WARNING !!!! THIS DOESN'T WORK WITH IMPORTED DATA OBJECTS - const factName = treeViewStatus.activeItems[0].id!.split(".")[0]; //WARNING !!!! THIS DOESN'T WORK WITH IMPORTED DATA OBJECTS - const factClassName = isRootType - ? treeViewStatus.activeItems[0].customBadgeContent!.toString() - : rootDataObject.customBadgeContent!.toString(); - const factMappingValueType = isRootType - ? TEST_SCENARIO_EXPRESSION_TYPE[TEST_SCENARIO_EXPRESSION_TYPE.EXPRESSION] - : TEST_SCENARIO_EXPRESSION_TYPE[TEST_SCENARIO_EXPRESSION_TYPE.NOT_EXPRESSION]; - - const factMappingToUpdate: SceSim__FactMappingType = deepClonedFactMappings[selectedColumnMetadata!.index]; - factMappingToUpdate.className = { __$$text: className }; - factMappingToUpdate.factAlias = { __$$text: factName }; - factMappingToUpdate.factIdentifier.className = { __$$text: factClassName }; - factMappingToUpdate.factIdentifier.name = { __$$text: factName }; - factMappingToUpdate.factMappingValueType = { __$$text: factMappingValueType }; - factMappingToUpdate.expressionAlias = { __$$text: expressionAlias }; - factMappingToUpdate.expressionElements = { - ExpressionElement: expressionElementsSteps.map((ee) => { - return { step: { __$$text: ee } }; - }), - }; - - const deepClonedRowsData: SceSim__FactMappingValuesTypes[] = JSON.parse( - JSON.stringify(retrieveRowsDataFromModel(prevState.ScenarioSimulationModel, isBackground)) - ); - - deepClonedRowsData.forEach((fmv, index) => { - const factMappingValues = fmv.factMappingValues.FactMappingValue!; - const newFactMappingValues = [...factMappingValues]; - - const factMappingValueToUpdateIndex = retrieveFactMappingValueIndexByIdentifiers( - newFactMappingValues, - selectedColumnMetadata!.factMapping.factIdentifier, - selectedColumnMetadata!.factMapping.expressionIdentifier - ); - const factMappingValueToUpdate = factMappingValues[factMappingValueToUpdateIndex]; - newFactMappingValues[factMappingValueToUpdateIndex] = { - ...factMappingValueToUpdate, - factIdentifier: { className: { __$$text: factClassName }, name: { __$$text: factName } }, - // rawValue: { - // __$$text: update.value, //TODO 2 related - // }, - }; - - deepClonedRowsData[index].factMappingValues.FactMappingValue = newFactMappingValues; + const isBackground = selectedColumnMetadata!.isBackground; + const isRootType = isDataObjectRootParent(dataObjects, treeViewStatus.activeItems[0].id!.toString()); + const rootDataObject = findDataObjectRootParent(dataObjects, treeViewStatus.activeItems[0].id!.toString()); + const className = treeViewStatus.activeItems[0].customBadgeContent!.toString(); + const expressionAlias = isRootType ? "Expression " : treeViewStatus.activeItems[0].name!.toString(); + const expressionElementsSteps = treeViewStatus.activeItems[0].id!.split(".").filter((step) => !!step.trim()); //WARNING !!!! THIS DOESN'T WORK WITH IMPORTED DATA OBJECTS see kie-issues#1514 + const factName = treeViewStatus.activeItems[0].id!.split(".")[0]; //WARNING !!!! THIS DOESN'T WORK WITH IMPORTED DATA OBJECTS see kie-issues#1514 + const factClassName = isRootType + ? treeViewStatus.activeItems[0].customBadgeContent!.toString() + : rootDataObject.customBadgeContent!.toString(); + const factMappingValueType = isRootType ? "EXPRESSION" : "NOT_EXPRESSION"; + + testScenarioEditorStoreApi.setState((state) => { + const factMappings = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const factMappingValuesTypes = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; + + const { updatedFactMapping } = updateColumn({ + className: className, + expressionAlias: expressionAlias, + expressionElementsSteps: expressionElementsSteps, + expressionIdentifierName: selectedColumnMetadata!.factMapping.expressionIdentifier.name?.__$$text, + expressionIdentifierType: selectedColumnMetadata!.factMapping.expressionIdentifier.type?.__$$text, + factMappings: factMappings, + factClassName: factClassName, + factIdentifierClassName: selectedColumnMetadata!.factMapping.factIdentifier.className?.__$$text, + factIdentifierName: selectedColumnMetadata!.factMapping.factIdentifier.name?.__$$text, + factMappingValuesTypes: factMappingValuesTypes, + factMappingValueType: factMappingValueType, + factName: factName, + selectedColumnIndex: selectedColumnMetadata!.index, }); - /** Updating the selectedColumn */ - updateSelectedColumnMetaData({ - factMapping: JSON.parse(JSON.stringify(factMappingToUpdate)), + state.dispatch(state).table.updateSelectedColumn({ + factMapping: updatedFactMapping, index: selectedColumnMetadata!.index, isBackground: isBackground, }); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping - : deepClonedFactMappings, - }, - }, - scesimData: { - Scenario: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimData.Scenario - : deepClonedRowsData, - }, - }, - background: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? deepClonedFactMappings - : prevState.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping, - }, - }, - scesimData: { - BackgroundData: isBackground - ? deepClonedRowsData - : prevState.ScenarioSimulationModel.background.scesimData.BackgroundData, - }, - }, - }, - }; }); }, [ @@ -498,9 +426,8 @@ function TestScenarioDataSelectorPanel({ findDataObjectRootParent, isDataObjectRootParent, selectedColumnMetadata, + testScenarioEditorStoreApi, treeViewStatus.activeItems, - updateSelectedColumnMetaData, - updateTestScenarioModel, ] ); @@ -552,12 +479,12 @@ function TestScenarioDataSelectorPanel({ return ( <> - {assetType === TestScenarioType[TestScenarioType.DMN] + {testScenarioType === "DMN" ? i18n.drawer.dataSelector.descriptionDMN : i18n.drawer.dataSelector.descriptionRule} void; - onUpdateSettingField: (field: string, value: boolean | string) => void; - scesimModel: SceSimModel; - selectedColumnMetaData: TestScenarioSelectedColumnMetaData | null; - selectedDock: TestScenarioEditorDock; - testScenarioSettings: TestScenarioSettings; - updateSelectedColumnMetaData: React.Dispatch>; - updateTestScenarioModel: React.Dispatch>; -}) { +function TestScenarioDrawerPanel({ fileName, onDrawerClose }: { fileName: string; onDrawerClose: () => void }) { const { i18n } = useTestScenarioEditorI18n(); + const navigation = useTestScenarioEditorStore((state) => state.navigation); + const settingsModel = useTestScenarioEditorStore((state) => state.scesim.model.ScenarioSimulationModel.settings); + const testScenarioType = settingsModel.type?.__$$text.toUpperCase(); return ( @@ -77,17 +51,17 @@ function TestScenarioDrawerPanel({ {(() => { - switch (selectedDock) { + switch (navigation.dock.selected) { case TestScenarioEditorDock.CHEATSHEET: return i18n.drawer.cheatSheet.title; case TestScenarioEditorDock.DATA_OBJECT: - return testScenarioSettings.assetType === TestScenarioType[TestScenarioType.DMN] + return testScenarioType === "DMN" ? i18n.drawer.dataSelector.titleDMN : i18n.drawer.dataSelector.titleRule; case TestScenarioEditorDock.SETTINGS: return i18n.drawer.settings.title; default: - throw new Error("Wrong state, an invalid dock has been selected " + selectedDock); + throw new Error("Wrong state, an invalid dock has been selected " + navigation.dock.selected); } })()} @@ -96,30 +70,15 @@ function TestScenarioDrawerPanel({ {(() => { - switch (selectedDock) { + switch (navigation.dock.selected) { case TestScenarioEditorDock.CHEATSHEET: - return ; + return ; case TestScenarioEditorDock.DATA_OBJECT: - return ( - - ); + return ; case TestScenarioEditorDock.SETTINGS: - return ( - - ); + return ; default: - throw new Error("Wrong state, an invalid dock has been selected " + selectedDock); + throw new Error("Wrong state, an invalid dock has been selected " + navigation.dock.selected); } })()} diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerSettingsPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerSettingsPanel.tsx index 6e6f421e9a4..9f522a2ccbf 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerSettingsPanel.tsx +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerSettingsPanel.tsx @@ -17,31 +17,38 @@ * under the License. */ import * as React from "react"; +import { useCallback } from "react"; import { Checkbox } from "@patternfly/react-core/dist/esm/components/Checkbox"; import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/esm/components/FormSelect"; +import { HelpIcon } from "@patternfly/react-icons/dist/esm/icons/help-icon"; +import { Icon } from "@patternfly/react-core/dist/esm/components/Icon/Icon"; import { TextInput } from "@patternfly/react-core/dist/js/components/TextInput"; import { Title } from "@patternfly/react-core/dist/js/components/Title"; +import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip"; -import { TestScenarioSettings, TestScenarioType } from "../TestScenarioEditor"; -import { useTestScenarioEditorI18n } from "../i18n"; +import { SceSim__settingsType } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; -import { HelpIcon } from "@patternfly/react-icons/dist/esm/icons/help-icon"; -import { Icon } from "@patternfly/react-core/dist/esm/components/Icon/Icon"; -import { Tooltip } from "@patternfly/react-core/dist/esm/components/Tooltip"; +import { useTestScenarioEditorI18n } from "../i18n"; +import { useTestScenarioEditorStore, useTestScenarioEditorStoreApi } from "../store/TestScenarioStoreContext"; import "./TestScenarioDrawerSettingsPanel.css"; -function TestScenarioDrawerSettingsPanel({ - fileName, - onUpdateSettingField, - testScenarioSettings, -}: { - fileName: string; - onUpdateSettingField: (field: string, value: boolean | string) => void; - testScenarioSettings: TestScenarioSettings; -}) { +function TestScenarioDrawerSettingsPanel({ fileName }: { fileName: string }) { const { i18n } = useTestScenarioEditorI18n(); + const settingsModel = useTestScenarioEditorStore((state) => state.scesim.model.ScenarioSimulationModel.settings); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + const testScenarioType = settingsModel.type?.__$$text.toUpperCase(); + + const updateSettingsField = useCallback( + (fieldName: keyof SceSim__settingsType, value: string | boolean) => + testScenarioEditorStoreApi.setState((state) => { + (state.scesim.model.ScenarioSimulationModel.settings[fieldName] as { __$$text: string | boolean }) = { + __$$text: value, + }; + }), + [testScenarioEditorStoreApi] + ); return ( <> @@ -54,11 +61,11 @@ function TestScenarioDrawerSettingsPanel({ - {testScenarioSettings.assetType === TestScenarioType[TestScenarioType.DMN] ? ( + {testScenarioType === "DMN" ? ( <> {i18n.drawer.settings.dmnModel} @@ -78,7 +85,7 @@ function TestScenarioDrawerSettingsPanel({ @@ -87,7 +94,7 @@ function TestScenarioDrawerSettingsPanel({ @@ -104,10 +111,10 @@ function TestScenarioDrawerSettingsPanel({ onUpdateSettingField("dmoSession", value)} + onChange={(value) => updateSettingsField("dmoSession", value)} placeholder={i18n.drawer.settings.kieSessionRulePlaceholder} type="text" - value={testScenarioSettings.kieSessionRule} + value={settingsModel.dmoSession?.__$$text} /> {i18n.drawer.settings.ruleFlowGroup} @@ -119,18 +126,18 @@ function TestScenarioDrawerSettingsPanel({ onUpdateSettingField("ruleFlowGroup", value)} + onChange={(value) => updateSettingsField("ruleFlowGroup", value)} placeholder={i18n.drawer.settings.ruleFlowGroupPlaceholder} type="text" - value={testScenarioSettings.ruleFlowGroup} + value={settingsModel.ruleFlowGroup?.__$$text} />
onUpdateSettingField("stateless", value)} + onChange={(value) => updateSettingsField("stateless", value)} />
@@ -145,9 +152,9 @@ function TestScenarioDrawerSettingsPanel({
onUpdateSettingField("skipFromBuild", value)} + onChange={(value) => updateSettingsField("skipFromBuild", value)} />
diff --git a/packages/scesim-editor/src/hook/useEffectAfterFirstRender.ts b/packages/scesim-editor/src/hook/useEffectAfterFirstRender.ts new file mode 100644 index 00000000000..df84f4b1ae9 --- /dev/null +++ b/packages/scesim-editor/src/hook/useEffectAfterFirstRender.ts @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { useEffect, useRef } from "react"; + +export function useEffectAfterFirstRender(effect: Parameters[0], b: React.DependencyList) { + const didMountRef = useRef(false); + + useEffect(() => { + if (didMountRef.current) { + return effect(); + } + didMountRef.current = true; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, b); +} diff --git a/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts b/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts index be68682f4d4..1eaebb8771b 100644 --- a/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts +++ b/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts @@ -115,6 +115,12 @@ interface TestScenarioEditorDictionary extends ReferenceDictionary { title: string; }; }; + errorFallBack: { + title: string; + body: string; + lastActionButton: string; + fileIssueHref: string; + }; sidebar: { cheatSheetTooltip: string; dataSelectorTooltip: string; diff --git a/packages/scesim-editor/src/i18n/locales/en.ts b/packages/scesim-editor/src/i18n/locales/en.ts index 72de8ce02cc..888f1e2b871 100644 --- a/packages/scesim-editor/src/i18n/locales/en.ts +++ b/packages/scesim-editor/src/i18n/locales/en.ts @@ -137,6 +137,12 @@ export const en: TestScenarioEditorI18n = { title: "Settings", }, }, + errorFallBack: { + title: "An unexpected error happened", + body: "This is a bug. Please consider reporting it so the DMN Editor can continue improving. See the details below.", + lastActionButton: "Try undoing last action", + fileIssueHref: "File an issue", + }, sidebar: { cheatSheetTooltip: "CheatSheet: Useful information for Test Scenario Usage", dataSelectorTooltip: "Selector: It provides a tool to edit your Scenarios", diff --git a/packages/scesim-editor/src/mutations/addColumn.ts b/packages/scesim-editor/src/mutations/addColumn.ts new file mode 100644 index 00000000000..ab5a22bf7a3 --- /dev/null +++ b/packages/scesim-editor/src/mutations/addColumn.ts @@ -0,0 +1,183 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 _ from "lodash"; +import { v4 as uuid } from "uuid"; + +import { + SceSim__FactMappingType, + SceSim__FactMappingValuesTypes, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; +import { InsertRowColumnsDirection } from "@kie-tools/boxed-expression-component/dist/api/BeeTable"; + +export function addColumn({ + beforeIndex, + factMappings, + factMappingValues, + insertDirection, + isInstance, + selectedColumnFactMappingIndex, +}: { + beforeIndex: number; + factMappings: SceSim__FactMappingType[]; + factMappingValues: SceSim__FactMappingValuesTypes[]; + insertDirection: InsertRowColumnsDirection; + isInstance: boolean; + selectedColumnFactMappingIndex: number; +}) { + const selectedColumnFactMapping = factMappings[selectedColumnFactMappingIndex]; + const targetColumnIndex = determineNewColumnTargetIndex( + factMappings, + insertDirection, + isInstance, + selectedColumnFactMappingIndex, + selectedColumnFactMapping + ); + + const instanceDefaultNames = factMappings + .filter((factMapping) => factMapping.factAlias!.__$$text.startsWith("INSTANCE-")) + .map((factMapping) => factMapping.factAlias!.__$$text); + + const isNewInstance = isInstance || selectedColumnFactMapping.factIdentifier.className?.__$$text === "java.lang.Void"; + + const newFactMapping = { + className: { __$$text: "java.lang.Void" }, + columnWidth: { __$$text: 150 }, + expressionAlias: { __$$text: "PROPERTY" }, + expressionElements: isNewInstance + ? undefined + : { + ExpressionElement: [ + { + step: { + __$$text: selectedColumnFactMapping.expressionElements!.ExpressionElement![0].step.__$$text, + }, + }, + ], + }, + expressionIdentifier: { + name: { __$$text: `_${uuid()}`.toLocaleUpperCase() }, + type: { __$$text: selectedColumnFactMapping.expressionIdentifier.type!.__$$text }, + }, + factAlias: { + __$$text: isNewInstance + ? getNextAvailablePrefixedName(instanceDefaultNames, "INSTANCE") + : selectedColumnFactMapping.factAlias.__$$text, + }, + factIdentifier: { + name: { + __$$text: isNewInstance + ? getNextAvailablePrefixedName(instanceDefaultNames, "INSTANCE") + : selectedColumnFactMapping.factIdentifier.name!.__$$text, + }, + className: { + __$$text: isNewInstance ? "java.lang.Void" : selectedColumnFactMapping.factIdentifier.className!.__$$text, + }, + }, + factMappingValueType: { __$$text: "NOT_EXPRESSION" }, + }; + + factMappings.splice(targetColumnIndex, 0, newFactMapping); + + factMappingValues.forEach((scenario) => { + scenario.factMappingValues.FactMappingValue!.splice(beforeIndex + 1, 0, { + expressionIdentifier: { + name: { __$$text: newFactMapping.expressionIdentifier.name.__$$text }, + type: { __$$text: newFactMapping.expressionIdentifier.type.__$$text }, + }, + factIdentifier: { + name: { __$$text: newFactMapping.factIdentifier.name.__$$text }, + className: { __$$text: newFactMapping.factIdentifier.className.__$$text }, + }, + rawValue: { __$$text: "", "@_class": "string" }, + }); + }); +} + +/* It determines in which index position a column should be added. In case of a field, the new column index is simply + in the right or in the left of the selected column. In case of a new instance, it's required to find the first column + index outside the selected Instance group. */ +const determineNewColumnTargetIndex = ( + factMappings: SceSim__FactMappingType[], + insertDirection: InsertRowColumnsDirection, + isInstance: boolean, + selectedColumnIndex: number, + selectedFactMapping: SceSim__FactMappingType +) => { + const groupType = selectedFactMapping.expressionIdentifier.type!.__$$text; + const instanceName = selectedFactMapping.factIdentifier.name!.__$$text; + const instanceType = selectedFactMapping.factIdentifier.className!.__$$text; + + if (!isInstance) { + if (insertDirection === InsertRowColumnsDirection.AboveOrRight) { + return selectedColumnIndex + 1; + } else { + return selectedColumnIndex; + } + } + + let newColumnTargetColumn = -1; + + if (insertDirection === InsertRowColumnsDirection.AboveOrRight) { + for (let i = selectedColumnIndex; i < factMappings.length; i++) { + const currentFM = factMappings[i]; + if ( + currentFM.expressionIdentifier.type!.__$$text === groupType && + currentFM.factIdentifier.name?.__$$text === instanceName && + currentFM.factIdentifier.className?.__$$text === instanceType + ) { + if (i == factMappings.length - 1) { + newColumnTargetColumn = i + 1; + } + } else { + newColumnTargetColumn = i; + break; + } + } + } else { + for (let i = selectedColumnIndex; i >= 0; i--) { + const currentFM = factMappings[i]; + + if ( + currentFM.expressionIdentifier.type!.__$$text === groupType && + currentFM.factIdentifier.name?.__$$text === instanceName && + currentFM.factIdentifier.className?.__$$text === instanceType + ) { + if (i == 0) { + newColumnTargetColumn = 0; + } + } else { + newColumnTargetColumn = i + 1; + break; + } + } + } + + return newColumnTargetColumn; +}; + +const getNextAvailablePrefixedName = ( + names: string[], + namePrefix: string, + lastIndex: number = names.length +): string => { + const candidate = `${namePrefix}-${lastIndex + 1}`; + const elemWithCandidateName = names.indexOf(candidate); + return elemWithCandidateName >= 0 ? getNextAvailablePrefixedName(names, namePrefix, lastIndex + 1) : candidate; +}; diff --git a/packages/scesim-editor/src/mutations/addRow.ts b/packages/scesim-editor/src/mutations/addRow.ts new file mode 100644 index 00000000000..65887921b4e --- /dev/null +++ b/packages/scesim-editor/src/mutations/addRow.ts @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { + SceSim__FactMappingType, + SceSim__FactMappingValuesTypes, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function addRow({ + beforeIndex, + factMappings, + factMappingValues, +}: { + beforeIndex: number; + factMappings: SceSim__FactMappingType[]; + factMappingValues: SceSim__FactMappingValuesTypes[]; +}) { + /* Creating a new Scenario (Row) composed by a list of FactMappingValues. The list order is not relevant. */ + const factMappingValuesItems = factMappings.map((factMapping) => { + return { + expressionIdentifier: { + name: { __$$text: factMapping.expressionIdentifier.name!.__$$text }, + type: { __$$text: factMapping.expressionIdentifier.type!.__$$text }, + }, + factIdentifier: { + name: { __$$text: factMapping.factIdentifier.name!.__$$text }, + className: { __$$text: factMapping.factIdentifier.className!.__$$text }, + }, + rawValue: { __$$text: "", "@_class": "string" }, + }; + }); + + const newScenario = { + factMappingValues: { + FactMappingValue: factMappingValuesItems, + }, + }; + + factMappingValues.splice(beforeIndex, 0, newScenario); +} diff --git a/packages/scesim-editor/src/mutations/deleteColumn.ts b/packages/scesim-editor/src/mutations/deleteColumn.ts new file mode 100644 index 00000000000..07137ff667d --- /dev/null +++ b/packages/scesim-editor/src/mutations/deleteColumn.ts @@ -0,0 +1,87 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 _, { isNumber } from "lodash"; +import { + SceSim__FactMappingType, + SceSim__FactMappingValuesTypes, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function deleteColumn({ + factMappingIndexToRemove, + factMappings, + factMappingValues, + isBackground, + isInstance, + selectedColumnIndex, +}: { + factMappingIndexToRemove: number; + factMappings: SceSim__FactMappingType[]; + factMappingValues: SceSim__FactMappingValuesTypes[]; + isBackground: boolean; + isInstance: boolean; + selectedColumnIndex: number; +}): { + deletedFactMappingIndexs: number[]; +} { + /* Retriving the FactMapping (Column) to be removed). If the user selected a single column, it finds the exact + FactMapping to delete. If the user selected an instance (group of columns), it retrives all the FactMappings + that belongs to the the instance group */ + const factMappingToRemove = factMappings[factMappingIndexToRemove]; + const groupType = factMappingToRemove.expressionIdentifier.type!.__$$text; + const instanceName = factMappingToRemove.factIdentifier.name!.__$$text; + const instanceType = factMappingToRemove.factIdentifier.className!.__$$text; + + const allFactMappingWithIndexesToRemove = isInstance + ? factMappings + .map((factMapping, index) => { + if ( + factMapping.expressionIdentifier.type!.__$$text === groupType && + factMapping.factIdentifier.name?.__$$text === instanceName && + factMapping.factIdentifier.className?.__$$text === instanceType + ) { + return { factMappingIndex: index, factMapping: factMapping }; + } else { + return {}; + } + }) + .filter((item) => isNumber(item.factMappingIndex)) + : [{ factMappingIndex: selectedColumnIndex + (isBackground ? 0 : 1), factMapping: factMappingToRemove }]; + + factMappings.splice(allFactMappingWithIndexesToRemove[0].factMappingIndex!, allFactMappingWithIndexesToRemove.length); + + factMappingValues.forEach((rowData) => { + allFactMappingWithIndexesToRemove.forEach((itemToRemove) => { + const factMappingValueColumnIndexToRemove = rowData.factMappingValues.FactMappingValue!.findIndex( + (factMappingValue) => + factMappingValue.factIdentifier.name?.__$$text === itemToRemove.factMapping!.factIdentifier.name?.__$$text && + factMappingValue.factIdentifier.className?.__$$text === + itemToRemove.factMapping!.factIdentifier.className?.__$$text && + factMappingValue.expressionIdentifier.name?.__$$text === + itemToRemove.factMapping!.expressionIdentifier.name?.__$$text && + factMappingValue.expressionIdentifier.type?.__$$text === + itemToRemove.factMapping!.expressionIdentifier.type?.__$$text + ); + + rowData.factMappingValues.FactMappingValue!.splice(factMappingValueColumnIndexToRemove, 1); + }); + }); + + return { deletedFactMappingIndexs: allFactMappingWithIndexesToRemove.flatMap((item) => item.factMappingIndex!) }; +} diff --git a/packages/scesim-editor/src/mutations/deleteRow.ts b/packages/scesim-editor/src/mutations/deleteRow.ts new file mode 100644 index 00000000000..26f14903a7d --- /dev/null +++ b/packages/scesim-editor/src/mutations/deleteRow.ts @@ -0,0 +1,30 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SceSim__FactMappingValuesTypes } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function deleteRow({ + rowIndex, + factMappingValues, +}: { + rowIndex: number; + factMappingValues: SceSim__FactMappingValuesTypes[]; +}) { + factMappingValues.splice(rowIndex, 1); +} diff --git a/packages/scesim-editor/src/mutations/duplicateRow.ts b/packages/scesim-editor/src/mutations/duplicateRow.ts new file mode 100644 index 00000000000..268da3f826f --- /dev/null +++ b/packages/scesim-editor/src/mutations/duplicateRow.ts @@ -0,0 +1,41 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SceSim__FactMappingValuesTypes } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function dupliacteRow({ + rowIndex, + factMappingValues, +}: { + rowIndex: number; + factMappingValues: SceSim__FactMappingValuesTypes[]; +}) { + /* It simply clones a Scenario (Row) and adds it in a current-cloned Scenario list */ + const factMappingValuesItems = JSON.parse( + JSON.stringify(factMappingValues[rowIndex].factMappingValues.FactMappingValue) + ); + + const newScenario = { + factMappingValues: { + FactMappingValue: factMappingValuesItems, + }, + }; + + factMappingValues.splice(rowIndex, 0, newScenario); +} diff --git a/packages/scesim-editor/src/mutations/updateCell.ts b/packages/scesim-editor/src/mutations/updateCell.ts new file mode 100644 index 00000000000..3a984fb32c7 --- /dev/null +++ b/packages/scesim-editor/src/mutations/updateCell.ts @@ -0,0 +1,52 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { + SceSim__FactMappingType, + SceSim__FactMappingValuesTypes, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function updateCell({ + columnIndex, + factMappings, + factMappingValuesTypes, + rowIndex, + value, +}: { + columnIndex: number; + factMappings: SceSim__FactMappingType[]; + factMappingValuesTypes: SceSim__FactMappingValuesTypes[]; + rowIndex: number; + value: any; +}) { + /* To update the related FactMappingValue, it compares every FactMappingValue associated with the Scenario (Row) + that contains the cell with the FactMapping (Column) fields factIdentifier and expressionIdentifier */ + const factMapping = factMappings[columnIndex]; + const factMappingValues = factMappingValuesTypes[rowIndex].factMappingValues.FactMappingValue!; + + const factMappingValueToUpdateIndex = factMappingValues.findIndex( + (factMappingValue) => + factMappingValue.factIdentifier.name?.__$$text === factMapping!.factIdentifier.name?.__$$text && + factMappingValue.factIdentifier.className?.__$$text === factMapping!.factIdentifier.className?.__$$text && + factMappingValue.expressionIdentifier.name?.__$$text === factMapping!.expressionIdentifier.name?.__$$text && + factMappingValue.expressionIdentifier.type?.__$$text === factMapping!.expressionIdentifier.type?.__$$text + ); + const factMappingValueToUpdate = factMappingValues[factMappingValueToUpdateIndex]; + factMappingValueToUpdate.rawValue = { __$$text: value }; +} diff --git a/packages/scesim-editor/src/mutations/updateColumn.ts b/packages/scesim-editor/src/mutations/updateColumn.ts new file mode 100644 index 00000000000..081e78de4c4 --- /dev/null +++ b/packages/scesim-editor/src/mutations/updateColumn.ts @@ -0,0 +1,84 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 _ from "lodash"; +import { + SceSim__FactMappingType, + SceSim__FactMappingValuesTypes, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function updateColumn({ + className, + expressionAlias, + expressionElementsSteps, + expressionIdentifierName, + expressionIdentifierType, + factMappings, + factMappingValuesTypes, + factMappingValueType, + factClassName, + factIdentifierClassName, + factIdentifierName, + factName, + selectedColumnIndex, +}: { + className: string; + expressionAlias: string; + expressionElementsSteps: string[]; + expressionIdentifierName: string | undefined; + expressionIdentifierType: string | undefined; + factMappings: SceSim__FactMappingType[]; + factMappingValuesTypes: SceSim__FactMappingValuesTypes[]; + factMappingValueType: "EXPRESSION" | "NOT_EXPRESSION"; + factClassName: string; + factIdentifierClassName: string | undefined; + factIdentifierName: string | undefined; + factName: string; + selectedColumnIndex: number; +}): { updatedFactMapping: SceSim__FactMappingType } { + const factMappingToUpdate = factMappings[selectedColumnIndex]; + factMappingToUpdate.className = { __$$text: className }; + factMappingToUpdate.factAlias = { __$$text: factName }; + factMappingToUpdate.factIdentifier.className = { __$$text: factClassName }; + factMappingToUpdate.factIdentifier.name = { __$$text: factName }; + factMappingToUpdate.factMappingValueType = { __$$text: factMappingValueType }; + factMappingToUpdate.expressionAlias = { __$$text: expressionAlias }; + factMappingToUpdate.expressionElements = { + ExpressionElement: expressionElementsSteps.map((ee) => { + return { step: { __$$text: ee } }; + }), + }; + + factMappingValuesTypes.forEach((fmv) => { + const factMappingValues = fmv.factMappingValues.FactMappingValue!; + const factMappingValueToUpdateIndex = factMappingValues.findIndex( + (factMappingValue) => + factMappingValue.factIdentifier.name?.__$$text === factIdentifierName && + factMappingValue.factIdentifier.className?.__$$text === factIdentifierClassName && + factMappingValue.expressionIdentifier.name?.__$$text === expressionIdentifierName && + factMappingValue.expressionIdentifier.type?.__$$text === expressionIdentifierType + ); + const factMappingValueToUpdate = factMappingValues[factMappingValueToUpdateIndex]; + factMappingValueToUpdate.factIdentifier.className = { __$$text: factClassName }; + factMappingValueToUpdate.factIdentifier.name = { __$$text: factName }; + //factMappingValueToUpdate.rawValue = { __$$text: update.value }, //TODO 2 related see kie-issues#1514 + }); + + return { updatedFactMapping: JSON.parse(JSON.stringify(factMappingToUpdate)) }; +} diff --git a/packages/scesim-editor/src/mutations/updateColumnWidth.ts b/packages/scesim-editor/src/mutations/updateColumnWidth.ts new file mode 100644 index 00000000000..8a59346ad03 --- /dev/null +++ b/packages/scesim-editor/src/mutations/updateColumnWidth.ts @@ -0,0 +1,38 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SceSim__FactMappingType } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +export function updateColumnWidth({ + factMappings, + columnIndex, + newWidth, + oldWidth, +}: { + factMappings: SceSim__FactMappingType[]; + columnIndex: number; + newWidth: number | undefined; + oldWidth: number | undefined; +}) { + if (newWidth && oldWidth !== newWidth) { + factMappings[columnIndex].columnWidth = { + __$$text: newWidth, + }; + } +} diff --git a/packages/scesim-editor/src/reactExt/ErrorBoundary.tsx b/packages/scesim-editor/src/reactExt/ErrorBoundary.tsx deleted file mode 100644 index e61f50af242..00000000000 --- a/packages/scesim-editor/src/reactExt/ErrorBoundary.tsx +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF 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 * as React from "react"; -import { ErrorInfo } from "react"; - -interface State { - hasError: boolean; -} - -interface Props { - children: React.ReactNode; - error: React.ReactNode; - setError?: React.Dispatch; -} - -export class ErrorBoundary extends React.Component { - constructor(props: any) { - super(props); - this.state = { hasError: false }; - } - - public reset() { - this.props.setError?.(null); - this.setState({ hasError: false }); - } - - public componentDidCatch(error: Error, errorInfo: ErrorInfo) { - this.props.setError?.(error); - console.error(error); - } - - public render() { - if (this.state.hasError) { - return this.props.error; - } - - return this.props.children; - } - - public static getDerivedStateFromError(error: Error) { - return { hasError: true }; - } -} - -export default ErrorBoundary; diff --git a/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx b/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx index df9671c6bb9..de41743c500 100644 --- a/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx +++ b/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx @@ -27,25 +27,32 @@ import CogIcon from "@patternfly/react-icons/dist/esm/icons/cog-icon"; import EditIcon from "@patternfly/react-icons/dist/esm/icons/edit-alt-icon"; import InfoIcon from "@patternfly/react-icons/dist/esm/icons/info-icon"; -import { TestScenarioEditorDock } from "../TestScenarioEditor"; import { useTestScenarioEditorI18n } from "../i18n"; +import { TestScenarioEditorDock } from "../store/TestScenarioEditorStore"; +import { useTestScenarioEditorStore, useTestScenarioEditorStoreApi } from "../store/TestScenarioStoreContext"; import "./TestScenarioSideBarMenu.css"; -function TestScenarioSideBarMenu({ - onSideBarButtonClicked, - selectedSideBarMenuItem, -}: { - onSideBarButtonClicked: (selected: TestScenarioEditorDock) => void; - selectedSideBarMenuItem: { isOpen: boolean; selected: TestScenarioEditorDock }; -}) { +function TestScenarioSideBarMenu() { const { i18n } = useTestScenarioEditorI18n(); + const navigation = useTestScenarioEditorStore((state) => state.navigation); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); const isSelectedMenuItem = useCallback( (item: TestScenarioEditorDock) => { - return selectedSideBarMenuItem.isOpen && selectedSideBarMenuItem.selected === item; + return navigation.dock.isOpen && navigation.dock.selected === item; }, - [selectedSideBarMenuItem] + [navigation.dock] + ); + + const updateSelectedDock = useCallback( + (selected: TestScenarioEditorDock) => { + testScenarioEditorStoreApi.setState((state) => { + state.navigation.dock.isOpen = true; + state.navigation.dock.selected = selected; + }); + }, + [testScenarioEditorStoreApi] ); return ( @@ -58,7 +65,7 @@ function TestScenarioSideBarMenu({ : "kie-scesim-editor-side-bar-menu--button" } variant="plain" - onClick={() => onSideBarButtonClicked(TestScenarioEditorDock.DATA_OBJECT)} + onClick={() => updateSelectedDock(TestScenarioEditorDock.DATA_OBJECT)} icon={} /> @@ -70,7 +77,7 @@ function TestScenarioSideBarMenu({ : "kie-scesim-editor-side-bar-menu--button" } icon={} - onClick={() => onSideBarButtonClicked(TestScenarioEditorDock.CHEATSHEET)} + onClick={() => updateSelectedDock(TestScenarioEditorDock.CHEATSHEET)} variant="plain" />
@@ -82,7 +89,7 @@ function TestScenarioSideBarMenu({ : "kie-scesim-editor-side-bar-menu--button" } icon={} - onClick={() => onSideBarButtonClicked(TestScenarioEditorDock.SETTINGS)} + onClick={() => updateSelectedDock(TestScenarioEditorDock.SETTINGS)} variant="plain" /> diff --git a/packages/scesim-editor/src/store/ComputedStateCache.ts b/packages/scesim-editor/src/store/ComputedStateCache.ts new file mode 100644 index 00000000000..400595c931b --- /dev/null +++ b/packages/scesim-editor/src/store/ComputedStateCache.ts @@ -0,0 +1,96 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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. + */ + +export type TypeOrReturnType = T extends (...args: any[]) => any ? ReturnType : T; + +export type CacheEntry = { + value: TypeOrReturnType | undefined; + dependencies: readonly any[]; +}; + +export type Cache = { + [K in keyof T]: CacheEntry; +}; + +let r: number = 0; + +export class ComputedStateCache> { + private readonly cache: Cache; + + constructor(initialValues: Cache) { + this.cache = { ...initialValues }; + } + + public cached( + key: K, + delegate: (...dependencies: D) => TypeOrReturnType, + dependencies: D + ): TypeOrReturnType { + r++; + + const cachedDeps = this.cache[key]?.dependencies ?? []; + + let depsAreEqual = cachedDeps.length === dependencies.length; + if (depsAreEqual) { + for (let i = 0; i < cachedDeps.length; i++) { + if (!Object.is(cachedDeps[i], dependencies[i])) { + depsAreEqual = false; + } + } + } + + if (depsAreEqual) { + return this.cache[key].value!; + } + + const v = delegate(...dependencies); + this.cache[key].dependencies = dependencies; + this.cache[key].value = v; + return v; + } + + public cachedData( + key: K, + delegate: (...data: Y) => TypeOrReturnType, + data: Y, + dependencies: D + ): TypeOrReturnType { + r++; + + const cachedDeps = this.cache[key]?.dependencies ?? []; + + let depsAreEqual = cachedDeps.length === dependencies.length; + if (depsAreEqual) { + for (let i = 0; i < cachedDeps.length; i++) { + if (!Object.is(cachedDeps[i], dependencies[i])) { + depsAreEqual = false; + } + } + } + + if (depsAreEqual) { + return this.cache[key].value!; + } + + const v = delegate(...data); + this.cache[key].dependencies = dependencies; + this.cache[key].value = v; + return v; + } +} diff --git a/packages/scesim-editor/src/store/TestScenarioEditorStore.ts b/packages/scesim-editor/src/store/TestScenarioEditorStore.ts new file mode 100644 index 00000000000..7be9d3f10ec --- /dev/null +++ b/packages/scesim-editor/src/store/TestScenarioEditorStore.ts @@ -0,0 +1,164 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { SceSimModel } from "@kie-tools/scesim-marshaller"; +import { enableMapSet } from "immer"; +import { create } from "zustand"; +import { immer } from "zustand/middleware/immer"; + +import { SceSim__FactMappingType } from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; + +import { ComputedStateCache } from "./ComputedStateCache"; +import { computeTestScenarioDataObjects } from "./computed/computeTestScenarioDataObjects"; + +enableMapSet(); // Necessary because `Computed` has a lot of Maps and Sets. + +export enum TestScenarioEditorDock { + CHEATSHEET, + DATA_OBJECT, + SETTINGS, +} + +export enum TestScenarioEditorTab { + BACKGROUND, + SIMULATION, +} + +export type TestScenarioAlert = { + enabled: boolean; + message?: string; + variant: "success" | "danger" | "warning" | "info" | "default"; +}; + +export type TestScenarioDataObject = { + id: string; + children?: TestScenarioDataObject[]; + customBadgeContent?: string; + isSimpleTypeFact?: boolean; + name: string; +}; + +export type TestScenarioSelectedColumnMetaData = { + factMapping: SceSim__FactMappingType; + index: number; + isBackground: boolean; +}; + +export interface State { + computed: (s: State) => Computed; + dispatch: (s: State) => Dispatch; + navigation: { + dock: { + isOpen: boolean; + selected: TestScenarioEditorDock; + }; + tab: TestScenarioEditorTab; + }; + scesim: { model: SceSimModel }; + table: { + background: { + selectedColumn: TestScenarioSelectedColumnMetaData | null; + }; + simulation: { + selectedColumn: TestScenarioSelectedColumnMetaData | null; + }; + }; +} + +// Read this to understand why we need computed as part of the store. +// https://github.com/pmndrs/zustand/issues/132#issuecomment-1120467721 +export type Computed = { + getTestScenarioDataObjects(): TestScenarioDataObject[]; +}; + +export type Dispatch = { + scesim: { + reset: () => void; + }; + table: { + updateSelectedColumn: (columnMetadata: TestScenarioSelectedColumnMetaData | null) => void; + }; +}; + +export const defaultStaticState = (): Omit => ({ + navigation: { + dock: { + isOpen: true, + selected: TestScenarioEditorDock.DATA_OBJECT, + }, + tab: TestScenarioEditorTab.SIMULATION, + }, + table: { + background: { + selectedColumn: null, + }, + simulation: { + selectedColumn: null, + }, + }, +}); + +export function createTestScenarioEditorStore(model: SceSimModel, computedCache: ComputedStateCache) { + console.trace("[TestScenarioEditorStore] Creating store with above model and empty cache "); + console.trace(model); + + const { ...defaultState } = defaultStaticState(); + return create( + immer(() => ({ + ...defaultState, + scesim: { + model: model, + }, + dispatch(state: State) { + return { + scesim: { + reset: () => { + state.navigation.tab = TestScenarioEditorTab.SIMULATION; + state.navigation.dock.isOpen = true; + state.navigation.dock.selected = TestScenarioEditorDock.DATA_OBJECT; + state.table.background.selectedColumn = null; + state.table.simulation.selectedColumn = null; + }, + }, + table: { + updateSelectedColumn: (columnMetadata: TestScenarioSelectedColumnMetaData) => { + if (state.navigation.tab === TestScenarioEditorTab.BACKGROUND) { + state.table.background.selectedColumn = columnMetadata; + } else { + state.table.simulation.selectedColumn = columnMetadata; + } + }, + }, + }; + }, + computed(state: State) { + return { + getTestScenarioDataObjects: () => { + return computedCache.cachedData( + "getTestScenarioDataObjects", + computeTestScenarioDataObjects, + [state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping], + [state.scesim.model.ScenarioSimulationModel.settings.type] + ); + }, + }; + }, + })) + ); +} diff --git a/packages/scesim-editor/src/store/TestScenarioStoreContext.ts b/packages/scesim-editor/src/store/TestScenarioStoreContext.ts new file mode 100644 index 00000000000..f910fc424dc --- /dev/null +++ b/packages/scesim-editor/src/store/TestScenarioStoreContext.ts @@ -0,0 +1,47 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { createContext, useContext } from "react"; +import { StoreApi, UseBoundStore } from "zustand"; +import { WithImmer } from "zustand/middleware/immer"; +import { useStoreWithEqualityFn } from "zustand/traditional"; +import { State } from "./TestScenarioEditorStore"; + +type ExtractState = StoreApi extends { getState: () => infer T } ? T : never; + +export type StoreApiType = UseBoundStore>>; + +export const TestScenarioEditorStoreApiContext = createContext({} as any); + +export function useTestScenarioEditorStore( + selector: (state: State) => StateSlice, + equalityFn?: (a: StateSlice, b: StateSlice) => boolean +) { + const store = useContext(TestScenarioEditorStoreApiContext); + + if (store === null) { + throw new Error("Can't use Test Scenario Editor Store outside of the TestScenarioEditor component."); + } + + return useStoreWithEqualityFn(store, selector, equalityFn); +} + +export function useTestScenarioEditorStoreApi() { + return useContext(TestScenarioEditorStoreApiContext); +} diff --git a/packages/scesim-editor/src/store/computed/computeTestScenarioDataObjects.ts b/packages/scesim-editor/src/store/computed/computeTestScenarioDataObjects.ts new file mode 100644 index 00000000000..228f4b1fbe8 --- /dev/null +++ b/packages/scesim-editor/src/store/computed/computeTestScenarioDataObjects.ts @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF 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 { Computed, State, TestScenarioDataObject } from "../TestScenarioEditorStore"; + +export function computeTestScenarioDataObjects( + factMappings: State["scesim"]["model"]["ScenarioSimulationModel"]["simulation"]["scesimModelDescriptor"]["factMappings"]["FactMapping"] +) { + /* To create the Data Object arrays we need an external source, in details: */ + /* DMN Data: Retrieving DMN type from linked DMN file */ + /* Java classes: Retrieving Java classes info from the user projects */ + /* At this time, none of the above are supported */ + /* Therefore, it tries to retrieve these info from the SCESIM file, if are present */ + + /* Retriving Data Object from the scesim file. + That makes sense for previously created scesim files */ + + const factsMappings = factMappings ?? []; + const dataObjects: TestScenarioDataObject[] = []; + + /* The first two FactMapping are related to the "Number" and "Description" columns. + If those columns only are present, no Data Objects can be detected in the scesim file */ + for (let i = 2; i < factsMappings.length; i++) { + if (factsMappings[i].className!.__$$text === "java.lang.Void") { + continue; + } + const factID = factsMappings[i].expressionElements!.ExpressionElement![0].step.__$$text; + const dataObject = dataObjects.find((value) => value.id === factID); + const isSimpleTypeFact = factsMappings[i].expressionElements!.ExpressionElement!.length === 1; + const propertyID = isSimpleTypeFact //POTENTIAL BUG + ? factsMappings[i].expressionElements!.ExpressionElement![0].step.__$$text.concat(".") + : factsMappings[i] + .expressionElements!.ExpressionElement!.map((expressionElement) => expressionElement.step.__$$text) + .join("."); + const propertyName = isSimpleTypeFact + ? "value" + : factsMappings[i].expressionElements!.ExpressionElement!.slice(-1)[0].step.__$$text; + if (dataObject) { + if (!dataObject.children?.some((value) => value.id === propertyID)) { + dataObject.children!.push({ + id: propertyID, + customBadgeContent: factsMappings[i].className.__$$text, + isSimpleTypeFact: isSimpleTypeFact, + name: propertyName, + }); + } + } else { + dataObjects.push({ + id: factID, + name: factsMappings[i].factAlias!.__$$text, + customBadgeContent: factsMappings[i].factIdentifier!.className!.__$$text, + children: [ + { + id: propertyID, + name: propertyName, + customBadgeContent: factsMappings[i].className.__$$text, + }, + ], + }); + } + } + + return dataObjects; +} diff --git a/packages/scesim-editor/src/common/TestScenarioCommonConstants.ts b/packages/scesim-editor/src/store/computed/initial.ts similarity index 76% rename from packages/scesim-editor/src/common/TestScenarioCommonConstants.ts rename to packages/scesim-editor/src/store/computed/initial.ts index 55edecff712..ce228895c2a 100644 --- a/packages/scesim-editor/src/common/TestScenarioCommonConstants.ts +++ b/packages/scesim-editor/src/store/computed/initial.ts @@ -17,9 +17,12 @@ * under the License. */ -export const EMPTY_TYPE = "java.lang.Void"; +import { Cache } from "../ComputedStateCache"; +import { Computed } from "../TestScenarioEditorStore"; -export enum TEST_SCENARIO_EXPRESSION_TYPE { - EXPRESSION, - NOT_EXPRESSION, -} +export const INITIAL_COMPUTED_CACHE: Cache = { + getTestScenarioDataObjects: { + value: undefined, + dependencies: [], + }, +}; diff --git a/packages/scesim-editor/src/table/TestScenarioTable.tsx b/packages/scesim-editor/src/table/TestScenarioTable.tsx index 32ec8ae704d..14a83ff5289 100644 --- a/packages/scesim-editor/src/table/TestScenarioTable.tsx +++ b/packages/scesim-editor/src/table/TestScenarioTable.tsx @@ -21,17 +21,7 @@ import * as React from "react"; import { useCallback, useMemo } from "react"; import * as ReactTable from "react-table"; -import _, { isNumber } from "lodash"; -import { v4 as uuid } from "uuid"; - -import { - SceSim__backgroundDatasType, - SceSim__backgroundType, - SceSim__FactMappingType, - SceSim__FactMappingValuesTypes, - SceSim__scenariosType, - SceSim__simulationType, -} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; +import _ from "lodash"; import { BeeTableContextMenuAllowedOperationsConditions, @@ -47,30 +37,32 @@ import { getColumnsAtLastLevel, } from "@kie-tools/boxed-expression-component/dist/table/BeeTable"; -import { SceSimModel } from "@kie-tools/scesim-marshaller"; +import { + SceSim__backgroundDatasType, + SceSim__backgroundType, + SceSim__FactMappingType, + SceSim__scenariosType, + SceSim__simulationType, +} from "@kie-tools/scesim-marshaller/dist/schemas/scesim-1_8/ts-gen/types"; import { useTestScenarioEditorI18n } from "../i18n"; -import { TestScenarioSelectedColumnMetaData, TestScenarioType } from "../TestScenarioEditor"; +import { useTestScenarioEditorStore, useTestScenarioEditorStoreApi } from "../store/TestScenarioStoreContext"; +import { addColumn } from "../mutations/addColumn"; +import { deleteColumn } from "../mutations/deleteColumn"; import "./TestScenarioTable.css"; -import { - retrieveFactMappingValueIndexByIdentifiers, - retrieveModelDescriptor, - retrieveRowsDataFromModel, -} from "../common/TestScenarioCommonFunctions"; +import { addRow } from "../mutations/addRow"; +import { deleteRow } from "../mutations/deleteRow"; +import { dupliacteRow } from "../mutations/duplicateRow"; +import { updateCell } from "../mutations/updateCell"; +import { updateColumnWidth } from "../mutations/updateColumnWidth"; function TestScenarioTable({ - assetType, tableData, scrollableParentRef, - updateSelectedColumnMetaData, - updateTestScenarioModel, }: { - assetType: string; tableData: SceSim__simulationType | SceSim__backgroundType; scrollableParentRef: React.RefObject; - updateSelectedColumnMetaData: React.Dispatch>; - updateTestScenarioModel: React.Dispatch>; }) { enum TestScenarioTableColumnHeaderGroup { EXPECT = "expect-header", @@ -89,6 +81,9 @@ function TestScenarioTable({ type ROWTYPE = any; // FIXME: https://github.com/apache/incubator-kie-issues/issues/169 const { i18n } = useTestScenarioEditorI18n(); + const testScenarioEditorStoreApi = useTestScenarioEditorStoreApi(); + const settingsModel = useTestScenarioEditorStore((state) => state.scesim.model.ScenarioSimulationModel.settings); + const testScenarioType = settingsModel.type?.__$$text.toUpperCase(); /** BACKGROUND TABLE MANAGMENT */ @@ -119,71 +114,33 @@ function TestScenarioTable({ const determineDataTypeLabel = useCallback( (dataType: string) => { let dataTypeLabel = dataType; - if (assetType === TestScenarioType[TestScenarioType.RULE]) { + if (testScenarioType === "RULE") { dataTypeLabel = dataTypeLabel.split(".").pop() ?? dataTypeLabel; } return dataTypeLabel.endsWith("Void") ? "" : dataTypeLabel; }, - [assetType] + [testScenarioType] ); /* It updates any column width change in the Model */ const setColumnWidth = useCallback( (inputIndex: number) => (newWidthAction: React.SetStateAction) => { - updateTestScenarioModel((prevState) => { - const oldWidth = retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings - .FactMapping![inputIndex].columnWidth?.__$$text; + testScenarioEditorStoreApi.setState((state) => { + const factMappings = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const oldWidth = factMappings[inputIndex].columnWidth?.__$$text; const newWidth = typeof newWidthAction === "function" ? newWidthAction(oldWidth) : newWidthAction; - let model = prevState; - if (newWidth && oldWidth !== newWidth) { - /* Cloning the FactMapping list and updating the new width */ - const deepClonedFactMappings: SceSim__FactMappingType[] = JSON.parse( - JSON.stringify( - retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings.FactMapping - ) - ); - const factMappingToUpdate = deepClonedFactMappings[inputIndex]; - - if (factMappingToUpdate.columnWidth?.__$$text) { - factMappingToUpdate.columnWidth.__$$text = newWidth; - } else { - factMappingToUpdate.columnWidth = { - __$$text: newWidth, - }; - } - - model = { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - ...prevState.ScenarioSimulationModel.simulation, - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping - : deepClonedFactMappings, - }, - }, - }, - background: { - ...prevState.ScenarioSimulationModel.background, - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? deepClonedFactMappings - : prevState.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping, - }, - }, - }, - }, - }; - } - - return model; + updateColumnWidth({ + factMappings: factMappings, + columnIndex: inputIndex, + newWidth: newWidth, + oldWidth: oldWidth, + }); }); }, - [isBackground, updateTestScenarioModel] + [isBackground, testScenarioEditorStoreApi] ); /* It determines the column data based on the given FactMapping (Scesim column representation). @@ -244,7 +201,7 @@ function TestScenarioTable({ | | +--------------------------------+-----+----------------------------------+-----+ | # | | givenInstance (given-instance) | ... | expectGroup (expect-instance) | ... | | | +----------------+---------------+-----+----------------------------------+-----+ - | | | field (given) | field (given)| ... | field (expect) | field (expect)| ... | + | | | field (given) | field (given) | ... | field (expect) | field (expect)| ... | +---+---------------+----------------+---------------+-----+-----------------+----------------+-----+ Every section has its related groupType in the rounded brackets, that are crucial to determine the correct context menu behavior (adding/removing an instance requires a different logic than @@ -507,140 +464,25 @@ function TestScenarioTable({ const onCellUpdates = useCallback( (cellUpdates: BeeTableCellUpdate[]) => { cellUpdates.forEach((update) => { - updateTestScenarioModel((prevState) => { - /* To update the related FactMappingValue, it compares every FactMappingValue associated with the Scenario (Row) - that contains the cell with the FactMapping (Column) fields factIdentifier and expressionIdentifier */ - const factMapping = retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings - .FactMapping![update.columnIndex + columnIndexStart]; - - const deepClonedRowsData: SceSim__FactMappingValuesTypes[] = JSON.parse( - JSON.stringify(retrieveRowsDataFromModel(prevState.ScenarioSimulationModel, isBackground)) - ); - const factMappingValues = deepClonedRowsData[update.rowIndex].factMappingValues.FactMappingValue!; - const newFactMappingValues = [...factMappingValues]; - - const factMappingValueToUpdateIndex = retrieveFactMappingValueIndexByIdentifiers( - newFactMappingValues, - factMapping.factIdentifier, - factMapping.expressionIdentifier - ); - const factMappingValueToUpdate = factMappingValues[factMappingValueToUpdateIndex]; - - if (factMappingValueToUpdate.rawValue) { - factMappingValueToUpdate.rawValue!.__$$text = update.value; - } else { - newFactMappingValues[factMappingValueToUpdateIndex] = { - ...factMappingValueToUpdate, - rawValue: { - __$$text: update.value, - }, - }; - } - - deepClonedRowsData[update.rowIndex].factMappingValues.FactMappingValue = newFactMappingValues; - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - ...prevState.ScenarioSimulationModel.simulation, - scesimData: { - Scenario: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimData.Scenario - : deepClonedRowsData, - }, - }, - background: { - ...prevState.ScenarioSimulationModel.background, - scesimData: { - BackgroundData: isBackground - ? deepClonedRowsData - : prevState.ScenarioSimulationModel.background.scesimData.BackgroundData, - }, - }, - }, - }; + testScenarioEditorStoreApi.setState((state) => { + const factMappings = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const factMappingValuesTypes = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; + + updateCell({ + columnIndex: update.columnIndex + columnIndexStart, + factMappings: factMappings, + factMappingValuesTypes: factMappingValuesTypes, + rowIndex: update.rowIndex, + value: update.value, + }); }); }); }, - [columnIndexStart, isBackground, updateTestScenarioModel] - ); - - const getNextAvailablePrefixedName = useCallback( - (names: string[], namePrefix: string, lastIndex: number = names.length): string => { - const candidate = `${namePrefix}-${lastIndex + 1}`; - const elemWithCandidateName = names.indexOf(candidate); - return elemWithCandidateName >= 0 ? getNextAvailablePrefixedName(names, namePrefix, lastIndex + 1) : candidate; - }, - [] - ); - - /* It determines in which index position a column should be added. In case of a field, the new column index - is simply in the right or in the left of the selected column. In case of a new instance, it's required to - find the first column index outside the selected Instance group. */ - const determineNewColumnTargetIndex = useCallback( - ( - factMappings: SceSim__FactMappingType[], - insertDirection: InsertRowColumnsDirection, - selectedColumnIndex: number, - selectedColumnGroupType: string, - selectedFactMapping: SceSim__FactMappingType - ) => { - const groupType = selectedFactMapping.expressionIdentifier.type!.__$$text; - const instanceName = selectedFactMapping.factIdentifier.name!.__$$text; - const instanceType = selectedFactMapping.factIdentifier.className!.__$$text; - - if ( - selectedColumnGroupType === TestScenarioTableColumnFieldGroup.EXPECT || - selectedColumnGroupType === TestScenarioTableColumnFieldGroup.GIVEN - ) { - if (insertDirection === InsertRowColumnsDirection.AboveOrRight) { - return selectedColumnIndex + 1; - } else { - return selectedColumnIndex; - } - } - - let newColumnTargetColumn = -1; - - if (insertDirection === InsertRowColumnsDirection.AboveOrRight) { - for (let i = selectedColumnIndex; i < factMappings.length; i++) { - const currentFM = factMappings[i]; - if ( - currentFM.expressionIdentifier.type!.__$$text === groupType && - currentFM.factIdentifier.name?.__$$text === instanceName && - currentFM.factIdentifier.className?.__$$text === instanceType - ) { - if (i == factMappings.length - 1) { - newColumnTargetColumn = i + 1; - } - } else { - newColumnTargetColumn = i; - break; - } - } - } else { - for (let i = selectedColumnIndex; i >= 0; i--) { - const currentFM = factMappings[i]; - - if ( - currentFM.expressionIdentifier.type!.__$$text === groupType && - currentFM.factIdentifier.name?.__$$text === instanceName && - currentFM.factIdentifier.className?.__$$text === instanceType - ) { - if (i == 0) { - newColumnTargetColumn = 0; - } - } else { - newColumnTargetColumn = i + 1; - break; - } - } - } - - return newColumnTargetColumn; - }, - [TestScenarioTableColumnFieldGroup] + [columnIndexStart, isBackground, testScenarioEditorStoreApi] ); /** @@ -687,143 +529,48 @@ function TestScenarioTable({ columnsCount: number; insertDirection: InsertRowColumnsDirection; }) => { - /* GIVEN and EXPECTED column types can be added only */ - if (TestScenarioTableColumnFieldGroup.OTHER === args.groupType) { + /* GIVEN and EXPECTED of FIELD and INSTANCE column types can be added only */ + if ( + TestScenarioTableColumnFieldGroup.OTHER === args.groupType || + TestScenarioTableColumnHeaderGroup.EXPECT === args.groupType || + TestScenarioTableColumnHeaderGroup.GIVEN === args.groupType + ) { + console.error("Can't add a " + args.groupType + " type column."); return; } const isInstance = args.groupType === TestScenarioTableColumnInstanceGroup.EXPECT || args.groupType === TestScenarioTableColumnInstanceGroup.GIVEN; - updateTestScenarioModel((prevState) => { - const factMappingList = retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings - .FactMapping!; - const selectedColumnIndex = determineSelectedColumnIndex(factMappingList, args.currentIndex, isInstance); - - /* Creating the new FactMapping based on the original selected column's FactMapping */ - const selectedColumnFactMapping = factMappingList[selectedColumnIndex]; - const targetColumnIndex = determineNewColumnTargetIndex( - factMappingList, - args.insertDirection, - selectedColumnIndex, - args.groupType, - selectedColumnFactMapping - ); - - const instanceDefaultNames = factMappingList - .filter((factMapping) => factMapping.factAlias!.__$$text.startsWith("INSTANCE-")) - .map((factMapping) => factMapping.factAlias!.__$$text); - - const isNewInstance = - isInstance || selectedColumnFactMapping.factIdentifier.className?.__$$text === "java.lang.Void"; - - const newFactMapping = { - className: { __$$text: "java.lang.Void" }, - columnWidth: { __$$text: 150 }, - expressionAlias: { __$$text: "PROPERTY" }, - expressionElements: isNewInstance - ? undefined - : { - ExpressionElement: [ - { - step: { - __$$text: selectedColumnFactMapping.expressionElements!.ExpressionElement![0].step.__$$text, - }, - }, - ], - }, - expressionIdentifier: { - name: { __$$text: `_${uuid()}`.toLocaleUpperCase() }, - type: { __$$text: selectedColumnFactMapping.expressionIdentifier.type!.__$$text }, - }, - factAlias: { - __$$text: isNewInstance - ? getNextAvailablePrefixedName(instanceDefaultNames, "INSTANCE") - : selectedColumnFactMapping.factAlias.__$$text, - }, - factIdentifier: { - name: { - __$$text: isNewInstance - ? getNextAvailablePrefixedName(instanceDefaultNames, "INSTANCE") - : selectedColumnFactMapping.factIdentifier.name!.__$$text, - }, - className: { - __$$text: isNewInstance ? "java.lang.Void" : selectedColumnFactMapping.factIdentifier.className!.__$$text, - }, - }, - factMappingValueType: { __$$text: "NOT_EXPRESSION" }, - }; - - /* Cloning the FactMapping list and putting the new one in the user defined index */ - const deepClonedFactMappings = JSON.parse( - JSON.stringify( - retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings.FactMapping - ) + testScenarioEditorStoreApi.setState((state) => { + const factMappings = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const factMappingValues = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; + const selectedColumnFactMappingIndex = determineSelectedColumnIndex( + factMappings, + args.currentIndex, + isInstance ); - deepClonedFactMappings.splice(targetColumnIndex, 0, newFactMapping); - /* Creating and adding a new FactMappingValue (cell) in every row, as a consequence of the new FactMapping (column) - we're going to introduce. The FactMappingValue will be linked with its related FactMapping via expressionIdentifier - and factIdentier data. That means, the column index of new FactMappingValue could be different in other Scenario (rows) */ - const deepClonedRowsData: SceSim__FactMappingValuesTypes[] = JSON.parse( - JSON.stringify(retrieveRowsDataFromModel(prevState.ScenarioSimulationModel, isBackground)) - ); - deepClonedRowsData.forEach((scenario) => { - scenario.factMappingValues.FactMappingValue!.splice(args.beforeIndex + 1, 0, { - expressionIdentifier: { - name: { __$$text: newFactMapping.expressionIdentifier.name.__$$text }, - type: { __$$text: newFactMapping.expressionIdentifier.type.__$$text }, - }, - factIdentifier: { - name: { __$$text: newFactMapping.factIdentifier.name.__$$text }, - className: { __$$text: newFactMapping.factIdentifier.className.__$$text }, - }, - rawValue: { __$$text: "", "@_class": "string" }, - }); + addColumn({ + beforeIndex: args.beforeIndex, + factMappings: factMappings, + factMappingValues: factMappingValues, + isInstance: isInstance, + insertDirection: args.insertDirection, + selectedColumnFactMappingIndex: selectedColumnFactMappingIndex, }); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping - : deepClonedFactMappings, - }, - }, - scesimData: { - Scenario: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimData.Scenario - : deepClonedRowsData, - }, - }, - background: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? deepClonedFactMappings - : prevState.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping, - }, - }, - scesimData: { - BackgroundData: isBackground - ? deepClonedRowsData - : prevState.ScenarioSimulationModel.background.scesimData.BackgroundData, - }, - }, - }, - }; }); }, [ - determineNewColumnTargetIndex, determineSelectedColumnIndex, - getNextAvailablePrefixedName, isBackground, - updateTestScenarioModel, + testScenarioEditorStoreApi, TestScenarioTableColumnFieldGroup, + TestScenarioTableColumnHeaderGroup, TestScenarioTableColumnInstanceGroup, ] ); @@ -833,127 +580,54 @@ function TestScenarioTable({ */ const onColumnDeleted = useCallback( (args: { columnIndex: number; groupType: string }) => { - updateTestScenarioModel((prevState) => { - const isInstance = - args.groupType === TestScenarioTableColumnInstanceGroup.EXPECT || - args.groupType === TestScenarioTableColumnInstanceGroup.GIVEN; - - const factMappings = retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings - .FactMapping!; - const columnIndexToRemove = determineSelectedColumnIndex(factMappings, args.columnIndex + 1, isInstance); - - /* Retriving the FactMapping (Column) to be removed). If the user selected a single column, it finds the exact - FactMapping to delete. If the user selected an instance (group of columns), it retrives all the FactMappings - that belongs to the the instance group */ - const factMappingToRemove = factMappings[columnIndexToRemove]; - const groupType = factMappingToRemove.expressionIdentifier.type!.__$$text; - const instanceName = factMappingToRemove.factIdentifier.name!.__$$text; - const instanceType = factMappingToRemove.factIdentifier.className!.__$$text; - - const allFactMappingWithIndexesToRemove = isInstance - ? factMappings - .map((factMapping, index) => { - if ( - factMapping.expressionIdentifier.type!.__$$text === groupType && - factMapping.factIdentifier.name?.__$$text === instanceName && - factMapping.factIdentifier.className?.__$$text === instanceType - ) { - return { factMappingIndex: index, factMapping: factMapping }; - } else { - return {}; - } - }) - .filter((item) => isNumber(item.factMappingIndex)) - : [{ factMappingIndex: args.columnIndex + columnIndexStart, factMapping: factMappingToRemove }]; - - /* Cloning the FactMappings list (Columns) and and removing the FactMapping (Column) at given index */ - const deepClonedFactMappings = JSON.parse( - JSON.stringify( - retrieveModelDescriptor(prevState.ScenarioSimulationModel, isBackground).factMappings.FactMapping - ) - ); - deepClonedFactMappings.splice( - allFactMappingWithIndexesToRemove[0].factMappingIndex, - allFactMappingWithIndexesToRemove.length - ); - - /* Cloning the Scenario List (Rows) and finding the Cell(s) to remove accordingly to the factMapping data of - the removed columns */ - const deepClonedRowsData: SceSim__FactMappingValuesTypes[] = JSON.parse( - JSON.stringify(retrieveRowsDataFromModel(prevState.ScenarioSimulationModel, isBackground) ?? []) - ); - deepClonedRowsData.forEach((rowData) => { - allFactMappingWithIndexesToRemove.forEach((itemToRemove) => { - const factMappingValueColumnIndexToRemove = retrieveFactMappingValueIndexByIdentifiers( - rowData.factMappingValues.FactMappingValue!, - itemToRemove.factMapping!.factIdentifier, - itemToRemove.factMapping!.expressionIdentifier - )!; - - return { - factMappingValues: { - FactMappingValue: rowData.factMappingValues.FactMappingValue!.splice( - factMappingValueColumnIndexToRemove, - 1 - ), - }, - }; - }); + /* GIVEN and EXPECTED of FIELD and INSTANCE column types can be deleted only */ + if ( + TestScenarioTableColumnFieldGroup.OTHER === args.groupType || + TestScenarioTableColumnHeaderGroup.EXPECT === args.groupType || + TestScenarioTableColumnHeaderGroup.GIVEN === args.groupType + ) { + console.error("Can't delete a " + args.groupType + " type column."); + return; + } + const isInstance = + args.groupType === TestScenarioTableColumnInstanceGroup.EXPECT || + args.groupType === TestScenarioTableColumnInstanceGroup.GIVEN; + testScenarioEditorStoreApi.setState((state) => { + const factMappings = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const factMappingValues = isBackground + ? state.scesim.model.ScenarioSimulationModel.background.scesimData.BackgroundData! + : state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; + const factMappingIndexToRemove = determineSelectedColumnIndex(factMappings, args.columnIndex + 1, isInstance); + + const { deletedFactMappingIndexs } = deleteColumn({ + factMappingIndexToRemove: factMappingIndexToRemove, + factMappings: factMappings, + factMappingValues: factMappingValues, + isBackground: isBackground, + isInstance: isInstance, + selectedColumnIndex: args.columnIndex, }); /** Updating the selectedColumn. When deleting, BEETable automatically shifts the selected cell in the left */ - const firstIndexOnTheLeft = Math.min( - ...allFactMappingWithIndexesToRemove.map((item) => item.factMappingIndex!) - ); + const firstIndexOnTheLeft = Math.min(...deletedFactMappingIndexs); const selectedColumnIndex = firstIndexOnTheLeft > 0 ? firstIndexOnTheLeft - 1 : 0; - updateSelectedColumnMetaData({ - factMapping: JSON.parse(JSON.stringify(deepClonedFactMappings[selectedColumnIndex])), + + state.dispatch(state).table.updateSelectedColumn({ + factMapping: JSON.parse(JSON.stringify(factMappings[selectedColumnIndex])), index: firstIndexOnTheLeft, - isBackground, + isBackground: isBackground, }); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping - : deepClonedFactMappings, - }, - }, - scesimData: { - Scenario: isBackground - ? prevState.ScenarioSimulationModel.simulation.scesimData.Scenario - : deepClonedRowsData, - }, - }, - background: { - scesimModelDescriptor: { - factMappings: { - FactMapping: isBackground - ? deepClonedFactMappings - : prevState.ScenarioSimulationModel.background.scesimModelDescriptor.factMappings.FactMapping, - }, - }, - scesimData: { - BackgroundData: isBackground - ? deepClonedRowsData - : prevState.ScenarioSimulationModel.background.scesimData.BackgroundData, - }, - }, - }, - }; }); }, [ - updateTestScenarioModel, - TestScenarioTableColumnInstanceGroup, - isBackground, determineSelectedColumnIndex, - columnIndexStart, - updateSelectedColumnMetaData, + isBackground, + testScenarioEditorStoreApi, + TestScenarioTableColumnFieldGroup, + TestScenarioTableColumnHeaderGroup, + TestScenarioTableColumnInstanceGroup, ] ); @@ -965,50 +639,15 @@ function TestScenarioTable({ if (isBackground) { throw new Error("Impossible state. Background table can have a single row only"); } - updateTestScenarioModel((prevState) => { - /* Creating a new Scenario (Row) composed by a list of FactMappingValues. The list order is not relevant. */ + testScenarioEditorStoreApi.setState((state) => { const factMappings = - prevState.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping ?? []; - const factMappingValuesItems = factMappings.map((factMapping) => { - return { - expressionIdentifier: { - name: { __$$text: factMapping.expressionIdentifier.name!.__$$text }, - type: { __$$text: factMapping.expressionIdentifier.type!.__$$text }, - }, - factIdentifier: { - name: { __$$text: factMapping.factIdentifier.name!.__$$text }, - className: { __$$text: factMapping.factIdentifier.className!.__$$text }, - }, - rawValue: { __$$text: "", "@_class": "string" }, - }; - }); + state.scesim.model.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping!; + const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; - const newScenario = { - factMappingValues: { - FactMappingValue: factMappingValuesItems, - }, - }; - - /* Cloning che current Scenario List and adding thew new Scenario previously created */ - const deepClonedScenarios = JSON.parse( - JSON.stringify(prevState.ScenarioSimulationModel.simulation.scesimData.Scenario) - ); - deepClonedScenarios.splice(args.beforeIndex, 0, newScenario); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - ...prevState.ScenarioSimulationModel.simulation, - scesimData: { - Scenario: deepClonedScenarios, - }, - }, - }, - }; + addRow({ beforeIndex: args.beforeIndex, factMappings: factMappings, factMappingValues: factMappingValues }); }); }, - [isBackground, updateTestScenarioModel] + [isBackground, testScenarioEditorStoreApi] ); /** @@ -1019,28 +658,13 @@ function TestScenarioTable({ if (isBackground) { throw new Error("Impossible state. Background table can have a single row only"); } - updateTestScenarioModel((prevState) => { - /* Just updating the Scenario List (Rows) cloning the current List and removing the row at the given rowIndex */ - const deepClonedScenarios = JSON.parse( - JSON.stringify(prevState.ScenarioSimulationModel.simulation.scesimData.Scenario ?? []) - ); - deepClonedScenarios.splice(args.rowIndex, 1); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - ...prevState.ScenarioSimulationModel.simulation, - scesimData: { - ...prevState.ScenarioSimulationModel.simulation.scesimData, - Scenario: deepClonedScenarios, - }, - }, - }, - }; + testScenarioEditorStoreApi.setState((state) => { + const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; + + deleteRow({ rowIndex: args.rowIndex, factMappingValues: factMappingValues }); }); }, - [isBackground, updateTestScenarioModel] + [isBackground, testScenarioEditorStoreApi] ); /** @@ -1051,41 +675,13 @@ function TestScenarioTable({ if (isBackground) { throw new Error("Impossible state. Background table can have a single row only"); } - updateTestScenarioModel((prevState) => { - /* It simply clones a Scenario (Row) and adds it in a current-cloned Scenario list */ - const clonedFactMappingValues = JSON.parse( - JSON.stringify( - prevState.ScenarioSimulationModel.simulation.scesimData.Scenario![args.rowIndex].factMappingValues - .FactMappingValue - ) - ); + testScenarioEditorStoreApi.setState((state) => { + const factMappingValues = state.scesim.model.ScenarioSimulationModel.simulation.scesimData.Scenario!; - const factMappingValues = { - factMappingValues: { - FactMappingValue: clonedFactMappingValues, - }, - }; - - const deepClonedScenarios = JSON.parse( - JSON.stringify(prevState.ScenarioSimulationModel.simulation.scesimData.Scenario ?? []) - ); - deepClonedScenarios.splice(args.rowIndex, 0, factMappingValues); - - return { - ScenarioSimulationModel: { - ...prevState.ScenarioSimulationModel, - simulation: { - ...prevState.ScenarioSimulationModel.simulation, - scesimData: { - ...prevState.ScenarioSimulationModel.simulation.scesimData, - Scenario: deepClonedScenarios, - }, - }, - }, - }; + dupliacteRow({ rowIndex: args.rowIndex, factMappingValues: factMappingValues }); }); }, - [isBackground, updateTestScenarioModel] + [isBackground, testScenarioEditorStoreApi] ); /** @@ -1093,9 +689,11 @@ function TestScenarioTable({ */ const onDataCellClick = useCallback( (_columnID: string) => { - updateSelectedColumnMetaData(null); + testScenarioEditorStoreApi.setState((state) => { + state.dispatch(state).table.updateSelectedColumn(null); + }); }, - [updateSelectedColumnMetaData] + [testScenarioEditorStoreApi] ); const onHeaderClick = useCallback( @@ -1105,7 +703,9 @@ function TestScenarioTable({ columnKey == TestScenarioTableColumnHeaderGroup.EXPECT || columnKey == TestScenarioTableColumnHeaderGroup.GIVEN ) { - updateSelectedColumnMetaData(null); + testScenarioEditorStoreApi.setState((state) => { + state.dispatch(state).table.updateSelectedColumn(null); + }); return; } @@ -1123,25 +723,23 @@ function TestScenarioTable({ selectedInstanceGroup?.columns[0].dataType === "" ) { const propertyID = selectedInstanceGroup?.columns[0].id; - let selectedFactMapping; - let selectedFactIndex; - if (propertyID) { - selectedFactMapping = modelDescriptor.factMappings.FactMapping!.find( - (factMapping) => factMapping.expressionIdentifier.name?.__$$text === propertyID - ); - selectedFactIndex = selectedFactMapping - ? modelDescriptor.factMappings.FactMapping!.indexOf(selectedFactMapping!) - : -1; - } - const selectedColumnMetaData = { - factMapping: JSON.parse(JSON.stringify(selectedFactMapping)), - index: selectedFactIndex ?? -1, - isBackground: isBackground, - }; - - updateSelectedColumnMetaData(selectedColumnMetaData); + const selectedFactMapping = modelDescriptor.factMappings.FactMapping!.find( + (factMapping) => factMapping.expressionIdentifier.name?.__$$text === propertyID + ); + const selectedFactIndex = selectedFactMapping + ? modelDescriptor.factMappings.FactMapping!.indexOf(selectedFactMapping!) + : -1; + testScenarioEditorStoreApi.setState((state) => { + state.dispatch(state).table.updateSelectedColumn({ + factMapping: JSON.parse(JSON.stringify(selectedFactMapping)), + index: selectedFactIndex ?? -1, + isBackground: isBackground, + }); + }); } else { - updateSelectedColumnMetaData(null); + testScenarioEditorStoreApi.setState((state) => { + state.dispatch(state).table.updateSelectedColumn(null); + }); } return; } @@ -1153,13 +751,13 @@ function TestScenarioTable({ ? modelDescriptor.factMappings.FactMapping!.indexOf(selectedFactMapping!) : -1; - const selectedColumnMetaData = { - factMapping: JSON.parse(JSON.stringify(selectedFactMapping)), - index: selectedFactIndex ?? -1, - isBackground: isBackground, - }; - - updateSelectedColumnMetaData(selectedColumnMetaData ?? null); + testScenarioEditorStoreApi.setState((state) => { + state.dispatch(state).table.updateSelectedColumn({ + factMapping: JSON.parse(JSON.stringify(selectedFactMapping)), + index: selectedFactIndex ?? -1, + isBackground: isBackground, + }); + }); }, [ TestScenarioTableColumnFieldGroup, @@ -1167,7 +765,7 @@ function TestScenarioTable({ isBackground, tableColumns.instancesGroup, tableData, - updateSelectedColumnMetaData, + testScenarioEditorStoreApi, ] ); diff --git a/packages/scesim-editor/stories/dev/DevWebApp.stories.tsx b/packages/scesim-editor/stories/dev/DevWebApp.stories.tsx index 9d17767d2bf..b9d91f5a091 100644 --- a/packages/scesim-editor/stories/dev/DevWebApp.stories.tsx +++ b/packages/scesim-editor/stories/dev/DevWebApp.stories.tsx @@ -17,26 +17,22 @@ * under the License. */ -import React, { useCallback, useRef, useState, useEffect } from "react"; +import React, { useCallback, useRef, useState } from "react"; import type { Meta, StoryObj } from "@storybook/react"; -import { SceSimEditorWrapper, SceSimEditorWrapperProps } from "../scesimEditorStoriesWrapper"; -import { Button, Flex, FlexItem, Page, PageSection } from "@patternfly/react-core/dist/js"; -import { TestScenarioEditorRef } from "../../src/TestScenarioEditor"; -import { SceSimMarshaller } from "../../../scesim-marshaller/src/index"; -import { SceSimModel, getMarshaller } from "@kie-tools/scesim-marshaller"; +import "@patternfly/react-core/dist/styles/base.css"; +import { Button } from "@patternfly/react-core/dist/js/components/Button"; +import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex"; +import { Page, PageSection } from "@patternfly/react-core/dist/js/components/Page"; +import { Stack, StackItem } from "@patternfly/react-core/dist/js"; +import { SceSimMarshaller, SceSimModel, getMarshaller } from "@kie-tools/scesim-marshaller"; +import { OnSceSimModelChange, TestScenarioEditorProps } from "../../src/TestScenarioEditor"; +import { SceSimEditorWrapper } from "../scesimEditorStoriesWrapper"; import { emptySceSim } from "../misc/empty/Empty.stories"; import { isOldEnoughDrl } from "../useCases/IsOldEnoughRule.stories"; import { trafficViolationDmn } from "../useCases/TrafficViolationDmn.stories"; -import { useArgs } from "@storybook/preview-api"; - import "./DevWebApp.css"; -type DevWebAppProps = SceSimEditorWrapperProps; - -function DevWebApp(props: DevWebAppProps) { - const ref = useRef(null); - const [args, updateArgs] = useArgs(); - +function DevWebApp(args: TestScenarioEditorProps) { const [state, setState] = useState<{ marshaller: SceSimMarshaller; stack: SceSimModel[]; pointer: number }>(() => { const emptySceSimMarshaller = getMarshaller(emptySceSim); return { @@ -46,38 +42,68 @@ function DevWebApp(props: DevWebAppProps) { }; }); - const onSelectModel = useCallback((newModel) => { - const model = getMarshaller(newModel).parser.parse(); - setState((prev) => { - const newStack = prev.stack.slice(0, prev.pointer + 1); - return { - ...prev, - stack: [...newStack, model], - pointer: newStack.length, - }; - }); + const currentModel = state.stack[state.pointer]; + const downloadRef = useRef(null); + const isUndoEnabled = state.pointer > 0; + const isRedoEnabled = state.pointer !== state.stack.length - 1; + + const copyAsXml = useCallback(() => { + navigator.clipboard.writeText(state.marshaller.builder.build(currentModel)); + }, [currentModel, state.marshaller.builder]); + + const downloadAsXml = useCallback(() => { + if (downloadRef.current) { + const fileBlob = new Blob([state.marshaller.builder.build(currentModel)], { type: "text/xml" }); + downloadRef.current.download = `scesim-test-${makeid(10)}.scesim`; + downloadRef.current.href = URL.createObjectURL(fileBlob); + downloadRef.current.click(); + } + }, [currentModel, state.marshaller.builder]); + + const onDragOver = useCallback((e: React.DragEvent) => { + e.preventDefault(); // Necessary to disable the browser's default 'onDrop' handling. }, []); const onDrop = useCallback((e: React.DragEvent) => { + console.log("Test Scenario Editor :: Dev webapp :: File(s) dropped! Opening it."); + e.preventDefault(); // Necessary to disable the browser's default 'onDrop' handling. if (e.dataTransfer.items) { // Use DataTransferItemList interface to access the file(s) [...e.dataTransfer.items].forEach((item, i) => { if (item.kind === "file") { - const fileName = item.getAsFile()?.name; const reader = new FileReader(); - reader.addEventListener("load", ({ target }) => - ref.current?.setContent(fileName ?? "", target?.result as string) - ); + reader.addEventListener("load", ({ target }) => { + const marshaller = getMarshaller(target?.result as string); + setState({ marshaller, stack: [marshaller.parser.parse()], pointer: 0 }); + }); reader.readAsText(item.getAsFile() as any); } }); } }, []); - const onDragOver = useCallback((e: React.DragEvent) => { - e.preventDefault(); // Necessary to disable the browser's default 'onDrop' handling. + const onModelChange = useCallback((model) => { + setState((prev) => { + const newStack = prev.stack.slice(0, prev.pointer + 1); + return { + ...prev, + stack: [...newStack, model], + pointer: newStack.length, + }; + }); + }, []); + + const onSelectModel = useCallback( + (newModel) => { + onModelChange(getMarshaller(newModel).parser.parse()); + }, + [onModelChange] + ); + + const redo = useCallback(() => { + setState((prev) => ({ ...prev, pointer: Math.min(prev.stack.length - 1, prev.pointer + 1) })); }, []); const reset = useCallback(() => { @@ -89,40 +115,27 @@ function DevWebApp(props: DevWebAppProps) { }); }, []); - const currentModel = state.stack[state.pointer]; - - const downloadRef = useRef(null); - const downloadAsXml = useCallback(() => { - if (downloadRef.current) { - const fileBlob = new Blob([state.marshaller.builder.build(currentModel)], { type: "text/xml" }); - downloadRef.current.download = `scesim-${makeid(10)}.scesim`; - downloadRef.current.href = URL.createObjectURL(fileBlob); - downloadRef.current.click(); - } - }, [currentModel, state.marshaller.builder]); - - const copyAsXml = useCallback(() => { - navigator.clipboard.writeText(state.marshaller.builder.build(currentModel)); - }, [currentModel, state.marshaller.builder]); + const undo = useCallback(() => { + setState((prev) => ({ ...prev, pointer: Math.max(0, prev.pointer - 1) })); + }, []); - useEffect(() => { - updateArgs({ content: state.marshaller.builder.build(state.stack[state.stack.length - 1]) }); - }, [state.marshaller.builder, state.stack, updateArgs]); + /* TODO: DMN DATA Retieve + const externalModelsByNamespace = useMemo(() => { + return (currentModel.definitions.import ?? []).reduce((acc, i) => { + acc[i["@_namespace"]] = modelsByNamespace[i["@_namespace"]]; + return acc; + }, {} as ExternalModelsIndex); + }, [currentModel.definitions.import]); - useEffect(() => { - onSelectModel(args.content); - }, [args.content, onSelectModel]); + const onRequestExternalModelByPath = useCallback(async (path) => { + return availableModelsByPath[path] ?? null; + }, []);*/ return ( - <> -
- - + + + +

Test Scenario Editor :: Dev WebApp

@@ -130,6 +143,10 @@ function DevWebApp(props: DevWebAppProps) {
(Drag & drop a file anywhere to open it)
+
+
+ +     @@ -137,31 +154,56 @@ function DevWebApp(props: DevWebAppProps) {         |     - + +     + +     |     +     - +     - + - -
-
- - {SceSimEditorWrapper({ - pathRelativeToTheWorkspaceRoot: "dev.scesim", - content: state.marshaller.builder.build(state.stack[state.stack.length - 1]), - })} - -
-
- + + + + +
+ + {SceSimEditorWrapper({ + issueTrackerHref: args.issueTrackerHref, + model: currentModel, + onModelChange: onModelChange, + })} + + ); } @@ -177,18 +219,18 @@ function makeid(length: number) { return result; } -const meta: Meta = { +const meta: Meta = { title: "Dev/Web App", component: DevWebApp, }; export default meta; -type Story = StoryObj; +type Story = StoryObj; export const WebApp: Story = { render: (args) => DevWebApp(args), args: { - pathRelativeToTheWorkspaceRoot: "dev.scesim", - content: emptySceSim, + issueTrackerHref: "https://github.com/apache/incubator-kie-issues/issues/new", + model: getMarshaller(emptySceSim).parser.parse(), }, }; diff --git a/packages/scesim-editor/stories/examples/AvailableDMNModels.ts b/packages/scesim-editor/stories/examples/AvailableDMNModels.ts index 315e2451ebc..4d7722fa228 100644 --- a/packages/scesim-editor/stories/examples/AvailableDMNModels.ts +++ b/packages/scesim-editor/stories/examples/AvailableDMNModels.ts @@ -47,12 +47,3 @@ export const avaiableModels: { // }, // {} as Record // ); - -// export const modelsByNamespace = Object.values(avaiableModels).reduce((acc, v) => { -// if (v.type === "dmn") { -// acc[v.model.definitions["@_namespace"]] = v; -// } else if (v.type === "pmml") { -// acc[getPmmlNamespace({ normalizedPosixPathRelativeToTheOpenFile: v.normalizedPosixPathRelativeToTheOpenFile })] = v; -// } -// return acc; -// }, {} as DmnEditor.ExternalModelsIndex); diff --git a/packages/scesim-editor/stories/misc/empty/Empty.stories.tsx b/packages/scesim-editor/stories/misc/empty/Empty.stories.tsx index af7a17fc235..dc538bb058e 100644 --- a/packages/scesim-editor/stories/misc/empty/Empty.stories.tsx +++ b/packages/scesim-editor/stories/misc/empty/Empty.stories.tsx @@ -18,11 +18,12 @@ */ import type { Meta, StoryObj } from "@storybook/react"; -import { SceSimEditorWrapper, SceSimEditorWrapperProps } from "../../scesimEditorStoriesWrapper"; +import { getMarshaller } from "@kie-tools/scesim-marshaller"; import { TestScenarioEditor } from "../../../src/TestScenarioEditor"; +import { SceSimEditorWrapper, StorybookTestScenarioEditorProps } from "../../scesimEditorStoriesWrapper"; export const emptySceSim = ` - + @@ -180,12 +181,15 @@ const meta: Meta<{}> = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; + +const marshaller = getMarshaller(emptySceSim); +const model = marshaller.parser.parse(); export const Empty: Story = { render: (args) => SceSimEditorWrapper(args), args: { - pathRelativeToTheWorkspaceRoot: "empty.scesim", - content: emptySceSim, + model: marshaller.parser.parse(), + xml: marshaller.builder.build(model), }, }; diff --git a/packages/scesim-editor/stories/scesimEditor/SceSimEditor.mdx b/packages/scesim-editor/stories/scesimEditor/SceSimEditor.mdx index 33f1a2e32a8..c253f319b3f 100644 --- a/packages/scesim-editor/stories/scesimEditor/SceSimEditor.mdx +++ b/packages/scesim-editor/stories/scesimEditor/SceSimEditor.mdx @@ -17,9 +17,9 @@ import { Meta } from "@storybook/blocks"; - + -# SceSim Editor +# Test Scenario Editor Before deploying them into a production environment, test scenarios enable you to validate the functionality of - Business rules and business rule data (for rules-based test scenarios) diff --git a/packages/scesim-editor/stories/scesimEditorStoriesWrapper.tsx b/packages/scesim-editor/stories/scesimEditorStoriesWrapper.tsx index 656adba6208..7588aa49d41 100644 --- a/packages/scesim-editor/stories/scesimEditorStoriesWrapper.tsx +++ b/packages/scesim-editor/stories/scesimEditorStoriesWrapper.tsx @@ -18,25 +18,59 @@ */ import * as React from "react"; -import { useEffect, useRef } from "react"; -import { TestScenarioEditor, TestScenarioEditorRef } from "../src/TestScenarioEditor"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { useArgs } from "@storybook/preview-api"; +import { diff } from "deep-object-diff"; +import { SceSimModel, getMarshaller } from "@kie-tools/scesim-marshaller"; +import { TestScenarioEditor, TestScenarioEditorProps, TestScenarioEditorRef } from "../src/TestScenarioEditor"; +import { EMPTY_ONE_EIGHT } from "../src/resources/EmptyScesimFile"; -export interface SceSimEditorWrapperProps { - pathRelativeToTheWorkspaceRoot: string; - content: string; -} +export type StorybookTestScenarioEditorProps = TestScenarioEditorProps & { xml: string }; -export function SceSimEditorWrapper(props: SceSimEditorWrapperProps) { +export function SceSimEditorWrapper(props: Partial) { + const [args, updateArgs] = useArgs(); + const argsCopy = useRef(args); const ref = useRef(null); + const [modelArgs, setModelArgs] = useState(args.model); + const model = useMemo(() => props?.model ?? modelArgs, [modelArgs, props?.model]); + + const onModelChange = useMemo( + () => (props?.onModelChange ? props.onModelChange : setModelArgs), + [props?.onModelChange] + ); useEffect(() => { - /* Simulating a call from "Foundation" code */ - ref.current?.setContent(props.pathRelativeToTheWorkspaceRoot, props.content); - }, [ref, props.content, props.pathRelativeToTheWorkspaceRoot]); + if (Object.keys(diff(argsCopy.current.model, model)).length !== 0) { + updateArgs({ + ...argsCopy.current, + model: model, + xml: getMarshaller(EMPTY_ONE_EIGHT).builder.build(model), + }); + } + }, [updateArgs, model]); + + useEffect(() => { + if (Object.keys(diff(argsCopy.current, args)).length === 0) { + return; + } + argsCopy.current = args; + if (Object.keys(diff(args.model, model)).length === 0) { + return; + } + onModelChange(args.model); + }, [args, model, onModelChange]); + + const onModelDebounceStateChanged = useCallback(() => { + console.debug("[scesimEditorStoriesWrapper] Model Debounce state"); + }, []); return ( -
- -
+ ); } diff --git a/packages/scesim-editor/stories/useCases/IsOldEnoughRule.stories.tsx b/packages/scesim-editor/stories/useCases/IsOldEnoughRule.stories.tsx index 86592e1a75d..9f0e102caaf 100644 --- a/packages/scesim-editor/stories/useCases/IsOldEnoughRule.stories.tsx +++ b/packages/scesim-editor/stories/useCases/IsOldEnoughRule.stories.tsx @@ -19,8 +19,9 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; +import { getMarshaller } from "@kie-tools/scesim-marshaller"; import { TestScenarioEditor } from "../../src/TestScenarioEditor"; -import { SceSimEditorWrapper, SceSimEditorWrapperProps } from "../scesimEditorStoriesWrapper"; +import { SceSimEditorWrapper, StorybookTestScenarioEditorProps } from "../scesimEditorStoriesWrapper"; export const isOldEnoughDrl = ` @@ -394,12 +395,15 @@ const meta: Meta<{}> = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; + +const marshaller = getMarshaller(isOldEnoughDrl); +const model = marshaller.parser.parse(); export const IsOldEnough: Story = { render: (args) => SceSimEditorWrapper(args), args: { - pathRelativeToTheWorkspaceRoot: "isOldEnough.scesim", - content: isOldEnoughDrl, + model: marshaller.parser.parse(), + xml: marshaller.builder.build(model), }, }; diff --git a/packages/scesim-editor/stories/useCases/TrafficViolationDmn.stories.tsx b/packages/scesim-editor/stories/useCases/TrafficViolationDmn.stories.tsx index 031704f4650..19ba0102ca1 100644 --- a/packages/scesim-editor/stories/useCases/TrafficViolationDmn.stories.tsx +++ b/packages/scesim-editor/stories/useCases/TrafficViolationDmn.stories.tsx @@ -19,8 +19,9 @@ import React from "react"; import type { Meta, StoryObj } from "@storybook/react"; +import { getMarshaller } from "@kie-tools/scesim-marshaller"; import { TestScenarioEditor } from "../../src/TestScenarioEditor"; -import { SceSimEditorWrapper, SceSimEditorWrapperProps } from "../scesimEditorStoriesWrapper"; +import { SceSimEditorWrapper, StorybookTestScenarioEditorProps } from "../scesimEditorStoriesWrapper"; export const trafficViolationDmn = ` @@ -796,12 +797,15 @@ const meta: Meta<{}> = { }; export default meta; -type Story = StoryObj; +type Story = StoryObj; + +const marshaller = getMarshaller(trafficViolationDmn); +const model = marshaller.parser.parse(); export const TrafficViolation: Story = { render: (args) => SceSimEditorWrapper(args), args: { - pathRelativeToTheWorkspaceRoot: "trafficViolation.scesim", - content: trafficViolationDmn, + model: marshaller.parser.parse(), + xml: marshaller.builder.build(model), }, }; diff --git a/packages/scesim-editor/tests-e2e/__screenshots__/Google-Chrome/misc/emptyExpression/create-a-new-test-scenario.png b/packages/scesim-editor/tests-e2e/__screenshots__/Google-Chrome/misc/emptyExpression/create-a-new-test-scenario.png index 97f6e8b3d077e9f02f014570ec132ce3c51887ed..4334ab5e3a3234a7f0f36421d045487bbac8e8ff 100644 GIT binary patch literal 16204 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 L^tF^LcisORSOR%{ literal 16106 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 diff --git a/packages/scesim-editor/tests-e2e/__screenshots__/chromium/misc/emptyExpression/create-a-new-test-scenario.png b/packages/scesim-editor/tests-e2e/__screenshots__/chromium/misc/emptyExpression/create-a-new-test-scenario.png index e85c3f76dec5ff6693ca242e085359a7d17db4ce..7cd45f75364d9f9f94fb97f05f005f4621260186 100644 GIT binary patch literal 14175 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 literal 16066 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 literal 13961 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 uGZH$E5_w{;-u(}8P7sFx diff --git a/packages/scesim-marshaller/src/schemas/scesim-1_8/SceSim.xsd b/packages/scesim-marshaller/src/schemas/scesim-1_8/SceSim.xsd index 6f5ebe95ae7..efa8b3b494e 100644 --- a/packages/scesim-marshaller/src/schemas/scesim-1_8/SceSim.xsd +++ b/packages/scesim-marshaller/src/schemas/scesim-1_8/SceSim.xsd @@ -1,3 +1,20 @@ + + =17.0.2 <19.0.0' version: 17.0.2(react@17.0.2) react-error-boundary: - specifier: ^4.0.11 - version: 4.0.12(react@17.0.2) + specifier: ^4.0.13 + version: 4.0.13(react@17.0.2) reactflow: specifier: ^11.8.3 version: 11.10.1(@types/react@17.0.21)(immer@10.0.3(patch_hash=utu5oov26wz5mjuays57tp3ybu))(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -9332,6 +9332,9 @@ importers: '@patternfly/react-styles': specifier: ^4.92.6 version: 4.92.6 + immer: + specifier: ^10.0.3 + version: 10.0.3(patch_hash=utu5oov26wz5mjuays57tp3ybu) lodash: specifier: ^4.17.21 version: 4.17.21 @@ -9347,6 +9350,9 @@ importers: uuid: specifier: ^8.3.2 version: 8.3.2 + zustand: + specifier: ^4.4.2 + version: 4.4.2(patch_hash=7tws22nsyaxzkdpquvgytzpdve)(@types/react@17.0.21)(immer@10.0.3(patch_hash=utu5oov26wz5mjuays57tp3ybu))(react@17.0.2) devDependencies: '@babel/core': specifier: ^7.16.0 @@ -9420,9 +9426,15 @@ importers: cross-env: specifier: ^7.0.3 version: 7.0.3 + deep-object-diff: + specifier: ^1.1.9 + version: 1.1.9 file-loader: specifier: ^6.2.0 version: 6.2.0(webpack@5.94.0(webpack-cli@4.10.0)) + react-error-boundary: + specifier: ^4.0.13 + version: 4.0.13(react@17.0.2) rimraf: specifier: ^3.0.2 version: 3.0.2 @@ -27465,8 +27477,8 @@ packages: peerDependencies: react: '>=16.13.1' - react-error-boundary@4.0.12: - resolution: {integrity: sha512-kJdxdEYlb7CPC1A0SeUY38cHpjuu6UkvzKiAmqmOFL21VRfMhOcWxTCBgLVCO0VEMh9JhFNcVaXlV4/BTpiwOA==} + react-error-boundary@4.0.13: + resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} peerDependencies: react: '>=16.13.1' @@ -31009,7 +31021,7 @@ snapshots: '@babel/core': 7.24.9 '@babel/generator': 7.23.6 '@babel/parser': 7.23.9 - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@babel/traverse': 7.23.9 '@babel/types': 7.23.9 babel-preset-fbjs: 3.4.0(@babel/core@7.24.9) @@ -39387,15 +39399,15 @@ snapshots: '@radix-ui/number@1.0.1': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive@1.0.1': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-arrow@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -39405,7 +39417,7 @@ snapshots: '@radix-ui/react-collection@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-context': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -39418,28 +39430,28 @@ snapshots: '@radix-ui/react-compose-refs@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-context@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-direction@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-dismissable-layer@1.0.4(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -39453,14 +39465,14 @@ snapshots: '@radix-ui/react-focus-guards@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-focus-scope@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39472,7 +39484,7 @@ snapshots: '@radix-ui/react-id@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 optionalDependencies: @@ -39480,7 +39492,7 @@ snapshots: '@radix-ui/react-popper@1.1.2(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@floating-ui/react-dom': 2.0.2(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@radix-ui/react-arrow': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39499,7 +39511,7 @@ snapshots: '@radix-ui/react-portal@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -39509,7 +39521,7 @@ snapshots: '@radix-ui/react-primitive@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-slot': 1.0.2(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -39519,7 +39531,7 @@ snapshots: '@radix-ui/react-roving-focus@1.0.4(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39537,7 +39549,7 @@ snapshots: '@radix-ui/react-select@1.2.2(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/number': 1.0.1 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-collection': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) @@ -39567,7 +39579,7 @@ snapshots: '@radix-ui/react-separator@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -39577,7 +39589,7 @@ snapshots: '@radix-ui/react-slot@1.0.2(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-compose-refs': 1.0.1(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 optionalDependencies: @@ -39585,7 +39597,7 @@ snapshots: '@radix-ui/react-toggle-group@1.0.4(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39601,7 +39613,7 @@ snapshots: '@radix-ui/react-toggle@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) '@radix-ui/react-use-controllable-state': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39613,7 +39625,7 @@ snapshots: '@radix-ui/react-toolbar@1.0.4(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/primitive': 1.0.1 '@radix-ui/react-context': 1.0.1(@types/react@17.0.21)(react@17.0.2) '@radix-ui/react-direction': 1.0.1(@types/react@17.0.21)(react@17.0.2) @@ -39629,14 +39641,14 @@ snapshots: '@radix-ui/react-use-callback-ref@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-use-controllable-state@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 optionalDependencies: @@ -39644,7 +39656,7 @@ snapshots: '@radix-ui/react-use-escape-keydown@1.0.3(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-use-callback-ref': 1.0.1(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 optionalDependencies: @@ -39652,21 +39664,21 @@ snapshots: '@radix-ui/react-use-layout-effect@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-use-previous@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 optionalDependencies: '@types/react': 17.0.21 '@radix-ui/react-use-rect@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/rect': 1.0.1 react: 17.0.2 optionalDependencies: @@ -39674,7 +39686,7 @@ snapshots: '@radix-ui/react-use-size@1.0.1(@types/react@17.0.21)(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-use-layout-effect': 1.0.1(@types/react@17.0.21)(react@17.0.2) react: 17.0.2 optionalDependencies: @@ -39682,7 +39694,7 @@ snapshots: '@radix-ui/react-visually-hidden@1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@radix-ui/react-primitive': 1.0.3(@types/react-dom@17.0.8)(@types/react@17.0.21)(react-dom@17.0.2(react@17.0.2))(react@17.0.2) react: 17.0.2 react-dom: 17.0.2(react@17.0.2) @@ -39692,7 +39704,7 @@ snapshots: '@radix-ui/rect@1.0.1': dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 '@reactflow/background@11.3.6(@types/react@17.0.21)(immer@10.0.3(patch_hash=utu5oov26wz5mjuays57tp3ybu))(react-dom@17.0.2(react@17.0.2))(react@17.0.2)': dependencies: @@ -44355,7 +44367,7 @@ snapshots: babel-plugin-macros@2.8.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 cosmiconfig: 6.0.0 resolve: 1.22.8 @@ -46636,7 +46648,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 date-format@4.0.3: {} @@ -46982,7 +46994,7 @@ snapshots: dom-helpers@5.2.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 csstype: 3.0.11 dom-serialize@2.2.1: @@ -49019,7 +49031,7 @@ snapshots: history@5.3.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 hmac-drbg@1.0.1: dependencies: @@ -53811,7 +53823,7 @@ snapshots: dependencies: '@babel/core': 7.24.9 '@babel/generator': 7.23.6 - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 ast-types: 0.14.2 commander: 2.20.3 doctrine: 3.0.0 @@ -53884,9 +53896,9 @@ snapshots: '@babel/runtime': 7.23.6 react: 17.0.2 - react-error-boundary@4.0.12(react@17.0.2): + react-error-boundary@4.0.13(react@17.0.2): dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 react: 17.0.2 react-fast-compare@2.0.4: {} @@ -54341,11 +54353,11 @@ snapshots: regenerator-transform@0.15.1: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 regenerator-transform@0.15.2: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 regex-not@1.0.2: dependencies: @@ -54389,7 +54401,7 @@ snapshots: relay-runtime@12.0.0(encoding@0.1.13): dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 fbjs: 3.0.2(encoding@0.1.13) invariant: 2.2.4 transitivePeerDependencies: