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( );