From 5e501772a65c49b8804b4ea56108e26e7a35e329 Mon Sep 17 00:00:00 2001 From: Kai Rohwer Date: Sun, 10 Dec 2023 22:51:25 +0100 Subject: [PATCH] Move process page to SC --- .../processes/[processId]/loading.tsx | 19 ++++++++ .../processes/[processId]/page.tsx | 46 +++++++++++++++++-- .../[processId]/{_page.tsx => wrapper.tsx} | 35 +++++++------- .../processes/{loading.tsx => _loading.tsx} | 2 +- .../api/auth/[...nextauth]/auth-options.ts | 21 +++++++++ .../app/api/auth/[...nextauth]/route.ts | 22 +-------- src/management-system-v2/components/auth.tsx | 8 ++-- .../components/modeler.tsx | 31 ++++++------- .../lib/authorization/caslRules.ts | 22 +++++---- .../lib/authorization/permissionHelpers.ts | 2 +- .../lib/authorization/rolesHelper.ts | 17 ++++--- .../lib/data/legacy/_process.js | 17 +++++-- .../lib/data/legacy/iam/role-mappings.js | 1 + .../lib/data/legacy/iam/roles.js | 1 + .../lib/data/legacy/logging.js | 2 +- .../lib/data/processes.ts | 35 ++++++++++++++ 16 files changed, 198 insertions(+), 83 deletions(-) create mode 100644 src/management-system-v2/app/(dashboard)/processes/[processId]/loading.tsx rename src/management-system-v2/app/(dashboard)/processes/[processId]/{_page.tsx => wrapper.tsx} (87%) rename src/management-system-v2/app/(dashboard)/processes/{loading.tsx => _loading.tsx} (88%) create mode 100644 src/management-system-v2/app/api/auth/[...nextauth]/auth-options.ts diff --git a/src/management-system-v2/app/(dashboard)/processes/[processId]/loading.tsx b/src/management-system-v2/app/(dashboard)/processes/[processId]/loading.tsx new file mode 100644 index 000000000..b6aa4c08f --- /dev/null +++ b/src/management-system-v2/app/(dashboard)/processes/[processId]/loading.tsx @@ -0,0 +1,19 @@ +import Content from '@/components/content'; +import { Space, Spin } from 'antd'; +import { LoadingOutlined } from '@ant-design/icons'; + +const ProcessSkeleton = () => { + return ( + + + } /> + + + ); +}; + +export default ProcessSkeleton; diff --git a/src/management-system-v2/app/(dashboard)/processes/[processId]/page.tsx b/src/management-system-v2/app/(dashboard)/processes/[processId]/page.tsx index 96f0e4b5a..46b27e580 100644 --- a/src/management-system-v2/app/(dashboard)/processes/[processId]/page.tsx +++ b/src/management-system-v2/app/(dashboard)/processes/[processId]/page.tsx @@ -1,5 +1,45 @@ -import Auth from '@/components/auth'; -import Processes from './_page'; +import Auth, { getCurrentUser } from '@/components/auth'; +import Wrapper from './wrapper'; +import styles from './page.module.scss'; +import { FC, useEffect, useState } from 'react'; +import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'; +import Modeler from '@/components/modeler'; +import cn from 'classnames'; +import { getProcess, getProcessVersionBpmn, getProcesses } from '@/lib/data/legacy/process'; +import { toCaslResource } from '@/lib/ability/caslAbility'; + +type ProcessProps = { + params: { processId: string }; + searchParams: { version?: string }; +}; + +const Process = async ({ params: { processId }, searchParams }: ProcessProps) => { + // TODO: check if params is correct after fix release. And maybe don't need + // refresh in processes.tsx anymore? + console.log('processId', processId); + console.log('query', searchParams); + const selectedVersionId = searchParams.version ? searchParams.version : undefined; + const { ability } = await getCurrentUser(); + // Only load bpmn if no version selected. + const process = await getProcess(processId, !selectedVersionId); + const processes = await getProcesses(ability); + + if (!ability.can('view', toCaslResource('Process', process))) { + throw new Error('Forbidden.'); + } + + const selectedVersionBpmn = selectedVersionId + ? await getProcessVersionBpmn(processId, selectedVersionId) + : process.bpmn; + + // Since the user is able to minimize and close the page, everyting is in a + // client component from here. + return ( + + + + ); +}; export default Auth( { @@ -7,5 +47,5 @@ export default Auth( resource: 'Process', fallbackRedirect: '/processes', }, - Processes, + Process, ); diff --git a/src/management-system-v2/app/(dashboard)/processes/[processId]/_page.tsx b/src/management-system-v2/app/(dashboard)/processes/[processId]/wrapper.tsx similarity index 87% rename from src/management-system-v2/app/(dashboard)/processes/[processId]/_page.tsx rename to src/management-system-v2/app/(dashboard)/processes/[processId]/wrapper.tsx index 8f7939cb3..78fd60d69 100644 --- a/src/management-system-v2/app/(dashboard)/processes/[processId]/_page.tsx +++ b/src/management-system-v2/app/(dashboard)/processes/[processId]/wrapper.tsx @@ -1,7 +1,7 @@ 'use client'; import styles from './page.module.scss'; -import { FC, useEffect, useState } from 'react'; +import { FC, PropsWithChildren, useEffect, useState } from 'react'; import { useParams, usePathname, useRouter, useSearchParams } from 'next/navigation'; import Modeler from '@/components/modeler'; import cn from 'classnames'; @@ -25,13 +25,14 @@ import VersionCreationButton from '@/components/version-creation-button'; import ProcessCreationButton from '@/components/process-creation-button'; import { AuthCan } from '@/components/auth-can'; -type ProcessProps = { - params: { processId: string }; -}; +type WrapperProps = PropsWithChildren<{ + processName: string; + versions: { version: number; name: string; description: string }[]; +}>; const LATEST_VERSION = { version: -1, name: 'Latest Version', description: '' }; -const Processes: FC = () => { +const Wrapper = ({ children, processName, versions }: WrapperProps) => { // TODO: check if params is correct after fix release. And maybe don't need // refresh in processes.tsx anymore? const { processId } = useParams(); @@ -66,7 +67,7 @@ const Processes: FC = () => { const minimized = pathname !== `/processes/${processId}`; const selectedVersionId = parseInt(query.get('version') ?? '-1'); const selectedVersion = - process?.versions.find((version) => version.version === selectedVersionId) ?? LATEST_VERSION; + versions.find((version) => version.version === selectedVersionId) ?? LATEST_VERSION; useEffect(() => { // Reset closed state when page is not minimized anymore. @@ -106,8 +107,8 @@ const Processes: FC = () => { showSearch filterOption={filterOption} value={{ - value: process?.definitionId, - label: process?.definitionName, + value: processId, + label: processName, }} onSelect={(_, option) => { router.push(`/processes/${option.value}`); @@ -167,7 +168,7 @@ const Processes: FC = () => { )} - options={[LATEST_VERSION].concat(process?.versions ?? []).map(({ version, name }) => ({ + options={[LATEST_VERSION].concat(versions ?? []).map(({ version, name }) => ({ value: version, label: name, }))} @@ -183,19 +184,17 @@ const Processes: FC = () => { return ( /} - items={breadcrumItems} - /> - ) + /} + items={breadcrumItems} + /> } compact wrapperClass={cn(styles.Wrapper, { [styles.minimized]: minimized })} headerClass={cn(styles.HF, { [styles.minimizedHF]: minimized })} > - + {children} {minimized ? ( setClosed(true)} /> ) : null} @@ -203,4 +202,4 @@ const Processes: FC = () => { ); }; -export default Processes; +export default Wrapper; diff --git a/src/management-system-v2/app/(dashboard)/processes/loading.tsx b/src/management-system-v2/app/(dashboard)/processes/_loading.tsx similarity index 88% rename from src/management-system-v2/app/(dashboard)/processes/loading.tsx rename to src/management-system-v2/app/(dashboard)/processes/_loading.tsx index ce1c7f8df..7f898630c 100644 --- a/src/management-system-v2/app/(dashboard)/processes/loading.tsx +++ b/src/management-system-v2/app/(dashboard)/processes/_loading.tsx @@ -1,7 +1,7 @@ import Content from '@/components/content'; import { Skeleton, Space } from 'antd'; -const ProcessesSkeleton = async () => { +const ProcessesSkeleton = () => { return ( diff --git a/src/management-system-v2/app/api/auth/[...nextauth]/auth-options.ts b/src/management-system-v2/app/api/auth/[...nextauth]/auth-options.ts new file mode 100644 index 000000000..d91703661 --- /dev/null +++ b/src/management-system-v2/app/api/auth/[...nextauth]/auth-options.ts @@ -0,0 +1,21 @@ +import { AuthOptions } from 'next-auth'; +import { User } from '@/types/next-auth'; +import { randomUUID } from 'crypto'; + +export const nextAuthOptions: AuthOptions = { + secret: process.env.NEXTAUTH_SECRET, + providers: [], + callbacks: { + async jwt({ token, user, trigger }) { + if (trigger === 'signIn') token.csrfToken = randomUUID(); + if (user) token.user = user as User; + return token; + }, + session(args) { + const { session, token } = args; + if (token.user) session.user = token.user; + session.csrfToken = token.csrfToken; + return session; + }, + }, +}; diff --git a/src/management-system-v2/app/api/auth/[...nextauth]/route.ts b/src/management-system-v2/app/api/auth/[...nextauth]/route.ts index 695072af1..33a0edea1 100644 --- a/src/management-system-v2/app/api/auth/[...nextauth]/route.ts +++ b/src/management-system-v2/app/api/auth/[...nextauth]/route.ts @@ -1,27 +1,7 @@ -import { AuthOptions } from 'next-auth'; import NextAuth from 'next-auth/next'; import Auth0Provider from 'next-auth/providers/auth0'; -import { User } from '@/types/next-auth'; -import { randomUUID } from 'crypto'; import CredentialsProvider from 'next-auth/providers/credentials'; - -export const nextAuthOptions: AuthOptions = { - secret: process.env.NEXTAUTH_SECRET, - providers: [], - callbacks: { - async jwt({ token, user, trigger }) { - if (trigger === 'signIn') token.csrfToken = randomUUID(); - if (user) token.user = user as User; - return token; - }, - session(args) { - const { session, token } = args; - if (token.user) session.user = token.user; - session.csrfToken = token.csrfToken; - return session; - }, - }, -}; +import { nextAuthOptions } from './auth-options'; if (process.env.USE_AUTH0) { nextAuthOptions.providers.push( diff --git a/src/management-system-v2/components/auth.tsx b/src/management-system-v2/components/auth.tsx index 9e6d0444a..72f7ec6fd 100644 --- a/src/management-system-v2/components/auth.tsx +++ b/src/management-system-v2/components/auth.tsx @@ -1,12 +1,14 @@ -import 'server-only'; - import { ComponentProps, ComponentType, cache } from 'react'; import { getServerSession } from 'next-auth/next'; import { redirect } from 'next/navigation'; -import { nextAuthOptions } from '@/app/api/auth/[...nextauth]/route'; import { AuthCan, AuthCanProps } from './auth-can'; import { getAbilityForUser } from '@/lib/authorization/authorization'; +import { nextAuthOptions } from '@/app/api/auth/[...nextauth]/auth-options'; +// TODO: To enable PPR move the session redirect into this function, so it will +// be called when the session is first accessed and everything above can PPR. For +// permissions, each server component should check its permissions anyway, for +// composability. export const getCurrentUser = cache(async () => { const session = await getServerSession(nextAuthOptions); const ability = await getAbilityForUser(session?.user.id || ''); diff --git a/src/management-system-v2/components/modeler.tsx b/src/management-system-v2/components/modeler.tsx index 1cc832f0c..82ed67272 100644 --- a/src/management-system-v2/components/modeler.tsx +++ b/src/management-system-v2/components/modeler.tsx @@ -1,12 +1,12 @@ 'use client'; -import React, { FC, useEffect, useRef, useState } from 'react'; +import React, { FC, useEffect, useRef, useState, useTransition } from 'react'; import 'bpmn-js/dist/assets/bpmn-js.css'; import 'bpmn-js/dist/assets/diagram-js.css'; import 'bpmn-js/dist/assets/bpmn-font/css/bpmn.css'; import type ModelerType from 'bpmn-js/lib/Modeler'; import type ViewerType from 'bpmn-js/lib/NavigatedViewer'; -import { useParams, useSearchParams } from 'next/navigation'; +import { useParams, usePathname, useSearchParams } from 'next/navigation'; import ModelerToolbar from './modeler-toolbar'; import XmlEditor from './xml-editor'; @@ -18,6 +18,7 @@ import { useProcessBpmn } from '@/lib/process-queries'; import VersionToolbar from './version-toolbar'; import { copyProcessImage } from '@/lib/process-export/copy-process-image'; +import { updateProcess } from '@/lib/data/processes'; // Conditionally load the BPMN modeler only on the client, because it uses // "window" reference. It won't be included in the initial bundle, but will be @@ -32,25 +33,26 @@ const BPMNViewer = : null; type ModelerProps = React.HTMLAttributes & { - minimized: boolean; + processBpmn: string; }; -const Modeler: FC = ({ minimized, ...props }) => { +const Modeler = ({ processBpmn, ...divProps }: ModelerProps) => { const { processId } = useParams(); + const pathname = usePathname(); const [initialized, setInitialized] = useState(false); const [xmlEditorBpmn, setXmlEditorBpmn] = useState(undefined); const query = useSearchParams(); + const [isPending, startTransition] = useTransition(); const canvas = useRef(null); const modeler = useRef(null); - const { mutateAsync: updateProcessMutation } = usePutAsset('/process/{definitionId}'); - const setModeler = useModelerStateStore((state) => state.setModeler); const setSelectedElementId = useModelerStateStore((state) => state.setSelectedElementId); const setEditingDisabled = useModelerStateStore((state) => state.setEditingDisabled); /// Derived State + const minimized = pathname !== `/processes/${processId}`; const selectedVersionId = query.get('version'); useEffect(() => { @@ -88,9 +90,9 @@ const Modeler: FC = ({ minimized, ...props }) => { try { const { xml } = await modeler.current!.saveXML({ format: true }); /* await updateProcess(processId as string, { bpmn: xml! }); */ - await updateProcessMutation({ - params: { path: { definitionId: processId as string } }, - body: { bpmn: xml }, + + startTransition(async () => { + await updateProcess(processId as string, xml); }); } catch (err) { console.log(err); @@ -124,9 +126,7 @@ const Modeler: FC = ({ minimized, ...props }) => { modeler.current?.destroy(); }; // only reset the modeler if we switch between editing being enabled or disabled - }, [setModeler, selectedVersionId, processId, updateProcessMutation]); - - const { data: processBpmn } = useProcessBpmn(processId as string, selectedVersionId); + }, [setModeler, selectedVersionId, processId]); useEffect(() => { // only import the bpmn once (the effect will be retriggered when initialized is set to false at its end) @@ -165,9 +165,8 @@ const Modeler: FC = ({ minimized, ...props }) => { }); // if the bpmn contains unexpected content (text content for an element where the model does not define text) the modeler will remove it automatically => make sure the stored bpmn is the same as the one in the modeler const { xml: cleanedBpmn } = await modeler.current.saveXML({ format: true }); - await updateProcessMutation({ - params: { path: { definitionId: processId as string } }, - body: { bpmn: cleanedBpmn }, + startTransition(async () => { + await updateProcess(processId as string, cleanedBpmn); }); } }; @@ -188,7 +187,7 @@ const Modeler: FC = ({ minimized, ...props }) => { )} )} -
+
); }; diff --git a/src/management-system-v2/lib/authorization/caslRules.ts b/src/management-system-v2/lib/authorization/caslRules.ts index 3e70164d4..e6939285f 100644 --- a/src/management-system-v2/lib/authorization/caslRules.ts +++ b/src/management-system-v2/lib/authorization/caslRules.ts @@ -218,7 +218,7 @@ async function rulesForSharedResources(ability: CaslAbility, userId: string) { conditions: { conditions: { id: { $eq: share.resourceId }, - $: { $not_expired_value: share.expiredAt }, + $: { $not_expired_value: share.expiredAt ?? undefined }, }, }, }); @@ -240,7 +240,7 @@ function rulesForShares(resource: ResourceType, userId: string, expiration: stri conditions: { resourceOwner: { $eq: userId }, resourceType: { $eq_string_case_insensitive: resource }, - $: { $not_expired_value: expiration }, + $: { $not_expired_value: expiration ?? undefined }, }, conditionsOperator: 'and', }, @@ -253,7 +253,7 @@ function rulesForShares(resource: ResourceType, userId: string, expiration: stri conditions: { sharedBy: { $eq: userId }, resourceType: { $eq_string_case_insensitive: resource }, - $: { $not_expired_value: expiration }, + $: { $not_expired_value: expiration ?? undefined }, }, conditionsOperator: 'and', }, @@ -307,7 +307,7 @@ export async function rulesForUser(userId: string) { for (const resource of resources) { if (!(resource in role.permissions)) continue; - const permissionsForResource = role.permissions[resource]; + const permissionsForResource = role.permissions[resource]!; const actionsSet = new Set(); @@ -321,14 +321,14 @@ export async function rulesForUser(userId: string) { action: 'manage-roles', conditions: { conditions: { - $: { $not_expired_value: role.expiration }, + $: { $not_expired_value: role.expiration ?? undefined }, }, }, }); } if (actionsSet.has('share')) - translatedRules.push(...rulesForShares(resource, userId, role.expiration)); + translatedRules.push(...rulesForShares(resource, userId, role.expiration ?? null)); if (actionsSet.has('admin')) translatedRules.push({ @@ -337,12 +337,14 @@ export async function rulesForUser(userId: string) { conditions: { conditions: { resourceType: { $eq_string_case_insensitive: resource }, - $: { $not_expired_value: role.expiration }, + $: { $not_expired_value: role.expiration ?? undefined }, }, }, }); const ownershipConditions = - needOwnership.has(resource) && !actionsSet.has('admin') ? { owner: { $eq: userId } } : {}; + needOwnership.has(resource) && !actionsSet.has('admin') + ? { owner: { $eq: userId } } + : undefined; translatedRules.push({ subject: resource, @@ -350,7 +352,7 @@ export async function rulesForUser(userId: string) { conditions: { conditions: { ...ownershipConditions, - $: { $not_expired_value: role.expiration }, + $: { $not_expired_value: role.expiration ?? undefined }, }, }, }); @@ -371,7 +373,7 @@ export async function rulesForUser(userId: string) { // casl uses the ordering of the rules to decide // this way inverted rules allways decide over normal rules - translatedRules.sort((a, b) => +a.inverted - +b.inverted); + translatedRules.sort((a, b) => Number(a.inverted) - Number(b.inverted)); return { rules: packRules(translatedRules), expiration: firstExpiration }; } diff --git a/src/management-system-v2/lib/authorization/permissionHelpers.ts b/src/management-system-v2/lib/authorization/permissionHelpers.ts index d9f1a8c84..3dd0714e3 100644 --- a/src/management-system-v2/lib/authorization/permissionHelpers.ts +++ b/src/management-system-v2/lib/authorization/permissionHelpers.ts @@ -15,7 +15,7 @@ export function permissionNumberToIdentifiers(permission: number): ResourceActio return ['admin']; } - const actions = []; + const actions: ResourceActionType[] = []; // starts at 1 because none would be allways included and // ends at length-1 because admin needs to be added with adminPermissions number diff --git a/src/management-system-v2/lib/authorization/rolesHelper.ts b/src/management-system-v2/lib/authorization/rolesHelper.ts index 961d3a8ff..706e5f9af 100644 --- a/src/management-system-v2/lib/authorization/rolesHelper.ts +++ b/src/management-system-v2/lib/authorization/rolesHelper.ts @@ -12,21 +12,26 @@ type Role = { export function getAppliedRolesForUser(userId: string): Role[] { if (userId === '') - return [Object.values(roleMetaObjects).find((role) => role.default && role.name === '@guest')]; + return [ + Object.values(roleMetaObjects).find((role: any) => role.default && role.name === '@guest'), + ] as Role[]; const userRoles: Role[] = []; const adminRole = Object.values(roleMetaObjects).find( - (role) => role.default && role.name === '@admin', - ); - if (adminRole.members.map((member) => member.userId).includes(userId)) userRoles.push(adminRole); + (role: any) => role.default && role.name === '@admin', + ) as any; + if (adminRole.members.map((member: any) => member.userId).includes(userId)) + userRoles.push(adminRole); userRoles.push( - Object.values(roleMetaObjects).find((role) => role.default && role.name === '@everyone'), + Object.values(roleMetaObjects).find( + (role: any) => role.default && role.name === '@everyone', + ) as Role, ); if (roleMappingsMetaObjects.users.hasOwnProperty(userId)) { - roleMappingsMetaObjects.users[userId].forEach((role) => { + roleMappingsMetaObjects.users[userId].forEach((role: any) => { const roleObject = roleMetaObjects[role.roleId]; if (roleObject.expiration === null || new Date(roleObject.expiration) > new Date()) userRoles.push(roleMetaObjects[role.roleId]); diff --git a/src/management-system-v2/lib/data/legacy/_process.js b/src/management-system-v2/lib/data/legacy/_process.js index 4624b6ba9..40a64e1f8 100644 --- a/src/management-system-v2/lib/data/legacy/_process.js +++ b/src/management-system-v2/lib/data/legacy/_process.js @@ -73,6 +73,17 @@ export async function getProcesses(ability, includeBPMN = false) { return userProcesses; } +export async function getProcess(processDefinitionsId, includeBPMN = false) { + const process = processMetaObjects[processDefinitionsId]; + + if (!process) { + throw new Error(`Process with id ${processDefinitionsId} could not be found!`); + } + + const bpmn = includeBPMN ? await getProcessBpmn(processDefinitionsId) : null; + return toExternalFormat({ ...process, bpmn }); +} + /** * Throws if process with given id doesn't exist * @@ -129,7 +140,7 @@ export async function addProcess(processData) { * * @param {String} processDefinitionsId * @param {String} newBpmn - * @returns {Object} - contains the new process meta information + * @returns {Promise} - contains the new process meta information */ export async function updateProcess(processDefinitionsId, newInfo) { checkIfProcessExists(processDefinitionsId); @@ -278,7 +289,7 @@ export async function addProcessVersion(processDefinitionsId, bpmn) { * * @param {String} processDefinitionsId * @param {String} version - * @returns {string} the bpmn of the specific process version + * @returns {Promise} the bpmn of the specific process version */ export async function getProcessVersionBpmn(processDefinitionsId, version) { let existingProcess = processMetaObjects[processDefinitionsId]; @@ -311,7 +322,7 @@ function removeExcessiveInformation(processInfo) { * Returns the process definition for the process with the given id * * @param {String} processDefinitionsId - * @returns {String} - the process definition + * @returns {Promise} - the process definition */ export async function getProcessBpmn(processDefinitionsId) { checkIfProcessExists(processDefinitionsId); diff --git a/src/management-system-v2/lib/data/legacy/iam/role-mappings.js b/src/management-system-v2/lib/data/legacy/iam/role-mappings.js index 1608022aa..49ca87a99 100644 --- a/src/management-system-v2/lib/data/legacy/iam/role-mappings.js +++ b/src/management-system-v2/lib/data/legacy/iam/role-mappings.js @@ -2,6 +2,7 @@ import { v4 } from 'uuid'; import store from '../store.js'; import { roleMetaObjects } from './roles.js'; +/** @type {any} - object containing all role mappings */ export let roleMappingsMetaObjects = {}; /** diff --git a/src/management-system-v2/lib/data/legacy/iam/roles.js b/src/management-system-v2/lib/data/legacy/iam/roles.js index fcab74615..12362d1ef 100644 --- a/src/management-system-v2/lib/data/legacy/iam/roles.js +++ b/src/management-system-v2/lib/data/legacy/iam/roles.js @@ -4,6 +4,7 @@ import { roleMigrations } from './migrations/role-migrations.js'; import { mergeIntoObject } from '../../../helpers/javascriptHelpers'; import { roleMappingsMetaObjects } from './role-mappings.js'; +/** @type {any} - object containing all roles */ export let roleMetaObjects = {}; /** diff --git a/src/management-system-v2/lib/data/legacy/logging.js b/src/management-system-v2/lib/data/legacy/logging.js index 02af0b702..9d789a454 100644 --- a/src/management-system-v2/lib/data/legacy/logging.js +++ b/src/management-system-v2/lib/data/legacy/logging.js @@ -8,7 +8,7 @@ const myFormat = winston.format.printf(({ timestamp, level, message }) => { const config = getBackendConfig(); -console.log(`Created logger with logging level ${config.logLevel}`); +//console.log(`Created logger with logging level ${config.logLevel}`); const logger = winston.createLogger({ level: config.logLevel, diff --git a/src/management-system-v2/lib/data/processes.ts b/src/management-system-v2/lib/data/processes.ts index d3c197c53..8633164a4 100644 --- a/src/management-system-v2/lib/data/processes.ts +++ b/src/management-system-v2/lib/data/processes.ts @@ -11,7 +11,10 @@ import { getProcessMetaObjects, toExternalFormat, addProcess as _addProcess, + getProcessBpmn, + updateProcess as _updateProcess, } from './legacy/_process'; +import { addDocumentation, setDefinitionsName } from '@proceed/bpmn-helper'; export const deleteProcesses = async (definitionIds: string[]) => { const processMetaObjects: any = getProcessMetaObjects(); @@ -47,3 +50,35 @@ export const addProcess = async (newProcess: any) => { return toExternalFormat({ ...process, bpmn }); }; + +export const updateProcess = async ( + definitionsId: string, + bpmn?: string, + description?: string, + name?: string, +) => { + const { ability } = await getCurrentUser(); + + const processMetaObjects: any = getProcessMetaObjects(); + const process = processMetaObjects[definitionsId]; + + if (!process) { + throw new Error('A process with this id does not exist.'); + } + + if (!ability.can('update', toCaslResource('Process', process))) { + throw new Error('Forbidden.'); + } + + // Either replace or update the old BPMN. + let newBpmn = bpmn ?? (await getProcessBpmn(definitionsId)); + if (description !== undefined) { + newBpmn = (await addDocumentation(newBpmn, description)) as string; + } + if (name !== undefined) { + newBpmn = (await setDefinitionsName(newBpmn, name)) as string; + } + + const newProcessInfo = await _updateProcess(definitionsId, { bpmn: newBpmn }); + return toExternalFormat({ ...newProcessInfo, bpmn: newBpmn }); +};