mirror of
https://github.com/status-im/spiff-arena.git
synced 2025-02-11 09:16:45 +00:00
Merge pull request #24 from sartography/feature/carbon_process_model_show
Feature/carbon process model show
This commit is contained in:
commit
fdfb455b02
@ -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:
|
||||||
|
@ -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)
|
||||||
|
@ -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()}
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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));
|
||||||
|
// }
|
||||||
|
@ -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;
|
||||||
|
@ -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;
|
||||||
|
@ -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()}
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user