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:
jasquat 2023-09-20 11:14:20 -04:00 committed by GitHub
parent 86096d6d97
commit 1db2e7d973
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 208 additions and 75 deletions

View File

@ -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;
}

View File

@ -1840,6 +1840,9 @@ export default function ProcessInstanceListTable({
</Column>
);
}
if (!headerElement && !filterButtonLink) {
return null;
}
return (
<>
<Column

View File

@ -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;

View File

@ -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>
</>
);
}

View File

@ -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>
);
}