Skip to content

Commit 22e2b5f

Browse files
authored
Merge pull request #721 from PROCEED-Labs/fix-import-bpmn-preview
Export/Import Improvements
2 parents 25fd372 + 1546424 commit 22e2b5f

5 files changed

Lines changed: 140 additions & 32 deletions

File tree

src/management-system-v2/components/bpmn-canvas.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -399,7 +399,7 @@ const BPMNCanvas = forwardRef<BPMNCanvasRef, BPMNCanvasProps>(
399399
return () => resizeObserver.disconnect();
400400
}, [resizeWithContainer]);
401401

402-
return <div className={className} style={{ height: '100%' }} ref={canvas}></div>;
402+
return <div className={className} style={{ height: '100%', width: '100%' }} ref={canvas}></div>;
403403
},
404404
);
405405

src/management-system-v2/components/bpmn-viewer.tsx

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ const BPMNViewer: FC<BPMNViewerProps> = ({
6060
};
6161

6262
type LazyLoadingBPMNViewerProps = BPMNViewerProps & {
63+
visible?: boolean;
6364
fallback?: React.ReactNode;
6465
};
6566

@@ -68,15 +69,21 @@ export const LazyBPMNViewer: FC<LazyLoadingBPMNViewerProps> = ({
6869
...props
6970
}) => {
7071
const ViewerContainerRef = useRef(null);
71-
const visible = useLazyRendering(ViewerContainerRef);
72+
const internalVisible = useLazyRendering(ViewerContainerRef);
73+
74+
const _visible = props.visible !== undefined ? props.visible : internalVisible;
75+
const _props = { ...props, visible: undefined };
7276

7377
return (
7478
<>
75-
<div ref={ViewerContainerRef} style={{ height: '100%', width: '100%' }}>
76-
{visible /* This ensures, that only elements, that are visible or close to beeing visible are rendered -> reduces requests for bpmn/xml */ ? (
79+
<div
80+
ref={ViewerContainerRef}
81+
style={{ height: '100%', width: '100%', display: 'flex', justifyContent: 'center' }}
82+
>
83+
{_visible /* This ensures, that only elements, that are visible or close to beeing visible are rendered -> reduces requests for bpmn/xml */ ? (
7784
<Suspense fallback={fallback}>
7885
{/* Prevent sequential rendering/ get from showing the Icon-list */}
79-
<BPMNViewer {...props} />
86+
<BPMNViewer {..._props} />
8087
</Suspense>
8188
) : (
8289
fallback

src/management-system-v2/components/process-modal.tsx

Lines changed: 74 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
Skeleton,
1919
Breadcrumb,
2020
} from 'antd';
21+
import { FolderOutlined } from '@ant-design/icons';
2122
import { MdArrowBackIos, MdArrowForwardIos } from 'react-icons/md';
2223
import { UserError } from '@/lib/user-error';
2324
import { useAddControlCallback } from '@/lib/controls-store';
@@ -31,14 +32,15 @@ import dynamic from 'next/dynamic';
3132

3233
import '@toast-ui/editor/dist/toastui-editor.css';
3334
import type { Editor as EditorClass } from '@toast-ui/react-editor';
35+
import { isDummyFolderProcess } from '@/lib/process-export/export-preparation';
3436
const TextEditor = dynamic(() => import('@/components/text-editor'), {
3537
ssr: false,
3638
loading: () => <Skeleton.Input size="large" />,
3739
});
3840

3941
export type ProcessModalMode = 'create' | 'edit' | 'copy' | 'import';
4042

41-
type ProcessModalProps<T extends { name: string; description: string }> = {
43+
type ProcessModalProps<T extends { id?: string; name: string; description: string }> = {
4244
open: boolean;
4345
title: string;
4446
okText?: string;
@@ -52,6 +54,7 @@ type ProcessModalProps<T extends { name: string; description: string }> = {
5254

5355
const ProcessModal = <
5456
T extends {
57+
id?: string;
5558
name: string;
5659
description: string;
5760
userDefinedId?: string;
@@ -214,6 +217,8 @@ const ProcessModal = <
214217
{ level: 2, blocking: open, dependencies: [open] },
215218
);
216219

220+
const [fullyOpen, setFullyOpen] = useState(false);
221+
217222
const renderFormContent = () => {
218223
if (!initialData) {
219224
return <ProcessInputs index={0} readonly={readonly} />;
@@ -255,13 +260,38 @@ const ProcessModal = <
255260
>
256261
{initialData.map((process, index) => (
257262
<Card key={index}>
258-
{process.bpmn && (
259-
<>
260-
<LazyBPMNViewer previewBpmn={process.bpmn} reduceLogo={true} fitOnResize />
261-
<Divider style={{ width: '100%', marginLeft: '-20%' }} />
262-
</>
263-
)}
264-
<ProcessInputsImport key={index} index={index} readonly={readonly} />
263+
<>
264+
{process.bpmn && (
265+
<>
266+
{isDummyFolderProcess(process) ? (
267+
<div
268+
style={{
269+
height: '150px',
270+
width: '100%',
271+
display: 'flex',
272+
justifyContent: 'center',
273+
}}
274+
>
275+
<FolderOutlined style={{ fontSize: '100px' }} />
276+
</div>
277+
) : (
278+
<LazyBPMNViewer
279+
previewBpmn={process.bpmn}
280+
reduceLogo={true}
281+
fitOnResize
282+
visible={fullyOpen}
283+
/>
284+
)}
285+
<Divider style={{ width: '100%', marginLeft: '-20%' }} />
286+
</>
287+
)}
288+
</>
289+
<ProcessInputsImport
290+
key={index}
291+
index={index}
292+
readonly={readonly}
293+
isFolder={isDummyFolderProcess(process)}
294+
/>
265295
</Card>
266296
))}
267297
</Carousel>
@@ -309,6 +339,7 @@ const ProcessModal = <
309339
flexDirection: 'column',
310340
},
311341
}}
342+
afterOpenChange={(open) => setFullyOpen(open)}
312343
>
313344
<div style={{ overflowY: 'auto', flex: 1 }} className="Hide-Scroll-Bar">
314345
{nameCollisions.length > 0 && showCollisions && (
@@ -395,6 +426,7 @@ type ProcessInputsProps = {
395426
index: number;
396427
initialName?: string;
397428
readonly?: boolean;
429+
isFolder?: boolean;
398430
};
399431

400432
const ProcessInputs = ({ index, initialName, readonly = false }: ProcessInputsProps) => {
@@ -486,7 +518,7 @@ const ProcessInputs = ({ index, initialName, readonly = false }: ProcessInputsPr
486518
);
487519
};
488520

489-
const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) => {
521+
const ProcessInputsImport = ({ index, readonly = false, isFolder }: ProcessInputsProps) => {
490522
const instance = Form.useFormInstance();
491523
const data = instance.getFieldsValue()[index];
492524

@@ -497,6 +529,13 @@ const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) =>
497529
<Form.Item hidden name={[index, 'folderPath']}>
498530
<Input disabled />
499531
</Form.Item>
532+
{isFolder && (
533+
<Alert
534+
style={{ marginBottom: '10px' }}
535+
type="warning"
536+
title="This imports an empty folder without any processes."
537+
/>
538+
)}
500539
{!!data?.folderPath && (
501540
<Form.Item label="Import Path">
502541
<Card size="small">
@@ -509,24 +548,32 @@ const ProcessInputsImport = ({ index, readonly = false }: ProcessInputsProps) =>
509548
</Card>
510549
</Form.Item>
511550
)}
512-
<ProcessInputs index={index} readonly={readonly} />
513-
<Form.Item name={[index, 'creator']} label="Original Creator" rules={[{ required: false }]}>
514-
<Input disabled />
515-
</Form.Item>
516-
<Form.Item
517-
name={[index, 'creatorUsername']}
518-
label="Original Creator Username"
519-
rules={[{ required: false }]}
520-
>
521-
<Input disabled />
522-
</Form.Item>
523-
<Form.Item
524-
name={[index, 'createdOn']}
525-
label="Original Creation Date"
526-
rules={[{ required: false }]}
527-
>
528-
<Input disabled />
529-
</Form.Item>
551+
{!isFolder && (
552+
<>
553+
<ProcessInputs index={index} readonly={readonly} />
554+
<Form.Item
555+
name={[index, 'creator']}
556+
label="Original Creator"
557+
rules={[{ required: false }]}
558+
>
559+
<Input disabled />
560+
</Form.Item>
561+
<Form.Item
562+
name={[index, 'creatorUsername']}
563+
label="Original Creator Username"
564+
rules={[{ required: false }]}
565+
>
566+
<Input disabled />
567+
</Form.Item>
568+
<Form.Item
569+
name={[index, 'createdOn']}
570+
label="Original Creation Date"
571+
rules={[{ required: false }]}
572+
>
573+
<Input disabled />
574+
</Form.Item>
575+
</>
576+
)}
530577
</>
531578
);
532579
};

src/management-system-v2/lib/data/processes.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
generateScriptTaskFileName,
99
generateStartFormFileName,
1010
generateUserTaskFileName,
11+
getDefinitionsId,
1112
getDefinitionsVersionInformation,
1213
getElementsByTagName,
1314
setDefinitionsName,
@@ -66,6 +67,7 @@ import { getFolderById, getRootFolder } from './db/folders';
6667
import { truthyFilter } from '../typescript-utils';
6768
import { createFolder, getFolder, getFolderContents } from './folders';
6869
import Ability from '../ability/abilityHelper';
70+
import { isDummyFolderProcess } from '../process-export/export-preparation';
6971

7072
// Import necessary functions from processModule
7173

@@ -275,6 +277,12 @@ export const addProcesses = async (
275277
value.folderId = folder.id;
276278
}
277279

280+
if (isDummyFolderProcess(value)) {
281+
// if the process represents a placeholder for importing empty folders then skip adding the
282+
// process after the folder structure was created
283+
continue;
284+
}
285+
278286
// bpmn prop gets deleted in addProcess()
279287
const process = await _addProcess({ ...newProcess, folderId: value.folderId });
280288

@@ -395,6 +403,9 @@ export const importProcesses = async (processData: ProcessData[], spaceId: strin
395403
return importedProcesses;
396404
}
397405

406+
// filter out dummy processes that were included in the import to generate empty folders
407+
processData = processData.filter((p) => !isDummyFolderProcess(p));
408+
398409
for (let idx = 0; idx < importedProcesses.length; idx++) {
399410
const process = importedProcesses[idx];
400411
const artefacts = processData[idx].artefacts;

src/management-system-v2/lib/process-export/export-preparation.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ import {
2121
toBpmnXml,
2222
getAllElements,
2323
getStartFormFileNameMapping,
24+
initXml,
25+
setDefinitionsId,
26+
setDefinitionsName,
2427
} from '@proceed/bpmn-helper';
2528

2629
import { asyncMap, asyncFilter } from '../helpers/javascriptHelpers';
@@ -250,6 +253,30 @@ async function ensureProcessInfo(
250253
isImport = false,
251254
ability?: Ability,
252255
) {
256+
if (definitionId.startsWith(dummyProcessId)) {
257+
let bpmn = initXml();
258+
bpmn = (await setDefinitionsId(bpmn, definitionId)) as string;
259+
bpmn = (await setDefinitionsName(bpmn, 'Placeholder Process To Keep The Folder')) as string;
260+
exportData[definitionId] = {
261+
definitionName: 'Placeholder Process To Keep The Folder',
262+
folderPath,
263+
isImport,
264+
versions: {
265+
latest: {
266+
bpmn,
267+
isImport,
268+
layers: [],
269+
imports: [],
270+
},
271+
},
272+
scriptTasks: [],
273+
userTasks: [],
274+
images: [],
275+
};
276+
277+
return;
278+
}
279+
253280
if (!exportData[definitionId]) {
254281
const process = await getProcess(definitionId, spaceId, undefined, ability);
255282

@@ -295,6 +322,12 @@ async function ensureProcessInfo(
295322
}
296323
}
297324

325+
export const dummyProcessId = '___empty_dummy_process___';
326+
327+
export function isDummyFolderProcess(input: { id?: string }) {
328+
return 'id' in input && input.id?.startsWith(dummyProcessId);
329+
}
330+
298331
/**
299332
* Gets the data that is needed to export all the requested processes with the given options
300333
*
@@ -321,6 +354,16 @@ export async function prepareExport(
321354
if (isUserErrorResponse(folder)) throw folder.error.message;
322355
if (isUserErrorResponse(content)) throw content.error.message;
323356

357+
// make sure empty folders are exported by placing a dummy process into the folder
358+
if (!content.length) {
359+
return [
360+
{
361+
definitionId: `${dummyProcessId}_${path.split('/').join('_')}_${folder.name}`,
362+
folderPath: path + '/' + folder.name,
363+
},
364+
];
365+
}
366+
324367
return (
325368
await asyncMap(content, async (entry) => {
326369
if (entry.type !== 'folder') {

0 commit comments

Comments
 (0)