From 8e23770a515761835efa18b6bc292e16b7e644d1 Mon Sep 17 00:00:00 2001 From: Yeser Amer Date: Wed, 27 Mar 2024 15:57:57 +0100 Subject: [PATCH] kie-issues#738: Test Scenario Editor: Bee Table integration with Selector Panel (#2162) --- .../src/api/BeeTable.ts | 8 + .../src/table/BeeTable/BeeTable.tsx | 8 + .../src/table/BeeTable/BeeTableBody.tsx | 8 + .../src/table/BeeTable/BeeTableHeader.tsx | 8 + .../src/table/BeeTable/BeeTableTd.tsx | 14 + .../src/table/BeeTable/BeeTableTh.tsx | 6 +- .../table/BeeTable/BeeTableThResizable.tsx | 11 +- .../dev-webapp/src/AvailableDMNModels.ts | 58 + .../dev-webapp/src/DevWebApp.css | 4 + .../dev-webapp/src/DevWebApp.tsx | 74 +- .../dev-webapp/src/ExternalDmnModels.ts | 1002 ++++++++++++++ .../dev-webapp/src/ExternalScesimModels.ts | 1150 +++++++++++++++++ packages/scesim-editor/package.json | 2 +- .../scesim-editor/src/TestScenarioEditor.tsx | 193 +-- .../src/common/TestScenarioCommonConstants.ts | 25 + .../common/TestScenarioCommonFunctions.tsx | 60 + .../creation/TestScenarioCreationPanel.css | 23 + .../creation/TestScenarioCreationPanel.tsx | 187 +++ .../TestScenarioDrawerCheatSheetPanel.tsx | 4 +- .../TestScenarioDrawerDataObjectsPanel.tsx | 183 --- ...> TestScenarioDrawerDataSelectorPanel.css} | 7 + .../TestScenarioDrawerDataSelectorPanel.tsx | 637 +++++++++ .../src/drawer/TestScenarioDrawerPanel.tsx | 23 +- .../src/i18n/TestScenarioEditorI18n.ts | 27 +- packages/scesim-editor/src/i18n/locales/en.ts | 30 +- packages/scesim-editor/src/index.ts | 1 + .../src/resources/EmptyScesimFile.ts | 2 +- .../src/sidebar/TestScenarioSideBarMenu.tsx | 2 +- .../src/table/TestScenarioTable.tsx | 265 ++-- packages/scesim-marshaller/src/index.ts | 2 +- .../src/schemas/scesim-1_8/SceSim.xsd | 14 +- .../tests-data--manual/simple.scesim | 2 +- pnpm-lock.yaml | 6 +- repo/graph.dot | 2 +- 34 files changed, 3597 insertions(+), 451 deletions(-) create mode 100644 packages/scesim-editor/dev-webapp/src/AvailableDMNModels.ts create mode 100644 packages/scesim-editor/dev-webapp/src/ExternalDmnModels.ts create mode 100644 packages/scesim-editor/dev-webapp/src/ExternalScesimModels.ts create mode 100644 packages/scesim-editor/src/common/TestScenarioCommonConstants.ts create mode 100644 packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx create mode 100644 packages/scesim-editor/src/creation/TestScenarioCreationPanel.css create mode 100644 packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx delete mode 100644 packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.tsx rename packages/scesim-editor/src/drawer/{TestScenarioDrawerDataObjectsPanel.css => TestScenarioDrawerDataSelectorPanel.css} (90%) create mode 100644 packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx diff --git a/packages/boxed-expression-component/src/api/BeeTable.ts b/packages/boxed-expression-component/src/api/BeeTable.ts index 1a562eb3d52..d6f620d2c1e 100644 --- a/packages/boxed-expression-component/src/api/BeeTable.ts +++ b/packages/boxed-expression-component/src/api/BeeTable.ts @@ -68,6 +68,14 @@ export interface BeeTableProps { getRowKey?: (row: ReactTable.Row) => string; /** Custom function for getting column key prop, and avoid using the column index */ getColumnKey?: (column: ReactTable.ColumnInstance) => string; + /** Function to be executed when a column's header is clicked */ + onHeaderClick?: (columnKey: string) => void; + /** Function to be executed when a key up event occurs in a column's header */ + onHeaderKeyUp?: (columnKey: string) => void; + /** Function to be executed when a column's data cell is clicked */ + onDataCellClick?: (columnID: string) => void; + /** Function to be executed when a column's data cell is clicked */ + onDataCellKeyUp?: (columnID: string) => void; /** Disable/Enable cell edits. Enabled by default */ isReadOnly?: boolean; /** Enable keyboard navigation */ diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx index 93bf7c517b5..b8f00e99f7d 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx @@ -84,6 +84,10 @@ export function BeeTableInternal({ onRowDeleted, onColumnAdded, onColumnDeleted, + onHeaderClick, + onHeaderKeyUp, + onDataCellClick, + onDataCellKeyUp, controllerCell = ROW_INDEX_COLUMN_ACCESOR, cellComponentByColumnAccessor, rows, @@ -627,6 +631,8 @@ export function BeeTableInternal({ tableColumns={columnsWithAddedIndexColumns} reactTableInstance={reactTableInstance} onColumnAdded={onColumnAdded2} + onHeaderClick={onHeaderClick} + onHeaderKeyUp={onHeaderKeyUp} lastColumnMinWidth={lastColumnMinWidth} setEditing={setEditing} /> @@ -641,6 +647,8 @@ export function BeeTableInternal({ reactTableInstance={reactTableInstance} additionalRow={additionalRow} onRowAdded={onRowAdded2} + onDataCellClick={onDataCellClick} + onDataCellKeyUp={onDataCellKeyUp} lastColumnMinWidth={lastColumnMinWidth} /> diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTableBody.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTableBody.tsx index 33df80ba7a5..0d6755e07fe 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTableBody.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTableBody.tsx @@ -37,6 +37,10 @@ export interface BeeTableBodyProps { getRowKey: (row: ReactTable.Row) => string; /** Custom function for getting column key prop, and avoid using the column index */ getColumnKey: (column: ReactTable.ColumnInstance) => string; + /** Function to be executed when a column's data cell is clicked */ + onDataCellClick?: (columnID: string) => void; + /** Function to be executed when a key up event occurs in a column's data cell */ + onDataCellKeyUp?: (columnID: string) => void; /** */ onRowAdded?: (args: { beforeIndex: number; rowsCount: number; insertDirection: InsertRowColumnsDirection }) => void; @@ -58,6 +62,8 @@ export function BeeTableBody({ getRowKey, getColumnKey, onRowAdded, + onDataCellClick, + onDataCellKeyUp, shouldRenderRowIndexColumn, shouldShowRowsInlineControls, resizerStopBehavior, @@ -82,6 +88,8 @@ export function BeeTableBody({ row={row} rowIndex={rowIndex} column={reactTableInstance.allColumns[cellIndex]} + onDataCellClick={onDataCellClick} + onDataCellKeyUp={onDataCellKeyUp} onRowAdded={onRowAdded} isActive={false} shouldRenderInlineButtons={ diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx index 00d0080f4be..909e944f44b 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTableHeader.tsx @@ -66,6 +66,10 @@ export interface BeeTableHeaderProps { tableColumns: ReactTable.Column[]; /** Function to be executed when columns are modified */ onColumnUpdates?: (columnUpdates: BeeTableColumnUpdate[]) => void; + /** Function to be executed when a column's header is clicked */ + onHeaderClick?: (columnKey: string) => void; + /** Function to be executed when a key up event occurs in a column's header */ + onHeaderKeyUp?: (columnKey: string) => void; /** Option to enable or disable header edits */ isEditableHeader: boolean; /** */ @@ -94,6 +98,8 @@ export function BeeTableHeader({ onColumnUpdates, isEditableHeader, onColumnAdded, + onHeaderClick, + onHeaderKeyUp, shouldRenderRowIndexColumn, shouldShowRowsInlineControls, resizerStopBehavior, @@ -187,6 +193,8 @@ export function BeeTableHeader({ {!visitedColumns.has(column) && ( { column: ReactTable.ColumnInstance; resizerStopBehavior: ResizerStopBehavior; lastColumnMinWidth?: number; + onDataCellClick?: (columnID: string) => void; + onDataCellKeyUp?: (columnID: string) => void; } export type HoverInfo = @@ -67,6 +69,8 @@ export function BeeTableTd({ resizerStopBehavior, onRowAdded, lastColumnMinWidth, + onDataCellClick, + onDataCellKeyUp, }: BeeTableTdProps) { const [isResizing, setResizing] = useState(false); const [hoverInfo, setHoverInfo] = useState({ isHovered: false }); @@ -212,11 +216,21 @@ export function BeeTableTd({ [column.isWidthConstant, hoverInfo.isHovered, isActive, isResizing, resizingWidth?.isPivoting] ); + const onClick = useCallback(() => { + return onDataCellClick?.(column.id); + }, [column, onDataCellClick]); + + const onKeyUp = useCallback(() => { + return onDataCellKeyUp?.(column.id); + }, [column, onDataCellClick]); + return ( { className: string; thProps: Partial; onClick?: React.MouseEventHandler; + onHeaderKeyUp?: React.KeyboardEventHandler; isLastLevelColumn: boolean; rowIndex: number; rowSpan: number; @@ -68,6 +69,7 @@ export function BeeTableTh({ className, thProps, onClick, + onHeaderKeyUp, columnIndex, columnKey, rowIndex, @@ -92,7 +94,8 @@ export function BeeTableTh({ beforeIndex: hoverInfo.part === "left" ? columnIndex - 1 : columnIndex, groupType: groupType, columnsCount: 1, - insertDirection: InsertRowColumnsDirection.AboveOrRight, + insertDirection: + hoverInfo.part === "left" ? InsertRowColumnsDirection.BelowOrLeft : InsertRowColumnsDirection.AboveOrRight, currentIndex: columnIndex, }); @@ -189,6 +192,7 @@ export function BeeTableTh({ onMouseDown={onMouseDown} onDoubleClick={onDoubleClick} onClick={onClick} + onKeyUp={onHeaderKeyUp} className={`${className} ${cssClasses}`} tabIndex={-1} > diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTableThResizable.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTableThResizable.tsx index a64065a5174..1983f1574a6 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTableThResizable.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTableThResizable.tsx @@ -46,7 +46,8 @@ export interface BeeTableThResizableProps { getColumnKey: (column: ReactTable.ColumnInstance) => string; getColumnLabel: (groupType: string | undefined) => string | undefined; onExpressionHeaderUpdated: (args: Pick) => void; - onHeaderClick?: (columnKey: string) => () => void; + onHeaderClick?: (columnKey: string) => void; + onHeaderKeyUp?: (columnKey: string) => void; reactTableInstance: ReactTable.TableInstance; headerCellInfo: React.ReactElement; shouldShowColumnsInlineControls: boolean; @@ -68,6 +69,7 @@ export function BeeTableThResizable({ getColumnKey, onExpressionHeaderUpdated, onHeaderClick, + onHeaderKeyUp, headerCellInfo, onColumnAdded, resizerStopBehavior, @@ -91,10 +93,14 @@ export function BeeTableThResizable({ return cssClasses.join(" "); }, [columnKey, column.dataType, column.groupType]); - const onClick = useMemo(() => { + const onClick = useCallback(() => { return onHeaderClick?.(columnKey); }, [columnKey, onHeaderClick]); + const onKeyUp = useCallback(() => { + return onHeaderKeyUp?.(columnKey); + }, [columnKey, onHeaderKeyUp]); + const { resizingWidth, setResizingWidth } = useBeeTableResizableCell( columnIndex, resizerStopBehavior, @@ -187,6 +193,7 @@ export function BeeTableThResizable({ }, }} onClick={onClick} + onHeaderKeyUp={onKeyUp} columnKey={columnKey} columnIndex={columnIndex} rowIndex={rowIndex} diff --git a/packages/scesim-editor/dev-webapp/src/AvailableDMNModels.ts b/packages/scesim-editor/dev-webapp/src/AvailableDMNModels.ts new file mode 100644 index 00000000000..315e2451ebc --- /dev/null +++ b/packages/scesim-editor/dev-webapp/src/AvailableDMNModels.ts @@ -0,0 +1,58 @@ +/* + * 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 { DmnLatestModel, getMarshaller } from "@kie-tools/dmn-marshaller"; +import { LOAN_PRE_QUALIFICATION, TRAFFIC_VIOLATION } from "./ExternalDmnModels"; + +export const loanPreQualification = getMarshaller(LOAN_PRE_QUALIFICATION, { upgradeTo: "latest" }).parser.parse(); +export const trafficViolationModel = getMarshaller(TRAFFIC_VIOLATION, { upgradeTo: "latest" }).parser.parse(); + +export const avaiableModels: { + model: DmnLatestModel; + normalizedPosixPathRelativeToTheOpenFile: string; + svg: string; +}[] = [ + { + model: loanPreQualification, + svg: "", + normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-dmn-models/loan-pre-qualification.dmn", + }, + { + model: trafficViolationModel, + svg: "", + normalizedPosixPathRelativeToTheOpenFile: "dev-webapp/available-dmn-models/traffic-violation.dmn", + }, +]; + +// export const availableModelsByPath: Record = Object.values(avaiableModels).reduce( +// (acc, v) => { +// acc[v.normalizedPosixPathRelativeToTheOpenFile] = v; +// return acc; +// }, +// {} 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/dev-webapp/src/DevWebApp.css b/packages/scesim-editor/dev-webapp/src/DevWebApp.css index 56cf69df0c8..0488bb7cbfa 100644 --- a/packages/scesim-editor/dev-webapp/src/DevWebApp.css +++ b/packages/scesim-editor/dev-webapp/src/DevWebApp.css @@ -23,3 +23,7 @@ --pf-c-page__main-section--PaddingBottom: 0px; --pf-c-page__main-section--PaddingLeft: 0px; } + +.dev-webapp--example-dropdown { + z-index: 99999; +} diff --git a/packages/scesim-editor/dev-webapp/src/DevWebApp.tsx b/packages/scesim-editor/dev-webapp/src/DevWebApp.tsx index 2204541c45c..b4708c9e8d4 100644 --- a/packages/scesim-editor/dev-webapp/src/DevWebApp.tsx +++ b/packages/scesim-editor/dev-webapp/src/DevWebApp.tsx @@ -18,17 +18,23 @@ */ import * as React from "react"; -import { useCallback, useEffect, useRef } from "react"; +import { useCallback, useEffect, useRef, useState } from "react"; import { TestScenarioEditor, TestScenarioEditorRef } from "../../src/TestScenarioEditor"; +import { Button } from "@patternfly/react-core/dist/js/components/Button"; +import { Dropdown, DropdownToggle, DropdownItem } from "@patternfly/react-core/dist/js/components/Dropdown"; import { Flex, FlexItem } from "@patternfly/react-core/dist/js/layouts/Flex"; import { Page, PageSection } from "@patternfly/react-core/dist/js/components/Page"; +import { LOAN_PRE_QUALIFICATION, TRAFFIC_VIOLATION } from "./ExternalDmnModels"; +import { IS_OLD_ENOUGH_RULE, TRAFFIC_VIOLATION_DMN } from "./ExternalScesimModels"; + import "./DevWebApp.css"; export function DevWebApp() { const ref = useRef(null); + const [isExampleDropdownOpen, setExampleDropdownIsOpen] = useState(false); useEffect(() => { /* Simulating a call from "Foundation" code */ @@ -77,6 +83,46 @@ export function DevWebApp() { } }, []); + const onOpenStaticScesimExample = useCallback((fileName: string, content: string) => { + ref.current?.setContent(fileName, content); + }, []); + + const dropdownExamplesItems = [ + onOpenStaticScesimExample("TrafficViolationTest.scesim", TRAFFIC_VIOLATION_DMN)} + > + DMN-Based: TrafficViolationTest + , + onOpenStaticScesimExample("AreTheyOldEnoughTest.scesim", IS_OLD_ENOUGH_RULE)} + > + Rule-Based: AreTheyOldEnoughTest + , + ]; + + const onExampleDropdownToggle = useCallback((isOpen: boolean) => { + setExampleDropdownIsOpen(isOpen); + }, []); + + const onExampleDropdownSelect = useCallback(() => { + setExampleDropdownIsOpen(false); + const element = document.getElementById("toggle-basic"); + element?.focus(); + }, []); + + // const onRequestExternalModelByPath = useCallback>(async (path) => { + // return availableModelsByPath[path] ?? null; + // }, []); + + // const onRequestExternalModelsAvailableToInclude = + // useCallback(async () => { + // return Object.keys(availableModelsByPath); + // }, []); + return ( <> @@ -89,12 +135,30 @@ export function DevWebApp() {
(Drag & drop a file anywhere to open it)
-     |     -     - + + Examples + + } + isOpen={isExampleDropdownOpen} + dropdownItems={dropdownExamplesItems} + /> +     + +     +     - + diff --git a/packages/scesim-editor/dev-webapp/src/ExternalDmnModels.ts b/packages/scesim-editor/dev-webapp/src/ExternalDmnModels.ts new file mode 100644 index 00000000000..f282f9c7cad --- /dev/null +++ b/packages/scesim-editor/dev-webapp/src/ExternalDmnModels.ts @@ -0,0 +1,1002 @@ +/* + * 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 const LOAN_PRE_QUALIFICATION = ` + + + + + Product_Type + + + number + + + number + + + number + + + + string + + "M","D","S" + + + + + number + + + Marital_Status + + + string + + "Unemployed","Employed","Self-employed","Student" + + + + boolean + + + + number + + + number + + + number + + + number + + + number + + + + + + Risk_Category + + + number + + + + + string + + + number + + + + string + + "Ineligible","Eligible" + + + + string + + "Decline","Bureau","Through" + + + + string + + "Full","Mini","None" + + + + string + + "Standard Loan","Special Loan" + + + + string + + "High","Medium","Low","Very Low","Decline" + + + + string + + "Poor","Bad","Fair","Good","Excellent" + + + + string + + "Insufficient","Sufficient" + + + + string + + "Sufficient","Insufficient" + + + + string + + "Not Qualified","Qualified" + + + + + number + + [300..850] + + + + + + string + + "Qualified","Not Qualified" + + + + string + + + + + + + + + + + + 0.36 + + + + + + + + + + + + + + + + + + + + + + + PITI + + + + (Requested Product.Amount*((Requested Product.Rate/100)/12))/(1-(1/(1+(Requested Product.Rate/100)/12)**-Requested Product.Term)) + + + + + + Applicant Data.Monthly.Tax + + + + + + Applicant Data.Monthly.Insurance + + + + + + Applicant Data.Monthly.Income + + + + + + + + + if Client PITI <= Lender Acceptable PITI() +then "Sufficient" +else "Insufficient" + + + + + + + + + + + + + + (pmt + tax + insurance) / income + + + + + + + + + + + + + + + + + + + + + + + + DTI + + + + Applicant Data.Monthly.Repayments + Applicant Data.Monthly.Expenses + + + + + + Applicant Data.Monthly.Income + + + + + + + + + if Client DTI <= Lender Acceptable DTI() +then "Sufficient" +else "Insufficient" + + + + + + + + + + + + + + Credit Score.FICO + + + + + + + >= 750 + + + "Excellent" + + + + + + + + [700..750) + + + "Good" + + + + + + + + [650..700) + + + "Fair" + + + + + + + + [600..650) + + + "Poor" + + + + + + + + < 600 + + + "Bad" + + + + + + + + + + + + + + + + + + + + + + + Credit Score Rating + + + + + Back End Ratio + + + + + Front End Ratio + + + + + + + + "Poor", "Bad" + + + - + + + - + + + "Not Qualified" + + + "Credit Score too low." + + + + + + + + - + + + "Insufficient" + + + "Sufficient" + + + "Not Qualified" + + + "Debt to income ratio is too high." + + + + + + + + - + + + "Sufficient" + + + "Insufficient" + + + "Not Qualified" + + + "Mortgage payment to income ratio is too high." + + + + + + + + - + + + "Insufficient" + + + "Insufficient" + + + "Not Qualified" + + + "Debt to income ratio is too high AND mortgage payment to income ratio is too high." + + + + + + + + "Fair", "Good", "Excellent" + + + "Sufficient" + + + "Sufficient" + + + "Qualified" + + + "The borrower has been successfully prequalified for the requested loan." + + + + + + + + + + + + + + + + + + + d / i + + + + + + + + + 0.28 + + + + + + + + + 209 + + + 50 + 209 + + + 50 + + + 120 + + + + 1036 + + + 1036 + + + 1036 + + + 1036 + + + 1158 + + + 454 + + + 50 + 300 + + + 50 + + + 120 + + + + 550 + + + 550 + + + 672 + + + 60 + 133 + 147 + 335 + + + 60 + 233 + 133 + 129 + 135 + 681 + 138 + + + 150 + + + 50 + 150 + + + 228 + + + 50 + 228 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +export const TRAFFIC_VIOLATION = ` + + + + + string + + + number + + + string + + + string + + + number + + + + + string + + + date + + + string + + "speed", "parking", "driving under the influence" + + + + number + + + number + + + + + number + + + number + + + + + + + + + + + + + + Violation.Type + + + + + Violation.Actual Speed - Violation.Speed Limit + + + + + + + "speed" + + + [10..30) + + + 500 + + + 3 + + + + + "speed" + + + >= 30 + + + 1000 + + + 7 + + + + + "parking" + + + - + + + 100 + + + 1 + + + + + "driving under the influence" + + + - + + + 1000 + + + 5 + + + + + + + + + Should the driver be suspended due to points on his license? + "Yes", "No" + + + + + + + + + + + + Driver.Points + Fine.Points + + + + + if Total Points >= 20 then "Yes" else "No" + + + + + + + + + + 50.0 + 254.0 + 329.0 + 119.0 + 100.0 + 186.0 + + + 50.0 + 100.0 + 398.0 + + + 398.0 + + + 398.0 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; diff --git a/packages/scesim-editor/dev-webapp/src/ExternalScesimModels.ts b/packages/scesim-editor/dev-webapp/src/ExternalScesimModels.ts new file mode 100644 index 00000000000..1c107b733c2 --- /dev/null +++ b/packages/scesim-editor/dev-webapp/src/ExternalScesimModels.ts @@ -0,0 +1,1150 @@ +/* + * 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 const IS_OLD_ENOUGH_RULE = ` + + + + + + + Index + OTHER + + + # + java.lang.Integer + + java.lang.Integer + # + NOT_EXPRESSION + 70.0 + + + + + Description + OTHER + + + Scenario description + java.lang.String + + java.lang.String + Scenario description + NOT_EXPRESSION + 300.0 + + + + + Applicant + + + age + + + + 1|1 + GIVEN + + + 1|1 + mortgages.mortgages.Applicant + + java.lang.Integer + Applicant + age + NOT_EXPRESSION + 212.60000000000002 + + + + + LoanApplication + + + approved + + + + 1591876615315 + GIVEN + + + 1591622209590 + mortgages.mortgages.LoanApplication + + java.lang.Boolean + LoanApplication + approved + NOT_EXPRESSION + 212.60000000000002 + + + + + IncomeSource + + + amount + + + + 1591622221147 + GIVEN + + + 1591622221147 + mortgages.mortgages.IncomeSource + + java.lang.Integer + IncomeSource + amount + NOT_EXPRESSION + 212.60000000000002 + + + + + LoanApplication + + + approved + + + + 1|2 + EXPECT + + + 1591622209590 + mortgages.mortgages.LoanApplication + + java.lang.Boolean + LoanApplication + approved + NOT_EXPRESSION + 212.60000000000002 + + + + + LoanApplication + + + explanation + + + + 1591874776961 + EXPECT + + + 1591622209590 + mortgages.mortgages.LoanApplication + + java.lang.String + LoanApplication + explanation + NOT_EXPRESSION + 212.60000000000002 + + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Young Bob want a loan + + + + # + java.lang.Integer + + + Index + OTHER + + 1 + + + + 1|1 + mortgages.mortgages.Applicant + + + 1|1 + GIVEN + + 17 + + + + 1591622221147 + mortgages.mortgages.IncomeSource + + + 1591622221147 + GIVEN + + 0 + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1|2 + EXPECT + + false + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1591874776961 + EXPECT + + Underage + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1591876615315 + GIVEN + + true + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Adult Anna want a loan + + + + # + java.lang.Integer + + + Index + OTHER + + 2 + + + + 1|1 + mortgages.mortgages.Applicant + + + 1|1 + GIVEN + + 27 + + + + 1591622221147 + mortgages.mortgages.IncomeSource + + + 1591622221147 + GIVEN + + 0 + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1|2 + EXPECT + + true + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1591874776961 + EXPECT + + null + + + + 1591622209590 + mortgages.mortgages.LoanApplication + + + 1591876615315 + GIVEN + + true + + + + + + + + + + + + 1|1 + GIVEN + + + Empty + java.lang.Void + + java.lang.Void + INSTANCE 1 + PROPERTY 1 + NOT_EXPRESSION + 114.0 + + + + + + + + + Empty + java.lang.Void + + + 1|1 + GIVEN + + + + + + + + RULE + false + false + + + + + +`; + +export const TRAFFIC_VIOLATION_DMN = ` + + + + + + + + Index + OTHER + + + # + java.lang.Integer + + java.lang.Integer + # + 70 + NOT_EXPRESSION + + + + + Description + OTHER + + + Scenario description + java.lang.String + + java.lang.String + Scenario description + 300 + NOT_EXPRESSION + + + + + Driver + + + Points + + + + 1|5 + GIVEN + + + Driver + Driver + + number + Driver + Points + + 114 + NOT_EXPRESSION + + + + + Violation + + + Type + + + + 1|8 + GIVEN + + + Violation + Violation + + Type + Violation + Type + + 114 + NOT_EXPRESSION + + + + + Violation + + + Speed Limit + + + + 1|9 + GIVEN + + + Violation + Violation + + number + Violation + Speed Limit + + 114 + NOT_EXPRESSION + + + + + Violation + + + Actual Speed + + + + 1|10 + GIVEN + + + Violation + Violation + + number + Violation + Actual Speed + + 114 + NOT_EXPRESSION + + + + + Fine + + + Amount + + + + 1|11 + EXPECT + + + Fine + Fine + + number + Fine + Amount + + 114 + NOT_EXPRESSION + + + + + Fine + + + Points + + + + 1|12 + EXPECT + + + Fine + Fine + + number + Fine + Points + + 114 + NOT_EXPRESSION + + + + + Should the driver be suspended? + + + + 1|13 + EXPECT + + + Should the driver be suspended? + Should the driver be suspended? + + string + Should the driver be suspended? + value + + 114 + NOT_EXPRESSION + + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Above speed limit: 10km/h and 30 km/h + + + + Driver + Driver + + + 1|5 + GIVEN + + 10 + + + + Violation + Violation + + + 1|8 + GIVEN + + "speed" + + + + Violation + Violation + + + 1|9 + GIVEN + + 100 + + + + Violation + Violation + + + 1|10 + GIVEN + + 120 + + + + Fine + Fine + + + 1|11 + EXPECT + + 500 + + + + Fine + Fine + + + 1|12 + EXPECT + + 3 + + + + Should the driver be suspended? + Should the driver be suspended? + + + 1|13 + EXPECT + + "No" + + + + # + java.lang.Integer + + + Index + OTHER + + 1 + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Above speed limit: more than 30 km/h + + + + Driver + Driver + + + 1|5 + GIVEN + + 10 + + + + Violation + Violation + + + 1|8 + GIVEN + + "speed" + + + + Violation + Violation + + + 1|9 + GIVEN + + 100 + + + + Violation + Violation + + + 1|10 + GIVEN + + 150 + + + + Fine + Fine + + + 1|11 + EXPECT + + 1000 + + + + Fine + Fine + + + 1|12 + EXPECT + + 7 + + + + Should the driver be suspended? + Should the driver be suspended? + + + 1|13 + EXPECT + + "No" + + + + # + java.lang.Integer + + + Index + OTHER + + 2 + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Parking violation + + + + Driver + Driver + + + 1|5 + GIVEN + + 10 + + + + Violation + Violation + + + 1|8 + GIVEN + + "parking" + + + + Violation + Violation + + + 1|9 + GIVEN + + + + + Violation + Violation + + + 1|10 + GIVEN + + + + + Fine + Fine + + + 1|11 + EXPECT + + 100 + + + + Fine + Fine + + + 1|12 + EXPECT + + 1 + + + + Should the driver be suspended? + Should the driver be suspended? + + + 1|13 + EXPECT + + "No" + + + + # + java.lang.Integer + + + Index + OTHER + + 3 + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + DUI violation + + + + Driver + Driver + + + 1|5 + GIVEN + + 10 + + + + Violation + Violation + + + 1|8 + GIVEN + + "driving under the influence" + + + + Violation + Violation + + + 1|9 + GIVEN + + + + + Violation + Violation + + + 1|10 + GIVEN + + + + + Fine + Fine + + + 1|11 + EXPECT + + 1000 + + + + Fine + Fine + + + 1|12 + EXPECT + + 5 + + + + Should the driver be suspended? + Should the driver be suspended? + + + 1|13 + EXPECT + + "No" + + + + # + java.lang.Integer + + + Index + OTHER + + 4 + + + + + + + + Scenario description + java.lang.String + + + Description + OTHER + + Driver suspended + + + + Driver + Driver + + + 1|5 + GIVEN + + 15 + + + + Violation + Violation + + + 1|8 + GIVEN + + "speed" + + + + Violation + Violation + + + 1|9 + GIVEN + + 100 + + + + Violation + Violation + + + 1|10 + GIVEN + + 140 + + + + Fine + Fine + + + 1|11 + EXPECT + + 1000 + + + + Fine + Fine + + + 1|12 + EXPECT + + 7 + + + + Should the driver be suspended? + Should the driver be suspended? + + + 1|13 + EXPECT + + "Yes" + + + + # + java.lang.Integer + + + Index + OTHER + + 5 + + + + + + + + + + + + 1|1 + GIVEN + + + Empty + java.lang.Void + + java.lang.Void + INSTANCE 1 + PROPERTY 1 + 114 + NOT_EXPRESSION + + + + + + + + + Empty + java.lang.Void + + + 1|1 + GIVEN + + + + + + + + src/main/resources/Traffic Violation.dmn + DMN + https://github.com/kiegroup/drools/kie-dmn/_A4BCA8B8-CF08-433F-93B2-A2598F19ECFF + Traffic Violation + false + false + + + + +`; diff --git a/packages/scesim-editor/package.json b/packages/scesim-editor/package.json index 64f880e351d..9a306a8388a 100644 --- a/packages/scesim-editor/package.json +++ b/packages/scesim-editor/package.json @@ -17,6 +17,7 @@ "@kie-tools-core/patternfly-base": "workspace:*", "@kie-tools/boxed-expression-component": "workspace:*", "@kie-tools/i18n-common-dictionary": "workspace:*", + "@kie-tools/scesim-marshaller": "workspace:*", "@patternfly/react-core": "^4.276.6", "@patternfly/react-icons": "^4.93.6", "@patternfly/react-styles": "^4.92.6", @@ -33,7 +34,6 @@ "@kie-tools-core/webpack-base": "workspace:*", "@kie-tools/eslint": "workspace:*", "@kie-tools/root-env": "workspace:*", - "@kie-tools/scesim-marshaller": "workspace:*", "@kie-tools/tsconfig": "workspace:*", "@types/lodash": "^4.14.168", "@types/react": "^17.0.6", diff --git a/packages/scesim-editor/src/TestScenarioEditor.tsx b/packages/scesim-editor/src/TestScenarioEditor.tsx index 70ebd4ef9b3..62f15be68f3 100644 --- a/packages/scesim-editor/src/TestScenarioEditor.tsx +++ b/packages/scesim-editor/src/TestScenarioEditor.tsx @@ -34,21 +34,14 @@ import { import { Alert } from "@patternfly/react-core/dist/js/components/Alert"; import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye"; -import { Button } from "@patternfly/react-core/dist/js/components/Button"; -import { Checkbox } from "@patternfly/react-core/dist/js/components/Checkbox"; import { Drawer, DrawerContent, DrawerContentBody } from "@patternfly/react-core/dist/js/components/Drawer"; import { EmptyState, EmptyStateBody, EmptyStateIcon } from "@patternfly/react-core/dist/js/components/EmptyState"; -import { Form, FormGroup } from "@patternfly/react-core/dist/js/components/Form"; -import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/js/components/FormSelect"; import { Icon } from "@patternfly/react-core/dist/js/components/Icon"; import { Spinner } from "@patternfly/react-core/dist/js/components/Spinner"; import { Tabs, Tab, TabTitleIcon, TabTitleText } from "@patternfly/react-core/dist/js/components/Tabs"; -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/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 ErrorIcon from "@patternfly/react-icons/dist/esm/icons/error-circle-o-icon"; import TableIcon from "@patternfly/react-icons/dist/esm/icons/table-icon"; import HelpIcon from "@patternfly/react-icons/dist/esm/icons/help-icon"; @@ -62,6 +55,7 @@ import { useTestScenarioEditorI18n } from "./i18n"; import { EMPTY_ONE_EIGHT } from "./resources/EmptyScesimFile"; import "./TestScenarioEditor.css"; +import TestScenarioCreationPanel from "./creation/TestScenarioCreationPanel"; /* Constants */ @@ -103,14 +97,16 @@ export type TestScenarioAlert = { export type TestScenarioDataObject = { id: string; - name: string; - customBadgeContent?: string; children?: TestScenarioDataObject[]; + customBadgeContent?: string; + isSimpleTypeFact?: boolean; + name: string; }; export type TestScenarioEditorRef = { /* TODO Convert these to Promises */ getContent(): string; + getDiagramSvg: () => Promise; setContent(pathRelativeToTheWorkspaceRoot: string, content: string): void; }; @@ -125,111 +121,11 @@ export type TestScenarioSettings = { ruleFlowGroup?: string; }; -/* Sub-Components */ - -function TestScenarioCreationPanel({ - onCreateScesimButtonClicked, -}: { - onCreateScesimButtonClicked: ( - assetType: string, - isStatelessSessionRule: boolean, - isTestSkipped: boolean, - kieSessionRule: string, - ruleFlowGroup: string - ) => void; -}) { - const assetsOption = [ - { value: "", label: "Select a type", disabled: true }, - { value: TestScenarioType[TestScenarioType.DMN], label: "Decision (DMN)", disabled: false }, - { value: TestScenarioType[TestScenarioType.RULE], label: "Rule (DRL)", disabled: false }, - ]; - - const [assetType, setAssetType] = React.useState(""); - const [kieSessionRule, setKieSessionRule] = React.useState(""); - const [ruleFlowGroup, setRuleFlowGroup] = React.useState(""); - const [isTestSkipped, setTestSkipped] = React.useState(false); - const [isStatelessSessionRule, setStatelessSessionRule] = React.useState(false); - - return ( - - - - Create a new Test Scenario - -
- - setAssetType(value)} - > - {assetsOption.map((option, index) => ( - - ))} - - - {assetType === TestScenarioType[TestScenarioType.DMN] && ( - - - - - - )} - {assetType === TestScenarioType[TestScenarioType.RULE] && ( - <> - - "} - onChange={(value) => setKieSessionRule(value)} - type="text" - value={kieSessionRule} - /> - - - "} - onChange={(value) => setRuleFlowGroup(value)} - type="text" - value={ruleFlowGroup} - /> - - - { - setStatelessSessionRule(value); - }} - /> - - - )} - - { - setTestSkipped(value); - }} - /> - -
- -
- ); -} +export type TestScenarioSelectedColumnMetaData = { + factMapping: SceSim__FactMappingType; + index: number; + isBackground: boolean; +}; function TestScenarioMainPanel({ fileName, @@ -245,17 +141,19 @@ function TestScenarioMainPanel({ 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 scenarioTableScrollableElementRef = useRef(null); const backgroundTableScrollableElementRef = useRef(null); const onTabChanged = useCallback((_event, tab) => { + setSelectedColumnMetaData(null); setTab(tab); }, []); - const [dockPanel, setDockPanel] = useState({ isOpen: true, selected: TestScenarioEditorDock.DATA_OBJECT }); - const closeDockPanel = useCallback(() => { setDockPanel((prev) => { return { ...prev, isOpen: false }; @@ -266,8 +164,14 @@ function TestScenarioMainPanel({ setDockPanel({ isOpen: true, selected: selected }); }, []); + useEffect(() => { + setDockPanel({ isOpen: true, selected: TestScenarioEditorDock.DATA_OBJECT }); + setSelectedColumnMetaData(null); + setTab(TestScenarioEditorTab.EDITOR); + }, [fileName]); + /** This is TEMPORARY */ - const dataObjectsFromScesim = useMemo(() => { + 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 */ @@ -278,8 +182,7 @@ function TestScenarioMainPanel({ That makes sense for previously created scesim files */ const factsMappings: SceSim__FactMappingType[] = - scesimModel!.ScenarioSimulationModel["simulation"]!["scesimModelDescriptor"]!["factMappings"]!["FactMapping"] ?? - []; + scesimModel.ScenarioSimulationModel.simulation.scesimModelDescriptor.factMappings.FactMapping ?? []; const dataObjects: TestScenarioDataObject[] = []; @@ -289,43 +192,54 @@ function TestScenarioMainPanel({ if (factsMappings[i].className!.__$$text === "java.lang.Void") { continue; } - const dataObject = dataObjects.find((value) => value.id === factsMappings[i]["factAlias"].__$$text); + 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 === factsMappings[i]["expressionAlias"]?.__$$text)) { + if (!dataObject.children?.some((value) => value.id === propertyID)) { dataObject.children!.push({ - id: factsMappings[i]["expressionAlias"]!.__$$text, - name: factsMappings[i]["expressionAlias"]!.__$$text, - customBadgeContent: factsMappings[i]["className"].__$$text, + id: propertyID, + customBadgeContent: factsMappings[i].className.__$$text, + isSimpleTypeFact: isSimpleTypeFact, + name: propertyName, }); } } else { dataObjects.push({ - id: factsMappings[i]["factAlias"].__$$text, - name: factsMappings[i]["factAlias"].__$$text, - customBadgeContent: factsMappings[i]["factIdentifier"]!["className"]!.__$$text, + id: factID, + name: factsMappings[i].factAlias!.__$$text, + customBadgeContent: factsMappings[i].factIdentifier!.className!.__$$text, children: [ { - id: factsMappings[i]["expressionAlias"]!.__$$text, - name: factsMappings[i]["expressionAlias"]!.__$$text, - customBadgeContent: factsMappings[i]["className"].__$$text, + id: propertyID, + name: propertyName, + customBadgeContent: factsMappings[i].className.__$$text, }, ], }); } } - return dataObjects; - }, [scesimModel]); + setDataObjects(dataObjects); + }, [scesimModel.ScenarioSimulationModel.settings.type]); /** It determines the Alert State */ useEffect(() => { - const assetType = scesimModel.ScenarioSimulationModel["settings"]!["type"]!.__$$text; + const assetType = scesimModel.ScenarioSimulationModel.settings.type!.__$$text; let alertEnabled = false; let alertMessage = ""; let alertVariant: "default" | "danger" | "warning" | "info" | "success" = "danger"; - if (dataObjectsFromScesim.length > 0) { + if (dataObjects.length > 0) { alertMessage = assetType === TestScenarioType[TestScenarioType.DMN] ? i18n.alerts.dmnDataRetrievedFromScesim @@ -341,7 +255,7 @@ function TestScenarioMainPanel({ } setAlert({ enabled: alertEnabled, message: alertMessage, variant: alertVariant }); - }, [dataObjectsFromScesim, i18n, scesimModel.ScenarioSimulationModel]); + }, [dataObjects, i18n, scesimModel.ScenarioSimulationModel.settings.type]); return ( <> @@ -350,10 +264,12 @@ function TestScenarioMainPanel({ } > @@ -399,6 +317,7 @@ function TestScenarioMainPanel({ assetType={scesimModel.ScenarioSimulationModel.settings.type!.__$$text} tableData={scesimModel.ScenarioSimulationModel.simulation} scrollableParentRef={scenarioTableScrollableElementRef} + updateSelectedColumnMetaData={setSelectedColumnMetaData} updateTestScenarioModel={updateTestScenarioModel} /> @@ -427,6 +346,7 @@ function TestScenarioMainPanel({ assetType={scesimModel.ScenarioSimulationModel.settings.type!.__$$text} tableData={scesimModel.ScenarioSimulationModel.background} scrollableParentRef={backgroundTableScrollableElementRef} + updateSelectedColumnMetaData={setSelectedColumnMetaData} updateTestScenarioModel={updateTestScenarioModel} /> @@ -504,6 +424,7 @@ const TestScenarioEditorInternal = ({ forwardRef }: { forwardRef?: React.Ref ({ getContent: () => marshaller.builder.build(scesimModel), + getDiagramSvg: async () => undefined, setContent: (normalizedPosixPathRelativeToTheWorkspaceRoot, content) => { console.debug("SCESIM setContent called"); console.debug("=== FILE CONTENT ==="); diff --git a/packages/scesim-editor/src/common/TestScenarioCommonConstants.ts b/packages/scesim-editor/src/common/TestScenarioCommonConstants.ts new file mode 100644 index 00000000000..55edecff712 --- /dev/null +++ b/packages/scesim-editor/src/common/TestScenarioCommonConstants.ts @@ -0,0 +1,25 @@ +/* + * 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 const EMPTY_TYPE = "java.lang.Void"; + +export enum TEST_SCENARIO_EXPRESSION_TYPE { + EXPRESSION, + NOT_EXPRESSION, +} diff --git a/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx b/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx new file mode 100644 index 00000000000..cd9b95ca376 --- /dev/null +++ b/packages/scesim-editor/src/common/TestScenarioCommonFunctions.tsx @@ -0,0 +1,60 @@ +/* + * 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.css b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.css new file mode 100644 index 00000000000..e4c4aa96e26 --- /dev/null +++ b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.css @@ -0,0 +1,23 @@ +/* + * 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. + */ + +.kie-scesim-editor-creation-panel--info-icon { + margin-left: 5px; + vertical-align: top; +} diff --git a/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx new file mode 100644 index 00000000000..9dfd3812fca --- /dev/null +++ b/packages/scesim-editor/src/creation/TestScenarioCreationPanel.tsx @@ -0,0 +1,187 @@ +/* + * 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 { Button } from "@patternfly/react-core/dist/js/components/Button"; +import { Checkbox } from "@patternfly/react-core/dist/js/components/Checkbox"; +import { EmptyState, EmptyStateIcon } from "@patternfly/react-core/dist/js/components/EmptyState"; +import { Form, FormGroup } from "@patternfly/react-core/dist/js/components/Form"; +import { FormSelect, FormSelectOption } from "@patternfly/react-core/dist/js/components/FormSelect"; +import { HelpIcon } from "@patternfly/react-icons/dist/esm/icons/help-icon"; +import { Icon } from "@patternfly/react-core/dist/js/components/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/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 { useTestScenarioEditorI18n } from "../i18n"; + +import "./TestScenarioCreationPanel.css"; + +function TestScenarioCreationPanel({ + onCreateScesimButtonClicked, +}: { + onCreateScesimButtonClicked: ( + assetType: string, + isStatelessSessionRule: boolean, + isTestSkipped: boolean, + kieSessionRule: string, + ruleFlowGroup: string + ) => void; +}) { + 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 }, + ]; + + const [assetType, setAssetType] = React.useState(""); + 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(""); + + return ( + + + + {i18n.creationPanel.title} + +
+ + setAssetType(value)} + value={assetType} + > + {assetsOption.map((option, index) => ( + + ))} + + + {assetType === TestScenarioType[TestScenarioType.DMN] && ( + <> + + + + + + + + {i18n.creationPanel.autoFillTable} + + + + + + + } + onChange={(value: boolean) => { + setAutoFillTableEnabled(value); + }} + /> + + + )} + {assetType === TestScenarioType[TestScenarioType.RULE] && ( + <> + + setKieSessionRule(value)} + placeholder={"<" + i18n.creationPanel.optional + ">"} + type="text" + value={kieSessionRule} + /> + + + setRuleFlowGroup(value)} + placeholder={"<" + i18n.creationPanel.optional + ">"} + type="text" + value={ruleFlowGroup} + /> + + + + {i18n.creationPanel.statelessSession} + + + + + + + } + onChange={(value) => { + setStatelessSessionRule(value); + }} + /> + + + )} + + + {i18n.creationPanel.testSkip} + + + + + + + } + onChange={(value: boolean) => { + setTestSkipped(value); + }} + /> + +
+ +
+ ); +} + +export default TestScenarioCreationPanel; diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx index 5bfd936d30a..c3b14567fdb 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerCheatSheetPanel.tsx @@ -33,8 +33,8 @@ function TestScenarioDrawerCheatSheetPanel({ assetType }: { assetType: string }) {i18n.drawer.cheatSheet.paragraph2( assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataObjects.titleDMN - : i18n.drawer.dataObjects.titleRule + ? i18n.drawer.dataSelector.titleDMN + : i18n.drawer.dataSelector.titleRule )} {i18n.drawer.cheatSheet.paragraph3(i18n.tab.backgroundTabTitle, i18n.tab.scenarioTabTitle)} diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.tsx deleted file mode 100644 index 3be9e957ab0..00000000000 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.tsx +++ /dev/null @@ -1,183 +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 { useCallback, useEffect, useState } from "react"; - -import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye"; -import { Button } from "@patternfly/react-core/dist/js/components/Button/"; -import { Divider } from "@patternfly/react-core/dist/js/components/Divider/"; -import { EmptyState, EmptyStateBody, EmptyStateIcon } from "@patternfly/react-core/dist/js/components/EmptyState"; -import { HelpIcon } from "@patternfly/react-icons/dist/esm/icons/help-icon"; -import { Icon } from "@patternfly/react-core/dist/js/components/Icon"; -import { Text } from "@patternfly/react-core/dist/js/components/Text"; -import { Title } from "@patternfly/react-core/dist/js/components/Title"; -import { Toolbar } from "@patternfly/react-core/dist/js/components/Toolbar/"; -import { ToolbarItem } from "@patternfly/react-core/dist/js/components/Toolbar/"; -import { ToolbarContent } from "@patternfly/react-core/dist/js/components/Toolbar/"; -import { Tooltip } from "@patternfly/react-core/dist/js/components/Tooltip/"; -import { TreeView, TreeViewDataItem } from "@patternfly/react-core/dist/js/components/TreeView/"; -import { TreeViewSearch } from "@patternfly/react-core/dist/js/components/TreeView/"; -import { WarningTriangleIcon } from "@patternfly/react-icons/dist/esm/icons/warning-triangle-icon"; - -import { TestScenarioDataObject, TestScenarioType } from "../TestScenarioEditor"; -import { useTestScenarioEditorI18n } from "../i18n"; - -import "./TestScenarioDrawerDataObjectsPanel.css"; - -function TestScenarioDataObjectsPanel({ - assetType, - dataObjects, -}: { - assetType: string; - dataObjects: TestScenarioDataObject[]; -}) { - const { i18n } = useTestScenarioEditorI18n(); - - const [allExpanded, setAllExpanded] = useState(false); - const [filteredItems, setFilteredItems] = useState({ items: dataObjects, isFiltered: false }); - const [treeViewActiveItems, setTreeViewActiveItems] = useState([]); - - useEffect(() => { - setFilteredItems({ items: dataObjects, isFiltered: false }); - setAllExpanded(false); - }, [dataObjects]); - - const filterItems = useCallback((item, input) => { - if (item.name.toLowerCase().includes(input.toLowerCase())) { - return true; - } - - if (item.children) { - return ( - (item.children = item.children - .map((object: TestScenarioDataObject) => Object.assign({}, object)) - .filter((child: TestScenarioDataObject) => filterItems(child, input))).length > 0 - ); - } - }, []); - - const onSearchTreeView = useCallback( - (event) => { - const input = event.target.value; - if (input === "") { - setFilteredItems({ items: dataObjects, isFiltered: false }); - } else { - const filtered = dataObjects - .map((object) => Object.assign({}, object)) - .filter((item) => filterItems(item, input)); - setFilteredItems({ items: filtered, isFiltered: true }); - } - }, - [dataObjects, filterItems] - ); - - const onSelectTreeViewItem = useCallback((_event, treeViewItem: TreeViewDataItem) => { - console.log(treeViewItem); - setTreeViewActiveItems([treeViewItem]); - }, []); - - const onAllExpandedToggle = useCallback((_event) => { - setAllExpanded((prev) => !prev); - }, []); - - const toolbar = ( - - - - - - - - ); - - return ( - <> - - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataObjects.descriptionDMN - : i18n.drawer.dataObjects.descriptionRule} - - - - - - - -
- {dataObjects.length > 0 ? ( - - ) : ( - - - - - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataObjects.emptyDataObjectsTitleDMN - : i18n.drawer.dataObjects.emptyDataObjectsTitleRule} - - - {assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataObjects.emptyDataObjectsDescriptionDMN - : i18n.drawer.dataObjects.emptyDataObjectsDescriptionRule} - - - - )} -
- -
- - - -
- - ); -} - -export default TestScenarioDataObjectsPanel; diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.css b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.css similarity index 90% rename from packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.css rename to packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.css index a60cac241b0..83809bc7224 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataObjectsPanel.css +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.css @@ -35,6 +35,13 @@ overflow: scroll; } +.kie-scesim-editor-drawer-data-objects--selector-disabled { + height: 500px; + overflow: scroll; + opacity: 0.25; + pointer-events: none; +} + .kie-scesim-editor-drawer-data-objects--selector-title { margin-top: 10px; } diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx new file mode 100644 index 00000000000..d1a1d554ea7 --- /dev/null +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerDataSelectorPanel.tsx @@ -0,0 +1,637 @@ +/* + * 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, useMemo, useState } from "react"; + +import { Bullseye } from "@patternfly/react-core/dist/js/layouts/Bullseye"; +import { Button } from "@patternfly/react-core/dist/js/components/Button/"; +import { Divider } from "@patternfly/react-core/dist/js/components/Divider/"; +import { EmptyState, EmptyStateBody, EmptyStateIcon } from "@patternfly/react-core/dist/js/components/EmptyState"; +import { HelpIcon } from "@patternfly/react-icons/dist/esm/icons/help-icon"; +import { Icon } from "@patternfly/react-core/dist/js/components/Icon"; +import { Text } from "@patternfly/react-core/dist/js/components/Text"; +import { Title } from "@patternfly/react-core/dist/js/components/Title"; +import { Toolbar, ToolbarContent, ToolbarItem } from "@patternfly/react-core/dist/js/components/Toolbar/"; +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 { EMPTY_TYPE, TEST_SCENARIO_EXPRESSION_TYPE } from "../common/TestScenarioCommonConstants"; +import { + retrieveFactMappingValueIndexByIdentifiers, + retrieveModelDescriptor, + retrieveRowsDataFromModel, +} from "../common/TestScenarioCommonFunctions"; + +import "./TestScenarioDrawerDataSelectorPanel.css"; + +const enum TestScenarioDataSelectorState { + DISABLED, // All subcomponents are DISABLED + ENABLED, // All subcomponents are ENABLED + 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>; +}) { + const { i18n } = useTestScenarioEditorI18n(); + + const [allExpanded, setAllExpanded] = useState(false); + const [dataSelectorStatus, setDataSelectorStatus] = useState(TestScenarioDataSelectorState.DISABLED); + const [filteredItems, setFilteredItems] = useState(dataObjects); + const [treeViewStatus, setTreeViewStatus] = useState({ + activeItems: [] as TreeViewDataItem[], + searchKey: "", + isExpanded: false, + }); + + const filterDataObjectsByID = useCallback((item, itemID) => { + if (item.id === itemID) { + return true; + } + + if (item.children) { + return ( + (item.children = item.children + .map((object: TestScenarioDataObject) => Object.assign({}, object)) + .filter((child: TestScenarioDataObject) => filterDataObjectsByID(child, itemID))).length > 0 + ); + } + + return false; + }, []); + + const findDataObjectRootParent = useCallback( + (dataObjects: TestScenarioDataObject[], itemId: string) => { + const filtered = dataObjects + .map((object) => Object.assign({}, object)) + .filter((item) => filterDataObjectsByID(item, itemId)); + + return filtered[0]; + }, + [filterDataObjectsByID] + ); + + const isDataObjectRootParent = useCallback((dataObjects: TestScenarioDataObject[], itemID: string) => { + return dataObjects.map((object) => Object.assign({}, object)).filter((item) => item.id === itemID).length === 1; + }, []); + + const filterTypesItems = useCallback((dataObject, factIdentifierName) => { + return dataObject.name === factIdentifierName; + }, []); + + const filterDataObjectByExpressionElements = useCallback( + (dataObject: TestScenarioDataObject, allExpressionElements: SceSim__expressionElementsType[]) => { + let filtered = true; + for (const expressionElements of allExpressionElements) { + if ( + !expressionElements || + !expressionElements.ExpressionElement || + expressionElements.ExpressionElement.length === 0 + ) { + continue; + } + if (expressionElements.ExpressionElement[0].step.__$$text === dataObject.name) { + filtered = false; + break; + } + } + return filtered; + }, + [] + ); + + const filterDataObjectChildrenByExpressionElements = useCallback( + (dataObject: TestScenarioDataObject, allExpressionElements: SceSim__expressionElementsType[]) => { + if (dataObject.children) { + for (const expressionElements of allExpressionElements) { + if ( + !expressionElements || + !expressionElements.ExpressionElement || + expressionElements.ExpressionElement.length === 0 + ) { + continue; + } + if (expressionElements.ExpressionElement[0].step.__$$text === dataObject.name) { + const selected: TestScenarioDataObject[] = dataObject.children.filter((dataObjectChild) => { + const fieldName = expressionElements.ExpressionElement!.at(-1)!.step.__$$text; + if (dataObject.isSimpleTypeFact) { + fieldName.concat("."); + } + return dataObjectChild.name !== fieldName; + }); + dataObject.children = selected; + } + } + } + }, + [] + ); + + /** 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 assignedExpressionElements = testScenarioDescriptor.factMappings.FactMapping!.map( + (factMapping) => factMapping.expressionElements! + ); + + const filteredDataObjects: TestScenarioDataObject[] = dataObjects + .map((object) => JSON.parse(JSON.stringify(object))) // Deep copy: the Objects may mutate due to children filtering + .filter((dataObject) => !filterDataObjectByExpressionElements(dataObject, [expressionElement])); + filteredDataObjects.forEach((dataObject) => + filterDataObjectChildrenByExpressionElements(dataObject, assignedExpressionElements) + ); + return filteredDataObjects; + }, + [ + dataObjects, + filterDataObjectByExpressionElements, + filterDataObjectChildrenByExpressionElements, + scesimModel.ScenarioSimulationModel, + ] + ); + + const filterItems = useCallback((item, input) => { + if (item.name.toLowerCase().includes(input.toLowerCase())) { + return true; + } + + if (item.children) { + return ( + (item.children = item.children + .map((object: TestScenarioDataObject) => Object.assign({}, object)) + .filter((child: TestScenarioDataObject) => filterItems(child, input))).length > 0 + ); + } + }, []); + + useEffect(() => { + console.debug("========SELECTOR PANEL USE EFFECT==========="); + console.debug("Selected Column:"); + console.debug(selectedColumnMetadata); + console.debug("All Data Objects:"); + console.debug(dataObjects); + + /** + * Case 1: No columns selected OR a column of OTHER type (eg. Description column). + * In such a case, the selector status is disabled with no filtered items and no active TreeViewItems + */ + if (!selectedColumnMetadata || selectedColumnMetadata.factMapping.expressionIdentifier.type?.__$$text == "OTHER") { + setDataSelectorStatus(TestScenarioDataSelectorState.DISABLED); + setFilteredItems(dataObjects); + setTreeViewStatus({ activeItems: [], searchKey: "", isExpanded: false }); + console.debug("Case 1"); + console.debug("=============USE EFFECT END==============="); + return; + } + + /** + * Case 2: A GIVEN / EXPECT column with EMPTY field (2nd level header) is selected. + * In such a case, the selector status is enabled with no active TreeViewItems and a filtered items list which shows: + * - 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) { + const isFactIdentifierAssigned = + selectedColumnMetadata.factMapping.factIdentifier.className!.__$$text !== EMPTY_TYPE; + + let filteredDataObjects: TestScenarioDataObject[] = []; + if (isFactIdentifierAssigned) { + const expressionElement = selectedColumnMetadata.factMapping.expressionElements!; + filteredDataObjects = filterOutAlreadyAssignedDataObjectsAndChildren( + expressionElement, + selectedColumnMetadata.isBackground + ); + } else { + const testScenarioDescriptor = retrieveModelDescriptor( + scesimModel.ScenarioSimulationModel, + selectedColumnMetadata.isBackground + ); + const assignedExpressionElements = testScenarioDescriptor.factMappings.FactMapping!.map( + (factMapping) => factMapping.expressionElements! + ); + + filteredDataObjects = dataObjects + .map((object) => JSON.parse(JSON.stringify(object))) // Deep copy: the Objects may mutate due to children filtering + .filter((dataObject) => filterDataObjectByExpressionElements(dataObject, assignedExpressionElements)); + } + + /** Applying User search key to the filteredDataObjects, if present */ + const isUserFilterPresent = treeViewStatus.searchKey.trim() !== ""; + if (isUserFilterPresent) { + filteredDataObjects = filteredDataObjects.filter((item) => filterItems(item, treeViewStatus.searchKey)); + } + + setDataSelectorStatus(TestScenarioDataSelectorState.ENABLED); + setFilteredItems(filteredDataObjects); + setTreeViewStatus((prev) => { + return { + ...prev, + activeItems: [], + isExpanded: isFactIdentifierAssigned || isUserFilterPresent, + }; + }); + console.debug("Case 2"); + console.debug("Filtered Data Objects:"); + console.debug(filteredDataObjects); + console.debug("=============USE EFFECT END==============="); + return; + } + + /** + * Case 3 (Final): A GIVEN / EXPECT column with a defined INSTANCE and FIELD + * In such a case, the selector enabled the TreeView only the Instance and Field of the selected columns is activated TreeViewItems and shown (filtered): + */ + 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 isSimpleTypeFact = + selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement!.length === 1 && + selectedColumnMetadata.factMapping.className.__$$text !== EMPTY_TYPE; + let fieldID: string; + if (isExpressionType) { + fieldID = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text; + } else if (isSimpleTypeFact) { + fieldID = selectedColumnMetadata.factMapping.expressionElements!.ExpressionElement![0].step.__$$text.concat("."); + } else { + fieldID = selectedColumnMetadata.factMapping + .expressionElements!.ExpressionElement!.map((expressionElement) => expressionElement.step.__$$text) + .join("."); + } + + //TODO 1 This not work with multiple level and expressions fields. + const treeViewItemToActivate = filteredDataObjects + .reduce((acc: TestScenarioDataObject[], item) => { + return item.children ? acc.concat(item.children) : acc; + }, []) + .filter((item) => item.id === fieldID); + + setDataSelectorStatus(TestScenarioDataSelectorState.TREEVIEW_ENABLED_ONLY); + setFilteredItems(filteredDataObjects); + setTreeViewStatus({ activeItems: treeViewItemToActivate, searchKey: "", isExpanded: true }); + console.debug("Case 3"); + console.debug("=============USE EFFECT END==============="); + }, [ + dataObjects, + filterDataObjectByExpressionElements, + filterDataObjectChildrenByExpressionElements, + filterOutAlreadyAssignedDataObjectsAndChildren, + filterItems, + filterTypesItems, + scesimModel, + selectedColumnMetadata, + treeViewStatus.searchKey, + ]); + + const treeViewEmptyStatus = useMemo(() => { + const isTreeViewNotEmpty = dataObjects.length > 0; + const treeViewEmptyIcon = filteredItems.length === 0 ? WarningTriangleIcon : WarningTriangleIcon; + const title = + dataObjects.length === 0 + ? assetType === TestScenarioType[TestScenarioType.DMN] + ? i18n.drawer.dataSelector.emptyDataObjectsTitleDMN + : i18n.drawer.dataSelector.emptyDataObjectsTitleRule + : "No more properties"; //TODO CHANGE + const description = + dataObjects.length === 0 + ? assetType === TestScenarioType[TestScenarioType.DMN] + ? i18n.drawer.dataSelector.emptyDataObjectsDescriptionDMN + : i18n.drawer.dataSelector.emptyDataObjectsDescriptionRule + : "All the properties have been already assigned"; //TODO CHANGE + + { + assetType === TestScenarioType[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]); + + const insertDataObjectButtonStatus = useMemo(() => { + if (!selectedColumnMetadata) { + return { + message: i18n.drawer.dataSelector.insertDataObjectTooltipColumnSelectionMessage, + enabled: false, + }; + } + + const oneActiveTreeViewItem = treeViewStatus.activeItems.length === 1; + if (!oneActiveTreeViewItem) { + return { message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectSelectionMessage, enabled: false }; + } + + const expressionElement = selectedColumnMetadata.factMapping.expressionElements!; + + const filteredDataObjects: TestScenarioDataObject[] = filterOutAlreadyAssignedDataObjectsAndChildren( + expressionElement, + selectedColumnMetadata.isBackground + ); + const isAlreadyAssigined = + filteredDataObjects.length === 1 && + !filteredDataObjects[0].children?.find((child) => child.id === treeViewStatus.activeItems[0].id); + + if (oneActiveTreeViewItem && isAlreadyAssigined) { + return { + message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectAlreadyAssignedMessage, + enabled: false, + }; + } + + return { message: i18n.drawer.dataSelector.insertDataObjectTooltipDataObjectAssignMessage, enabled: true }; + }, [filterOutAlreadyAssignedDataObjectsAndChildren, i18n, selectedColumnMetadata, treeViewStatus.activeItems]); + + const onAllExpandedToggle = useCallback((_event) => { + setAllExpanded((prev) => !prev); + }, []); + + // CHECK + const onInsertDataObjectClick = useCallback( + /** TODO 2 : NEED A POPUP ASKING IF WE WANT TO REPLACE VALUES OR NOT */ + + () => { + 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; + }); + + /** Updating the selectedColumn */ + updateSelectedColumnMetaData({ + factMapping: JSON.parse(JSON.stringify(factMappingToUpdate)), + 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, + }, + }, + }, + }; + }); + }, + [ + dataObjects, + findDataObjectRootParent, + isDataObjectRootParent, + selectedColumnMetadata, + treeViewStatus.activeItems, + updateSelectedColumnMetaData, + updateTestScenarioModel, + ] + ); + + const onClearSelectionClicked = useCallback((_event) => { + setTreeViewStatus((prev) => { + return { + ...prev, + activeItems: [], + }; + }); + }, []); + + const onSearchTreeView = useCallback( + (event) => + setTreeViewStatus((prev) => { + return { + ...prev, + searchKey: event.target.value, + }; + }), + [] + ); + + const onSelectTreeViewItem = useCallback((_event, treeViewItem: TreeViewDataItem) => { + setTreeViewStatus((prev) => { + return { + ...prev, + activeItems: [treeViewItem], + }; + }); + }, []); + + const treeViewSearchToolbar = ( + + + + + + + + ); + + return ( + <> + + {assetType === TestScenarioType[TestScenarioType.DMN] + ? i18n.drawer.dataSelector.descriptionDMN + : i18n.drawer.dataSelector.descriptionRule} + + + + + + + +
+ {treeViewEmptyStatus.enabled ? ( +
+ +
+ ) : ( + + + + + {treeViewEmptyStatus.title} + + {treeViewEmptyStatus.description} + + + )} +
+ +
+ + + + + +
+ + ); +} + +export default TestScenarioDataSelectorPanel; diff --git a/packages/scesim-editor/src/drawer/TestScenarioDrawerPanel.tsx b/packages/scesim-editor/src/drawer/TestScenarioDrawerPanel.tsx index e5fe9babd57..01775a49d7e 100644 --- a/packages/scesim-editor/src/drawer/TestScenarioDrawerPanel.tsx +++ b/packages/scesim-editor/src/drawer/TestScenarioDrawerPanel.tsx @@ -29,12 +29,15 @@ import { } from "@patternfly/react-core/dist/js/components/Drawer"; import { Text, TextContent, TextVariants } from "@patternfly/react-core/dist/js/components/Text"; -import TestScenarioDrawerDataObjectsPanel from "./TestScenarioDrawerDataObjectsPanel"; +import { SceSimModel } from "@kie-tools/scesim-marshaller"; + +import TestScenarioDrawerDataSelectorPanel from "./TestScenarioDrawerDataSelectorPanel"; import TestScenarioDrawerCheatSheetPanel from "./TestScenarioDrawerCheatSheetPanel"; import TestScenarioDrawerSettingsPanel from "../drawer/TestScenarioDrawerSettingsPanel"; import { TestScenarioDataObject, TestScenarioEditorDock, + TestScenarioSelectedColumnMetaData, TestScenarioSettings, TestScenarioType, } from "../TestScenarioEditor"; @@ -45,15 +48,23 @@ function TestScenarioDrawerPanel({ fileName, onDrawerClose, onUpdateSettingField, + scesimModel, + selectedColumnMetaData, selectedDock, testScenarioSettings, + updateSelectedColumnMetaData, + updateTestScenarioModel, }: { dataObjects: TestScenarioDataObject[]; fileName: string; onDrawerClose: () => void; onUpdateSettingField: (field: string, value: boolean | string) => void; + scesimModel: SceSimModel; + selectedColumnMetaData: TestScenarioSelectedColumnMetaData | null; selectedDock: TestScenarioEditorDock; testScenarioSettings: TestScenarioSettings; + updateSelectedColumnMetaData: React.Dispatch>; + updateTestScenarioModel: React.Dispatch>; }) { const { i18n } = useTestScenarioEditorI18n(); @@ -71,8 +82,8 @@ function TestScenarioDrawerPanel({ return i18n.drawer.cheatSheet.title; case TestScenarioEditorDock.DATA_OBJECT: return testScenarioSettings.assetType === TestScenarioType[TestScenarioType.DMN] - ? i18n.drawer.dataObjects.titleDMN - : i18n.drawer.dataObjects.titleRule; + ? i18n.drawer.dataSelector.titleDMN + : i18n.drawer.dataSelector.titleRule; case TestScenarioEditorDock.SETTINGS: return i18n.drawer.settings.title; default: @@ -90,9 +101,13 @@ function TestScenarioDrawerPanel({ return ; case TestScenarioEditorDock.DATA_OBJECT: return ( - ); case TestScenarioEditorDock.SETTINGS: diff --git a/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts b/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts index b4bebb872cc..be68682f4d4 100644 --- a/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts +++ b/packages/scesim-editor/src/i18n/TestScenarioEditorI18n.ts @@ -27,6 +27,25 @@ interface TestScenarioEditorDictionary extends ReferenceDictionary { dmnDataRetrievedFromScesim: string; ruleDataRetrievedFromScesim: string; }; + creationPanel: { + assetsGroup: string; + assetsOption: { + dmn: string; + noChoice: string; + rule: string; + }; + autoFillTable: string; + autoFillTableTooltip: string; + createButton: string; + dmnGroup: string; + dmnNoChoice: string; + kieSessionGroup: string; + kieAgendaGroup: string; + optional: string; + statelessSession: string; + testSkip: string; + title: string; + }; drawer: { cheatSheet: { expression1DMN: string; @@ -57,7 +76,7 @@ interface TestScenarioEditorDictionary extends ReferenceDictionary { paragraph6Rule: string; title: string; }; - dataObjects: { + dataSelector: { clearSelection: string; collapseAll: string; dataObjectsDescriptionDMN: string; @@ -70,6 +89,10 @@ interface TestScenarioEditorDictionary extends ReferenceDictionary { emptyDataObjectsDescriptionRule: string; expandAll: string; insertDataObject: string; + insertDataObjectTooltipColumnSelectionMessage: string; + insertDataObjectTooltipDataObjectSelectionMessage: string; + insertDataObjectTooltipDataObjectAlreadyAssignedMessage: string; + insertDataObjectTooltipDataObjectAssignMessage: string; titleDMN: string; titleRule: string; }; @@ -94,7 +117,7 @@ interface TestScenarioEditorDictionary extends ReferenceDictionary { }; sidebar: { cheatSheetTooltip: string; - dataObjectsTooltip: string; + dataSelectorTooltip: string; settingsTooltip: string; }; tab: { diff --git a/packages/scesim-editor/src/i18n/locales/en.ts b/packages/scesim-editor/src/i18n/locales/en.ts index 3c409cb5c5c..72de8ce02cc 100644 --- a/packages/scesim-editor/src/i18n/locales/en.ts +++ b/packages/scesim-editor/src/i18n/locales/en.ts @@ -32,6 +32,26 @@ export const en: TestScenarioEditorI18n = { ruleDataRetrievedFromScesim: "Impossibile to retrieve the Java Classes from your project, therefore they have been restored from the scesim file. These data might be NOT synchronized. You can view and edit this asset, but dropping a column could lose its related Java Class data.", }, + creationPanel: { + assetsGroup: "Asset type", + assetsOption: { + dmn: "Decision (DMN)", + noChoice: "Select a type", + rule: "Rule (DRL)", + }, + autoFillTable: "Autofill DMN Data", + autoFillTableTooltip: + "If checked, the Test Scenario table will be automatically populated based on the provided DMN data (Input nodes assigned as GIVEN columns and Output nodes assigned as EXPECT colums)", + createButton: "Create", + dmnGroup: "Select DMN", + dmnNoChoice: "Select a DMN file", + kieSessionGroup: "KIE Session", + kieAgendaGroup: "Group", + optional: "Optional", + statelessSession: "Stateless Session", + testSkip: "Skip this file during the test", + title: "Create a new Test Scenario", + }, drawer: { cheatSheet: { paragraph1: "To start off, use contextual menus in the table to insert or edit or delete new columns and rows.", @@ -72,7 +92,7 @@ export const en: TestScenarioEditorI18n = { expression10DMN: "An empty cell is skipped from the evaluation.", title: "Cheatsheet", }, - dataObjects: { + dataSelector: { clearSelection: "Clear selection", collapseAll: "Collapse all", dataObjectsDescriptionDMN: "DMN Nodes are Input or Decision nodes defined in your DMN asset.", @@ -87,6 +107,12 @@ export const en: TestScenarioEditorI18n = { emptyDataObjectsDescriptionRule: "Impossible to retrieve the Java Classes from project.", expandAll: "Expand all", insertDataObject: "Assign", + insertDataObjectTooltipColumnSelectionMessage: + "Please select an column's field header to add or change a Type in the table.", + insertDataObjectTooltipDataObjectSelectionMessage: + "Please select a single field to assign it in the selected column", + insertDataObjectTooltipDataObjectAlreadyAssignedMessage: "The selected Field has been already assigned", + insertDataObjectTooltipDataObjectAssignMessage: "Click here to assign the selected field to the focused column.", titleDMN: "DMN Nodes selector", titleRule: "Java Classes selector", }, @@ -113,7 +139,7 @@ export const en: TestScenarioEditorI18n = { }, sidebar: { cheatSheetTooltip: "CheatSheet: Useful information for Test Scenario Usage", - dataObjectsTooltip: "Selector: It provides a tool to edit your Scenarios", + dataSelectorTooltip: "Selector: It provides a tool to edit your Scenarios", settingsTooltip: "Setting: Properties of this Test Scenario asset", }, tab: { diff --git a/packages/scesim-editor/src/index.ts b/packages/scesim-editor/src/index.ts index ef1dfc8ddb4..ca4e208323c 100644 --- a/packages/scesim-editor/src/index.ts +++ b/packages/scesim-editor/src/index.ts @@ -17,4 +17,5 @@ * under the License. */ +export * from "./resources"; export * from "./TestScenarioEditor"; diff --git a/packages/scesim-editor/src/resources/EmptyScesimFile.ts b/packages/scesim-editor/src/resources/EmptyScesimFile.ts index 32eedb07e7e..242aebd1ed6 100644 --- a/packages/scesim-editor/src/resources/EmptyScesimFile.ts +++ b/packages/scesim-editor/src/resources/EmptyScesimFile.ts @@ -18,7 +18,7 @@ */ export const EMPTY_ONE_EIGHT = ` - + diff --git a/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx b/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx index 2bfd0fc46a8..df9671c6bb9 100644 --- a/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx +++ b/packages/scesim-editor/src/sidebar/TestScenarioSideBarMenu.tsx @@ -50,7 +50,7 @@ function TestScenarioSideBarMenu({ return (
- +