From 566f38391820d522d66ff8f4e427cce665a605aa Mon Sep 17 00:00:00 2001 From: jasquat <2487833+jasquat@users.noreply.github.com> Date: Fri, 27 Oct 2023 14:54:45 -0400 Subject: [PATCH] adds basic support to use certain components in extensions w/ burnettk (#597) Co-authored-by: jasquat --- .../services/element_units_service.py | 2 +- .../src/components/InstructionsForEndUser.tsx | 7 +-- .../src/components/MarkdownDisplayForFile.tsx | 10 +-- .../src/components/MarkdownRenderer.tsx | 16 +++++ .../src/components/ProcessInstanceRun.tsx | 16 ++--- .../src/components/ProcessModelListTiles.tsx | 30 +-------- .../src/extension_ui_schema_interfaces.ts | 6 ++ spiffworkflow-frontend/src/interfaces.ts | 3 + .../src/routes/Extension.tsx | 63 ++++++++++++++----- spiffworkflow-frontend/src/routes/MyTasks.tsx | 31 +-------- .../src/routes/OnboardingView.tsx | 14 ++--- .../src/routes/ProcessModelShow.tsx | 34 +--------- .../src/routes/TaskShow.tsx | 6 +- 13 files changed, 103 insertions(+), 135 deletions(-) create mode 100644 spiffworkflow-frontend/src/components/MarkdownRenderer.tsx diff --git a/spiffworkflow-backend/src/spiffworkflow_backend/services/element_units_service.py b/spiffworkflow-backend/src/spiffworkflow_backend/services/element_units_service.py index 81340e79..ecebda69 100644 --- a/spiffworkflow-backend/src/spiffworkflow_backend/services/element_units_service.py +++ b/spiffworkflow-backend/src/spiffworkflow_backend/services/element_units_service.py @@ -58,7 +58,7 @@ class ElementUnitsService: # make mypy happy return None - current_app.logger.info(f"Checking element unit cache @ {cache_key} :: '{process_id}' - '{element_id}'") + current_app.logger.debug(f"Checking element unit cache @ {cache_key} :: '{process_id}' - '{element_id}'") bpmn_spec_json = spiff_element_units.workflow_from_cached_element_unit( cache_dir, cache_key, process_id, element_id diff --git a/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx b/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx index b0484bec..644a7cc8 100644 --- a/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx +++ b/spiffworkflow-frontend/src/components/InstructionsForEndUser.tsx @@ -1,8 +1,8 @@ import React, { useEffect, useState } from 'react'; // @ts-ignore -import MDEditor from '@uiw/react-md-editor'; import { Toggle } from '@carbon/react'; import FormattingService from '../services/FormattingService'; +import MarkdownRenderer from './MarkdownRenderer'; type OwnProps = { task: any; @@ -87,9 +87,8 @@ export default function InstructionsForEndUser({ https://www.npmjs.com/package/@uiw/react-md-editor switches to dark mode by default by respecting @media (prefers-color-scheme: dark) This makes it look like our site is broken, so until the rest of the site supports dark mode, turn off dark mode for this component. */} -
- -
+ + {showCollapseToggle()} diff --git a/spiffworkflow-frontend/src/components/MarkdownDisplayForFile.tsx b/spiffworkflow-frontend/src/components/MarkdownDisplayForFile.tsx index 15be7204..0be0f259 100644 --- a/spiffworkflow-frontend/src/components/MarkdownDisplayForFile.tsx +++ b/spiffworkflow-frontend/src/components/MarkdownDisplayForFile.tsx @@ -1,6 +1,6 @@ -import MDEditor from '@uiw/react-md-editor'; import { useEffect, useState } from 'react'; import HttpService from '../services/HttpService'; +import MarkdownRenderer from './MarkdownRenderer'; type OwnProps = { apiPath: string; @@ -24,9 +24,11 @@ export default function MarkdownDisplayForFile({ apiPath }: OwnProps) { if (markdownContents) { return ( -
- -
+ ); } diff --git a/spiffworkflow-frontend/src/components/MarkdownRenderer.tsx b/spiffworkflow-frontend/src/components/MarkdownRenderer.tsx new file mode 100644 index 00000000..f4d52ad2 --- /dev/null +++ b/spiffworkflow-frontend/src/components/MarkdownRenderer.tsx @@ -0,0 +1,16 @@ +import MDEditor from '@uiw/react-md-editor'; + +export default function MarkdownRenderer(props: any) { + let wrapperClassName = ''; + const propsToUse = props; + if ('wrapperClassName' in propsToUse) { + wrapperClassName = propsToUse.wrapperClassName; + delete propsToUse.wrapperClassName; + } + return ( +
+ {/* eslint-disable-next-line react/jsx-props-no-spreading */} + +
+ ); +} diff --git a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx index 95e6a517..c8c53cf2 100644 --- a/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx +++ b/spiffworkflow-frontend/src/components/ProcessInstanceRun.tsx @@ -66,20 +66,21 @@ const storeRecentProcessModelInLocalStorage = ( type OwnProps = { processModel: ProcessModel; - onSuccessCallback: Function; className?: string; checkPermissions?: boolean; + buttonText?: string; }; export default function ProcessInstanceRun({ processModel, - onSuccessCallback, className, checkPermissions = true, + buttonText = 'Start', }: OwnProps) { const navigate = useNavigate(); const { addError, removeError } = useAPIError(); const [disableStartButton, setDisableStartButton] = useState(false); + const modifiedProcessModelId = modifyProcessIdentifierForPathParam( processModel.id ); @@ -98,16 +99,15 @@ export default function ProcessInstanceRun({ const onProcessInstanceRun = (processInstance: any) => { const processInstanceId = (processInstance as any).id; navigate( - `/process-instances/for-me/${modifyProcessIdentifierForPathParam( - processModel.id - )}/${processInstanceId}/interstitial` + `/process-instances/for-me/${modifiedProcessModelId}/${processInstanceId}/interstitial` ); - onSuccessCallback(processInstance); }; const processModelRun = (processInstance: any) => { removeError(); - storeRecentProcessModelInLocalStorage(processModel); + if (processModel) { + storeRecentProcessModelInLocalStorage(processModel); + } HttpService.makeCallToBackend({ path: `/process-instances/${modifiedProcessModelId}/${processInstance.id}/run`, successCallback: onProcessInstanceRun, @@ -141,7 +141,7 @@ export default function ProcessInstanceRun({ disabled={disableStartButton} size="md" > - Start + {buttonText} ); diff --git a/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx b/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx index d92c992b..5c55b5ec 100644 --- a/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx +++ b/spiffworkflow-frontend/src/components/ProcessModelListTiles.tsx @@ -1,17 +1,16 @@ import { ReactElement, useEffect, useState } from 'react'; -import { Link, useSearchParams } from 'react-router-dom'; +import { useSearchParams } from 'react-router-dom'; import { Tile, // @ts-ignore } from '@carbon/react'; import HttpService from '../services/HttpService'; -import { ProcessModel, ProcessInstance, ProcessGroup } from '../interfaces'; +import { ProcessModel, ProcessGroup } from '../interfaces'; import { modifyProcessIdentifierForPathParam, truncateString, } from '../helpers'; import ProcessInstanceRun from './ProcessInstanceRun'; -import { Notification } from './Notification'; type OwnProps = { headerElement?: ReactElement; @@ -28,8 +27,6 @@ export default function ProcessModelListTiles({ const [processModels, setProcessModels] = useState( null ); - const [processInstance, setProcessInstance] = - useState(null); useEffect(() => { const setProcessModelsFromResult = (result: any) => { @@ -48,27 +45,6 @@ export default function ProcessModelListTiles({ }); }, [searchParams, processGroup]); - const processInstanceRunResultTag = () => { - if (processInstance) { - return ( - setProcessInstance(null)} - > - - view - - - ); - } - return null; - }; - const processModelsDisplayArea = () => { let displayText = null; if (processModels && processModels.length > 0) { @@ -95,7 +71,6 @@ export default function ProcessModelListTiles({

@@ -114,7 +89,6 @@ export default function ProcessModelListTiles({ return ( <> {headerElement} - {processInstanceRunResultTag()} {processModelsDisplayArea()} ); diff --git a/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts b/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts index 980e79d4..cb6959b6 100644 --- a/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts +++ b/spiffworkflow-frontend/src/extension_ui_schema_interfaces.ts @@ -37,10 +37,16 @@ export interface UiSchemaAction { full_api_path?: boolean; } +export interface UiSchemaPageComponent { + name: string; + arguments: object; +} + export interface UiSchemaPageDefinition { header: string; api: string; + components?: UiSchemaPageComponent[]; form?: UiSchemaForm; markdown_instruction_filename?: string; navigate_instead_of_post_to_api?: boolean; diff --git a/spiffworkflow-frontend/src/interfaces.ts b/spiffworkflow-frontend/src/interfaces.ts index dbd3eb07..311d6eb8 100644 --- a/spiffworkflow-frontend/src/interfaces.ts +++ b/spiffworkflow-frontend/src/interfaces.ts @@ -146,6 +146,9 @@ export interface ProcessReference { } export type ObjectWithStringKeysAndValues = { [key: string]: string }; +export type ObjectWithStringKeysAndFunctionValues = { + [key: string]: Function; +}; export interface FilterOperator { id: string; diff --git a/spiffworkflow-frontend/src/routes/Extension.tsx b/spiffworkflow-frontend/src/routes/Extension.tsx index 7b302418..aa83f09c 100644 --- a/spiffworkflow-frontend/src/routes/Extension.tsx +++ b/spiffworkflow-frontend/src/routes/Extension.tsx @@ -1,10 +1,13 @@ import { useCallback, useEffect, useState } from 'react'; import { Button } from '@carbon/react'; -import MDEditor from '@uiw/react-md-editor'; import { useParams, useSearchParams } from 'react-router-dom'; import { Editor } from '@monaco-editor/react'; import { useUriListForPermissions } from '../hooks/UriListForPermissions'; -import { ProcessFile, ProcessModel } from '../interfaces'; +import { + ObjectWithStringKeysAndFunctionValues, + ProcessFile, + ProcessModel, +} from '../interfaces'; import HttpService from '../services/HttpService'; import useAPIError from '../hooks/UseApiError'; import { recursivelyChangeNullAndUndefined } from '../helpers'; @@ -13,10 +16,13 @@ import { BACKEND_BASE_URL } from '../config'; import { ExtensionPostBody, ExtensionUiSchema, + UiSchemaPageComponent, UiSchemaPageDefinition, } from '../extension_ui_schema_interfaces'; import ErrorDisplay from '../components/ErrorDisplay'; import FormattingService from '../services/FormattingService'; +import ProcessInstanceRun from '../components/ProcessInstanceRun'; +import MarkdownRenderer from '../components/MarkdownRenderer'; type OwnProps = { displayErrors?: boolean; @@ -43,9 +49,16 @@ export default function Extension({ displayErrors = true }: OwnProps) { }>({}); const [uiSchemaPageDefinition, setUiSchemaPageDefinition] = useState(null); + const [readyForComponentsToDisplay, setReadyForComponentsToDisplay] = + useState(false); const { addError, removeError } = useAPIError(); + const supportedComponents: ObjectWithStringKeysAndFunctionValues = { + ProcessInstanceRun, + MarkdownRenderer, + }; + const setConfigsIfDesiredSchemaFile = useCallback( // eslint-disable-next-line sonarjs/cognitive-complexity (extensionUiSchemaFile: ProcessFile | null, pm: ProcessModel) => { @@ -57,6 +70,7 @@ export default function Extension({ displayErrors = true }: OwnProps) { ); setMarkdownToRenderOnLoad(newMarkdown); } + setReadyForComponentsToDisplay(true); }; if ( @@ -98,6 +112,8 @@ export default function Extension({ displayErrors = true }: OwnProps) { httpMethod: 'POST', postBody, }); + } else { + setReadyForComponentsToDisplay(true); } } } @@ -238,7 +254,7 @@ export default function Extension({ displayErrors = true }: OwnProps) { } }; - if (uiSchemaPageDefinition) { + if (readyForComponentsToDisplay && uiSchemaPageDefinition) { const componentsToDisplay = [

{uiSchemaPageDefinition.header}

]; const markdownContentsToRender = []; @@ -261,12 +277,28 @@ export default function Extension({ displayErrors = true }: OwnProps) { if (markdownContentsToRender.length > 0) { componentsToDisplay.push( -
- -
+ + ); + } + + if (uiSchemaPageDefinition.components) { + uiSchemaPageDefinition.components.forEach( + (component: UiSchemaPageComponent) => { + if (supportedComponents[component.name]) { + const argumentsForComponent: any = component.arguments; + componentsToDisplay.push( + supportedComponents[component.name](argumentsForComponent) + ); + } else { + console.error( + `Extension tried to use component with name '${component.name}' but that is not allowed.` + ); + } + } ); } @@ -304,13 +336,12 @@ export default function Extension({ displayErrors = true }: OwnProps) { if (processedTaskData) { if (markdownToRenderOnSubmit) { componentsToDisplay.push( -
- -
+ ); } else { componentsToDisplay.push( diff --git a/spiffworkflow-frontend/src/routes/MyTasks.tsx b/spiffworkflow-frontend/src/routes/MyTasks.tsx index 6c4c925d..a62eb1b7 100644 --- a/spiffworkflow-frontend/src/routes/MyTasks.tsx +++ b/spiffworkflow-frontend/src/routes/MyTasks.tsx @@ -2,7 +2,6 @@ import { useEffect, useState } from 'react'; // @ts-ignore import { Button, Table } from '@carbon/react'; import { Link, useSearchParams } from 'react-router-dom'; -import { Notification } from '../components/Notification'; import PaginationForTable from '../components/PaginationForTable'; import { getPageInfoFromSearchParams, @@ -12,7 +11,6 @@ import { import HttpService from '../services/HttpService'; import { PaginationObject, - ProcessInstance, ProcessModel, RecentProcessModel, } from '../interfaces'; @@ -25,8 +23,6 @@ export default function MyTasks() { const [searchParams] = useSearchParams(); const [tasks, setTasks] = useState([]); const [pagination, setPagination] = useState(null); - const [processInstance, setProcessInstance] = - useState(null); useEffect(() => { const getTasks = () => { @@ -52,27 +48,6 @@ export default function MyTasks() { ); }, [searchParams]); - const processInstanceRunResultTag = () => { - if (processInstance) { - return ( - setProcessInstance(null)} - > - - view - - - ); - } - return null; - }; - let recentProcessModels: RecentProcessModel[] = []; const recentProcessModelsString = localStorage.getItem('recentProcessModels'); if (recentProcessModelsString !== null) { @@ -163,10 +138,7 @@ export default function MyTasks() { - + ); @@ -220,7 +192,6 @@ export default function MyTasks() { } return ( <> - {processInstanceRunResultTag()} {tasksWaitingForMe}
{relevantProcessModelSection} diff --git a/spiffworkflow-frontend/src/routes/OnboardingView.tsx b/spiffworkflow-frontend/src/routes/OnboardingView.tsx index 5903b26c..84c2334a 100644 --- a/spiffworkflow-frontend/src/routes/OnboardingView.tsx +++ b/spiffworkflow-frontend/src/routes/OnboardingView.tsx @@ -1,9 +1,9 @@ import React, { useEffect, useState } from 'react'; -import MDEditor from '@uiw/react-md-editor'; import { useLocation, useNavigate } from 'react-router-dom'; import HttpService from '../services/HttpService'; import { Onboarding } from '../interfaces'; import { objectIsEmpty } from '../helpers'; +import MarkdownRenderer from '../components/MarkdownRenderer'; export default function OnboardingView() { const [onboarding, setOnboarding] = useState(null); @@ -40,13 +40,11 @@ export default function OnboardingView() { onboarding.instructions.length > 0 ) { return ( -
- -
+ ); } return null; diff --git a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx index bb89c251..af00ad00 100644 --- a/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx +++ b/spiffworkflow-frontend/src/routes/ProcessModelShow.tsx @@ -38,12 +38,7 @@ import { modifyProcessIdentifierForPathParam, setPageTitle, } from '../helpers'; -import { - PermissionsToCheck, - ProcessFile, - ProcessInstance, - ProcessModel, -} from '../interfaces'; +import { PermissionsToCheck, ProcessFile, ProcessModel } from '../interfaces'; import ButtonWithConfirmation from '../components/ButtonWithConfirmation'; import ProcessInstanceListTable from '../components/ProcessInstanceListTable'; import { usePermissionFetcher } from '../hooks/PermissionService'; @@ -59,8 +54,6 @@ export default function ProcessModelShow() { const navigate = useNavigate(); const [processModel, setProcessModel] = useState(null); - const [processInstance, setProcessInstance] = - useState(null); const [reloadModel, setReloadModel] = useState(false); const [filesToUpload, setFilesToUpload] = useState(null); const [showFileUploadModal, setShowFileUploadModal] = @@ -120,25 +113,6 @@ export default function ProcessModelShow() { }); }, [reloadModel, modifiedProcessModelId]); - const processInstanceRunResultTag = () => { - if (processInstance) { - return ( - setProcessInstance(null)} - > - - view - - - ); - } - return null; - }; - const onUploadedCallback = () => { setReloadModel(true); }; @@ -729,10 +703,7 @@ export default function ProcessModelShow() { ability={ability} > <> - +

@@ -753,7 +724,6 @@ export default function ProcessModelShow() { ]} /> {processModelPublishMessage()} - {processInstanceRunResultTag()}

Process Model: {processModel.display_name} diff --git a/spiffworkflow-frontend/src/routes/TaskShow.tsx b/spiffworkflow-frontend/src/routes/TaskShow.tsx index b5abcdff..82ebac66 100644 --- a/spiffworkflow-frontend/src/routes/TaskShow.tsx +++ b/spiffworkflow-frontend/src/routes/TaskShow.tsx @@ -4,7 +4,6 @@ import { useNavigate, useParams } from 'react-router-dom'; import { Grid, Column, Button, ButtonSet, Loading } from '@carbon/react'; import { useDebouncedCallback } from 'use-debounce'; -import MDEditor from '@uiw/react-md-editor'; import HttpService from '../services/HttpService'; import useAPIError from '../hooks/UseApiError'; import { @@ -23,6 +22,7 @@ import CustomForm from '../components/CustomForm'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import InstructionsForEndUser from '../components/InstructionsForEndUser'; import UserService from '../services/UserService'; +import MarkdownRenderer from '../components/MarkdownRenderer'; export default function TaskShow() { // get a basic task which doesn't get the form data so we can load @@ -397,9 +397,7 @@ export default function TaskShow() { if (guestConfirmationText) { pageElements.push( -
- -
+ ); } else if (basicTask && taskData) { pageElements.push();