Feature/upgrade react router dom (#1050)

* updated react-router-dom to match react-router version w/ burnettk

* disable save button on process model edit diagram page unless a change has been made w/ burnettk

* remove web components from form data on extensions page to avoid potential errors w/ burnettk

* updates based on coderabbit w/ burnettk

* fixed cypress issues

---------

Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
jasquat 2024-02-16 11:01:26 -05:00 committed by GitHub
parent 9b8cb58e99
commit 73df645408
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 152 additions and 178 deletions

View File

@ -36,7 +36,7 @@ const updateDmnText = (oldText, newText, elementId = 'wonderful_process') => {
// wait for a little bit for the xml to get set before saving // wait for a little bit for the xml to get set before saving
// FIXME: gray out save button or add spinner while xml is loading // FIXME: gray out save button or add spinner while xml is loading
cy.wait(500); cy.wait(500);
cy.contains('Save').click(); cy.getBySel('process-model-file-save-button').click();
}; };
const updateBpmnPythonScript = (pythonScript, elementId = 'process_script') => { const updateBpmnPythonScript = (pythonScript, elementId = 'process_script') => {
@ -47,7 +47,7 @@ const updateBpmnPythonScript = (pythonScript, elementId = 'process_script') => {
// wait for a little bit for the xml to get set before saving // wait for a little bit for the xml to get set before saving
cy.wait(500); cy.wait(500);
cy.contains('Save').click(); cy.getBySel('process-model-file-save-button').click();
}; };
// NOTE: anytime the status dropdown box is clicked on, click off of it // NOTE: anytime the status dropdown box is clicked on, click off of it

View File

@ -82,7 +82,8 @@ describe('process-models', () => {
cy.get('#bio-properties-panel-name').clear(); cy.get('#bio-properties-panel-name').clear();
cy.get('#bio-properties-panel-name').type('Start Event Name'); cy.get('#bio-properties-panel-name').type('Start Event Name');
cy.wait(500); cy.wait(500);
cy.contains('Save').click(); cy.getBySel('process-model-file-changed');
cy.getBySel('process-model-file-save-button').click();
cy.contains('Start Event Name'); cy.contains('Start Event Name');
cy.get(fileNameInputSelector).type(bpmnFileName); cy.get(fileNameInputSelector).type(bpmnFileName);
cy.contains(saveChangesButtonText).click(); cy.contains(saveChangesButtonText).click();
@ -101,7 +102,7 @@ describe('process-models', () => {
cy.get('#bio-properties-panel-id').clear(); cy.get('#bio-properties-panel-id').clear();
cy.get('#bio-properties-panel-id').type(decisionAcceptanceTestId); cy.get('#bio-properties-panel-id').type(decisionAcceptanceTestId);
cy.contains('General').click(); cy.contains('General').click();
cy.contains('Save').click(); cy.getBySel('process-model-file-save-button').click();
cy.get(fileNameInputSelector).type(dmnFileName); cy.get(fileNameInputSelector).type(dmnFileName);
cy.contains(saveChangesButtonText).click(); cy.contains(saveChangesButtonText).click();
cy.contains(`Process Model File: ${dmnFileName}`); cy.contains(`Process Model File: ${dmnFileName}`);

View File

@ -56,7 +56,7 @@
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-jsonschema-form": "^1.8.1", "react-jsonschema-form": "^1.8.1",
"react-router": "^6.22.0", "react-router": "^6.22.0",
"react-router-dom": "6.3.0", "react-router-dom": "^6.22.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"serve": "^14.0.0", "serve": "^14.0.0",
"timepicker": "^1.13.18", "timepicker": "^1.13.18",
@ -15604,14 +15604,6 @@
"he": "bin/he" "he": "bin/he"
} }
}, },
"node_modules/history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
"dependencies": {
"@babel/runtime": "^7.7.6"
}
},
"node_modules/hmac-drbg": { "node_modules/hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -26244,29 +26236,21 @@
} }
}, },
"node_modules/react-router-dom": { "node_modules/react-router-dom": {
"version": "6.3.0", "version": "6.22.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz",
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==",
"dependencies": { "dependencies": {
"history": "^5.2.0", "@remix-run/router": "1.15.0",
"react-router": "6.3.0" "react-router": "6.22.0"
},
"engines": {
"node": ">=14.0.0"
}, },
"peerDependencies": { "peerDependencies": {
"react": ">=16.8", "react": ">=16.8",
"react-dom": ">=16.8" "react-dom": ">=16.8"
} }
}, },
"node_modules/react-router-dom/node_modules/react-router": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
"dependencies": {
"history": "^5.2.0"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-scripts": { "node_modules/react-scripts": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz", "resolved": "https://registry.npmjs.org/react-scripts/-/react-scripts-5.0.1.tgz",
@ -45705,14 +45689,6 @@
"resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
"integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
}, },
"history": {
"version": "5.3.0",
"resolved": "https://registry.npmjs.org/history/-/history-5.3.0.tgz",
"integrity": "sha512-ZqaKwjjrAYUYfLG+htGaIIZ4nioX2L70ZUMIFysS3xvBsSG4x/n1V6TXV3N8ZYNuFGlDirFg32T7B6WOUPDYcQ==",
"requires": {
"@babel/runtime": "^7.7.6"
}
},
"hmac-drbg": { "hmac-drbg": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz",
@ -53463,22 +53439,12 @@
} }
}, },
"react-router-dom": { "react-router-dom": {
"version": "6.3.0", "version": "6.22.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.3.0.tgz", "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.22.0.tgz",
"integrity": "sha512-uaJj7LKytRxZNQV8+RbzJWnJ8K2nPsOOEuX7aQstlMZKQT0164C+X2w6bnkqU3sjtLvpd5ojrezAyfZ1+0sStw==", "integrity": "sha512-z2w+M4tH5wlcLmH3BMMOMdrtrJ9T3oJJNsAlBJbwk+8Syxd5WFJ7J5dxMEW0/GEXD1BBis4uXRrNIz3mORr0ag==",
"requires": { "requires": {
"history": "^5.2.0", "@remix-run/router": "1.15.0",
"react-router": "6.3.0" "react-router": "6.22.0"
},
"dependencies": {
"react-router": {
"version": "6.3.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.3.0.tgz",
"integrity": "sha512-7Wh1DzVQ+tlFjkeo+ujvjSqSJmkt1+8JO+T5xklPlgrh70y7ogx75ODRW0ThWhY7S+6yEDks8TYrtQe/aoboBQ==",
"requires": {
"history": "^5.2.0"
}
}
} }
}, },
"react-scripts": { "react-scripts": {

View File

@ -51,7 +51,7 @@
"react-icons": "^5.0.1", "react-icons": "^5.0.1",
"react-jsonschema-form": "^1.8.1", "react-jsonschema-form": "^1.8.1",
"react-router": "^6.22.0", "react-router": "^6.22.0",
"react-router-dom": "6.3.0", "react-router-dom": "^6.22.0",
"react-scripts": "^5.0.1", "react-scripts": "^5.0.1",
"serve": "^14.0.0", "serve": "^14.0.0",
"timepicker": "^1.13.18", "timepicker": "^1.13.18",

View File

@ -1,4 +1,3 @@
import { BrowserRouter } from 'react-router-dom';
import { defineAbility } from '@casl/ability'; import { defineAbility } from '@casl/ability';
import React from 'react'; import React from 'react';
@ -10,14 +9,11 @@ export default function App() {
const ability = defineAbility(() => {}); const ability = defineAbility(() => {});
return ( return (
<div className="cds--white"> <div className="cds--white">
{/* @ts-ignore */}
<AbilityContext.Provider value={ability}>
<APIErrorProvider> <APIErrorProvider>
<BrowserRouter> <AbilityContext.Provider value={ability}>
<ContainerForExtensions /> <ContainerForExtensions />
</BrowserRouter>
</APIErrorProvider>
</AbilityContext.Provider> </AbilityContext.Provider>
</APIErrorProvider>
</div> </div>
); );
} }

View File

@ -1,5 +1,5 @@
import { Content } from '@carbon/react'; import { Content } from '@carbon/react';
import { Routes, Route } from 'react-router-dom'; import { createBrowserRouter, RouterProvider, Outlet } from 'react-router-dom';
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { ErrorBoundary } from 'react-error-boundary'; import { ErrorBoundary } from 'react-error-boundary';
@ -100,33 +100,32 @@ export default function ContainerForExtensions() {
]); ]);
const routeComponents = () => { const routeComponents = () => {
return ( return [
<Routes> {
<Route path: '*',
path="/*" element: <BaseRoutes extensionUxElements={extensionUxElements} />,
element={<BaseRoutes extensionUxElements={extensionUxElements} />} },
/> { path: 'editor/*', element: <EditorRoutes /> },
<Route path="/editor/*" element={<EditorRoutes />} /> { path: 'extensions/:page_identifier', element: <Extension /> },
<Route path="/extensions/:page_identifier" element={<Extension />} /> { path: 'login', element: <Login /> },
<Route path="/login" element={<Login />} /> ];
</Routes>
);
}; };
const backendIsDownPage = () => { const backendIsDownPage = () => {
return <BackendIsDown />; return [<BackendIsDown />];
}; };
const innerComponents = () => { const innerComponents = () => {
if (backendIsUp === null) { if (backendIsUp === null) {
return null; return [];
} }
if (backendIsUp) { if (backendIsUp) {
return routeComponents(); return <Outlet />;
} }
return backendIsDownPage(); return backendIsDownPage();
}; };
const layout = () => {
return ( return (
<> <>
<NavigationBar extensionUxElements={extensionUxElements} />; <NavigationBar extensionUxElements={extensionUxElements} />;
@ -138,4 +137,14 @@ export default function ContainerForExtensions() {
</Content> </Content>
</> </>
); );
};
const router = createBrowserRouter([
{
path: '*',
Component: layout,
children: routeComponents(),
},
]);
return <RouterProvider router={router} />;
} }

View File

@ -1,6 +1,9 @@
import MDEditor from '@uiw/react-md-editor'; import MDEditor from '@uiw/react-md-editor';
import FormattingService from '../services/FormattingService';
export default function MarkdownRenderer(props: any) { export default function MarkdownRenderer(props: any) {
const { source } = props;
const newMarkdown = FormattingService.checkForSpiffFormats(source);
let wrapperClassName = ''; let wrapperClassName = '';
const propsToUse = props; const propsToUse = props;
if ('wrapperClassName' in propsToUse) { if ('wrapperClassName' in propsToUse) {
@ -10,7 +13,7 @@ export default function MarkdownRenderer(props: any) {
return ( return (
<div data-color-mode="light" className={wrapperClassName}> <div data-color-mode="light" className={wrapperClassName}>
{/* eslint-disable-next-line react/jsx-props-no-spreading */} {/* eslint-disable-next-line react/jsx-props-no-spreading */}
<MDEditor.Markdown {...propsToUse} /> <MDEditor.Markdown {...{ ...propsToUse, ...{ source: newMarkdown } }} />
</div> </div>
); );
} }

View File

@ -7,6 +7,7 @@ import {
} from '@carbon/icons-react'; } from '@carbon/icons-react';
// @ts-ignore // @ts-ignore
import { Button } from '@carbon/react'; import { Button } from '@carbon/react';
import { ObjectWithStringKeysAndValues } from '../interfaces';
type OwnProps = { type OwnProps = {
title: string; title: string;
@ -17,6 +18,7 @@ type OwnProps = {
allowTogglingFullMessage?: boolean; allowTogglingFullMessage?: boolean;
timeout?: number; timeout?: number;
withBottomMargin?: boolean; withBottomMargin?: boolean;
'data-qa'?: string;
}; };
export function Notification({ export function Notification({
@ -28,6 +30,7 @@ export function Notification({
allowTogglingFullMessage = false, allowTogglingFullMessage = false,
timeout, timeout,
withBottomMargin = true, withBottomMargin = true,
'data-qa': dataQa,
}: OwnProps) { }: OwnProps) {
const [showMessage, setShowMessage] = useState<boolean>( const [showMessage, setShowMessage] = useState<boolean>(
!allowTogglingFullMessage !allowTogglingFullMessage
@ -48,8 +51,15 @@ export function Notification({
classes = `${classes} with-bottom-margin`; classes = `${classes} with-bottom-margin`;
} }
const additionalProps: ObjectWithStringKeysAndValues = {};
if (dataQa) {
additionalProps['data-qa'] = dataQa;
}
return ( return (
<div role="status" className={classes}> // we control the props added to the variable so we know it's fine
// eslint-disable-next-line react/jsx-props-no-spreading
<div role="status" className={classes} {...additionalProps}>
<div className="cds--inline-notification__details"> <div className="cds--inline-notification__details">
<div className="cds--inline-notification__text-wrapper"> <div className="cds--inline-notification__text-wrapper">
{iconComponent} {iconComponent}

View File

@ -91,6 +91,7 @@ type OwnProps = {
url?: string; url?: string;
callers?: ProcessReference[]; callers?: ProcessReference[];
activeUserElement?: React.ReactElement; activeUserElement?: React.ReactElement;
disableSaveButton?: boolean;
}; };
const FitViewport = 'fit-viewport'; const FitViewport = 'fit-viewport';
@ -121,6 +122,7 @@ export default function ReactDiagramEditor({
url, url,
callers, callers,
activeUserElement, activeUserElement,
disableSaveButton,
}: OwnProps) { }: OwnProps) {
const [diagramXMLString, setDiagramXMLString] = useState(''); const [diagramXMLString, setDiagramXMLString] = useState('');
const [diagramModelerState, setDiagramModelerState] = useState(null); const [diagramModelerState, setDiagramModelerState] = useState(null);
@ -708,7 +710,13 @@ export default function ReactDiagramEditor({
a={targetUris.processModelFileShowPath} a={targetUris.processModelFileShowPath}
ability={ability} ability={ability}
> >
<Button onClick={handleSave}>Save</Button> <Button
onClick={handleSave}
disabled={disableSaveButton}
data-qa="process-model-file-save-button"
>
Save
</Button>
</Can> </Can>
<Can <Can
I="DELETE" I="DELETE"

View File

@ -4,6 +4,8 @@
* See below for more details. * See below for more details.
*/ */
import { FunctionComponent } from 'react';
// Current version of the extension uischema. // Current version of the extension uischema.
export type ExtensionUiSchemaVersion = '0.1' | '0.2'; export type ExtensionUiSchemaVersion = '0.1' | '0.2';
@ -210,4 +212,8 @@ export interface ExtensionApiResponse {
// The markdown string rendered from the process model. // The markdown string rendered from the process model.
rendered_results_markdown?: string; rendered_results_markdown?: string;
} }
export type SupportedComponentList = {
[key in keyof typeof UiSchemaPageComponentList]: FunctionComponent<any>;
};
/** ************************************* */ /** ************************************* */

View File

@ -1,62 +0,0 @@
/**
* These hooks re-implement the now removed useBlocker and usePrompt hooks in 'react-router-dom'.
* Thanks for the idea @piecyk https://github.com/remix-run/react-router/issues/8139#issuecomment-953816315
* Source: https://github.com/remix-run/react-router/commit/256cad70d3fd4500b1abcfea66f3ee622fb90874#diff-b60f1a2d4276b2a605c05e19816634111de2e8a4186fe9dd7de8e344b65ed4d3L344-L381
*/
import { useCallback, useContext, useEffect } from 'react';
import { UNSAFE_NavigationContext as NavigationContext } from 'react-router-dom';
/**
* Blocks all navigation attempts. This is useful for preventing the page from
* changing until some condition is met, like saving form data.
*
* @param blocker
* @param when
* @see https://reactrouter.com/api/useBlocker
*/
export function useBlocker(blocker: any, when: any = true) {
const { navigator } = useContext(NavigationContext);
useEffect(() => {
if (!when) {
return undefined;
}
const unblock = (navigator as any).block((tx: any) => {
const autoUnblockingTx = {
...tx,
retry() {
// Automatically unblock the transition so it can play all the way
// through before retrying it. TODO: Figure out how to re-enable
// this block if the transition is cancelled for some reason.
unblock();
tx.retry();
},
};
blocker(autoUnblockingTx);
});
return unblock;
}, [navigator, blocker, when]);
}
/**
* Prompts the user with an Alert before they leave the current screen.
*
* @param message
* @param when
*/
export function usePrompt(message: any, when: any = true) {
const blocker = useCallback(
(tx: any) => {
// eslint-disable-next-line no-alert
if (window.confirm(message)) {
tx.retry();
}
},
[message]
);
useBlocker(blocker, when);
}

View File

@ -148,9 +148,6 @@ export interface ProcessReference {
} }
export type ObjectWithStringKeysAndValues = { [key: string]: string }; export type ObjectWithStringKeysAndValues = { [key: string]: string };
export type ObjectWithStringKeysAndFunctionValues = {
[key: string]: Function;
};
export interface FilterOperator { export interface FilterOperator {
id: string; id: string;

View File

@ -1,9 +1,9 @@
import { Routes, Route, useLocation } from 'react-router-dom'; import { useLocation, Routes, Route } from 'react-router-dom';
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import ProcessModelEditDiagram from './ProcessModelEditDiagram';
import ErrorDisplay from '../components/ErrorDisplay'; import ErrorDisplay from '../components/ErrorDisplay';
import LoginHandler from '../components/LoginHandler'; import LoginHandler from '../components/LoginHandler';
import ProcessModelEditDiagram from './ProcessModelEditDiagram';
export default function EditorRoutes() { export default function EditorRoutes() {
const location = useLocation(); const location = useLocation();

View File

@ -1,12 +1,8 @@
import { useCallback, useEffect, useState } from 'react'; import { createElement, useCallback, useEffect, useState } from 'react';
import { useParams, useSearchParams } from 'react-router-dom'; import { useParams, useSearchParams } from 'react-router-dom';
import { Editor } from '@monaco-editor/react'; import { Editor } from '@monaco-editor/react';
import { useUriListForPermissions } from '../hooks/UriListForPermissions'; import { useUriListForPermissions } from '../hooks/UriListForPermissions';
import { import { ProcessFile, ProcessModel } from '../interfaces';
ObjectWithStringKeysAndFunctionValues,
ProcessFile,
ProcessModel,
} from '../interfaces';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import useAPIError from '../hooks/UseApiError'; import useAPIError from '../hooks/UseApiError';
import { recursivelyChangeNullAndUndefined, makeid } from '../helpers'; import { recursivelyChangeNullAndUndefined, makeid } from '../helpers';
@ -16,6 +12,7 @@ import {
ExtensionApiResponse, ExtensionApiResponse,
ExtensionPostBody, ExtensionPostBody,
ExtensionUiSchema, ExtensionUiSchema,
SupportedComponentList,
UiSchemaPageComponent, UiSchemaPageComponent,
UiSchemaPageDefinition, UiSchemaPageDefinition,
} from '../extension_ui_schema_interfaces'; } from '../extension_ui_schema_interfaces';
@ -65,7 +62,7 @@ export default function Extension({
const { addError, removeError } = useAPIError(); const { addError, removeError } = useAPIError();
const supportedComponents: ObjectWithStringKeysAndFunctionValues = { const supportedComponents: SupportedComponentList = {
CreateNewInstance, CreateNewInstance,
CustomForm, CustomForm,
MarkdownRenderer, MarkdownRenderer,
@ -98,7 +95,6 @@ export default function Extension({
); );
const processLoadResult = useCallback( const processLoadResult = useCallback(
(result: ExtensionApiResponse, pageDefinition: UiSchemaPageDefinition) => { (result: ExtensionApiResponse, pageDefinition: UiSchemaPageDefinition) => {
setFormData(result.task_data);
if (pageDefinition.navigate_to_on_load) { if (pageDefinition.navigate_to_on_load) {
const optionString = interpolateNavigationString( const optionString = interpolateNavigationString(
pageDefinition.navigate_to_on_load, pageDefinition.navigate_to_on_load,
@ -114,6 +110,8 @@ export default function Extension({
); );
setMarkdownToRenderOnLoad(newMarkdown); setMarkdownToRenderOnLoad(newMarkdown);
} }
const taskDataCopy = { ...result.task_data };
if ( if (
pageDefinition.on_load && pageDefinition.on_load &&
pageDefinition.on_load.ui_schema_page_components_variable pageDefinition.on_load.ui_schema_page_components_variable
@ -123,7 +121,18 @@ export default function Extension({
pageDefinition.on_load.ui_schema_page_components_variable pageDefinition.on_load.ui_schema_page_components_variable
] ]
); );
// we were getting any AJV8Validator error when we had this data in the task data
// when we attempted to submit a form using this task data.
// The error was:
// Uncaught RangeError: Maximum call stack size exceeded
//
// Removing the ui schema page components dictionary seems to resolve it.
delete taskDataCopy[
pageDefinition.on_load.ui_schema_page_components_variable
];
} }
setFormData(taskDataCopy);
setReadyForComponentsToDisplay(true); setReadyForComponentsToDisplay(true);
}, },
[interpolateNavigationString] [interpolateNavigationString]
@ -303,7 +312,7 @@ export default function Extension({
// eslint-disable-next-line sonarjs/cognitive-complexity // eslint-disable-next-line sonarjs/cognitive-complexity
const renderComponentArguments = (component: UiSchemaPageComponent) => { const renderComponentArguments = (component: UiSchemaPageComponent) => {
const argumentsForComponent: any = component.arguments; const argumentsForComponent: any = component.arguments;
if (processModel) { if (processModel && argumentsForComponent) {
Object.keys(argumentsForComponent).forEach((argName: string) => { Object.keys(argumentsForComponent).forEach((argName: string) => {
const argValue = argumentsForComponent[argName]; const argValue = argumentsForComponent[argName];
if ( if (
@ -368,7 +377,10 @@ export default function Extension({
if (supportedComponents[componentName]) { if (supportedComponents[componentName]) {
const argumentsForComponent = renderComponentArguments(component); const argumentsForComponent = renderComponentArguments(component);
componentsToDisplay.push( componentsToDisplay.push(
supportedComponents[componentName](argumentsForComponent) createElement(
supportedComponents[componentName],
argumentsForComponent
)
); );
} else { } else {
console.error( console.error(

View File

@ -47,7 +47,6 @@ import {
} from '../interfaces'; } from '../interfaces';
import ProcessSearch from '../components/ProcessSearch'; import ProcessSearch from '../components/ProcessSearch';
import { Notification } from '../components/Notification'; import { Notification } from '../components/Notification';
import { usePrompt } from '../hooks/UsePrompt';
import ActiveUsers from '../components/ActiveUsers'; import ActiveUsers from '../components/ActiveUsers';
import { useFocusedTabStatus } from '../hooks/useFocusedTabStatus'; import { useFocusedTabStatus } from '../hooks/useFocusedTabStatus';
@ -137,8 +136,6 @@ export default function ProcessModelEditDiagram() {
const [callers, setCallers] = useState<ProcessReference[]>([]); const [callers, setCallers] = useState<ProcessReference[]>([]);
usePrompt('Changes you made may not be saved.', diagramHasChanges);
const getProcessesCallback = useCallback((onProcessesFetched?: Function) => { const getProcessesCallback = useCallback((onProcessesFetched?: Function) => {
const processResults = (result: any) => { const processResults = (result: any) => {
const selectionArray = result.map((item: any) => { const selectionArray = result.map((item: any) => {
@ -1054,7 +1051,7 @@ export default function ProcessModelEditDiagram() {
path = generatePath( path = generatePath(
'/editor/process-models/:process_model_id/files/:file_name', '/editor/process-models/:process_model_id/files/:file_name',
{ {
process_model_id: params.process_model_id, process_model_id: params.process_model_id || null,
file_name: file.name, file_name: file.name,
} }
); );
@ -1063,7 +1060,7 @@ export default function ProcessModelEditDiagram() {
path = generatePath( path = generatePath(
'/editor/process-models/:process_model_id/files?file_type=dmn', '/editor/process-models/:process_model_id/files?file_type=dmn',
{ {
process_model_id: params.process_model_id, process_model_id: params.process_model_id || null,
} }
); );
} }
@ -1121,6 +1118,7 @@ export default function ProcessModelEditDiagram() {
onElementsChanged={onElementsChanged} onElementsChanged={onElementsChanged}
callers={callers} callers={callers}
activeUserElement={<ActiveUsers />} activeUserElement={<ActiveUsers />}
disableSaveButton={!diagramHasChanges}
/> />
); );
}; };
@ -1131,6 +1129,8 @@ export default function ProcessModelEditDiagram() {
<Notification <Notification
title="File Saved: " title="File Saved: "
onClose={() => setDisplaySaveFileMessage(false)} onClose={() => setDisplaySaveFileMessage(false)}
hideCloseButton
timeout={3000}
> >
Changes to the file were saved. Changes to the file were saved.
</Notification> </Notification>
@ -1139,6 +1139,34 @@ export default function ProcessModelEditDiagram() {
return null; return null;
}; };
const unsavedChangesMessage = () => {
if (diagramHasChanges) {
return (
<Notification
title="Unsaved changes."
type="error"
hideCloseButton
data-qa="process-model-file-changed"
>
Please save to avoid losing your work.
</Notification>
);
}
return null;
};
const pageModals = () => {
return (
<>
{newFileNameBox()}
{scriptEditorAndTests()}
{markdownEditor()}
{jsonSchemaEditor()}
{processModelSelector()}
</>
);
};
// if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it // if a file name is not given then this is a new model and the ReactDiagramEditor component will handle it
if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) { if ((bpmnXmlForDiagramRendering || !params.file_name) && processModel) {
const processModelFileName = processModelFile ? processModelFile.name : ''; const processModelFileName = processModelFile ? processModelFile.name : '';
@ -1159,13 +1187,13 @@ export default function ProcessModelEditDiagram() {
Process Model File{processModelFile ? ': ' : ''} Process Model File{processModelFile ? ': ' : ''}
{processModelFileName} {processModelFileName}
</h1> </h1>
{pageModals()}
{unsavedChangesMessage()}
{saveFileMessage()} {saveFileMessage()}
{appropriateEditor()} {appropriateEditor()}
{newFileNameBox()}
{scriptEditorAndTests()}
{markdownEditor()}
{jsonSchemaEditor()}
{processModelSelector()}
<div id="diagram-container" /> <div id="diagram-container" />
</> </>
); );