From 0cf4dce5cc432c665b9ab1ebaae9db522928f67f Mon Sep 17 00:00:00 2001 From: sb-cecilialiu Date: Tue, 17 Dec 2024 06:28:26 -0600 Subject: [PATCH 01/10] MAT-7795 set up discard and delete dialog --- package.json | 2 +- src/CqlBuilderPanel/CqlBuilderPanel.tsx | 1 + .../functionsSection/FunctionsSection.tsx | 3 + .../functionsSection/functions/Functions.tsx | 59 ++++++++++++++++++- 4 files changed, 62 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index 61c9378d..b2ba8842 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ }, "dependencies": { "@madie/cql-antlr-parser": "^1.0.8", - "@madie/madie-design-system": "^1.2.37", + "@madie/madie-design-system": "^1.2.39", "@material-ui/core": "^4.12.4", "@mui/icons-material": "^5.5.1", "@mui/lab": "^5.0.0-alpha.73", diff --git a/src/CqlBuilderPanel/CqlBuilderPanel.tsx b/src/CqlBuilderPanel/CqlBuilderPanel.tsx index 227dfca0..fda312e0 100644 --- a/src/CqlBuilderPanel/CqlBuilderPanel.tsx +++ b/src/CqlBuilderPanel/CqlBuilderPanel.tsx @@ -247,6 +247,7 @@ export default function CqlBuilderPanel({ loading={loading} cql={measureStoreCql} isCQLUnchanged={isCQLUnchanged} + resetCql={resetCql} /> )} diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx index 995a428b..6aca4988 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx @@ -18,6 +18,7 @@ export interface FunctionProps { cql: string; isCQLUnchanged: boolean; functions?: FunctionLookup[]; + resetCql: Function; } const getArgumentNames = (logic: string) => { const args = logic.substring(logic.indexOf("(") + 1, logic.indexOf(")")); @@ -31,6 +32,7 @@ export default function FunctionsSection({ cql, isCQLUnchanged, cqlBuilderLookupsTypes, + resetCql, }: FunctionProps) { const [activeTab, setActiveTab] = useState("function"); @@ -94,6 +96,7 @@ export default function FunctionsSection({ functions={functionLookups} isCQLUnchanged={isCQLUnchanged} cql={cql} + resetCql={resetCql} /> )} diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index fa1fc66f..02648097 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -15,7 +15,10 @@ import ToolTippedIcon from "../../../toolTippedIcon/ToolTippedIcon"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import BorderColorOutlinedIcon from "@mui/icons-material/BorderColorOutlined"; import Skeleton from "@mui/material/Skeleton"; -import { Pagination } from "@madie/madie-design-system/dist/react"; +import { + Pagination, + MadieConfirmDialog, +} from "@madie/madie-design-system/dist/react"; import Tooltip from "@mui/material/Tooltip"; const TH = tw.th`p-3 text-left text-sm font-bold capitalize`; @@ -39,6 +42,7 @@ const Functions = ({ loading, functions, isCQLUnchanged, + resetCql, }: FunctionProps) => { const [totalPages, setTotalPages] = useState(0); const [totalItems, setTotalItems] = useState(0); @@ -46,6 +50,12 @@ const Functions = ({ const [visibleFunctions, setVisibleFunctions] = useState( [] ); + const [selectedFunction, setSelectedFunction] = useState(); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [discardDialog, setDiscardDialog] = useState({ + open: false, + operation: null, + }); const [offset, setOffset] = useState(0); const [currentLimit, setCurrentLimit] = useState(5); @@ -134,7 +144,14 @@ const Functions = ({ "data-testid": `delete-button-${row.cell.row.id}`, "aria-label": `delete-button-${row.cell.row.id}`, size: "small", - onClick: (e) => {}, + onClick: (e) => { + setSelectedFunction(row.row.original.name); + if (!isCQLUnchanged) { + setDiscardDialog({ open: true, operation: "delete" }); + } else { + setDeleteDialogOpen(true); + } + }, }} > @@ -262,6 +279,44 @@ const Functions = ({ hidePrevButton={!canGoPrev} /> + { + resetCql(); + if (discardDialog?.operation === "delete") { + setDiscardDialog({ + open: false, + operation: "delete", + }); + setDeleteDialogOpen(true); + } + }} + onClose={() => { + setDiscardDialog({ + open: false, + operation: null, + }); + }} + /> + { + //handleDefinitionDelete(selectedDefinition); + setDeleteDialogOpen(false); + }} + onClose={() => setDeleteDialogOpen(false)} + action="delete" + dialogTitle="Are you sure?" + name={"delete this Function"} + warning={"This action cannot be undone!"} + continueText="Yes, Delete" + /> ); }; From f0e0a133edeb513c3338ece8bad2d70d07883ef5 Mon Sep 17 00:00:00 2001 From: sb-cecilialiu Date: Tue, 17 Dec 2024 08:11:32 -0600 Subject: [PATCH 02/10] MAT-7795 add handleFunctionDelete --- src/AceEditor/madie-ace-editor.tsx | 2 ++ src/CqlBuilderPanel/CqlBuilderPanel.tsx | 2 ++ .../functionsSection/FunctionsSection.tsx | 3 +++ .../functionsSection/functions/Functions.tsx | 13 +++++++++++-- .../CqlEditorWithTerminology.tsx | 2 ++ 5 files changed, 20 insertions(+), 2 deletions(-) diff --git a/src/AceEditor/madie-ace-editor.tsx b/src/AceEditor/madie-ace-editor.tsx index d375fe6a..78f75a39 100644 --- a/src/AceEditor/madie-ace-editor.tsx +++ b/src/AceEditor/madie-ace-editor.tsx @@ -26,6 +26,7 @@ import { Definition } from "../CqlBuilderPanel/definitionsSection/definitionBuil import { SelectedLibrary } from "../CqlBuilderPanel/Includes/CqlLibraryDetailsDialog"; import { Funct } from "../CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder"; import CqlVersion from "@madie/cql-antlr-parser/dist/src/dto/CqlVersion"; +import { FunctionLookup } from "../model/CqlBuilderLookup"; export interface EditorPropsType { value: string; @@ -47,6 +48,7 @@ export interface EditorPropsType { ) => void; handleDeleteLibrary?: (lib: SelectedLibrary) => void; handleApplyFunction?: (funct: Funct) => void; + handleFunctionDelete?: (funct: FunctionLookup) => void; parseDebounceTime?: number; inboundAnnotations?: Ace.Annotation[]; inboundErrorMarkers?: Ace.MarkerLike[]; diff --git a/src/CqlBuilderPanel/CqlBuilderPanel.tsx b/src/CqlBuilderPanel/CqlBuilderPanel.tsx index fda312e0..2afe8898 100644 --- a/src/CqlBuilderPanel/CqlBuilderPanel.tsx +++ b/src/CqlBuilderPanel/CqlBuilderPanel.tsx @@ -37,6 +37,7 @@ export default function CqlBuilderPanel({ handleDefinitionEdit, handleDefinitionDelete, handleApplyFunction, + handleFunctionDelete, resetCql, getCqlDefinitionReturnTypes, makeExpanded, @@ -248,6 +249,7 @@ export default function CqlBuilderPanel({ cql={measureStoreCql} isCQLUnchanged={isCQLUnchanged} resetCql={resetCql} + handleFunctionDelete={handleFunctionDelete} /> )} diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx index 6aca4988..5fe1e87f 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx @@ -19,6 +19,7 @@ export interface FunctionProps { isCQLUnchanged: boolean; functions?: FunctionLookup[]; resetCql: Function; + handleFunctionDelete?: Function; } const getArgumentNames = (logic: string) => { const args = logic.substring(logic.indexOf("(") + 1, logic.indexOf(")")); @@ -33,6 +34,7 @@ export default function FunctionsSection({ isCQLUnchanged, cqlBuilderLookupsTypes, resetCql, + handleFunctionDelete, }: FunctionProps) { const [activeTab, setActiveTab] = useState("function"); @@ -97,6 +99,7 @@ export default function FunctionsSection({ isCQLUnchanged={isCQLUnchanged} cql={cql} resetCql={resetCql} + handleFunctionDelete={handleFunctionDelete} /> )} diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index 02648097..f757daa2 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -43,6 +43,7 @@ const Functions = ({ functions, isCQLUnchanged, resetCql, + handleFunctionDelete, }: FunctionProps) => { const [totalPages, setTotalPages] = useState(0); const [totalItems, setTotalItems] = useState(0); @@ -145,7 +146,15 @@ const Functions = ({ "aria-label": `delete-button-${row.cell.row.id}`, size: "small", onClick: (e) => { - setSelectedFunction(row.row.original.name); + const tableData = table.getRow(row.cell.row.id).original; + const functionToDelete = { + functionName: tableData.name, + comment: tableData.comment, + functionsArguments: tableData.arguments, + fluentFunction: tableData.isFluent, + expressionValue: tableData.logic, + }; + setSelectedFunction(tableData); if (!isCQLUnchanged) { setDiscardDialog({ open: true, operation: "delete" }); } else { @@ -307,7 +316,7 @@ const Functions = ({ { - //handleDefinitionDelete(selectedDefinition); + handleFunctionDelete(selectedFunction); setDeleteDialogOpen(false); }} onClose={() => setDeleteDialogOpen(false)} diff --git a/src/cqlEditorWithTerminology/CqlEditorWithTerminology.tsx b/src/cqlEditorWithTerminology/CqlEditorWithTerminology.tsx index 136ea762..99d1bdc4 100644 --- a/src/cqlEditorWithTerminology/CqlEditorWithTerminology.tsx +++ b/src/cqlEditorWithTerminology/CqlEditorWithTerminology.tsx @@ -24,6 +24,7 @@ const CqlEditorWithTerminology = ({ handleDeleteLibrary, handleDefinitionEdit, handleApplyFunction, + handleFunctionDelete, height, parseDebounceTime = 1500, inboundAnnotations, @@ -123,6 +124,7 @@ const CqlEditorWithTerminology = ({ handleEditLibrary={handleEditLibrary} handleDeleteLibrary={handleDeleteLibrary} handleApplyFunction={handleApplyFunction} + handleFunctionDelete={handleFunctionDelete} resetCql={resetCql} getCqlDefinitionReturnTypes={getCqlDefinitionReturnTypes} /> From 679764399c00faf76e59b034e99eae5ee5f49750 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 17 Dec 2024 09:48:13 -0800 Subject: [PATCH 03/10] MAT-7796: Edit dialog layout --- src/CqlBuilderPanel/CqlBuilderPanel.tsx | 4 +- .../EditFunctionDialog.test.tsx | 28 ++++++ .../functionsSection/EditFunctionDialog.tsx | 89 +++++++++++++++++++ .../FunctionsSection.test.tsx | 34 +++++++ .../functionsSection/FunctionsSection.tsx | 15 ++-- .../functionBuilder/FunctionBuilder.test.tsx | 12 +++ .../functionBuilder/FunctionBuilder.tsx | 69 ++++++++++---- .../functionsSection/functions/Functions.tsx | 69 ++++++++++++-- src/common/UseToast.tsx | 24 +++++ 9 files changed, 316 insertions(+), 28 deletions(-) create mode 100644 src/CqlBuilderPanel/functionsSection/EditFunctionDialog.test.tsx create mode 100644 src/CqlBuilderPanel/functionsSection/EditFunctionDialog.tsx create mode 100644 src/common/UseToast.tsx diff --git a/src/CqlBuilderPanel/CqlBuilderPanel.tsx b/src/CqlBuilderPanel/CqlBuilderPanel.tsx index 227dfca0..45d7391c 100644 --- a/src/CqlBuilderPanel/CqlBuilderPanel.tsx +++ b/src/CqlBuilderPanel/CqlBuilderPanel.tsx @@ -4,6 +4,7 @@ import ValueSetsSection from "./ValueSets/ValueSets"; import CodesSection from "./codesSection/CodesSection"; import DefinitionsSection from "./definitionsSection/DefinitionsSection"; import FunctionsSection from "./functionsSection/FunctionsSection"; +// import FunctionsSection from "" import { useFeatureFlags } from "@madie/madie-util"; import IncludesTabSection from "./Includes/Includes"; import Parameters from "./Parameters/Parameters"; @@ -51,7 +52,7 @@ export default function CqlBuilderPanel({ return "includes"; })(); - const [activeTab, setActiveTab] = useState(getStartingPage); + const [activeTab, setActiveTab] = useState("functions"); const [cqlBuilderLookupsTypes, setCqlBuilderLookupsTypes] = useState(); const [errors, setErrors] = useState(null); @@ -247,6 +248,7 @@ export default function CqlBuilderPanel({ loading={loading} cql={measureStoreCql} isCQLUnchanged={isCQLUnchanged} + resetCql={resetCql} /> )} diff --git a/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.test.tsx b/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.test.tsx new file mode 100644 index 00000000..1f206713 --- /dev/null +++ b/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.test.tsx @@ -0,0 +1,28 @@ +import * as React from "react"; +import { parseArgumentsFromLogicString } from "./EditFunctionDialog"; + +describe("parseArgumentsFromLogicString", () => { + test("Can parse out empty arguments", () => { + const logicString = `define function "Empty Arguments" (): true`; + const result = parseArgumentsFromLogicString(logicString); + expect(result).toEqual([]); + }); + test("Can parse out multiple arguments", () => { + const logicString2 = `define function "Function name here" (arg1 "Integer", arg2 "Integer", arg3 "Date"):\n true`; + const result = parseArgumentsFromLogicString(logicString2); + + expect(result).toEqual([ + { argumentName: "arg1", dataType: "Integer" }, + { argumentName: "arg2", dataType: "Integer" }, + { argumentName: "arg3", dataType: "Date" }, + ]); + }); + test("Can parse out arguments with commas embedded in dataType", () => { + const logicString3 = `define fluent function "Numerator Observation"(Encounter "Encounter, Performed" ): + duration in hours of Encounter.relevantPeriod`; + const result = parseArgumentsFromLogicString(logicString3); + expect(result).toEqual([ + { argumentName: "Encounter", dataType: "Encounter, Performed" }, + ]); + }); +}); diff --git a/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.tsx b/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.tsx new file mode 100644 index 00000000..60185f53 --- /dev/null +++ b/src/CqlBuilderPanel/functionsSection/EditFunctionDialog.tsx @@ -0,0 +1,89 @@ +import React from "react"; +import { MadieDialog } from "@madie/madie-design-system/dist/react"; +import FunctionBuilder from "./functionBuilder/FunctionBuilder"; + +interface PropTypes { + open: boolean; + onClose: () => void; + cqlBuilderLookupsTypes?: any; + funct?: any; + setEditFunctionDialogOpen: Function; + handleApplyFunction: Function; + handleFunctionEdit: Function; +} + +export const parseArgumentsFromLogicString = (logicString) => { + // `s` flag for multiline content + const argumentListRegex = /\(([^)]*)\)/s; + const match = logicString.match(argumentListRegex); + + // Nobody in parenthesis + if (!match) { + return []; + } + + const argumentsString = match[1].trim(); + // no args + if (!argumentsString) { + return []; + } + + // Regex to match argument and data type pairs + const argumentRegex = /([\w]+)\s+"([^"]+)"/g; + const results = []; + + let argumentMatch; + while ((argumentMatch = argumentRegex.exec(argumentsString)) !== null) { + const [, argumentName, dataType] = argumentMatch; + results.push({ argumentName, dataType }); + } + return results; +}; + +const EditFunctionDialog = ({ + funct, + handleFunctionEdit, + onClose, + open, + setEditFunctionDialogOpen, + cqlBuilderLookupsTypes, + handleApplyFunction, +}: PropTypes) => { + // a property is passed called argument names that does not seem to work for anything with commas. + // the following is regex to grab arguments and dataTypes using a matcher and add them to the function to edit. + + const updatedFunction = { + ...funct, + fluentFunction: funct?.isFluent === true ? true : false, + functionsArguments: parseArgumentsFromLogicString( + funct?.logic ? funct?.logic : "" + ), + expressionEditorValue: funct?.logic, + }; + + return ( + + + + ); +}; + +export default EditFunctionDialog; diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx index ed7173df..e2e81369 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx @@ -5,6 +5,8 @@ import userEvent from "@testing-library/user-event"; import { mockMeasureStoreCql } from "../__mocks__/MockMeasureStoreCql"; import { cqlBuilderLookup } from "../__mocks__/MockCqlBuilderLookupsTypes"; +const resetCql = jest.fn(); + const props = { canEdit: true, loading: false, @@ -12,6 +14,7 @@ const props = { cql: mockMeasureStoreCql, isCQLUnchanged: false, cqlBuilderLookupsTypes: cqlBuilderLookup, + resetCql, }; describe("FunctionsSection", () => { @@ -62,4 +65,35 @@ describe("FunctionsSection", () => { expect(funct).toBeInTheDocument(); expect(savedfunctions).toBeInTheDocument(); }); + + it("Should open a confirmation dialog on click", async () => { + render(); + const funct = await screen.findByTestId("function-tab"); + const savedfunctions = await screen.findByText("Saved Functions (2)"); + expect(funct).toBeInTheDocument(); + expect(savedfunctions).toBeInTheDocument(); + await waitFor(() => { + expect(funct).toHaveAttribute("aria-selected", "true"); + }); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "false"); + }); + userEvent.click(savedfunctions); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "true"); + }); + const editButon0 = screen.getByTestId("edit-button-0"); + userEvent.click(editButon0); + expect(screen.getByTestId("discard-dialog")).toBeInTheDocument(); + expect(screen.getByText("Discard Changes?")).toBeInTheDocument(); + const cancelBtn = screen.getByTestId("discard-dialog-cancel-button"); + const discardBtn = screen.getByTestId("discard-dialog-continue-button"); + expect(cancelBtn).toBeInTheDocument(); + expect(discardBtn).toBeInTheDocument(); + + userEvent.click(discardBtn); + await waitFor(() => { + expect(screen.getByText("Edit")).toBeInTheDocument(); + }); + }); }); diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx index 995a428b..cfb09de7 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx @@ -3,10 +3,7 @@ import "./Functions.scss"; import FunctionSectionNavTabs from "./FunctionSectionNavTabs"; import Functions from "./functions/Functions"; import FunctionBuilder from "./functionBuilder/FunctionBuilder"; -import { - CqlBuilderLookup, - FunctionLookup, -} from "../..//model/CqlBuilderLookup"; +import { CqlBuilderLookup, FunctionLookup } from "../../model/CqlBuilderLookup"; import * as _ from "lodash"; import { CqlAntlr } from "@madie/cql-antlr-parser/dist/src"; @@ -18,7 +15,10 @@ export interface FunctionProps { cql: string; isCQLUnchanged: boolean; functions?: FunctionLookup[]; + resetCql?: Function; + cqlBuilderLookupTypes?: any; } + const getArgumentNames = (logic: string) => { const args = logic.substring(logic.indexOf("(") + 1, logic.indexOf(")")); return args.split(","); @@ -27,10 +27,11 @@ const getArgumentNames = (logic: string) => { export default function FunctionsSection({ canEdit, handleApplyFunction, - loading, cql, isCQLUnchanged, cqlBuilderLookupsTypes, + resetCql, + loading, }: FunctionProps) { const [activeTab, setActiveTab] = useState("function"); @@ -70,7 +71,6 @@ export default function FunctionsSection({ }) || [] ); functionLookups = _.sortBy(functionLookups, (o) => o.name?.toLowerCase()); - return ( <> )} diff --git a/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.test.tsx b/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.test.tsx index b9e2a2eb..7c7ef62a 100644 --- a/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.test.tsx +++ b/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.test.tsx @@ -251,8 +251,20 @@ describe("CQL Function Builder Tests", () => { const functionArgumentTable = screen.getByTestId("function-argument-tbl"); expect(functionArgumentTable).toBeInTheDocument(); + await waitFor(() => { + expect( + screen.getByTestId("function-builder-success") + ).toBeInTheDocument(); + }); const tableRow = functionArgumentTable.querySelector("tbody").children[0]; expect(tableRow.children[1].textContent).toEqual("Test"); + const closeButton = screen.getByTestId( + "function-builder-toast-close-button" + ); + userEvent.click(closeButton); + await waitFor(() => { + expect(closeButton).not.toBeInTheDocument(); + }); }); it("Should delete argument from the table", async () => { diff --git a/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.tsx b/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.tsx index 7c4e285e..0649337e 100644 --- a/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.tsx +++ b/src/CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder.tsx @@ -6,6 +6,7 @@ import { Button, TextArea, TextField, + Toast, } from "@madie/madie-design-system/dist/react"; import "../Functions.scss"; import { FunctionSectionSchemaValidator } from "../../../validations/FunctionSectionSchemaValidator"; @@ -17,12 +18,14 @@ import ArgumentSection from "../argumentSection/ArgumentSection"; import ExpressionEditor from "../../definitionsSection/expressionSection/ExpressionEditor"; import { getNewExpressionsAndLines } from "../../common/utils"; import { CqlBuilderLookup } from "../../../model/CqlBuilderLookup"; +import UseToast from "../../../common/UseToast"; export interface Funct { - functionName?: string; + name?: string; fluentFunction?: boolean; functionsArguments: any; comment?: string; + expressionEditorValue?: string; } export interface FunctionProps { @@ -33,6 +36,7 @@ export interface FunctionProps { funct?: Funct; onClose?: Function; operation?: string; + setEditFunctionDialogOpen?: Function; } export default function FunctionBuilder({ @@ -52,13 +56,14 @@ export default function FunctionBuilder({ const [confirmationDialog, setConfirmationDialog] = useState(false); const [cursorPosition, setCursorPosition] = useState(null); const [autoInsert, setAutoInsert] = useState(false); + const formik = useFormik({ initialValues: { - functionName: funct?.functionName || "", + functionName: funct?.name || "", comment: funct?.comment || "", fluentFunction: funct?.fluentFunction || true, functionsArguments: funct?.functionsArguments || [], - expressionEditorValue: "", + expressionEditorValue: funct?.expressionEditorValue || "", type: "", name: "", }, @@ -76,7 +81,16 @@ export default function FunctionBuilder({ }); // going to pass dirty down to know when we need to reset sub form const { resetForm, dirty } = formik; - + // toast utilities + const { + toastOpen, + setToastOpen, + toastMessage, + setToastMessage, + toastType, + setToastType, + onToastClose, + } = UseToast(); // update formik, and expressionEditor, cursor, lines const updateExpressionAndLines = ( newEditorExpressionValue, @@ -99,6 +113,11 @@ export default function FunctionBuilder({ const addArgumentToFunctionsArguments = (fn) => { const newArgs = [...formik.values.functionsArguments, fn]; formik.setFieldValue("functionsArguments", newArgs); + setToastMessage( + `Argument ${fn.argumentName} has been successfully added to the function.` + ); + setToastType("success"); + setToastOpen(true); }; const deleteArgumentFromFunctionArguments = (fn) => { @@ -223,17 +242,21 @@ export default function FunctionBuilder({ @@ -247,6 +270,22 @@ export default function FunctionBuilder({ }} /> + ); } diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index fa1fc66f..5e7d2d1f 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; import _ from "lodash"; import tw from "twin.macro"; import "styled-components/macro"; -import { FunctionLookup } from "../../../model/CqlBuilderLookup"; +import { FunctionLookup, Lookup } from "../../../model/CqlBuilderLookup"; import { FunctionProps } from "../FunctionsSection"; import { ColumnDef, @@ -15,7 +15,11 @@ import ToolTippedIcon from "../../../toolTippedIcon/ToolTippedIcon"; import DeleteOutlineIcon from "@mui/icons-material/DeleteOutline"; import BorderColorOutlinedIcon from "@mui/icons-material/BorderColorOutlined"; import Skeleton from "@mui/material/Skeleton"; -import { Pagination } from "@madie/madie-design-system/dist/react"; +import { + Pagination, + MadieDiscardDialog, +} from "@madie/madie-design-system/dist/react"; +import EditFunctionDialog from "../EditFunctionDialog"; import Tooltip from "@mui/material/Tooltip"; const TH = tw.th`p-3 text-left text-sm font-bold capitalize`; @@ -39,18 +43,20 @@ const Functions = ({ loading, functions, isCQLUnchanged, + resetCql, + cqlBuilderLookupsTypes, + handleApplyFunction, }: FunctionProps) => { + // pagination utilities const [totalPages, setTotalPages] = useState(0); const [totalItems, setTotalItems] = useState(0); const [visibleItems, setVisibleItems] = useState(0); const [visibleFunctions, setVisibleFunctions] = useState( [] ); - const [offset, setOffset] = useState(0); const [currentLimit, setCurrentLimit] = useState(5); const [currentPage, setCurrentPage] = useState(1); - const canGoPrev = currentPage > 1; const canGoNext = (() => { return currentPage < totalPages; @@ -62,11 +68,20 @@ const Functions = ({ setCurrentLimit(e.target.value); setCurrentPage(1); }; - useEffect(() => { managePagination(); }, [functions, currentPage, currentLimit]); + // edit dialog utilities + const [discardDialog, setDiscardDialog] = useState({ + open: false, + operation: null, + }); + const [editFunctionDialogOpen, setEditFunctionDialogOpen] = + useState(); + const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); + const [selectedFunction, setSelectedFunction] = useState(); + const handleFunctionEdit = () => {}; // table data const data = visibleFunctions; @@ -145,7 +160,14 @@ const Functions = ({ "data-testid": `edit-button-${row.cell.row.id}`, "aria-label": `edit-button-${row.cell.row.id}`, size: "small", - onClick: (e) => {}, + onClick: (e) => { + setSelectedFunction(table.getRow(row.cell.row.id).original); + if (!isCQLUnchanged) { + setDiscardDialog({ open: true, operation: "edit" }); + } else { + setEditFunctionDialogOpen(true); + } + }, }} > @@ -262,6 +284,41 @@ const Functions = ({ hidePrevButton={!canGoPrev} /> + { + resetCql(); + if (discardDialog?.operation === "edit") { + setDiscardDialog({ + open: false, + operation: "edit", + }); + setEditFunctionDialogOpen(true); + } else if (discardDialog?.operation === "delete") { + setDiscardDialog({ + open: false, + operation: "delete", + }); + setDeleteDialogOpen(true); + } + }} + onClose={() => { + setDiscardDialog({ + open: false, + operation: null, + }); + }} + /> + + setEditFunctionDialogOpen(false)} + cqlBuilderLookupsTypes={cqlBuilderLookupsTypes} + handleApplyFunction={handleApplyFunction} + handleFunctionEdit={handleFunctionEdit} + /> ); }; diff --git a/src/common/UseToast.tsx b/src/common/UseToast.tsx new file mode 100644 index 00000000..f9915c2d --- /dev/null +++ b/src/common/UseToast.tsx @@ -0,0 +1,24 @@ +import { useState } from "react"; + +function UseToast() { + const [toastOpen, setToastOpen] = useState(false); + const [toastMessage, setToastMessage] = useState(""); + const [toastType, setToastType] = useState("danger"); + + const onToastClose = () => { + setToastOpen(false); + setToastMessage(""); + setToastType("danger"); + }; + + return { + toastOpen, + setToastOpen, + toastMessage, + setToastMessage, + toastType, + setToastType, + onToastClose, + }; +} +export default UseToast; From ff1fa3a29b753c068217ba3b6b08ca1247230bea Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 17 Dec 2024 09:51:46 -0800 Subject: [PATCH 04/10] MAT-7796: Remove artificat and testing --- src/CqlBuilderPanel/CqlBuilderPanel.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/CqlBuilderPanel/CqlBuilderPanel.tsx b/src/CqlBuilderPanel/CqlBuilderPanel.tsx index 45d7391c..fda312e0 100644 --- a/src/CqlBuilderPanel/CqlBuilderPanel.tsx +++ b/src/CqlBuilderPanel/CqlBuilderPanel.tsx @@ -4,7 +4,6 @@ import ValueSetsSection from "./ValueSets/ValueSets"; import CodesSection from "./codesSection/CodesSection"; import DefinitionsSection from "./definitionsSection/DefinitionsSection"; import FunctionsSection from "./functionsSection/FunctionsSection"; -// import FunctionsSection from "" import { useFeatureFlags } from "@madie/madie-util"; import IncludesTabSection from "./Includes/Includes"; import Parameters from "./Parameters/Parameters"; @@ -52,7 +51,7 @@ export default function CqlBuilderPanel({ return "includes"; })(); - const [activeTab, setActiveTab] = useState("functions"); + const [activeTab, setActiveTab] = useState(getStartingPage); const [cqlBuilderLookupsTypes, setCqlBuilderLookupsTypes] = useState(); const [errors, setErrors] = useState(null); From 2c17201f650f4e5e6123fc7894c0b4064e206bc2 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 17 Dec 2024 10:08:21 -0800 Subject: [PATCH 05/10] MAT-7796: Add test coverage --- .../FunctionsSection.test.tsx | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx index e2e81369..50c4cf5d 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.test.tsx @@ -96,4 +96,85 @@ describe("FunctionsSection", () => { expect(screen.getByText("Edit")).toBeInTheDocument(); }); }); + + it("Should open edit dialog on click", async () => { + render(); + const funct = await screen.findByTestId("function-tab"); + const savedfunctions = await screen.findByText("Saved Functions (2)"); + expect(funct).toBeInTheDocument(); + expect(savedfunctions).toBeInTheDocument(); + await waitFor(() => { + expect(funct).toHaveAttribute("aria-selected", "true"); + }); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "false"); + }); + userEvent.click(savedfunctions); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "true"); + }); + const editButon0 = screen.getByTestId("edit-button-0"); + userEvent.click(editButon0); + await waitFor(() => { + expect(screen.getByText("Edit")).toBeInTheDocument(); + }); + }); + + it("Should close discard dialog on click", async () => { + render(); + const funct = await screen.findByTestId("function-tab"); + const savedfunctions = await screen.findByText("Saved Functions (2)"); + expect(funct).toBeInTheDocument(); + expect(savedfunctions).toBeInTheDocument(); + await waitFor(() => { + expect(funct).toHaveAttribute("aria-selected", "true"); + }); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "false"); + }); + userEvent.click(savedfunctions); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "true"); + }); + const editButon0 = screen.getByTestId("edit-button-0"); + userEvent.click(editButon0); + expect(screen.getByTestId("discard-dialog")).toBeInTheDocument(); + expect(screen.getByText("Discard Changes?")).toBeInTheDocument(); + const cancelBtn = screen.getByTestId("discard-dialog-cancel-button"); + expect(cancelBtn).toBeInTheDocument(); + userEvent.click(cancelBtn); + + await waitFor(() => { + expect(screen.queryByTestId("discard-dialog")).not.toBeInTheDocument(); + }); + }); + + it("Should close edit dialog on click", async () => { + render(); + const funct = await screen.findByTestId("function-tab"); + const savedfunctions = await screen.findByText("Saved Functions (2)"); + expect(funct).toBeInTheDocument(); + expect(savedfunctions).toBeInTheDocument(); + await waitFor(() => { + expect(funct).toHaveAttribute("aria-selected", "true"); + }); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "false"); + }); + userEvent.click(savedfunctions); + await waitFor(() => { + expect(savedfunctions).toHaveAttribute("aria-selected", "true"); + }); + const editButon0 = screen.getByTestId("edit-button-0"); + userEvent.click(editButon0); + await waitFor(() => { + expect(screen.getByText("Edit")).toBeInTheDocument(); + }); + const closeButton = screen.getByRole("button", { name: "Close" }); + expect(closeButton).toBeInTheDocument(); + userEvent.click(closeButton); + await waitFor(() => { + expect(screen.queryByTestId("discard-dialog")).not.toBeInTheDocument(); + }); + }); }); From e9b06b7b2dbf874e87fd1776bc60fbb42e5ae57f Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 17 Dec 2024 10:13:13 -0800 Subject: [PATCH 06/10] MAT-7796: Remove unused code --- .../functionsSection/functions/Functions.tsx | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index 5e7d2d1f..9b05b5e8 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -294,12 +294,6 @@ const Functions = ({ operation: "edit", }); setEditFunctionDialogOpen(true); - } else if (discardDialog?.operation === "delete") { - setDiscardDialog({ - open: false, - operation: "delete", - }); - setDeleteDialogOpen(true); } }} onClose={() => { From 3c0482e2f8a2043fbe1700b794eb1d18d7de8fae Mon Sep 17 00:00:00 2001 From: sb-cecilialiu Date: Tue, 17 Dec 2024 12:39:05 -0600 Subject: [PATCH 07/10] MAT-7995 use new version of cql-antlr-parser --- package-lock.json | 8 ++++---- package.json | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5d3ec80..4682c00b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "@madie/madie-editor", "version": "0.0.6", "dependencies": { - "@madie/cql-antlr-parser": "^1.0.8", + "@madie/cql-antlr-parser": "^1.0.9", "@madie/madie-design-system": "^1.2.37", "@material-ui/core": "^4.12.4", "@mui/icons-material": "^5.5.1", @@ -3590,9 +3590,9 @@ "license": "MIT" }, "node_modules/@madie/cql-antlr-parser": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@madie/cql-antlr-parser/-/cql-antlr-parser-1.0.8.tgz", - "integrity": "sha512-/N8wcqjLfm0oRlZDqbzqmN+DFCVN98IcHlEYXPip7jks7EjK1fXXjIv9iL05fk2o0isAuCbwrKn4bvmOUqBJrw==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@madie/cql-antlr-parser/-/cql-antlr-parser-1.0.9.tgz", + "integrity": "sha512-q1LxO1fv9NLMz6p1rU9gR2YfW/7EQI8OJtaq036PuCum0y1cGfWOFG1hKSf9l6si+uod+q4qSALEiYTXaN6IoA==", "license": "CREATIVE COMMONS ATTRIBUTION 4.0 INTERNATIONAL LICENSE", "dependencies": { "antlr4ts": "^0.5.0-alpha.4", diff --git a/package.json b/package.json index 61c9378d..ffe9f103 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "webpack-merge": "^5.8.0" }, "dependencies": { - "@madie/cql-antlr-parser": "^1.0.8", + "@madie/cql-antlr-parser": "^1.0.9", "@madie/madie-design-system": "^1.2.37", "@material-ui/core": "^4.12.4", "@mui/icons-material": "^5.5.1", From f73e4eb1ea6a700a74597571de05186759ef2562 Mon Sep 17 00:00:00 2001 From: mcmcphillips Date: Tue, 17 Dec 2024 12:29:10 -0800 Subject: [PATCH 08/10] MAT-7796: Address feedback --- src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx | 1 - src/CqlBuilderPanel/functionsSection/functions/Functions.tsx | 4 ++-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx index cfb09de7..2aef2648 100644 --- a/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx +++ b/src/CqlBuilderPanel/functionsSection/FunctionsSection.tsx @@ -16,7 +16,6 @@ export interface FunctionProps { isCQLUnchanged: boolean; functions?: FunctionLookup[]; resetCql?: Function; - cqlBuilderLookupTypes?: any; } const getArgumentNames = (logic: string) => { diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index 9b05b5e8..e98f4c39 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect, useMemo, useCallback } from "react"; import _ from "lodash"; import tw from "twin.macro"; import "styled-components/macro"; -import { FunctionLookup, Lookup } from "../../../model/CqlBuilderLookup"; +import { FunctionLookup } from "../../../model/CqlBuilderLookup"; import { FunctionProps } from "../FunctionsSection"; import { ColumnDef, @@ -80,7 +80,7 @@ const Functions = ({ const [editFunctionDialogOpen, setEditFunctionDialogOpen] = useState(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); - const [selectedFunction, setSelectedFunction] = useState(); + const [selectedFunction, setSelectedFunction] = useState(); const handleFunctionEdit = () => {}; // table data const data = visibleFunctions; From fac2c9045a01373332869102565f6312728c3b3c Mon Sep 17 00:00:00 2001 From: sb-cecilialiu Date: Wed, 18 Dec 2024 07:49:21 -0600 Subject: [PATCH 09/10] MAT-7795 use CqlFunction to line up with madie-measure --- src/AceEditor/madie-ace-editor.tsx | 4 ++-- .../functionsSection/functions/Functions.tsx | 13 ++++++++----- src/model/CqlFunction.ts | 13 +++++++++++++ 3 files changed, 23 insertions(+), 7 deletions(-) create mode 100644 src/model/CqlFunction.ts diff --git a/src/AceEditor/madie-ace-editor.tsx b/src/AceEditor/madie-ace-editor.tsx index 78f75a39..a981443c 100644 --- a/src/AceEditor/madie-ace-editor.tsx +++ b/src/AceEditor/madie-ace-editor.tsx @@ -26,7 +26,7 @@ import { Definition } from "../CqlBuilderPanel/definitionsSection/definitionBuil import { SelectedLibrary } from "../CqlBuilderPanel/Includes/CqlLibraryDetailsDialog"; import { Funct } from "../CqlBuilderPanel/functionsSection/functionBuilder/FunctionBuilder"; import CqlVersion from "@madie/cql-antlr-parser/dist/src/dto/CqlVersion"; -import { FunctionLookup } from "../model/CqlBuilderLookup"; +import { CQLFunction } from "../model/CqlFunction"; export interface EditorPropsType { value: string; @@ -48,7 +48,7 @@ export interface EditorPropsType { ) => void; handleDeleteLibrary?: (lib: SelectedLibrary) => void; handleApplyFunction?: (funct: Funct) => void; - handleFunctionDelete?: (funct: FunctionLookup) => void; + handleFunctionDelete?: (funct: CQLFunction) => void; parseDebounceTime?: number; inboundAnnotations?: Ace.Annotation[]; inboundErrorMarkers?: Ace.MarkerLike[]; diff --git a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx index f757daa2..c3fe7d95 100644 --- a/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx +++ b/src/CqlBuilderPanel/functionsSection/functions/Functions.tsx @@ -20,6 +20,7 @@ import { MadieConfirmDialog, } from "@madie/madie-design-system/dist/react"; import Tooltip from "@mui/material/Tooltip"; +import { CQLFunction } from "../../../model/CqlFunction"; const TH = tw.th`p-3 text-left text-sm font-bold capitalize`; const TD = tw.td`p-3 text-left text-sm break-all`; @@ -51,7 +52,7 @@ const Functions = ({ const [visibleFunctions, setVisibleFunctions] = useState( [] ); - const [selectedFunction, setSelectedFunction] = useState(); + const [selectedFunction, setSelectedFunction] = useState(); const [deleteDialogOpen, setDeleteDialogOpen] = useState(false); const [discardDialog, setDiscardDialog] = useState({ open: false, @@ -147,14 +148,16 @@ const Functions = ({ size: "small", onClick: (e) => { const tableData = table.getRow(row.cell.row.id).original; - const functionToDelete = { + const functionToDelete: CQLFunction = { functionName: tableData.name, comment: tableData.comment, - functionsArguments: tableData.arguments, - fluentFunction: tableData.isFluent, + functionsArguments: row.cell.row.original.argumentNames, + fluentFunction: + tableData.isFluent === "Yes" ? true : false, expressionValue: tableData.logic, + expression: tableData.logic, }; - setSelectedFunction(tableData); + setSelectedFunction(functionToDelete); if (!isCQLUnchanged) { setDiscardDialog({ open: true, operation: "delete" }); } else { diff --git a/src/model/CqlFunction.ts b/src/model/CqlFunction.ts new file mode 100644 index 00000000..404539ae --- /dev/null +++ b/src/model/CqlFunction.ts @@ -0,0 +1,13 @@ +export interface CQLFunctionArgument { + argumentName?: string; + dataType?: string; +} + +export interface CQLFunction { + functionName?: string; + expression?: string; + comment?: string; + fluentFunction?: boolean; + expressionValue?: string; + functionsArguments?: CQLFunctionArgument[]; +} From f0d5c972210729a49e7b9abc0bc631102445535b Mon Sep 17 00:00:00 2001 From: sb-cecilialiu Date: Wed, 18 Dec 2024 07:52:19 -0600 Subject: [PATCH 10/10] MAT-7795 use CqlFunction to line up with madie-measure --- package-lock.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index d5d3ec80..59bac2d5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "0.0.6", "dependencies": { "@madie/cql-antlr-parser": "^1.0.8", - "@madie/madie-design-system": "^1.2.37", + "@madie/madie-design-system": "^1.2.39", "@material-ui/core": "^4.12.4", "@mui/icons-material": "^5.5.1", "@mui/lab": "^5.0.0-alpha.73", @@ -3600,9 +3600,9 @@ } }, "node_modules/@madie/madie-design-system": { - "version": "1.2.37", - "resolved": "https://registry.npmjs.org/@madie/madie-design-system/-/madie-design-system-1.2.37.tgz", - "integrity": "sha512-sIz3pN+gM0iXA64t4TUxmipsK4gIPHWI2auMVaaQt7fJvrjldpTuldzVmRjhbWwLsqWTJ1Ms86aLCQc9MIrUkA==", + "version": "1.2.39", + "resolved": "https://registry.npmjs.org/@madie/madie-design-system/-/madie-design-system-1.2.39.tgz", + "integrity": "sha512-3ywRn6h571yNgnskL3jhJB5WFfFLmWI3pdleiHSo7LT+aV6FHkmOJhLeaQFt07BG74vT2bG6MHyCmvJFpzhB/Q==", "license": "CC0-1.0", "dependencies": { "@cmsgov/design-system": "^5.0.2",