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>
|
||||
);
|
||||
}
|
||||
if (!headerElement && !filterButtonLink) {
|
||||
return null;
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<Column
|
||||
|
|
|
@ -216,6 +216,14 @@ h1.with-icons {
|
|||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.with-icons {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.readme-container {
|
||||
max-width: 640px;
|
||||
}
|
||||
|
||||
dl {
|
||||
display: block;
|
||||
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;
|
||||
}
|
||||
|
||||
/* 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 {
|
||||
text-align: right;
|
||||
padding-bottom: 10px;
|
||||
|
|
|
@ -9,8 +9,6 @@ import {
|
|||
View,
|
||||
} from '@carbon/icons-react';
|
||||
import {
|
||||
Accordion,
|
||||
AccordionItem,
|
||||
Button,
|
||||
Column,
|
||||
Dropdown,
|
||||
|
@ -24,6 +22,11 @@ import {
|
|||
TableHead,
|
||||
TableHeader,
|
||||
TableRow,
|
||||
Tabs,
|
||||
Tab,
|
||||
TabList,
|
||||
TabPanels,
|
||||
TabPanel,
|
||||
} from '@carbon/react';
|
||||
import { Can } from '@casl/react';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
|
@ -48,10 +51,12 @@ import { useUriListForPermissions } from '../hooks/UriListForPermissions';
|
|||
import ProcessInstanceRun from '../components/ProcessInstanceRun';
|
||||
import { Notification } from '../components/Notification';
|
||||
import ProcessModelTestRun from '../components/ProcessModelTestRun';
|
||||
import MarkdownDisplayForFile from '../components/MarkdownDisplayForFile';
|
||||
|
||||
export default function ProcessModelShow() {
|
||||
const params = useParams();
|
||||
const { addError, removeError } = useAPIError();
|
||||
const navigate = useNavigate();
|
||||
|
||||
const [processModel, setProcessModel] = useState<ProcessModel | null>(null);
|
||||
const [processInstance, setProcessInstance] =
|
||||
|
@ -62,7 +67,8 @@ export default function ProcessModelShow() {
|
|||
useState<boolean>(false);
|
||||
const [processModelPublished, setProcessModelPublished] = useState<any>(null);
|
||||
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 permissionRequestData: PermissionsToCheck = {
|
||||
|
@ -98,6 +104,15 @@ export default function ProcessModelShow() {
|
|||
setProcessModel(result);
|
||||
setReloadModel(false);
|
||||
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({
|
||||
path: `/process-models/${modifiedProcessModelId}`,
|
||||
|
@ -562,44 +577,124 @@ export default function ProcessModelShow() {
|
|||
);
|
||||
};
|
||||
|
||||
const processModelFilesSection = () => {
|
||||
return (
|
||||
<Grid
|
||||
condensed
|
||||
fullWidth
|
||||
className="megacondensed process-model-files-section"
|
||||
>
|
||||
<Column md={8} lg={14} sm={4}>
|
||||
<Accordion align="end" open className="megacondensed-button">
|
||||
<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>
|
||||
}
|
||||
>
|
||||
const readmeFileArea = () => {
|
||||
if (readmeFile) {
|
||||
return (
|
||||
<div className="readme-container">
|
||||
<Grid condensed fullWidth className="megacondensed">
|
||||
<Column md={7} lg={15} sm={3}>
|
||||
<p className="with-icons">{readmeFile.name}</p>
|
||||
</Column>
|
||||
<Column md={1} lg={1} sm={1}>
|
||||
<Can
|
||||
I="POST"
|
||||
I="GET"
|
||||
a={targetUris.processModelFileCreatePath}
|
||||
ability={ability}
|
||||
>
|
||||
{addFileComponent()}
|
||||
<br />
|
||||
<Button
|
||||
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>
|
||||
{processModelFileList()}
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</Column>
|
||||
</Grid>
|
||||
</Column>
|
||||
</Grid>
|
||||
<hr />
|
||||
<MarkdownDisplayForFile
|
||||
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"
|
||||
hasIconOnly
|
||||
href={`/admin/process-models/${modifiedProcessModelId}/edit`}
|
||||
>
|
||||
Edit process model
|
||||
</Button>
|
||||
/>
|
||||
</Can>
|
||||
<Can I="DELETE" a={targetUris.processModelShowPath} ability={ability}>
|
||||
<ButtonWithConfirmation
|
||||
|
@ -707,28 +800,7 @@ export default function ProcessModelShow() {
|
|||
</Stack>
|
||||
<p className="process-description">{processModel.description}</p>
|
||||
{processModel.primary_file_name ? processStartButton : null}
|
||||
{processModelFilesSection()}
|
||||
<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>
|
||||
<div className="with-top-margin">{tabArea()}</div>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -6,6 +6,7 @@ import { useNavigate, useParams, useSearchParams } from 'react-router-dom';
|
|||
// @ts-ignore
|
||||
import { Button, ButtonSet, Modal } from '@carbon/react';
|
||||
import { Can } from '@casl/react';
|
||||
import MDEditor from '@uiw/react-md-editor';
|
||||
import ProcessBreadcrumb from '../components/ProcessBreadcrumb';
|
||||
import HttpService from '../services/HttpService';
|
||||
import ButtonWithConfirmation from '../components/ButtonWithConfirmation';
|
||||
|
@ -57,6 +58,7 @@ export default function ReactFormEditor() {
|
|||
|
||||
const hasDiagram = fileExtension === 'bpmn' || fileExtension === 'dmn';
|
||||
const hasFormBuilder = fileExtension === 'json';
|
||||
const defaultFileName = searchParams.get('default_file_name');
|
||||
|
||||
const editorDefaultLanguage = (() => {
|
||||
if (fileExtension === 'json') {
|
||||
|
@ -106,7 +108,8 @@ export default function ReactFormEditor() {
|
|||
setProcessModelFile(file);
|
||||
}
|
||||
if (!params.file_name) {
|
||||
const fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
||||
const fileNameWithExtension =
|
||||
defaultFileName ?? `${newFileName}.${fileExtension}`;
|
||||
navigate(
|
||||
`/admin/process-models/${modifiedProcessModelId}/form/${fileNameWithExtension}`
|
||||
);
|
||||
|
@ -119,7 +122,7 @@ export default function ReactFormEditor() {
|
|||
|
||||
let url = `/process-models/${modifiedProcessModelId}/files`;
|
||||
let httpMethod = 'PUT';
|
||||
let fileNameWithExtension = params.file_name;
|
||||
let fileNameWithExtension = params.file_name || defaultFileName;
|
||||
|
||||
if (newFileName) {
|
||||
fileNameWithExtension = `${newFileName}.${fileExtension}`;
|
||||
|
@ -219,6 +222,30 @@ export default function ReactFormEditor() {
|
|||
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) {
|
||||
const processModelFileName = processModelFile ? processModelFile.name : '';
|
||||
const formBuildFileParam = params.file_name
|
||||
|
@ -318,13 +345,7 @@ export default function ReactFormEditor() {
|
|||
<ActiveUsers />
|
||||
</Can>
|
||||
</ButtonSet>
|
||||
<Editor
|
||||
height={600}
|
||||
width="auto"
|
||||
defaultLanguage={editorDefaultLanguage}
|
||||
defaultValue={processModelFileContents || ''}
|
||||
onChange={(value) => setProcessModelFileContents(value || '')}
|
||||
/>
|
||||
{editorArea()}
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue