From 74659fd653e29b01de9b7bd16b727144be8c354e Mon Sep 17 00:00:00 2001 From: Jozef Marko Date: Wed, 18 Dec 2024 15:20:31 +0100 Subject: [PATCH] kie-issues#1547: DMN Editor: Render evaluation highlights in the Boxed Expression Editor (#2681) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Luiz João Motta --- .../src/BoxedExpressionEditor.tsx | 3 + .../src/BoxedExpressionEditorContext.tsx | 3 + .../src/api/BeeTable.ts | 2 + .../ConditionalExpression.tsx | 12 +- .../DecisionTableExpression.tsx | 5 + .../src/table/BeeTable/BeeTable.css | 76 ++++++++ .../src/table/BeeTable/BeeTable.tsx | 2 + .../src/table/BeeTable/BeeTableBody.tsx | 32 ++- .../src/table/BeeTable/BeeTableTd.tsx | 22 ++- .../stories/boxedExpressionStoriesWrapper.tsx | 1 + .../Conditional/Conditional.stories.tsx | 184 ++++++++++++++++++ .../DecisionTable/DecisionTable.stories.tsx | 77 ++++++++ 12 files changed, 411 insertions(+), 8 deletions(-) diff --git a/packages/boxed-expression-component/src/BoxedExpressionEditor.tsx b/packages/boxed-expression-component/src/BoxedExpressionEditor.tsx index 920e4110257..de4354ec49a 100644 --- a/packages/boxed-expression-component/src/BoxedExpressionEditor.tsx +++ b/packages/boxed-expression-component/src/BoxedExpressionEditor.tsx @@ -59,6 +59,7 @@ export interface BoxedExpressionEditorProps { isReadOnly?: boolean; /** PMML models available to use on Boxed PMML Function */ pmmlDocuments?: PmmlDocument[]; + evaluationHitsCountById?: Map; /** The containing HTMLElement which is scrollable */ scrollableParentRef: React.RefObject; /** Parsed variables used for syntax coloring and auto-complete */ @@ -79,6 +80,7 @@ export function BoxedExpressionEditor({ isResetSupportedOnRootExpression, scrollableParentRef, pmmlDocuments, + evaluationHitsCountById, onRequestFeelVariables, widthsById, onWidthsChange, @@ -103,6 +105,7 @@ export function BoxedExpressionEditor({ isReadOnly={isReadOnly} dataTypes={dataTypes} pmmlDocuments={pmmlDocuments} + evaluationHitsCountById={evaluationHitsCountById} onRequestFeelVariables={onRequestFeelVariables} widthsById={widthsById} hideDmn14BoxedExpressions={hideDmn14BoxedExpressions} diff --git a/packages/boxed-expression-component/src/BoxedExpressionEditorContext.tsx b/packages/boxed-expression-component/src/BoxedExpressionEditorContext.tsx index ce879f8808c..5bf11e36f4e 100644 --- a/packages/boxed-expression-component/src/BoxedExpressionEditorContext.tsx +++ b/packages/boxed-expression-component/src/BoxedExpressionEditorContext.tsx @@ -34,6 +34,7 @@ export interface BoxedExpressionEditorContextType { pmmlDocuments?: PmmlDocument[]; dataTypes: DmnDataType[]; isReadOnly?: boolean; + evaluationHitsCountById?: Map; // State currentlyOpenContextMenu: string | undefined; @@ -74,6 +75,7 @@ export function BoxedExpressionEditorContextProvider({ beeGwtService, children, pmmlDocuments, + evaluationHitsCountById, scrollableParentRef, onRequestFeelVariables, widthsById, @@ -114,6 +116,7 @@ export function BoxedExpressionEditorContextProvider({ dataTypes, isReadOnly, pmmlDocuments, + evaluationHitsCountById, //state // FIXME: Move to a separate context (https://github.com/apache/incubator-kie-issues/issues/168) currentlyOpenContextMenu, diff --git a/packages/boxed-expression-component/src/api/BeeTable.ts b/packages/boxed-expression-component/src/api/BeeTable.ts index 68c662f4ba2..4a2db5972ca 100644 --- a/packages/boxed-expression-component/src/api/BeeTable.ts +++ b/packages/boxed-expression-component/src/api/BeeTable.ts @@ -97,6 +97,8 @@ export interface BeeTableProps { shouldShowColumnsInlineControls: boolean; resizerStopBehavior: ResizerStopBehavior; lastColumnMinWidth?: number; + /** Method should return true for table rows, that can display evaluation hits count, false otherwise. If not set, BeeTableBody defaults to false. */ + supportsEvaluationHitsCount?: (row: ReactTable.Row) => boolean; } /** Possible status for the visibility of the Table's Header */ diff --git a/packages/boxed-expression-component/src/expressions/ConditionalExpression/ConditionalExpression.tsx b/packages/boxed-expression-component/src/expressions/ConditionalExpression/ConditionalExpression.tsx index ab9871d81e8..ac4923eacce 100644 --- a/packages/boxed-expression-component/src/expressions/ConditionalExpression/ConditionalExpression.tsx +++ b/packages/boxed-expression-component/src/expressions/ConditionalExpression/ConditionalExpression.tsx @@ -226,14 +226,23 @@ export function ConditionalExpression({ [setExpression] ); + const getRowKey = useCallback((row: ReactTable.Row) => { + return row.original.part["@_id"]; + }, []); + + const supportsEvaluationHitsCount = useCallback((row: ReactTable.Row) => { + return row.original.label !== "if"; + }, []); + return ( -
+
isReadOnly={isReadOnly} isEditableHeader={!isReadOnly} resizerStopBehavior={ResizerStopBehavior.SET_WIDTH_WHEN_SMALLER} tableId={id} + getRowKey={getRowKey} headerLevelCountForAppendingRowIndexColumn={1} headerVisibility={headerVisibility} cellComponentByColumnAccessor={cellComponentByColumnAccessor} @@ -247,6 +256,7 @@ export function ConditionalExpression({ shouldRenderRowIndexColumn={false} shouldShowRowsInlineControls={false} shouldShowColumnsInlineControls={false} + supportsEvaluationHitsCount={supportsEvaluationHitsCount} />
diff --git a/packages/boxed-expression-component/src/expressions/DecisionTableExpression/DecisionTableExpression.tsx b/packages/boxed-expression-component/src/expressions/DecisionTableExpression/DecisionTableExpression.tsx index e22e1da0ce0..ec9cf39be4b 100644 --- a/packages/boxed-expression-component/src/expressions/DecisionTableExpression/DecisionTableExpression.tsx +++ b/packages/boxed-expression-component/src/expressions/DecisionTableExpression/DecisionTableExpression.tsx @@ -1045,6 +1045,10 @@ export function DecisionTableExpression({ [beeTableRows.length] ); + const supportsEvaluationHitsCount = useCallback((row: ReactTable.Row) => { + return true; + }, []); + return (
@@ -1073,6 +1077,7 @@ export function DecisionTableExpression({ shouldRenderRowIndexColumn={true} shouldShowRowsInlineControls={true} shouldShowColumnsInlineControls={true} + supportsEvaluationHitsCount={supportsEvaluationHitsCount} // lastColumnMinWidth={lastColumnMinWidth} // FIXME: Check if this is a good strategy or not when doing https://github.com/apache/incubator-kie-issues/issues/181 />
diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.css b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.css index 7f18dc54aab..8a730ce590e 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.css +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.css @@ -203,6 +203,82 @@ border: 0; } +.expression-container-box + .conditional-expression + .table-component + tr.evaluation-hits-count-row-overlay + > td:first-child::before { + content: ""; + position: absolute; + background-color: rgb(215, 201, 255, 0.5); + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.expression-container-box + .conditional-expression + .table-component + tr.evaluation-hits-count-row-overlay + > td + > div + > div + > div + > .logic-type-selected-header::before { + content: ""; + position: absolute; + background-color: rgb(215, 201, 255, 0.5); + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.expression-container-box + .decision-table-expression + .table-component + tr.evaluation-hits-count-row-overlay + > td::before { + content: ""; + position: absolute; + background-color: rgb(215, 201, 255, 0.5); + left: 0; + top: 0; + width: 100%; + height: 100%; +} + +.evaluation-hits-count-badge-non-colored::before { + content: attr(data-evaluation-hits-count); + font-size: 0.8em; + text-align: left; + color: white; + background-color: var(--pf-global--palette--black-600); + position: absolute; + top: 0px; + left: 0px; + height: 40px; + width: 40px; + clip-path: polygon(0% 100%, 100% 0%, 0% 0%); + padding-left: 0.2em; +} + +.evaluation-hits-count-badge-colored::before { + content: attr(data-evaluation-hits-count); + font-size: 0.8em; + text-align: left; + color: white; + background-color: rgb(134, 106, 212); + position: absolute; + top: 0px; + left: 0px; + height: 40px; + width: 40px; + clip-path: polygon(0% 100%, 100% 0%, 0% 0%); + padding-left: 0.2em; +} + .expression-container .table-component table tbody tr td.row-index-cell-column { padding: 1.1em 0; } diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx index 6bd456c9f80..6fcadbf14f3 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTable.tsx @@ -103,6 +103,7 @@ export function BeeTableInternal({ resizerStopBehavior, lastColumnMinWidth, rowWrapper, + supportsEvaluationHitsCount, }: BeeTableProps & { selectionRef?: React.RefObject; }) { @@ -657,6 +658,7 @@ export function BeeTableInternal({ onDataCellKeyUp={onDataCellKeyUp} lastColumnMinWidth={lastColumnMinWidth} isReadOnly={isReadOnly} + supportsEvaluationHitsCount={supportsEvaluationHitsCount} /> { /** Table instance */ @@ -55,6 +56,8 @@ export interface BeeTableBodyProps { rowWrapper?: React.FunctionComponent>; isReadOnly: boolean; + /** See BeeTable.ts */ + supportsEvaluationHitsCount?: (row: ReactTable.Row) => boolean; } export function BeeTableBody({ @@ -72,18 +75,37 @@ export function BeeTableBody({ lastColumnMinWidth, rowWrapper, isReadOnly, + supportsEvaluationHitsCount, }: BeeTableBodyProps) { + const { evaluationHitsCountById } = useBoxedExpressionEditor(); + const renderRow = useCallback( (row: ReactTable.Row, rowIndex: number) => { reactTableInstance.prepareRow(row); + const rowKey = getRowKey(row); + const rowEvaluationHitsCount = evaluationHitsCountById ? evaluationHitsCountById?.get(rowKey) ?? 0 : undefined; + const canDisplayEvaluationHitsCountRowOverlay = + rowEvaluationHitsCount !== undefined && (supportsEvaluationHitsCount?.(row) ?? false); + const rowClassName = `${rowKey}${canDisplayEvaluationHitsCountRowOverlay && rowEvaluationHitsCount > 0 ? " evaluation-hits-count-row-overlay" : ""}`; + + let evaluationHitsCountBadgeColumnIndex = -1; const renderTr = () => ( - + {row.cells.map((cell, cellIndex) => { const columnKey = getColumnKey(reactTableInstance.allColumns[cellIndex]); + const isColumnToRender = + (cell.column.isRowIndexColumn && shouldRenderRowIndexColumn) || !cell.column.isRowIndexColumn; + if (evaluationHitsCountBadgeColumnIndex === -1 && isColumnToRender) { + // We store the index of the first column in the row + // We show evaluation hits count badge in this column + evaluationHitsCountBadgeColumnIndex = cellIndex; + } + const canDisplayEvaluationHitsCountBadge = + canDisplayEvaluationHitsCountRowOverlay && cellIndex === evaluationHitsCountBadgeColumnIndex; return ( - {((cell.column.isRowIndexColumn && shouldRenderRowIndexColumn) || !cell.column.isRowIndexColumn) && ( + {isColumnToRender && ( resizerStopBehavior={resizerStopBehavior} shouldShowRowsInlineControls={shouldShowRowsInlineControls} @@ -104,6 +126,8 @@ export function BeeTableBody({ cellIndex === reactTableInstance.allColumns.length - 1 ? lastColumnMinWidth : undefined } isReadOnly={isReadOnly} + canDisplayEvaluationHitsCountBadge={canDisplayEvaluationHitsCountBadge} + evaluationHitsCount={rowEvaluationHitsCount} /> )} @@ -114,8 +138,6 @@ export function BeeTableBody({ const RowWrapper = rowWrapper; - const rowKey = getRowKey(row); - return ( {RowWrapper ? ( @@ -129,6 +151,8 @@ export function BeeTableBody({ ); }, [ + evaluationHitsCountById, + supportsEvaluationHitsCount, reactTableInstance, rowWrapper, getRowKey, diff --git a/packages/boxed-expression-component/src/table/BeeTable/BeeTableTd.tsx b/packages/boxed-expression-component/src/table/BeeTable/BeeTableTd.tsx index b9a0a71da04..a9ce90b8575 100644 --- a/packages/boxed-expression-component/src/table/BeeTable/BeeTableTd.tsx +++ b/packages/boxed-expression-component/src/table/BeeTable/BeeTableTd.tsx @@ -49,6 +49,10 @@ export interface BeeTableTdProps { onDataCellClick?: (columnID: string) => void; onDataCellKeyUp?: (columnID: string) => void; isReadOnly: boolean; + /** True means the table cell can display evaluation hits count. False means evaluation hits count is not displayed in the table cell. */ + canDisplayEvaluationHitsCountBadge?: boolean; + /** Actuall evaluation hits count number that will be displayed in the table cell if 'canDisplayEvaluationHitsCountBadge' is set to true. */ + evaluationHitsCount?: number; } export type HoverInfo = @@ -73,6 +77,8 @@ export function BeeTableTd({ onDataCellClick, onDataCellKeyUp, isReadOnly, + canDisplayEvaluationHitsCountBadge, + evaluationHitsCount, }: BeeTableTdProps) { const [isResizing, setResizing] = useState(false); const [hoverInfo, setHoverInfo] = useState({ isHovered: false }); @@ -225,6 +231,14 @@ export function BeeTableTd({ return onDataCellKeyUp?.(column.id); }, [column.id, onDataCellKeyUp]); + const evaluationHitsCountBadgeClassName = useMemo(() => { + return canDisplayEvaluationHitsCountBadge + ? (evaluationHitsCount ?? 0) > 0 + ? "evaluation-hits-count-badge-colored" + : "evaluation-hits-count-badge-non-colored" + : ""; + }, [canDisplayEvaluationHitsCountBadge, evaluationHitsCount]); + return ( ({ }} > {column.isRowIndexColumn ? ( - <>{rowIndexLabel} +
+ {rowIndexLabel} +
) : ( - <> +
{tdContent} {!isReadOnly && shouldRenderResizer && ( @@ -262,7 +278,7 @@ export function BeeTableTd({ setResizing={setResizing} /> )} - +
)} {!isReadOnly && diff --git a/packages/boxed-expression-component/stories/boxedExpressionStoriesWrapper.tsx b/packages/boxed-expression-component/stories/boxedExpressionStoriesWrapper.tsx index 92276209843..20c1cea9f8d 100644 --- a/packages/boxed-expression-component/stories/boxedExpressionStoriesWrapper.tsx +++ b/packages/boxed-expression-component/stories/boxedExpressionStoriesWrapper.tsx @@ -175,6 +175,7 @@ export function BoxedExpressionEditorStory(props?: Partial + BoxedExpressionEditorStory({ + evaluationHitsCountById: new Map([ + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B7", 70], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B8", 30], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B9", 40], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B0", 50], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B1", 20], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B2", 70], + ["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B3", 20], + ]), + }), + parameters: { exclude: ["dataTypes", "beeGwtService", "pmmlDocuments"] }, + args: { + ...EmptyExpression.args, + expression: { + __$$element: "conditional", + "@_id": generateUuid(), + "@_label": "Expression Name", + if: { + "@_id": generateUuid(), + expression: { + __$$element: "literalExpression", + "@_id": generateUuid(), + }, + }, + then: { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B7", + expression: { + __$$element: "decisionTable", + "@_id": generateUuid(), + "@_typeRef": "Any", + "@_hitPolicy": "UNIQUE", + input: [ + { + "@_id": generateUuid(), + inputExpression: { + "@_id": generateUuid(), + text: { __$$text: "input-1" }, + }, + }, + { + "@_id": generateUuid(), + inputExpression: { + "@_id": generateUuid(), + text: { __$$text: "input-2" }, + }, + }, + ], + output: [ + { + "@_id": generateUuid(), + "@_label": "output-1", + }, + { + "@_id": generateUuid(), + "@_label": "output-2", + }, + { + "@_id": generateUuid(), + "@_label": "output-3", + }, + ], + annotation: [ + { + "@_name": "Annotations", + }, + ], + rule: [ + { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B8", + inputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "E" } }, + { "@_id": generateUuid(), text: { __$$text: "E" } }, + ], + outputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + ], + annotationEntry: [{ text: { __$$text: "// Your annotations here" } }], + }, + { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B9", + inputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "E" } }, + { "@_id": generateUuid(), text: { __$$text: "E" } }, + ], + outputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + ], + annotationEntry: [{ text: { __$$text: "// Your annotations here" } }], + }, + ], + }, + }, + else: { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B2", + expression: { + __$$element: "conditional", + "@_id": generateUuid(), + "@_label": "Expression Name", + if: { + "@_id": generateUuid(), + expression: { + __$$element: "literalExpression", + "@_id": generateUuid(), + }, + }, + then: { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B0", + expression: { + __$$element: "literalExpression", + "@_id": generateUuid(), + }, + }, + else: { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B3", + expression: { + __$$element: "decisionTable", + "@_id": generateUuid(), + "@_typeRef": "Any", + "@_hitPolicy": "UNIQUE", + input: [ + { + "@_id": generateUuid(), + inputExpression: { + "@_id": generateUuid(), + text: { __$$text: "input-1" }, + }, + }, + { + "@_id": generateUuid(), + inputExpression: { + "@_id": generateUuid(), + text: { __$$text: "input-2" }, + }, + }, + ], + output: [ + { + "@_id": generateUuid(), + "@_label": "output-1", + }, + { + "@_id": generateUuid(), + "@_label": "output-2", + }, + { + "@_id": generateUuid(), + "@_label": "output-3", + }, + ], + annotation: [ + { + "@_name": "Annotations", + }, + ], + rule: [ + { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B1", + inputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "E" } }, + { "@_id": generateUuid(), text: { __$$text: "E" } }, + ], + outputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + ], + annotationEntry: [{ text: { __$$text: "// Your annotations here" } }], + }, + ], + }, + }, + }, + }, + }, + }, +}; + export const MonthlyFee: Story = { render: (args) => BoxedExpressionEditorStory(), parameters: { exclude: ["dataTypes", "beeGwtService", "pmmlDocuments"] }, diff --git a/packages/boxed-expression-component/stories/boxedExpressions/DecisionTable/DecisionTable.stories.tsx b/packages/boxed-expression-component/stories/boxedExpressions/DecisionTable/DecisionTable.stories.tsx index 0a8d78dfc98..859819df758 100644 --- a/packages/boxed-expression-component/stories/boxedExpressions/DecisionTable/DecisionTable.stories.tsx +++ b/packages/boxed-expression-component/stories/boxedExpressions/DecisionTable/DecisionTable.stories.tsx @@ -103,6 +103,83 @@ export const Base: Story = { }, }; +export const EvaluationHits: Story = { + render: (args) => + BoxedExpressionEditorStory({ + evaluationHitsCountById: new Map([["_1FA12B9F-288C-42E8-B77F-BE2D3702B7B8", 30]]), + }), + parameters: { exclude: ["dataTypes", "beeGwtService", "pmmlDocuments"] }, + args: { + ...EmptyExpression.args, + expression: { + __$$element: "decisionTable", + "@_id": "_92929AE6-3BB5-4217-B66E-07614680971D", + "@_label": "Expression Name", + "@_hitPolicy": "UNIQUE", + input: [ + { + "@_id": generateUuid(), + inputExpression: { + "@_id": generateUuid(), + text: { __$$text: "input-1" }, + "@_typeRef": undefined, + }, + }, + ], + output: [ + { + "@_id": generateUuid(), + "@_label": "output-1", + "@_typeRef": undefined, + }, + ], + annotation: [ + { + "@_name": "Annotations", + }, + ], + rule: [ + { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B8", + inputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "E" } }, + { "@_id": generateUuid(), text: { __$$text: "E" } }, + ], + outputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + ], + annotationEntry: [{ text: { __$$text: "// Your annotations here" } }], + }, + { + "@_id": "_1FA12B9F-288C-42E8-B77F-BE2D3702B7B4", + inputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "E" } }, + { "@_id": generateUuid(), text: { __$$text: "E" } }, + ], + outputEntry: [ + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + { "@_id": generateUuid(), text: { __$$text: "aaa" } }, + ], + annotationEntry: [{ text: { __$$text: "// Your annotations here" } }], + }, + ], + }, + widthsById: { + "_92929AE6-3BB5-4217-B66E-07614680971D": [ + BEE_TABLE_ROW_INDEX_COLUMN_WIDTH, + DECISION_TABLE_INPUT_DEFAULT_WIDTH, + DECISION_TABLE_OUTPUT_DEFAULT_WIDTH, + DECISION_TABLE_ANNOTATION_DEFAULT_WIDTH, + ], + }, + + isResetSupportedOnRootExpression: true, + }, +}; + export const Readonly: Story = { render: (args) => BoxedExpressionEditorStory(), parameters: { exclude: ["dataTypes", "beeGwtService", "pmmlDocuments"] },