From 3d2a50e1e9de4a6b2110dd3191817727e84e7346 Mon Sep 17 00:00:00 2001 From: Kai Rohwer Date: Thu, 14 Dec 2023 10:56:33 +0100 Subject: [PATCH] Convert process modals to RSC + refactor --- .../components/process-copy.tsx | 66 ---- .../components/process-creation-button.tsx | 48 ++- .../components/process-creation.tsx | 334 ------------------ .../components/process-edit-button.tsx | 57 +-- .../components/process-import.tsx | 35 +- .../components/process-list.tsx | 16 +- .../components/process-modal.tsx | 193 ++++++---- .../components/processes.tsx | 29 +- .../components/version-toolbar.tsx | 50 ++- .../lib/data/processes.ts | 84 ----- .../lib/data/processes.tsx | 149 ++++++++ .../lib/helpers/processHelpers.ts | 31 ++ src/management-system-v2/lib/user-error.ts | 37 ++ src/management-system-v2/package.json | 2 +- yarn.lock | 269 +++++++------- 15 files changed, 609 insertions(+), 791 deletions(-) delete mode 100644 src/management-system-v2/components/process-copy.tsx delete mode 100644 src/management-system-v2/components/process-creation.tsx delete mode 100644 src/management-system-v2/lib/data/processes.ts create mode 100644 src/management-system-v2/lib/data/processes.tsx create mode 100644 src/management-system-v2/lib/user-error.ts diff --git a/src/management-system-v2/components/process-copy.tsx b/src/management-system-v2/components/process-copy.tsx deleted file mode 100644 index 1f1994920..000000000 --- a/src/management-system-v2/components/process-copy.tsx +++ /dev/null @@ -1,66 +0,0 @@ -import { Dispatch, FC, Key, SetStateAction, useMemo } from 'react'; - -import ProcessCreationModal from './process-creation'; -import { useGetAsset } from '@/lib/fetch-data'; - -import { generateDefinitionsId } from '@proceed/bpmn-helper'; - -type ProcessCopyModalType = { - onClose: () => void; - open: boolean; - processKeys: React.Key[] | String[]; - setSelection: Dispatch>; -}; - -const ProcessCopyModal: FC = ({ - onClose, - open, - processKeys, - setSelection, -}) => { - const { data } = useGetAsset('/process', { - params: { - query: { noBpmn: false }, - }, - }); - - // get the process data that is needed to create copies of the processes - const blueprintForProcesses = useMemo(() => { - return (data || []) - .filter((process) => processKeys.includes(process.definitionId)) - .map((process) => ({ - definitionId: generateDefinitionsId(), - definitionName: `${process.definitionName} (Copy)`, - description: process.description, - bpmn: process.bpmn!, - originalId: process.definitionId, - })); - /* - Do not include data as dependency - The Blue-Print should only be determined by the processKeys - */ - }, [processKeys]); - - // change the selection from the processes to be copied to the newly created process copies - const handleCopyCreated = (definitionId: string) => { - const blueprint = blueprintForProcesses.find((bp) => bp.definitionId === definitionId); - if (blueprint) { - setSelection((prev) => [ - ...prev.filter((definitionId) => definitionId !== blueprint.originalId), - definitionId, - ]); - } - }; - - return ( - 1 ? 'es' : ''}`} - onCancel={onClose} - onCreated={handleCopyCreated} - > - ); -}; - -export default ProcessCopyModal; diff --git a/src/management-system-v2/components/process-creation-button.tsx b/src/management-system-v2/components/process-creation-button.tsx index 559c7619e..ab13c6e3e 100644 --- a/src/management-system-v2/components/process-creation-button.tsx +++ b/src/management-system-v2/components/process-creation-button.tsx @@ -1,42 +1,43 @@ 'use client'; -import React, { ReactNode, useState, useTransition } from 'react'; +import React, { ReactNode, useState } from 'react'; import { Button } from 'antd'; import type { ButtonProps } from 'antd'; import ProcessModal from './process-modal'; -import { createProcess } from '@/lib/helpers/processHelpers'; -import { addProcess } from '@/lib/data/processes'; +import { addProcesses } from '@/lib/data/processes'; import { useRouter } from 'next/navigation'; type ProcessCreationButtonProps = ButtonProps & { - createProcess?: (values: { name: string; description?: string }) => any; + customAction?: (values: { definitionName: string; description: string }) => Promise; wrapperElement?: ReactNode; }; /** * * Button to create Processes including a Modal for inserting needed values. Alternatively, a custom wrapper element can be used instead of a button. - * Custom function for creation of process using inserted values can be applied */ const ProcessCreationButton: React.FC = ({ - createProcess: customCreateProcess, wrapperElement, + customAction, ...props }) => { const [isProcessModalOpen, setIsProcessModalOpen] = useState(false); - const [isPending, startTransition] = useTransition(); const router = useRouter(); - const createNewProcess = async (values: { name: string; description?: string }) => { - const { metaInfo, bpmn } = await createProcess(values); - startTransition(async () => { - try { - await addProcess({ bpmn: bpmn, departments: [] }); - } catch (error) { - console.error(error); - } + const createNewProcess = async (values: { definitionName: string; description: string }[]) => { + // Invoke the custom handler otherwise use the default server action. + const process = await (customAction?.(values[0]) ?? + addProcesses(values).then((res) => (Array.isArray(res) ? res[0] : res))); + if (process && 'error' in process) { + return process; + } + setIsProcessModalOpen(false); + + if (process && 'definitionId' in process) { + router.push(`/processes/${process.definitionId}`); + } else { router.refresh(); - }); + } }; return ( @@ -58,15 +59,12 @@ const ProcessCreationButton: React.FC = ({ > )} { - setIsProcessModalOpen(false); - - if (values) { - customCreateProcess ? customCreateProcess(values) : createNewProcess(values); - } - }} - show={isProcessModalOpen} - > + open={isProcessModalOpen} + title="Create Process" + okText="Create" + onCancel={() => setIsProcessModalOpen(false)} + onSubmit={createNewProcess} + /> ); }; diff --git a/src/management-system-v2/components/process-creation.tsx b/src/management-system-v2/components/process-creation.tsx deleted file mode 100644 index 4ee230638..000000000 --- a/src/management-system-v2/components/process-creation.tsx +++ /dev/null @@ -1,334 +0,0 @@ -import React, { Key, useEffect, useMemo, useState } from 'react'; - -import { produce } from 'immer'; - -import { - Modal, - Button, - Tooltip, - Switch, - Checkbox, - Collapse, - Spin, - CollapseProps, - Input, -} from 'antd'; -import TextArea from 'antd/es/input/TextArea'; -import { LoadingOutlined } from '@ant-design/icons'; - -import styles from './process-creation.module.scss'; - -import { useUserPreferences } from '@/lib/user-preferences'; - -import { - toBpmnObject, - toBpmnXml, - setDefinitionsId, - setDefinitionsName, - setDefinitionsVersionInformation, - setTargetNamespace, - addDocumentation, - getDefinitionsId, -} from '@proceed/bpmn-helper'; -import { asyncForEach } from '@/lib/helpers/javascriptHelpers'; -import { useGetAsset, usePostAsset } from '@/lib/fetch-data'; - -export type ProcessData = { - definitionId: string; - definitionName: string; - description: string; - bpmn: string; -}; - -type ProcessDataOverwrite = { - definitionName?: string; - description?: string; - successful?: boolean; - failed?: boolean; -}; - -type ProcessCreationProps = { - creationType: string; - title: string; - processesData: ProcessData[]; - onCancel: () => void; - onCreated?: (definitionId: string) => void; -}; - -// TODO: check the validity of the process data and maybe show hints if data is missing - -const ProcessCreationModal: React.FC = ({ - creationType, - title, - processesData, - onCancel, - onCreated, -}) => { - const [loading, setLoading] = useState(false); - - // the data that is overwritten by the user before the processes are created (e.g. the user wants a different definition name that provided in the processesData) - const [dataChanges, setDataChanges] = useState<{ [definitionId: string]: ProcessDataOverwrite }>( - {}, - ); - - const { refetch: refreshData } = useGetAsset('/process', { - params: { - query: { noBpmn: true }, - }, - }); - - const { mutateAsync: addProcess } = usePostAsset('/process', { - onError: async (error, variables) => { - const id = (await getDefinitionsId(variables.body.bpmn)) as string; - setDataChanges( - produce((draft) => { - if (draft[id]) draft[id].failed = true; - else draft[id] = { failed: true }; - }), - ); - }, - onSuccess: (data) => { - if (onCreated) onCreated(data.definitionId as string); - - setDataChanges( - produce((draft) => { - if (draft[data.definitionId as string]) { - draft[data.definitionId as string].successful = true; - } else { - draft[data.definitionId as string] = { successful: true }; - } - }), - ); - }, - }); - - const numSuccesses = useMemo(() => { - return Object.values(dataChanges).reduce((acc, { successful }) => { - if (successful) return acc + 1; - else return acc; - }, 0); - }, [dataChanges]); - - const numFails = useMemo(() => { - return Object.values(dataChanges).reduce((acc, { failed }) => { - if (failed) return acc + 1; - else return acc; - }, 0); - }, [dataChanges]); - - const handleCancel = () => { - setDataChanges({}); - onCancel(); - }; - - useEffect(() => { - /* All done */ - if (processesData.length && numSuccesses + numFails === processesData.length) { - setLoading(false); - - /* All Successful */ - if (numSuccesses === processesData.length) { - setTimeout(() => { - handleCancel(); - refreshData(); - }, 2_000); - } - } - }, [dataChanges, processesData, refreshData]); - - const addPreferences = useUserPreferences.use.addPreferences(); - const isAccordion = useUserPreferences( - (store) => store.preferences[`${creationType.toLowerCase()}-modal-accordion`], - ) as boolean; - - const getFinalBpmn = async ({ definitionId, definitionName, description, bpmn }: ProcessData) => { - // write the necessary meta info into the bpmn to create the final bpmn that is sent to the backend - const bpmnObj = await toBpmnObject(bpmn); - await setDefinitionsId(bpmnObj, definitionId); - await setDefinitionsName(bpmnObj, dataChanges[definitionId]?.definitionName || definitionName); - await addDocumentation(bpmnObj, dataChanges[definitionId]?.description || description); - await setTargetNamespace(bpmnObj, definitionId); - - await setDefinitionsVersionInformation(bpmnObj, { - version: undefined, - versionName: undefined, - versionDescription: undefined, - versionBasedOn: undefined, - }); - - return await toBpmnXml(bpmnObj); - }; - - const handleSubmit = () => { - setLoading(true); - - asyncForEach(processesData, async (processData) => { - // only (re-)submit processes that were not yet submitted succesfully - if (dataChanges[processData.definitionId]?.successful) return; - // remove potential failed flags from previous runs - setDataChanges( - produce((draft) => { - if (draft[processData.definitionId]) draft[processData.definitionId].failed = false; - }), - ); - - const bpmn = await getFinalBpmn(processData); - - addProcess({ - body: { - bpmn, - departments: [], - variables: [], - }, - }); - }); - }; - - // create the ui that allows users to change some of the meta data (e.g. definitionName,description) of the process - const items: CollapseProps['items'] = processesData.map( - ({ definitionId, definitionName, description }) => { - /* Initial */ - return { - key: definitionId, - label: {definitionName}, - children: ( - <> -
- Process-Name: -
- { - setDataChanges({ - ...dataChanges, - [definitionId]: { - ...(dataChanges[definitionId] || {}), - definitionName: e.target.value, - }, - }); - }} - /> -
- Process-Description: -
- -