Feature/pm readme file (#495)
* refactored process model show page to use tabs * added ability to view and edit a process model readme file w/ burnettk --------- Co-authored-by: jasquat <jasquat@users.noreply.github.com>
This commit is contained in:
parent
5e19d68d49
commit
00158df03d
|
@ -0,0 +1,34 @@
|
||||||
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
|
import { useEffect, useState } from 'react';
|
||||||
|
import HttpService from '../services/HttpService';
|
||||||
|
|
||||||
|
type OwnProps = {
|
||||||
|
apiPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function MarkdownDisplayForFile({ apiPath }: OwnProps) {
|
||||||
|
const [markdownContents, setMarkdownContents] = useState<string | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const processResult = (result: any) => {
|
||||||
|
if (result.file_contents) {
|
||||||
|
setMarkdownContents(result.file_contents);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
HttpService.makeCallToBackend({
|
||||||
|
path: apiPath,
|
||||||
|
successCallback: processResult,
|
||||||
|
});
|
||||||
|
}, [apiPath]);
|
||||||
|
|
||||||
|
if (markdownContents) {
|
||||||
|
return (
|
||||||
|
<div data-color-mode="light" className="with-bottom-margin">
|
||||||
|
<MDEditor.Markdown linkTarget="_blank" source={markdownContents} />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
|
@ -1840,6 +1840,9 @@ export default function ProcessInstanceListTable({
|
||||||
</Column>
|
</Column>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
if (!headerElement && !filterButtonLink) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Column
|
<Column
|
||||||
|
|
|
@ -216,6 +216,14 @@ h1.with-icons {
|
||||||
margin-top: 5px;
|
margin-top: 5px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.with-icons {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.readme-container {
|
||||||
|
max-width: 640px;
|
||||||
|
}
|
||||||
|
|
||||||
dl {
|
dl {
|
||||||
display: block;
|
display: block;
|
||||||
grid-template-columns: 35% 65%;
|
grid-template-columns: 35% 65%;
|
||||||
|
@ -488,11 +496,6 @@ th.table-header-right-align .cds--data-table, th.table-header-right-align .cds--
|
||||||
margin-bottom: 1rem;
|
margin-bottom: 1rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* top and bottom margin since this is sort of the middle of three sections on the process model show page */
|
|
||||||
.process-model-files-section {
|
|
||||||
margin: 2rem 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter-icon {
|
.filter-icon {
|
||||||
text-align: right;
|
text-align: right;
|
||||||
padding-bottom: 10px;
|
padding-bottom: 10px;
|
||||||
|
|
|
@ -9,8 +9,6 @@ import {
|
||||||
View,
|
View,
|
||||||
} from '@carbon/icons-react';
|
} from '@carbon/icons-react';
|
||||||
import {
|
import {
|
||||||
Accordion,
|
|
||||||
AccordionItem,
|
|
||||||
Button,
|
Button,
|
||||||
Column,
|
Column,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
|
@ -24,6 +22,11 @@ import {
|
||||||
TableHead,
|
TableHead,
|
||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
|
Tabs,
|
||||||
|
Tab,
|
||||||
|
TabList,
|
||||||
|
TabPanels,
|
||||||
|
TabPanel,
|
||||||
} from '@carbon/react';
|
} from '@carbon/react';
|
||||||
import { Can } from '@casl/react';
|
import { Can } from '@casl/react';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
|
@ -48,10 +51,12 @@ import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
||||||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||||
import { Notification } from '../components/Notification';
|
import { Notification } from '../components/Notification';
|
||||||
import ProcessModelTestRun from '../components/ProcessModelTestRun';
|
import ProcessModelTestRun from '../components/ProcessModelTestRun';
|
||||||
|
import MarkdownDisplayForFile from '../components/MarkdownDisplayForFile';
|
||||||
|
|
||||||
export default function ProcessModelShow() {
|
export default function ProcessModelShow() {
|
||||||
const params = useParams();
|
const params = useParams();
|
||||||
const { addError, removeError } = useAPIError();
|
const { addError, removeError } = useAPIError();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
||||||
const [processInstance, setProcessInstance] =
|
const [processInstance, setProcessInstance] =
|
||||||
|
@ -62,7 +67,8 @@ export default function ProcessModelShow() {
|
||||||
useState<boolean>(false);
|
useState<boolean>(false);
|
||||||
const [processModelPublished, setProcessModelPublished] = useState<any>(null);
|
const [processModelPublished, setProcessModelPublished] = useState<any>(null);
|
||||||
const [publishDisabled, setPublishDisabled] = useState<boolean>(false);
|
const [publishDisabled, setPublishDisabled] = useState<boolean>(false);
|
||||||
const navigate = useNavigate();
|
const [selectedTabIndex, setSelectedTabIndex] = useState<number>(0);
|
||||||
|
const [readmeFile, setReadmeFile] = useState<ProcessFile | null>(null);
|
||||||
|
|
||||||
const { targetUris } = useUriListForPermissions();
|
const { targetUris } = useUriListForPermissions();
|
||||||
const permissionRequestData: PermissionsToCheck = {
|
const permissionRequestData: PermissionsToCheck = {
|
||||||
|
@ -98,6 +104,15 @@ export default function ProcessModelShow() {
|
||||||
setProcessModel(result);
|
setProcessModel(result);
|
||||||
setReloadModel(false);
|
setReloadModel(false);
|
||||||
setPageTitle([result.display_name]);
|
setPageTitle([result.display_name]);
|
||||||
|
|
||||||
|
let newTabIndex = 1;
|
||||||
|
result.files.forEach((file: ProcessFile) => {
|
||||||
|
if (file.name === 'README.md') {
|
||||||
|
setReadmeFile(file);
|
||||||
|
newTabIndex = 0;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
setSelectedTabIndex(newTabIndex);
|
||||||
};
|
};
|
||||||
HttpService.makeCallToBackend({
|
HttpService.makeCallToBackend({
|
||||||
path: `/process-models/${modifiedProcessModelId}`,
|
path: `/process-models/${modifiedProcessModelId}`,
|
||||||
|
@ -562,44 +577,124 @@ export default function ProcessModelShow() {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const processModelFilesSection = () => {
|
const readmeFileArea = () => {
|
||||||
return (
|
if (readmeFile) {
|
||||||
<Grid
|
return (
|
||||||
condensed
|
<div className="readme-container">
|
||||||
fullWidth
|
<Grid condensed fullWidth className="megacondensed">
|
||||||
className="megacondensed process-model-files-section"
|
<Column md={7} lg={15} sm={3}>
|
||||||
>
|
<p className="with-icons">{readmeFile.name}</p>
|
||||||
<Column md={8} lg={14} sm={4}>
|
</Column>
|
||||||
<Accordion align="end" open className="megacondensed-button">
|
<Column md={1} lg={1} sm={1}>
|
||||||
<AccordionItem
|
|
||||||
open
|
|
||||||
data-qa="files-accordion"
|
|
||||||
title={
|
|
||||||
<Stack orientation="horizontal">
|
|
||||||
<span>
|
|
||||||
<Button size="sm" kind="ghost">
|
|
||||||
Files
|
|
||||||
{processModel &&
|
|
||||||
processModel.bpmn_version_control_identifier &&
|
|
||||||
` (revision ${processModel.bpmn_version_control_identifier})`}
|
|
||||||
</Button>
|
|
||||||
</span>
|
|
||||||
</Stack>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Can
|
<Can
|
||||||
I="POST"
|
I="GET"
|
||||||
a={targetUris.processModelFileCreatePath}
|
a={targetUris.processModelFileCreatePath}
|
||||||
ability={ability}
|
ability={ability}
|
||||||
>
|
>
|
||||||
{addFileComponent()}
|
<Button
|
||||||
<br />
|
kind="ghost"
|
||||||
|
data-qa="process-model-readme-file-edit"
|
||||||
|
renderIcon={Edit}
|
||||||
|
iconDescription="Edit README.md"
|
||||||
|
hasIconOnly
|
||||||
|
href={`/admin/process-models/${modifiedProcessModelId}/form/${readmeFile.name}`}
|
||||||
|
/>
|
||||||
</Can>
|
</Can>
|
||||||
{processModelFileList()}
|
</Column>
|
||||||
</AccordionItem>
|
</Grid>
|
||||||
</Accordion>
|
<hr />
|
||||||
</Column>
|
<MarkdownDisplayForFile
|
||||||
</Grid>
|
apiPath={`/process-models/${modifiedProcessModelId}/files/${readmeFile.name}`}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<p>No README file found</p>
|
||||||
|
<Can
|
||||||
|
I="POST"
|
||||||
|
a={targetUris.processModelFileCreatePath}
|
||||||
|
ability={ability}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
className="with-top-margin"
|
||||||
|
data-qa="process-model-readme-file-create"
|
||||||
|
href={`/admin/process-models/${modifiedProcessModelId}/form?file_ext=md&default_file_name=README.md`}
|
||||||
|
size="md"
|
||||||
|
>
|
||||||
|
Add README.md
|
||||||
|
</Button>
|
||||||
|
</Can>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateSelectedTab = (newTabIndex: any) => {
|
||||||
|
setSelectedTabIndex(newTabIndex.selectedIndex);
|
||||||
|
};
|
||||||
|
|
||||||
|
const tabArea = () => {
|
||||||
|
if (!processModel) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tabs selectedIndex={selectedTabIndex} onChange={updateSelectedTab}>
|
||||||
|
<TabList aria-label="List of tabs">
|
||||||
|
<Tab>About</Tab>
|
||||||
|
<Tab>Files</Tab>
|
||||||
|
<Tab>My Process instances</Tab>
|
||||||
|
</TabList>
|
||||||
|
<TabPanels>
|
||||||
|
<TabPanel>{readmeFileArea()}</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
<Grid condensed fullWidth className="megacondensed">
|
||||||
|
<Column md={4} lg={8} sm={4}>
|
||||||
|
<Can
|
||||||
|
I="POST"
|
||||||
|
a={targetUris.processModelFileCreatePath}
|
||||||
|
ability={ability}
|
||||||
|
>
|
||||||
|
<div className="with-bottom-margin">
|
||||||
|
Files
|
||||||
|
{processModel &&
|
||||||
|
processModel.bpmn_version_control_identifier &&
|
||||||
|
` (revision ${processModel.bpmn_version_control_identifier})`}
|
||||||
|
</div>
|
||||||
|
{addFileComponent()}
|
||||||
|
<br />
|
||||||
|
</Can>
|
||||||
|
{processModelFileList()}
|
||||||
|
</Column>
|
||||||
|
</Grid>
|
||||||
|
</TabPanel>
|
||||||
|
<TabPanel>
|
||||||
|
{selectedTabIndex !== 2 ? null : (
|
||||||
|
<Can
|
||||||
|
I="POST"
|
||||||
|
a={targetUris.processInstanceListForMePath}
|
||||||
|
ability={ability}
|
||||||
|
>
|
||||||
|
<ProcessInstanceListTable
|
||||||
|
filtersEnabled={false}
|
||||||
|
showLinkToReport
|
||||||
|
variant="for-me"
|
||||||
|
additionalReportFilters={[
|
||||||
|
{
|
||||||
|
field_name: 'process_model_identifier',
|
||||||
|
field_value: processModel.id,
|
||||||
|
},
|
||||||
|
]}
|
||||||
|
perPageOptions={[2, 5, 25]}
|
||||||
|
showReports={false}
|
||||||
|
/>
|
||||||
|
<span data-qa="process-model-show-permissions-loaded" />
|
||||||
|
</Can>
|
||||||
|
)}
|
||||||
|
</TabPanel>
|
||||||
|
</TabPanels>
|
||||||
|
</Tabs>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -666,9 +761,7 @@ export default function ProcessModelShow() {
|
||||||
iconDescription="Edit Process Model"
|
iconDescription="Edit Process Model"
|
||||||
hasIconOnly
|
hasIconOnly
|
||||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||||
>
|
/>
|
||||||
Edit process model
|
|
||||||
</Button>
|
|
||||||
</Can>
|
</Can>
|
||||||
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>
|
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>
|
||||||
<ButtonWithConfirmation
|
<ButtonWithConfirmation
|
||||||
|
@ -707,28 +800,7 @@ export default function ProcessModelShow() {
|
||||||
</Stack>
|
</Stack>
|
||||||
<p className="process-description">{processModel.description}</p>
|
<p className="process-description">{processModel.description}</p>
|
||||||
{processModel.primary_file_name ? processStartButton : null}
|
{processModel.primary_file_name ? processStartButton : null}
|
||||||
{processModelFilesSection()}
|
<div className="with-top-margin">{tabArea()}</div>
|
||||||
<Can
|
|
||||||
I="POST"
|
|
||||||
a={targetUris.processInstanceListForMePath}
|
|
||||||
ability={ability}
|
|
||||||
>
|
|
||||||
<ProcessInstanceListTable
|
|
||||||
headerElement={<h2>My Process Instances</h2>}
|
|
||||||
filtersEnabled={false}
|
|
||||||
showLinkToReport
|
|
||||||
variant="for-me"
|
|
||||||
additionalReportFilters={[
|
|
||||||
{
|
|
||||||
field_name: 'process_model_identifier',
|
|
||||||
field_value: processModel.id,
|
|
||||||
},
|
|
||||||
]}
|
|
||||||
perPageOptions={[2, 5, 25]}
|
|
||||||
showReports={false}
|
|
||||||
/>
|
|
||||||
<span data-qa="process-model-show-permissions-loaded" />
|
|
||||||
</Can>
|
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import { Button, ButtonSet, Modal } from '@carbon/react';
|
import { Button, ButtonSet, Modal } from '@carbon/react';
|
||||||
import { Can } from '@casl/react';
|
import { Can } from '@casl/react';
|
||||||
|
import MDEditor from '@uiw/react-md-editor';
|
||||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||||
import HttpService from '../services/HttpService';
|
import HttpService from '../services/HttpService';
|
||||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||||
|
@ -57,6 +58,7 @@ export default function ReactFormEditor() {
|
||||||
|
|
||||||
const hasDiagram = fileExtension === 'bpmn' || fileExtension === 'dmn';
|
const hasDiagram = fileExtension === 'bpmn' || fileExtension === 'dmn';
|
||||||
const hasFormBuilder = fileExtension === 'json';
|
const hasFormBuilder = fileExtension === 'json';
|
||||||
|
const defaultFileName = searchParams.get('default_file_name');
|
||||||
|
|
||||||
const editorDefaultLanguage = (() => {
|
const editorDefaultLanguage = (() => {
|
||||||
if (fileExtension === 'json') {
|
if (fileExtension === 'json') {
|
||||||
|
@ -106,7 +108,8 @@ export default function ReactFormEditor() {
|
||||||
setProcessModelFile(file);
|
setProcessModelFile(file);
|
||||||
}
|
}
|
||||||
if (!params.file_name) {
|
if (!params.file_name) {
|
||||||
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
const fileNameWithExtension =
|
||||||
|
defaultFileName ?? `${newFileName}.${fileExtension}`;
|
||||||
navigate(
|
navigate(
|
||||||
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
|
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
|
||||||
);
|
);
|
||||||
|
@ -119,7 +122,7 @@ export default function ReactFormEditor() {
|
||||||
|
|
||||||
let url = `/process-models/${modifiedProcessModelId}/files`;
|
let url = `/process-models/${modifiedProcessModelId}/files`;
|
||||||
let httpMethod = 'PUT';
|
let httpMethod = 'PUT';
|
||||||
let fileNameWithExtension = params.file_name;
|
let fileNameWithExtension = params.file_name || defaultFileName;
|
||||||
|
|
||||||
if (newFileName) {
|
if (newFileName) {
|
||||||
fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
||||||
|
@ -219,6 +222,30 @@ export default function ReactFormEditor() {
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const editorArea = () => {
|
||||||
|
if (fileExtension === 'md') {
|
||||||
|
return (
|
||||||
|
<div data-color-mode="light">
|
||||||
|
<MDEditor
|
||||||
|
height={600}
|
||||||
|
highlightEnable={false}
|
||||||
|
value={processModelFileContents || ''}
|
||||||
|
onChange={(value) => setProcessModelFileContents(value || '')}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Editor
|
||||||
|
height={600}
|
||||||
|
width="auto"
|
||||||
|
defaultLanguage={editorDefaultLanguage}
|
||||||
|
defaultValue={processModelFileContents || ''}
|
||||||
|
onChange={(value) => setProcessModelFileContents(value || '')}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if (processModelFile || !params.file_name) {
|
if (processModelFile || !params.file_name) {
|
||||||
const processModelFileName = processModelFile ? processModelFile.name : '';
|
const processModelFileName = processModelFile ? processModelFile.name : '';
|
||||||
const formBuildFileParam = params.file_name
|
const formBuildFileParam = params.file_name
|
||||||
|
@ -318,13 +345,7 @@ export default function ReactFormEditor() {
|
||||||
<ActiveUsers />
|
<ActiveUsers />
|
||||||
</Can>
|
</Can>
|
||||||
</ButtonSet>
|
</ButtonSet>
|
||||||
<Editor
|
{editorArea()}
|
||||||
height={600}
|
|
||||||
width="auto"
|
|
||||||
defaultLanguage={editorDefaultLanguage}
|
|
||||||
defaultValue={processModelFileContents || ''}
|
|
||||||
onChange={(value) => setProcessModelFileContents(value || '')}
|
|
||||||
/>
|
|
||||||
</main>
|
</main>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue