From c4043b1f54687f4dad59fbb09198a119c96a9b1b Mon Sep 17 00:00:00 2001 From: Jozef Marko Date: Wed, 22 Jan 2025 12:11:25 +0100 Subject: [PATCH 1/2] kie-issues#1773: DMN Editor: Render badges with evaluation status on Decision nodes Closes: https://github.com/apache/incubator-kie-issues/issues/1773 --- packages/dmn-editor/src/DmnEditor.css | 33 +++++++++++++++++ .../dmn-editor/src/diagram/nodes/Nodes.tsx | 7 +++- .../EmptyWithAvailableExternalModels.mdx | 35 +++++++++---------- 3 files changed, 55 insertions(+), 20 deletions(-) diff --git a/packages/dmn-editor/src/DmnEditor.css b/packages/dmn-editor/src/DmnEditor.css index 5c6ec0e2821..9c19b7e0f9d 100644 --- a/packages/dmn-editor/src/DmnEditor.css +++ b/packages/dmn-editor/src/DmnEditor.css @@ -104,6 +104,39 @@ } /* (end) nodes */ +/* (begin) decision node evaluation status */ +.kie-dmn-editor--decision-node--evaluation-status-failure::before { + content: " \2716"; + 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; +} + +.kie-dmn-editor--decision-node--evaluation-status-success::before { + content: " \2714"; + 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; +} + +/* (end) decision node evaluation status */ + /* (begin) decisionService and group nodes */ /* DECISION SERVICES AND GROUPS HAVE A SPECIFIC SELECTION MECHANISM TO ALLOW EDITING EDGES INSIDE THEM */ .kie-dmn-editor--node-decisionService-visibleRect { diff --git a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx index 51c673e6388..178a14913c1 100644 --- a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx +++ b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx @@ -402,6 +402,11 @@ export const DecisionNode = React.memo( ); }); + const isEvaluationHighlightsEnabled = useDmnEditorStore((s) => s.diagram.overlays.enableEvaluationHighlights); + const evaluationStatusClassName = isEvaluationHighlightsEnabled + ? "kie-dmn-editor--decision-node--evaluation-status-success" + : ""; + return ( <> @@ -421,7 +426,7 @@ export const DecisionNode = React.memo(
Date: Wed, 22 Jan 2025 17:34:14 +0100 Subject: [PATCH 2/2] EvaluationStatus story --- packages/dmn-editor/src/DmnEditor.tsx | 4 + packages/dmn-editor/src/DmnEditorContext.tsx | 4 + .../dmn-editor/src/diagram/nodes/Nodes.tsx | 9 +- .../stories/dmnEditorStoriesWrapper.tsx | 1 + .../evaluationStatus/EvaluationStatus.mdx | 25 + .../EvaluationStatus.stories.tsx | 854 ++++++++++++++++++ 6 files changed, 894 insertions(+), 3 deletions(-) create mode 100644 packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.mdx create mode 100644 packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.stories.tsx diff --git a/packages/dmn-editor/src/DmnEditor.tsx b/packages/dmn-editor/src/DmnEditor.tsx index 3a1b18aa288..25d6433c6bc 100644 --- a/packages/dmn-editor/src/DmnEditor.tsx +++ b/packages/dmn-editor/src/DmnEditor.tsx @@ -169,6 +169,10 @@ export type DmnEditorProps = { * Notifies the caller when the DMN Editor performs a new edit after the debounce time. */ onModelDebounceStateChanged?: (changed: boolean) => void; + /** + * Its a map telling if given key (decisionId) was evaluated with value ("success" or "failure") + */ + evaluationStatus?: Map; }; export const DmnEditorInternal = ({ diff --git a/packages/dmn-editor/src/DmnEditorContext.tsx b/packages/dmn-editor/src/DmnEditorContext.tsx index 21c8e4bf4bc..0137caf2068 100644 --- a/packages/dmn-editor/src/DmnEditorContext.tsx +++ b/packages/dmn-editor/src/DmnEditorContext.tsx @@ -30,6 +30,7 @@ export type DmnEditorContextProviderProps = Pick< | "model" | "onRequestToJumpToPath" | "onRequestToResolvePath" + | "evaluationStatus" >; export type DmnModelBeforeEditing = DmnLatestModel; @@ -41,6 +42,7 @@ export type DmnEditorContextType = Pick< | "issueTrackerHref" | "onRequestToJumpToPath" | "onRequestToResolvePath" + | "evaluationStatus" > & { dmnModelBeforeEditingRef: React.MutableRefObject; dmnEditorRootElementRef: React.RefObject; @@ -65,6 +67,7 @@ export function DmnEditorContextProvider(props: React.PropsWithChildren{props.children}; diff --git a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx index 178a14913c1..1e12d956909 100644 --- a/packages/dmn-editor/src/diagram/nodes/Nodes.tsx +++ b/packages/dmn-editor/src/diagram/nodes/Nodes.tsx @@ -73,6 +73,7 @@ import { propsHaveSameValuesDeep } from "../memoization/memoization"; import { useExternalModels } from "../../includedModels/DmnEditorDependenciesContext"; import { NODE_LAYERS } from "../../store/computed/computeDiagramData"; import { useSettings } from "../../settings/DmnEditorSettingsContext"; +import { useDmnEditor } from "../../DmnEditorContext"; export type ElementFilter = E extends any ? E["__$$element"] extends Filter @@ -403,9 +404,11 @@ export const DecisionNode = React.memo( }); const isEvaluationHighlightsEnabled = useDmnEditorStore((s) => s.diagram.overlays.enableEvaluationHighlights); - const evaluationStatusClassName = isEvaluationHighlightsEnabled - ? "kie-dmn-editor--decision-node--evaluation-status-success" - : ""; + const { evaluationStatus } = useDmnEditor(); + const evaluationStatusClassName = + isEvaluationHighlightsEnabled && evaluationStatus!.has(decision["@_id"]) + ? `kie-dmn-editor--decision-node--evaluation-status-${evaluationStatus!.get(decision["@_id"])}` + : ""; return ( <> diff --git a/packages/dmn-editor/stories/dmnEditorStoriesWrapper.tsx b/packages/dmn-editor/stories/dmnEditorStoriesWrapper.tsx index 25191c97e9e..54075deb252 100644 --- a/packages/dmn-editor/stories/dmnEditorStoriesWrapper.tsx +++ b/packages/dmn-editor/stories/dmnEditorStoriesWrapper.tsx @@ -109,6 +109,7 @@ export function DmnEditorWrapper(props?: Partial) { issueTrackerHref={props?.issueTrackerHref ?? args.issueTrackerHref} onRequestToJumpToPath={props?.onRequestToJumpToPath ?? args.onRequestToJumpToPath} onModelDebounceStateChanged={onModelDebounceStateChanged} + evaluationStatus={props?.evaluationStatus || args.evaluationStatus} />
diff --git a/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.mdx b/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.mdx new file mode 100644 index 00000000000..c636e53be1d --- /dev/null +++ b/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.mdx @@ -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. */} + +import EvaluationStatus from "./EvaluationStatus.stories"; +import { Meta } from "@storybook/blocks"; + + + +## Evaluation Status Demo + +Please use `Enable evaluation highlights` toggle to activate and deactivate the evaluation highlight feature. In this story you can see the evaluation status directly on the Decision node shape. diff --git a/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.stories.tsx b/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.stories.tsx new file mode 100644 index 00000000000..96064b4f473 --- /dev/null +++ b/packages/dmn-editor/stories/misc/evaluationStatus/EvaluationStatus.stories.tsx @@ -0,0 +1,854 @@ +/* + * 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, useMemo, useState } from "react"; +import type { Meta, StoryObj } from "@storybook/react"; +import { DmnLatestModel, DmnMarshaller, getMarshaller } from "@kie-tools/dmn-marshaller"; +import { ns as dmn15ns } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/ts-gen/meta"; +import { generateUuid } from "@kie-tools/boxed-expression-component/dist/api"; +import { DMN15_SPEC } from "@kie-tools/dmn-marshaller/dist/schemas/dmn-1_5/Dmn15Spec"; +import { DmnEditor, DmnEditorProps, OnDmnModelChange } from "@kie-tools/dmn-editor/dist/DmnEditor"; +import { normalize, Normalized } from "@kie-tools/dmn-marshaller/dist/normalization/normalize"; + +import { DmnEditorWrapper } from "../../dmnEditorStoriesWrapper"; + +const initialModel = ` + + + + + 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 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; + +function EvaluationStatus(args: DmnEditorProps) { + const [state, setState] = useState<{ + marshaller: DmnMarshaller; + stack: Normalized[]; + pointer: number; + }>(() => { + const initialDmnMarshaller = getMarshaller(initialModel, { upgradeTo: "latest" }); + return { + marshaller: initialDmnMarshaller, + stack: [normalize(initialDmnMarshaller.parser.parse())], + pointer: 0, + }; + }); + + const currentModel = state.stack[state.pointer]; + + const onModelChange = useCallback((model) => { + setState((prev) => { + const newStack = prev.stack.slice(0, prev.pointer + 1); + return { + ...prev, + stack: [...newStack, model], + pointer: newStack.length, + }; + }); + }, []); + + return ( + <> + {DmnEditorWrapper({ + model: currentModel, + originalVersion: args.originalVersion, + onModelChange, + externalContextName: args.externalContextName, + externalContextDescription: args.externalContextDescription, + validationMessages: args.validationMessages, + evaluationResults: args.evaluationResults, + issueTrackerHref: args.issueTrackerHref, + evaluationStatus: args.evaluationStatus, + })} + + ); +} + +const meta: Meta = { + title: "Misc/EvaluationStatus", + component: DmnEditor, + includeStories: /^[A-Z]/, +}; + +export default meta; +type Story = StoryObj; + +export const EvaluationStatusStory: Story = { + render: (args) => EvaluationStatus(args), + args: { + model: getMarshaller(initialModel, { upgradeTo: "latest" }).parser.parse(), + originalVersion: "1.5", + evaluationResults: {}, + externalContextDescription: "External context description", + externalContextName: "Storybook - DMN Editor", + externalModelsByNamespace: {}, + issueTrackerHref: "", + validationMessages: {}, + evaluationStatus: new Map([ + ["_F0DC8923-5FC7-4200-8BD1-461D5F3714BE", "success"], + ["_D6F4234F-15B3-4F5B-B814-5F6FF29D2907", "failure"], + ]), + }, +};