Merge pull request #24 from sartography/feature/carbon_process_model_show

Feature/carbon process model show
This commit is contained in:
Kevin Burnett 2022-11-08 17:38:36 +00:00 committed by GitHub
commit fdfb455b02
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 525 additions and 171 deletions

View File

@ -7,6 +7,7 @@ from typing import Optional
from flask import current_app from flask import current_app
from flask_bpmn.api.api_error import ApiError from flask_bpmn.api.api_error import ApiError
from flask_bpmn.models.db import db from flask_bpmn.models.db import db
from SpiffWorkflow.util.deep_merge import DeepMerge # type: ignore
from SpiffWorkflow.task import Task as SpiffTask # type: ignore from SpiffWorkflow.task import Task as SpiffTask # type: ignore
from spiffworkflow_backend.models.process_instance import ProcessInstanceApi from spiffworkflow_backend.models.process_instance import ProcessInstanceApi
@ -105,6 +106,20 @@ class ProcessInstanceService:
title=title_value, title=title_value,
) )
next_task_trying_again = next_task
if (
not next_task
): # The Next Task can be requested to be a certain task, useful for parallel tasks.
# This may or may not work, sometimes there is no next task to complete.
next_task_trying_again = processor.next_task()
if next_task_trying_again is not None:
process_instance_api.next_task = (
ProcessInstanceService.spiff_task_to_api_task(
next_task_trying_again, add_docs_and_forms=True
)
)
return process_instance_api return process_instance_api
def get_process_instance(self, process_instance_id: int) -> Any: def get_process_instance(self, process_instance_id: int) -> Any:

View File

@ -1123,7 +1123,7 @@ class TestProcessApi(BaseTest):
) )
assert response.json is not None assert response.json is not None
# assert response.json['next_task'] is not None assert response.json['next_task'] is not None
active_tasks = ( active_tasks = (
db.session.query(ActiveTaskModel) db.session.query(ActiveTaskModel)

View File

@ -4,10 +4,14 @@ import { Button, Modal } from '@carbon/react';
type OwnProps = { type OwnProps = {
description?: string; description?: string;
buttonLabel: string; buttonLabel?: string;
onConfirmation: (..._args: any[]) => any; onConfirmation: (..._args: any[]) => any;
title?: string; title?: string;
confirmButtonLabel?: string; confirmButtonLabel?: string;
kind?: string;
renderIcon?: boolean;
iconDescription?: string | null;
hasIconOnly?: boolean;
}; };
export default function ButtonWithConfirmation({ export default function ButtonWithConfirmation({
@ -16,6 +20,10 @@ export default function ButtonWithConfirmation({
onConfirmation, onConfirmation,
title = 'Are you sure?', title = 'Are you sure?',
confirmButtonLabel = 'OK', confirmButtonLabel = 'OK',
kind = 'danger',
renderIcon = false,
iconDescription = null,
hasIconOnly = false,
}: OwnProps) { }: OwnProps) {
const [showConfirmationPrompt, setShowConfirmationPrompt] = useState(false); const [showConfirmationPrompt, setShowConfirmationPrompt] = useState(false);
@ -49,7 +57,13 @@ export default function ButtonWithConfirmation({
return ( return (
<> <>
<Button onClick={handleShowConfirmationPrompt} kind="danger"> <Button
onClick={handleShowConfirmationPrompt}
kind={kind}
renderIcon={renderIcon}
iconDescription={iconDescription}
hasIconOnly={hasIconOnly}
>
{buttonLabel} {buttonLabel}
</Button> </Button>
{confirmationDialog()} {confirmationDialog()}

View File

@ -1,12 +1,12 @@
import { Link } from 'react-router-dom'; // @ts-ignore
import Breadcrumb from 'react-bootstrap/Breadcrumb'; import { Breadcrumb, BreadcrumbItem } from '@carbon/react';
import { BreadcrumbItem } from '../interfaces'; import { HotCrumbItem } from '../interfaces';
type OwnProps = { type OwnProps = {
processModelId?: string; processModelId?: string;
processGroupId?: string; processGroupId?: string;
linkProcessModel?: boolean; linkProcessModel?: boolean;
hotCrumbs?: BreadcrumbItem[]; hotCrumbs?: HotCrumbItem[];
}; };
export default function ProcessBreadcrumb({ export default function ProcessBreadcrumb({
@ -22,18 +22,20 @@ export default function ProcessBreadcrumb({
if (lastItem === undefined) { if (lastItem === undefined) {
return null; return null;
} }
const lastCrumb = <Breadcrumb.Item active>{lastItem[0]}</Breadcrumb.Item>; const lastCrumb = (
const leadingCrumbLinks = hotCrumbs.map((crumb) => { <BreadcrumbItem isCurrentPage>{lastItem[0]}</BreadcrumbItem>
);
const leadingCrumbLinks = hotCrumbs.map((crumb: any) => {
const valueLabel = crumb[0]; const valueLabel = crumb[0];
const url = crumb[1]; const url = crumb[1];
return ( return (
<Breadcrumb.Item key={valueLabel} linkAs={Link} linkProps={{ to: url }}> <BreadcrumbItem key={valueLabel} href={url}>
{valueLabel} {valueLabel}
</Breadcrumb.Item> </BreadcrumbItem>
); );
}); });
return ( return (
<Breadcrumb> <Breadcrumb noTrailingSlash>
{leadingCrumbLinks} {leadingCrumbLinks}
{lastCrumb} {lastCrumb}
</Breadcrumb> </Breadcrumb>
@ -42,42 +44,38 @@ export default function ProcessBreadcrumb({
if (processModelId) { if (processModelId) {
if (linkProcessModel) { if (linkProcessModel) {
processModelBreadcrumb = ( processModelBreadcrumb = (
<Breadcrumb.Item <BreadcrumbItem
linkAs={Link} href={`/admin/process-models/${processGroupId}/${processModelId}`}
linkProps={{
to: `/admin/process-models/${processGroupId}/${processModelId}`,
}}
> >
Process Model: {processModelId} {`Process Model: ${processModelId}`}
</Breadcrumb.Item> </BreadcrumbItem>
); );
} else { } else {
processModelBreadcrumb = ( processModelBreadcrumb = (
<Breadcrumb.Item active> <BreadcrumbItem isCurrentPage>
Process Model: {processModelId} {`Process Model: ${processModelId}`}
</Breadcrumb.Item> </BreadcrumbItem>
); );
} }
processGroupBreadcrumb = ( processGroupBreadcrumb = (
<Breadcrumb.Item <BreadcrumbItem
linkAs={Link}
data-qa="process-group-breadcrumb-link" data-qa="process-group-breadcrumb-link"
linkProps={{ to: `/admin/process-groups/${processGroupId}` }} href={`/admin/process-groups/${processGroupId}`}
> >
Process Group: {processGroupId} {`Process Group: ${processGroupId}`}
</Breadcrumb.Item> </BreadcrumbItem>
); );
} else if (processGroupId) { } else if (processGroupId) {
processGroupBreadcrumb = ( processGroupBreadcrumb = (
<Breadcrumb.Item active>Process Group: {processGroupId}</Breadcrumb.Item> <BreadcrumbItem isCurrentPage>
{`Process Group: ${processGroupId}`}
</BreadcrumbItem>
); );
} }
return ( return (
<Breadcrumb> <Breadcrumb noTrailingSlash>
<Breadcrumb.Item linkAs={Link} linkProps={{ to: '/admin' }}> <BreadcrumbItem href="/admin">Process Groups</BreadcrumbItem>
Process Groups
</Breadcrumb.Item>
{processGroupBreadcrumb} {processGroupBreadcrumb}
{processModelBreadcrumb} {processModelBreadcrumb}
</Breadcrumb> </Breadcrumb>

View File

@ -40,6 +40,10 @@ span.bjs-crumb {
opacity: .4; opacity: .4;
} }
.accordion-item-label {
vertical-align: middle;
}
.diagram-editor-canvas { .diagram-editor-canvas {
border:1px solid #000000; border:1px solid #000000;
height:70vh; height:70vh;

View File

@ -1,10 +1,22 @@
// @use '@carbon/react/scss/themes'; // @use '@carbon/react/scss/themes';
// @use '@carbon/react/scss/theme' with ($theme: themes.$g100); // @use '@carbon/react/scss/theme' with ($theme: themes.$g100);
// @use '@carbon/react/scss/theme' with
// (
// $theme: (
// cds-link-primary: #525252
// )
// );
@use '@carbon/react'; @use '@carbon/react';
@use '@carbon/styles'; @use '@carbon/styles';
// @include grid.flex-grid(); // @include grid.flex-grid();
@use '@carbon/colors';
// @use '@carbon/react/scss/colors'; // @use '@carbon/react/scss/colors';
@use '@carbon/react/scss/themes';
// var(--cds-link-text-color, var(--cds-link-primary, #0f62fe))
// site is mainly using white theme. // site is mainly using white theme.
// header is mainly using g100 // header is mainly using g100
@ -13,3 +25,60 @@
// background-color: colors.$gray-100; // background-color: colors.$gray-100;
color: white; color: white;
} }
.cds--breadcrumb-item a.cds--link:hover {
color: #525252;
}
.cds--breadcrumb-item a.cds--link:visited {
color: #525252;
}
.cds--breadcrumb-item a.cds--link:visited:hover {
color: #525252;
}
.cds--breadcrumb-item a.cds--link {
color: #525252;
}
.cds--btn--ghost {
color: black;
}
.cds--btn--ghost:visited {
color: black;
}
.cds--btn--ghost:hover {
color: black;
}
.cds--btn--ghost:visited:hover {
color: black;
}
$slightly-lighter-gray: #474747;
$spiff-header-background-color: #161616;
.cds--header__global .cds--btn--primary {
background-color: $spiff-header-background-color;
}
.cds--btn--primary {
background-color: #393939;
}
.cds--btn--primary:hover {
background-color: $slightly-lighter-gray;
}
// .cds--btn--ghost:visited {
// color: black;
// }
// .cds--btn--ghost:hover {
// color: black;
// }
// .cds--btn--ghost:visited:hover {
// color: black;
// }
// :root {
// --cds-link-primary: #525252;
// }
// .card {
// background: var(--orange);
// --orange: hsl(255, 72%, var(--lightness));
// }

View File

@ -32,10 +32,12 @@ export interface ProcessFile {
references: ProcessFileReference[]; references: ProcessFileReference[];
size: number; size: number;
type: string; type: string;
file_contents?: string;
} }
export interface ProcessModel { export interface ProcessModel {
id: string; id: string;
description: string;
process_group_id: string; process_group_id: string;
display_name: string; display_name: string;
primary_file_name: string; primary_file_name: string;
@ -43,7 +45,7 @@ export interface ProcessModel {
} }
// tuple of display value and URL // tuple of display value and URL
export type BreadcrumbItem = [displayValue: string, url?: string]; export type HotCrumbItem = [displayValue: string, url?: string];
export interface ErrorForDisplay { export interface ErrorForDisplay {
message: string; message: string;

View File

@ -49,6 +49,7 @@ export default function ProcessModelEdit() {
}); });
}; };
// share with or delete from ProcessModelEditDiagram
const deleteProcessModel = () => { const deleteProcessModel = () => {
setErrorMessage(null); setErrorMessage(null);
const processModelToUse = processModel as any; const processModelToUse = processModel as any;

View File

@ -1,12 +1,44 @@
import { useContext, useEffect, useState } from 'react'; import { useContext, useEffect, useState } from 'react';
import { Link, useParams } from 'react-router-dom'; import { Link, useNavigate, useParams } from 'react-router-dom';
// @ts-ignore import {
import { Button, Stack } from '@carbon/react'; Add,
Upload,
Download,
TrashCan,
Favorite,
Edit,
// @ts-ignore
} from '@carbon/icons-react';
import {
Accordion,
AccordionItem,
Dropdown,
Button,
Stack,
ButtonSet,
Modal,
FileUploader,
Table,
TableHead,
TableHeader,
TableRow,
TableCell,
TableBody,
// @ts-ignore
} from '@carbon/react';
import ProcessBreadcrumb from '../components/ProcessBreadcrumb'; import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
import FileInput from '../components/FileInput';
import HttpService from '../services/HttpService'; import HttpService from '../services/HttpService';
import ErrorContext from '../contexts/ErrorContext'; import ErrorContext from '../contexts/ErrorContext';
import { RecentProcessModel } from '../interfaces'; 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 = ( const storeRecentProcessModelInLocalStorage = (
processModelForStorage: any, processModelForStorage: any,
@ -66,12 +98,16 @@ export default function ProcessModelShow() {
const params = useParams(); const params = useParams();
const setErrorMessage = (useContext as any)(ErrorContext)[1]; const setErrorMessage = (useContext as any)(ErrorContext)[1];
const [processModel, setProcessModel] = useState({}); const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
const [processInstanceResult, setProcessInstanceResult] = useState(null); const [processInstanceResult, setProcessInstanceResult] = useState(null);
const [reloadModel, setReloadModel] = useState(false); const [reloadModel, setReloadModel] = useState<boolean>(false);
const [filesToUpload, setFilesToUpload] = useState<any>(null);
const [showFileUploadModal, setShowFileUploadModal] =
useState<boolean>(false);
const navigate = useNavigate();
useEffect(() => { useEffect(() => {
const processResult = (result: object) => { const processResult = (result: ProcessModel) => {
setProcessModel(result); setProcessModel(result);
setReloadModel(false); setReloadModel(false);
storeRecentProcessModelInLocalStorage(result, params); storeRecentProcessModelInLocalStorage(result, params);
@ -100,96 +136,243 @@ export default function ProcessModelShow() {
}); });
}; };
let processInstanceResultTag = null; const processInstanceResultTag = () => {
if (processInstanceResult) { if (processModel && processInstanceResult) {
let takeMeToMyTaskBlurb = null; let takeMeToMyTaskBlurb = null;
// FIXME: ensure that the task is actually for the current user as well // FIXME: ensure that the task is actually for the current user as well
const processInstanceId = (processInstanceResult as any).id; const processInstanceId = (processInstanceResult as any).id;
const nextTask = (processInstanceResult as any).next_task; const nextTask = (processInstanceResult as any).next_task;
if (nextTask && nextTask.state === 'READY') { if (nextTask && nextTask.state === 'READY') {
takeMeToMyTaskBlurb = ( takeMeToMyTaskBlurb = (
<span> <span>
You have a task to complete. Go to{' '} You have a task to complete. Go to{' '}
<Link to={`/tasks/${processInstanceId}/${nextTask.id}`}>my task</Link> <Link to={`/tasks/${processInstanceId}/${nextTask.id}`}>
. my task
</span> </Link>
.
</span>
);
}
return (
<div className="alert alert-success" role="alert">
<p>
Process Instance {processInstanceId} kicked off (
<Link
to={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/process-instances/${processInstanceId}`}
data-qa="process-instance-show-link"
>
view
</Link>
). {takeMeToMyTaskBlurb}
</p>
</div>
); );
} }
processInstanceResultTag = ( return null;
<div className="alert alert-success" role="alert"> };
<p>
Process Instance {processInstanceId} kicked off (
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${
(processModel as any).id
}/process-instances/${processInstanceId}`}
data-qa="process-instance-show-link"
>
view
</Link>
). {takeMeToMyTaskBlurb}
</p>
</div>
);
}
const onUploadedCallback = () => { const onUploadedCallback = () => {
setReloadModel(true); setReloadModel(true);
}; };
const reloadModelOhYeah = (_httpResult: any) => {
setReloadModel(!reloadModel);
};
const processModelFileList = () => { // Remove this code from
let constructedTag; const onDeleteFile = (fileName: string) => {
const tags = (processModel as any).files.map((processModelFile: any) => { const url = `/process-models/${params.process_group_id}/${params.process_model_id}/files/${fileName}`;
const httpMethod = 'DELETE';
HttpService.makeCallToBackend({
path: url,
successCallback: reloadModelOhYeah,
httpMethod,
});
};
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 httpMethod = 'PUT';
const processModelToPass = {
primary_file_name: fileName,
};
HttpService.makeCallToBackend({
path: url,
successCallback: onUploadedCallback,
httpMethod,
postBody: processModelToPass,
});
};
const handleProcessModelFileResult = (processModelFile: ProcessFile) => {
if (
!('file_contents' in processModelFile) ||
processModelFile.file_contents === undefined
) {
setErrorMessage({
message: `Could not file file contents for file: ${processModelFile.name}`,
});
return;
}
let contentType = 'application/xml';
if (processModelFile.type === 'json') {
contentType = 'application/json';
}
const element = document.createElement('a');
const file = new Blob([processModelFile.file_contents], {
type: contentType,
});
const downloadFileName = processModelFile.name;
element.href = URL.createObjectURL(file);
element.download = downloadFileName;
document.body.appendChild(element);
element.click();
};
const downloadFile = (fileName: string) => {
setErrorMessage(null);
const processModelPath = `process-models/${params.process_group_id}/${params.process_model_id}`;
HttpService.makeCallToBackend({
path: `/${processModelPath}/files/${fileName}`,
successCallback: handleProcessModelFileResult,
});
};
const navigateToFileEdit = (processModelFile: ProcessFile) => {
if (processModel) {
if (processModelFile.name.match(/\.(dmn|bpmn)$/)) { if (processModelFile.name.match(/\.(dmn|bpmn)$/)) {
let primarySuffix = ''; navigate(
if (processModelFile.name === (processModel as any).primary_file_name) { `/admin/process-models/${processModel.process_group_id}/${processModel.id}/files/${processModelFile.name}`
primarySuffix = '- Primary File';
}
constructedTag = (
<li key={processModelFile.name}>
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/files/${processModelFile.name}`}
>
{processModelFile.name}
</Link>
{primarySuffix}
</li>
); );
} else if (processModelFile.name.match(/\.(json|md)$/)) { } else if (processModelFile.name.match(/\.(json|md)$/)) {
constructedTag = ( navigate(
<li key={processModelFile.name}> `/admin/process-models/${processModel.process_group_id}/${processModel.id}/form/${processModelFile.name}`
<Link
to={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form/${processModelFile.name}`}
>
{processModelFile.name}
</Link>
</li>
);
} else {
constructedTag = (
<li key={processModelFile.name}>{processModelFile.name}</li>
); );
} }
}
};
const renderButtonElements = (
processModelFile: ProcessFile,
isPrimaryBpmnFile: boolean
) => {
const elements = [];
elements.push(
<Button
kind="ghost"
renderIcon={Edit}
iconDescription="Edit File"
hasIconOnly
size="lg"
onClick={() => navigateToFileEdit(processModelFile)}
/>
);
elements.push(
<Button
kind="ghost"
renderIcon={Download}
iconDescription="Download File"
hasIconOnly
size="lg"
onClick={() => downloadFile(processModelFile.name)}
/>
);
elements.push(
<ButtonWithConfirmation
kind="ghost"
renderIcon={TrashCan}
iconDescription="Delete File"
hasIconOnly
description={`Delete file: ${processModelFile.name}`}
onConfirmation={() => {
onDeleteFile(processModelFile.name);
}}
confirmButtonLabel="Delete"
/>
);
if (processModelFile.name.match(/\.bpmn$/) && !isPrimaryBpmnFile) {
elements.push(
<Button
kind="ghost"
renderIcon={Favorite}
iconDescription="Set As Primary File"
hasIconOnly
size="lg"
onClick={() => onSetPrimaryFile(processModelFile.name)}
/>
);
}
return elements;
};
const processModelFileList = () => {
if (!processModel) {
return null;
}
let constructedTag;
const tags = processModel.files.map((processModelFile: ProcessFile) => {
const isPrimaryBpmnFile =
processModelFile.name === processModel.primary_file_name;
let actionsTableCell = null;
if (processModelFile.name.match(/\.(dmn|bpmn|json|md)$/)) {
actionsTableCell = (
<TableCell key={`${processModelFile.name}-cell`}>
{renderButtonElements(processModelFile, isPrimaryBpmnFile)}
</TableCell>
);
}
let primarySuffix = '';
if (isPrimaryBpmnFile) {
primarySuffix = '- Primary File';
}
constructedTag = (
<TableRow key={processModelFile.name}>
<TableCell key={`${processModelFile.name}-cell`}>
{processModelFile.name}
{primarySuffix}
</TableCell>
{actionsTableCell}
</TableRow>
);
return constructedTag; return constructedTag;
}); });
return <ul>{tags}</ul>; // return <ul>{tags}</ul>;
const headers = ['Name', 'Actions'];
return (
<Table size="lg" useZebraStyles={false}>
<TableHead>
<TableRow>
{headers.map((header) => (
<TableHeader id={header} key={header}>
{header}
</TableHeader>
))}
</TableRow>
</TableHead>
<TableBody>{tags}</TableBody>
</Table>
);
}; };
const processInstancesUl = () => { const processInstancesUl = () => {
if (!processModel) {
return null;
}
return ( return (
<ul> <ul>
<li> <li>
<Link <Link
to={`/admin/process-instances?process_group_identifier=${ to={`/admin/process-instances?process_group_identifier=${processModel.process_group_id}&process_model_identifier=${processModel.id}`}
(processModel as any).process_group_id
}&process_model_identifier=${(processModel as any).id}`}
data-qa="process-instance-list-link" data-qa="process-instance-list-link"
> >
List List
@ -197,9 +380,7 @@ export default function ProcessModelShow() {
</li> </li>
<li> <li>
<Link <Link
to={`/admin/process-models/${ to={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/process-instances/reports`}
(processModel as any).process_group_id
}/${(processModel as any).id}/process-instances/reports`}
data-qa="process-instance-reports-link" data-qa="process-instance-reports-link"
> >
Reports Reports
@ -209,79 +390,149 @@ export default function ProcessModelShow() {
); );
}; };
const processModelButtons = () => { const handleFileUploadCancel = () => {
setShowFileUploadModal(false);
};
const handleFileUpload = (event: any) => {
if (processModel) {
event.preventDefault();
const url = `/process-models/${processModel.process_group_id}/${processModel.id}/files`;
const formData = new FormData();
formData.append('file', filesToUpload[0]);
formData.append('fileName', filesToUpload[0].name);
HttpService.makeCallToBackend({
path: url,
successCallback: onUploadedCallback,
httpMethod: 'POST',
postBody: formData,
});
setShowFileUploadModal(false);
}
};
const fileUploadModal = () => {
return ( return (
<Stack orientation="horizontal" gap={3}> <Modal
<Button onClick={processInstanceCreateAndRun} variant="primary"> open={showFileUploadModal}
Run modalHeading="Upload File"
</Button> primaryButtonText="Upload"
<Button secondaryButtonText="Cancel"
href={`/admin/process-models/${ onSecondarySubmit={handleFileUploadCancel}
(processModel as any).process_group_id onRequestClose={handleFileUploadCancel}
}/${(processModel as any).id}/edit`} onRequestSubmit={handleFileUpload}
variant="secondary" >
> <FileUploader
Edit process model labelTitle="Upload files"
</Button> labelDescription="Max file size is 500mb. Only .bpmn, .dmn, and .json files are supported."
<Button buttonLabel="Add file"
href={`/admin/process-models/${ buttonKind="primary"
(processModel as any).process_group_id size="md"
}/${(processModel as any).id}/files?file_type=bpmn`} filenameStatus="edit"
variant="warning" role="button"
> accept={['.bpmn', '.dmn', '.json']}
Add New BPMN File disabled={false}
</Button> iconDescription="Delete file"
<Button name=""
href={`/admin/process-models/${ multiple={false}
(processModel as any).process_group_id onChange={(event: any) => setFilesToUpload(event.target.files)}
}/${(processModel as any).id}/files?file_type=dmn`} />
variant="success" </Modal>
>
Add New DMN File
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form?file_ext=json`}
variant="info"
>
Add New JSON File
</Button>
<Button
href={`/admin/process-models/${
(processModel as any).process_group_id
}/${(processModel as any).id}/form?file_ext=md`}
variant="info"
>
Add New Markdown File
</Button>
</Stack>
); );
}; };
if (Object.keys(processModel).length > 1) { const processModelButtons = () => {
if (!processModel) {
return null;
}
return (
<Accordion>
<AccordionItem
title={
<Stack orientation="horizontal">
<span>
<Button size="sm" kind="ghost">
Files
</Button>
</span>
</Stack>
}
>
<ButtonSet>
<Button
renderIcon={Upload}
onClick={() => setShowFileUploadModal(true)}
size="sm"
kind=""
className="button-white-background"
>
Upload File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/files?file_type=bpmn`}
size="sm"
>
New BPMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/files?file_type=dmn`}
size="sm"
>
New DMN File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/form?file_ext=json`}
size="sm"
>
New JSON File
</Button>
<Button
renderIcon={Add}
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/form?file_ext=md`}
size="sm"
>
New Markdown File
</Button>
</ButtonSet>
<br />
{processModelFileList()}
</AccordionItem>
</Accordion>
);
};
if (processModel) {
return ( return (
<> <>
{fileUploadModal()}
<ProcessBreadcrumb <ProcessBreadcrumb
processGroupId={(processModel as any).process_group_id} processGroupId={processModel.process_group_id}
processModelId={(processModel as any).id} processModelId={processModel.id}
/>
{processInstanceResultTag}
<FileInput
processModelId={(processModel as any).id}
processGroupId={(processModel as any).process_group_id}
onUploadedCallback={onUploadedCallback}
/> />
<h1>{processModel.display_name}</h1>
<p>{processModel.description}</p>
<Stack orientation="horizontal" gap={3}>
<Button onClick={processInstanceCreateAndRun} variant="primary">
Run
</Button>
<Button
href={`/admin/process-models/${processModel.process_group_id}/${processModel.id}/edit`}
variant="secondary"
>
Edit process model
</Button>
</Stack>
<br /> <br />
<br />
{processInstanceResultTag()}
{processModelButtons()} {processModelButtons()}
<br /> <br />
<br /> <br />
<h3>Process Instances</h3> <h3>Process Instances</h3>
{processInstancesUl()} {processInstancesUl()}
<br />
<br />
<h3>Files</h3>
{processModelFileList()}
</> </>
); );
} }