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 97f6e8b3d07..4334ab5e3a3 100644 Binary files a/packages/scesim-editor/tests-e2e/__screenshots__/Google-Chrome/misc/emptyExpression/create-a-new-test-scenario.png and b/packages/scesim-editor/tests-e2e/__screenshots__/Google-Chrome/misc/emptyExpression/create-a-new-test-scenario.png differ 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 e85c3f76dec..7cd45f75364 100644 Binary files a/packages/scesim-editor/tests-e2e/__screenshots__/chromium/misc/emptyExpression/create-a-new-test-scenario.png and b/packages/scesim-editor/tests-e2e/__screenshots__/chromium/misc/emptyExpression/create-a-new-test-scenario.png differ diff --git a/packages/scesim-editor/tests-e2e/__screenshots__/webkit/misc/emptyExpression/create-a-new-test-scenario.png b/packages/scesim-editor/tests-e2e/__screenshots__/webkit/misc/emptyExpression/create-a-new-test-scenario.png index 30c67fbe4bf..caa7de3b78b 100644 Binary files a/packages/scesim-editor/tests-e2e/__screenshots__/webkit/misc/emptyExpression/create-a-new-test-scenario.png and b/packages/scesim-editor/tests-e2e/__screenshots__/webkit/misc/emptyExpression/create-a-new-test-scenario.png differ 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) @@ -9316,6 +9316,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 @@ -9331,6 +9334,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 @@ -9404,9 +9410,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 @@ -27459,8 +27471,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' @@ -31007,7 +31019,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) @@ -39419,15 +39431,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) @@ -39437,7 +39449,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) @@ -39450,28 +39462,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) @@ -39485,14 +39497,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) @@ -39504,7 +39516,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: @@ -39512,7 +39524,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) @@ -39531,7 +39543,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) @@ -39541,7 +39553,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) @@ -39551,7 +39563,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) @@ -39569,7 +39581,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) @@ -39599,7 +39611,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) @@ -39609,7 +39621,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: @@ -39617,7 +39629,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) @@ -39633,7 +39645,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) @@ -39645,7 +39657,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) @@ -39661,14 +39673,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: @@ -39676,7 +39688,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: @@ -39684,21 +39696,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: @@ -39706,7 +39718,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: @@ -39714,7 +39726,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) @@ -39724,7 +39736,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: @@ -44411,7 +44423,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 @@ -46692,7 +46704,7 @@ snapshots: date-fns@2.30.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 date-format@4.0.3: {} @@ -47038,7 +47050,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: @@ -49075,7 +49087,7 @@ snapshots: history@5.3.0: dependencies: - '@babel/runtime': 7.23.6 + '@babel/runtime': 7.24.7 hmac-drbg@1.0.1: dependencies: @@ -53915,7 +53927,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 @@ -53988,9 +54000,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: {} @@ -54445,11 +54457,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: @@ -54493,7 +54505,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: