diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py index f27cf957a..1081494b3 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/routes/process_api_blueprint.py @@ -259,7 +259,7 @@ def process_model_update( ) -> Any: """Process_model_update.""" process_model_identifier = modified_process_model_identifier.replace(":", "/") - body_include_list = ["display_name", "primary_file_name", "primary_process_id"] + body_include_list = ["display_name", "primary_file_name", "primary_process_id", "description"] body_filtered = { include_item: body[include_item] for include_item in body_include_list diff --git a/spiffworkflow-frontend/src/components/ProcessGroupForm.tsx b/spiffworkflow-frontend/src/components/ProcessGroupForm.tsx index 128a13c0c..7f83923fc 100644 --- a/spiffworkflow-frontend/src/components/ProcessGroupForm.tsx +++ b/spiffworkflow-frontend/src/components/ProcessGroupForm.tsx @@ -95,7 +95,7 @@ export default function ProcessGroupForm({ const onDisplayNameChanged = (newDisplayName: any) => { setDisplayNameInvalid(false); const updateDict = { display_name: newDisplayName }; - if (!idHasBeenUpdatedByUser) { + if (!idHasBeenUpdatedByUser && mode === 'new') { Object.assign(updateDict, { id: slugifyString(newDisplayName) }); } updateProcessGroup(updateDict); diff --git a/spiffworkflow-frontend/src/components/ProcessModelForm.tsx b/spiffworkflow-frontend/src/components/ProcessModelForm.tsx new file mode 100644 index 000000000..e1e636d9c --- /dev/null +++ b/spiffworkflow-frontend/src/components/ProcessModelForm.tsx @@ -0,0 +1,193 @@ +import { useState } from 'react'; +import { useNavigate } from 'react-router-dom'; +// @ts-ignore +import { Button, ButtonSet, Form, Stack, TextInput } from '@carbon/react'; +import { + getGroupFromModifiedModelId, + modifyProcessModelPath, + slugifyString, +} from '../helpers'; +import HttpService from '../services/HttpService'; +import { ProcessModel } from '../interfaces'; +import ButtonWithConfirmation from './ButtonWithConfirmation'; + +type OwnProps = { + mode: string; + processModel: ProcessModel; + setProcessModel: (..._args: any[]) => any; +}; + +export default function ProcessModelForm({ + mode, + processModel, + setProcessModel, +}: OwnProps) { + const [identifierInvalid, setIdentifierInvalid] = useState(false); + const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] = + useState(false); + const [displayNameInvalid, setDisplayNameInvalid] = useState(false); + const navigate = useNavigate(); + const modifiedProcessModelPath = modifyProcessModelPath(processModel.id); + + const navigateToProcessModel = (_result: any) => { + if (processModel) { + navigate(`/admin/process-models/${modifiedProcessModelPath}`); + } + }; + + const navigateToProcessModels = (_result: any) => { + navigate( + `/admin/process-groups/${getGroupFromModifiedModelId( + modifiedProcessModelPath + )}` + ); + }; + + const hasValidIdentifier = (identifierToCheck: string) => { + return identifierToCheck.match(/^[a-z0-9][0-9a-z-]+[a-z0-9]$/); + }; + + const deleteProcessModel = () => { + HttpService.makeCallToBackend({ + path: `/process-models/${modifiedProcessModelPath}`, + successCallback: navigateToProcessModels, + httpMethod: 'DELETE', + }); + }; + + const handleFormSubmission = (event: any) => { + event.preventDefault(); + let hasErrors = false; + if (!hasValidIdentifier(processModel.id)) { + setIdentifierInvalid(true); + hasErrors = true; + } + if (processModel.display_name === '') { + setDisplayNameInvalid(true); + hasErrors = true; + } + if (hasErrors) { + return; + } + let path = `/process-models`; + if (mode === 'edit') { + path = `/process-models/${modifiedProcessModelPath}`; + } + let httpMethod = 'POST'; + if (mode === 'edit') { + httpMethod = 'PUT'; + } + const postBody = { + display_name: processModel.display_name, + description: processModel.description, + }; + if (mode === 'new') { + Object.assign(postBody, { + id: processModel.id, + }); + } + + HttpService.makeCallToBackend({ + path, + successCallback: navigateToProcessModel, + httpMethod, + postBody, + }); + }; + + const updateProcessModel = (newValues: any) => { + const processModelToCopy = { + ...processModel, + }; + Object.assign(processModelToCopy, newValues); + setProcessModel(processModelToCopy); + }; + + const onDisplayNameChanged = (newDisplayName: any) => { + setDisplayNameInvalid(false); + const updateDict = { display_name: newDisplayName }; + if (!idHasBeenUpdatedByUser && mode === 'new') { + Object.assign(updateDict, { id: slugifyString(newDisplayName) }); + } + updateProcessModel(updateDict); + }; + + const formElements = () => { + const textInputs = [ + { + onDisplayNameChanged(event.target.value); + }} + onBlur={(event: any) => console.log('event', event)} + />, + ]; + + if (mode === 'new') { + textInputs.push( + { + updateProcessModel({ id: event.target.value }); + // was invalid, and now valid + if (identifierInvalid && hasValidIdentifier(event.target.value)) { + setIdentifierInvalid(false); + } + setIdHasBeenUpdatedByUser(true); + }} + /> + ); + } + + textInputs.push( + + updateProcessModel({ description: event.target.value }) + } + /> + ); + + return textInputs; + }; + + const formButtons = () => { + const buttons = [ + , + ]; + if (mode === 'edit') { + buttons.push( + + ); + } + return {buttons}; + }; + return ( +
+ + {formElements()} + {formButtons()} + +
+ ); +} diff --git a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx index 98419d6c1..8d5ca90b9 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelSearch.tsx @@ -21,7 +21,7 @@ export default function ProcessModelSearch({ const shouldFilterProcessModel = (options: any) => { const processModel: ProcessModel = options.item; const { inputValue } = options; - return `${processModel.process_group_id}/${processModel.id} (${processModel.display_name})`.includes( + return `${processModel.id} (${processModel.display_name})`.includes( inputValue ); }; @@ -33,9 +33,10 @@ export default function ProcessModelSearch({ items={processModels} itemToString={(processModel: ProcessModel) => { if (processModel) { - return `${processModel.process_group_id}/${ - processModel.id - } (${truncateString(processModel.display_name, 20)})`; + return `${processModel.id} (${truncateString( + processModel.display_name, + 20 + )})`; } return null; }} diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index 342785ec5..1417a3a90 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -27,7 +27,6 @@ export interface ProcessFile { content_type: string; last_modified: string; name: string; - process_group_id: string; process_model_id: string; references: ProcessFileReference[]; size: number; @@ -38,7 +37,6 @@ export interface ProcessFile { export interface ProcessModel { id: string; description: string; - process_group_id: string; display_name: string; primary_file_name: string; files: ProcessFile[]; diff --git a/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx b/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx index 13f3d455f..e84ec5369 100644 --- a/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessGroupList.tsx @@ -13,7 +13,10 @@ import { import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import PaginationForTable from '../components/PaginationForTable'; import HttpService from '../services/HttpService'; -import { getPageInfoFromSearchParams } from '../helpers'; +import { + getPageInfoFromSearchParams, + modifyProcessModelPath, +} from '../helpers'; import { CarbonComboBoxSelection, ProcessGroup } from '../interfaces'; import ProcessModelSearch from '../components/ProcessModelSearch'; @@ -36,7 +39,7 @@ export default function ProcessGroupList() { }; const processResultForProcessModels = (result: any) => { const selectionArray = result.results.map((item: any) => { - const label = `${item.process_group_id}/${item.id}`; + const label = `${item.id}`; Object.assign(item, { label }); return item; }); @@ -120,7 +123,7 @@ export default function ProcessGroupList() { const processModelSearchOnChange = (selection: CarbonComboBoxSelection) => { const processModel = selection.selectedItem; navigate( - `/admin/process-models/${processModel.process_group_id}/${processModel.id}` + `/admin/process-models/${modifyProcessModelPath(processModel.id)}` ); }; return ( diff --git a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx index 9a37daf15..a234766bc 100644 --- a/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessInstanceList.tsx @@ -86,7 +86,6 @@ export default function ProcessInstanceList() { const parametersToGetFromSearchParams = useMemo(() => { return { - process_group_identifier: null, process_model_identifier: null, process_status: null, }; @@ -138,7 +137,7 @@ export default function ProcessInstanceList() { const processModelFullIdentifier = getProcessModelFullIdentifierFromSearchParams(searchParams); const selectionArray = result.results.map((item: any) => { - const label = `${item.process_group_id}/${item.id}`; + const label = `${item.id}`; Object.assign(item, { label }); if (label === processModelFullIdentifier) { setProcessModelSelection(item); @@ -244,7 +243,7 @@ export default function ProcessInstanceList() { } if (processModelSelection) { - queryParamString += `&process_group_identifier=${processModelSelection.process_group_id}&process_model_identifier=${processModelSelection.id}`; + queryParamString += `&process_model_identifier=${processModelSelection.id}`; } setErrorMessage(null); @@ -383,7 +382,6 @@ export default function ProcessInstanceList() { const buildTable = () => { const headerLabels: Record = { id: 'Process Instance Id', - process_group_identifier: 'Process Group', process_model_identifier: 'Process Model', start_in_seconds: 'Start Time', end_in_seconds: 'End Time', @@ -418,7 +416,7 @@ export default function ProcessInstanceList() { const formatProcessModelIdentifier = (row: any, identifier: any) => { return ( {identifier} @@ -433,7 +431,6 @@ export default function ProcessInstanceList() { const columnFormatters: Record = { id: formatProcessInstanceId, - process_group_identifier: formatProcessGroupIdentifier, process_model_identifier: formatProcessModelIdentifier, start_in_seconds: formatSecondsForDisplay, end_in_seconds: formatSecondsForDisplay, diff --git a/spiffworkflow-frontend/src/routes/ProcessModelEdit.tsx b/spiffworkflow-frontend/src/routes/ProcessModelEdit.tsx index c2cb88142..1e5a1a397 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelEdit.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelEdit.tsx @@ -1,113 +1,32 @@ -import { useState, useEffect, useContext } from 'react'; -import { useParams, useNavigate } from 'react-router-dom'; +import { useState, useEffect } from 'react'; +import { useParams } from 'react-router-dom'; // @ts-ignore -import { Button, Stack } from '@carbon/react'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import HttpService from '../services/HttpService'; -import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; -import ErrorContext from '../contexts/ErrorContext'; -import { - getGroupFromModifiedModelId, - modifyProcessModelPath, -} from '../helpers'; +import ProcessModelForm from '../components/ProcessModelForm'; export default function ProcessModelEdit() { - const [displayName, setDisplayName] = useState(''); const params = useParams(); - const navigate = useNavigate(); const [processModel, setProcessModel] = useState(null); - const setErrorMessage = (useContext as any)(ErrorContext)[1]; - const processModelPath = `process-models/${params.process_model_id}`; useEffect(() => { - const processResult = (result: any) => { - setProcessModel(result); - setDisplayName(result.display_name); - }; - console.log(`processModelPath: ${processModelPath}`); HttpService.makeCallToBackend({ path: `/${processModelPath}`, - successCallback: processResult, + successCallback: setProcessModel, }); }, [processModelPath]); - const navigateToProcessModel = (_result: any) => { - console.log(`processModelPath: ${processModelPath}`); - navigate(`/admin/${processModelPath}`); - }; - - const navigateToProcessModels = (_result: any) => { - const processGroupId = getGroupFromModifiedModelId( - (params as any).process_model_id - ); - const modifiedProcessGroupId = modifyProcessModelPath(processGroupId); - navigate(`/admin/process-groups/${modifiedProcessGroupId}`); - }; - - const updateProcessModel = (event: any) => { - const processModelToUse = processModel as any; - event.preventDefault(); - const processModelToPass = Object.assign(processModelToUse, { - display_name: displayName, - }); - console.log(`processModelPath: ${processModelPath}`); - HttpService.makeCallToBackend({ - path: `/${processModelPath}`, - successCallback: navigateToProcessModel, - httpMethod: 'PUT', - postBody: processModelToPass, - }); - }; - - // share with or delete from ProcessModelEditDiagram - const deleteProcessModel = () => { - setErrorMessage(null); - const processModelToUse = processModel as any; - const modifiedProcessModelId: String = modifyProcessModelPath( - (processModelToUse as any).id - ); - const processModelShowPath = `/process-models/${modifiedProcessModelId}`; - console.log(`processModelShowPath: ${processModelShowPath}`); - HttpService.makeCallToBackend({ - path: `${processModelShowPath}`, - successCallback: navigateToProcessModels, - httpMethod: 'DELETE', - failureCallback: setErrorMessage, - }); - }; - - const onDisplayNameChanged = (newDisplayName: any) => { - setDisplayName(newDisplayName); - }; - if (processModel) { return ( <> - -

Edit Process Group: {(processModel as any).id}

-
- - onDisplayNameChanged(e.target.value)} - /> -
-
- - - - - -
+ +

Edit Process Model: {(processModel as any).id}

+ ); } diff --git a/spiffworkflow-frontend/src/routes/ProcessModelNew.tsx b/spiffworkflow-frontend/src/routes/ProcessModelNew.tsx index 06c5853bb..e9a26455e 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelNew.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelNew.tsx @@ -1,74 +1,26 @@ import { useState } from 'react'; -import { useNavigate, useParams } from 'react-router-dom'; -import Button from 'react-bootstrap/Button'; -import Form from 'react-bootstrap/Form'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; -import { slugifyString } from '../helpers'; -import HttpService from '../services/HttpService'; +import { ProcessModel } from '../interfaces'; +import ProcessModelForm from '../components/ProcessModelForm'; export default function ProcessModelNew() { - const params = useParams(); - - const [identifier, setIdentifier] = useState(''); - const [idHasBeenUpdatedByUser, setIdHasBeenUpdatedByUser] = useState(false); - const [displayName, setDisplayName] = useState(''); - const navigate = useNavigate(); - - const navigateToNewProcessModel = (_result: any) => { - navigate(`/admin/process-models/${params.process_group_id}/${identifier}`); - }; - - const addProcessModel = (event: any) => { - event.preventDefault(); - HttpService.makeCallToBackend({ - path: `/process-models`, - successCallback: navigateToNewProcessModel, - httpMethod: 'POST', - postBody: { - id: `${params.process_group_id}/${identifier}`, - display_name: displayName, - description: displayName, - }, - }); - }; - - const onDisplayNameChanged = (newDisplayName: any) => { - setDisplayName(newDisplayName); - if (!idHasBeenUpdatedByUser) { - setIdentifier(slugifyString(newDisplayName)); - } - }; + const [processModel, setProcessModel] = useState({ + id: '', + display_name: '', + description: '', + primary_file_name: '', + files: [], + }); return ( <>

Add Process Model

-
- - Display Name: - onDisplayNameChanged(e.target.value)} - /> - - - ID: - { - setIdentifier(e.target.value); - setIdHasBeenUpdatedByUser(true); - }} - /> - - -
+ ); } diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index 42e4207bd..cd8647423 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -32,14 +32,6 @@ import { modifyProcessModelPath, unModifyProcessModelPath } from '../helpers'; import { ProcessFile, ProcessModel, RecentProcessModel } from '../interfaces'; import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; -// interface ProcessModelFileCarbonDropdownItem { -// label: string; -// action: string; -// processModelFile: ProcessFile; -// needsConfirmation: boolean; -// icon: any; -// } - const storeRecentProcessModelInLocalStorage = ( processModelForStorage: any, params: any @@ -184,7 +176,7 @@ export default function ProcessModelShow() { // Remove this code from const onDeleteFile = (fileName: string) => { - const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileName}`; + const url = `/process-models/${modifiedProcessModelId}/files/${fileName}`; const httpMethod = 'DELETE'; HttpService.makeCallToBackend({ path: url, @@ -193,15 +185,8 @@ export default function ProcessModelShow() { }); }; - // const onProcessModelFileAction = (selection: any) => { - // const { selectedItem } = selection; - // if (selectedItem.action === 'delete') { - // onDeleteFile(selectedItem.processModelFile.name); - // } - // }; - const onSetPrimaryFile = (fileName: string) => { - const url = `/process-models/${params.process_group_id}/${params.process_model_id}`; + const url = `/process-models/${modifiedProcessModelId}`; const httpMethod = 'PUT'; const processModelToPass = { @@ -241,7 +226,7 @@ export default function ProcessModelShow() { const downloadFile = (fileName: string) => { setErrorMessage(null); - const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`; + const processModelPath = `process-models/${modifiedProcessModelId}`; HttpService.makeCallToBackend({ path: `/${processModelPath}/files/${fileName}`, successCallback: handleProcessModelFileResult, @@ -405,7 +390,7 @@ export default function ProcessModelShow() { const handleFileUpload = (event: any) => { if (processModel) { event.preventDefault(); - const url = `/process-models/${processModel.process_group_id}/${processModel.id}/files`; + const url = `/process-models/${modifiedProcessModelId}/files`; const formData = new FormData(); formData.append('file', filesToUpload[0]); formData.append('fileName', filesToUpload[0].name); diff --git a/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx b/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx index fdd0c8513..d2ba03b79 100644 --- a/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx +++ b/spiffworkflow-frontend/src/routes/ReactFormEditor.tsx @@ -152,6 +152,14 @@ export default function ReactFormEditor() { processGroupId={params.process_group_id} processModelId={params.process_model_id} linkProcessModel + hotCrumbs={[ + ['Process Groups', '/admin'], + [ + `Process Model: ${params.process_model_id}`, + `process_model:${params.process_model_id}:link`, + ], + [(processModelFile as any).name || ''], + ]} />

Process Model File