From 7abb796108b74a07c4e65c9371fa0404ebd03c7c Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Wed, 24 Jun 2026 13:38:44 +0200 Subject: [PATCH 1/8] Users can now select to be shown archived deployments in the deployment view that are hidden by default --- .../executions/[processId]/page.tsx | 2 +- .../[processId]/process-deployment-view.tsx | 21 +++-- .../executions/deployments-list.tsx | 11 ++- .../executions/deployments-view.tsx | 53 +++++++++++-- .../(automation)/executions/page.tsx | 23 +++++- .../lib/data/deployment.ts | 76 ++++++++++--------- 6 files changed, 133 insertions(+), 53 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/page.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/page.tsx index ec3ac9f0f..70703fd70 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/page.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/page.tsx @@ -7,7 +7,7 @@ import { isUserErrorResponse } from '@/lib/user-error'; import { getProcessDeployments } from '@/lib/data/deployment'; async function Deployment({ processId, spaceId }: { processId: string; spaceId: string }) { - const deployments = await getProcessDeployments(spaceId, processId); + const deployments = await getProcessDeployments(spaceId, processId, undefined, true, true); if (isUserErrorResponse(deployments)) { return ( diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx index 416e762e1..72dfe53c6 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx @@ -83,7 +83,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string // get information where the process is deployed and which instances exist const { data: deployments, refetch: refetchDeployments } = useQuery({ queryFn: async () => { - const deployments = await getProcessDeployments(spaceId, processId); + const deployments = await getProcessDeployments(spaceId, processId, undefined, true, true); if (isUserErrorResponse(deployments)) return null; return deployments; }, @@ -315,11 +315,20 @@ export default function ProcessDeploymentView({ processId }: { processId: string }, ] : []), - ...deployments.map(({ version }) => ({ - label: version.name, - key: `${version.id}`, - disabled: false, - })), + ...deployments + .map(({ version }) => ({ + label: version.name, + key: `${version.id}`, + disabled: false, + })) + .filter((el, index, arr) => { + // make the entries unique so every version is only displayed once + for (let i = 0; i < index; ++i) { + if (arr[i].key === el.key) return false; + } + + return true; + }), ], selectable: true, onSelect: ({ key }) => { diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-list.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-list.tsx index 738e68f4a..077d50d50 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-list.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-list.tsx @@ -11,7 +11,7 @@ import processListStyles from '@/components/process-icon-list.module.scss'; type InputItem = { id: string; name: string; - versions: { id: string; name: string }[]; + versions: { id: string; name: string; deployed: boolean }[]; instances: string[]; }; export type DeployedProcessListProcess = ReplaceKeysWithHighlighted; @@ -95,9 +95,13 @@ const DeploymentsList = ({ dataIndex: 'id', key: 'Meta Data Button', title: '', - render: () => { + render: (_, record) => { return ( - ); @@ -138,6 +142,7 @@ const DeploymentsList = ({ style={{ float: 'right' }} type="text" onClick={() => removeDeployment(record.id)} + disabled={record.versions.every((v) => !v.deployed)} > diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx index 8f6f35ea5..2a0216400 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx @@ -1,7 +1,8 @@ 'use client'; -import { App, Button } from 'antd'; -import { useEffect, useState, useTransition } from 'react'; +import { App, Button, Checkbox, Tooltip, Typography } from 'antd'; +import { QuestionCircleOutlined } from '@ant-design/icons'; +import { useCallback, useEffect, useState, useTransition } from 'react'; import DeploymentsModal from './deployments-modal'; import Bar from '@/components/bar'; import useFuzySearch from '@/lib/useFuzySearch'; @@ -10,7 +11,7 @@ import { Folder } from '@/lib/data/folder-schema'; import { Process, ProcessMetadata } from '@/lib/data/process-schema'; import { useEnvironment } from '@/components/auth-can'; import { processUnchangedFromBasedOnVersion } from '@/lib/data/processes'; -import { useRouter } from 'next/navigation'; +import { usePathname, useRouter, useSearchParams } from 'next/navigation'; import { deployProcess as serverDeployProcess, removeDeployment as serverRemoveDeployment, @@ -34,12 +35,13 @@ const DeploymentsView = ({ deployedProcesses: { id: string; name: string; - versions: { id: string; name: string }[]; + versions: { id: string; name: string; deployed: boolean }[]; instances: string[]; }[]; }) => { const [modalIsOpen, setModalIsOpen] = useState(false); const app = App.useApp(); + const pathname = usePathname(); const space = useEnvironment(); const router = useRouter(); const queryClient = useQueryClient(); @@ -51,6 +53,24 @@ const DeploymentsView = ({ transformData: (matches) => matches.map((match) => match.item), }); + const query = useSearchParams(); + // Get a new searchParams string by merging the current + // searchParams with a provided key/value pair + // see: https://nextjs.org/docs/app/api-reference/functions/use-search-params#updating-searchparams + const createQueryString = useCallback( + (name: string, value: string) => { + const params = new URLSearchParams(query.toString()); + if (!value) { + params.delete(name); + } else { + params.set(name, value); + } + + return params.toString(); + }, + [query], + ); + const [checkingProcessVersion, startCheckingProcessVersion] = useTransition(); function deployProcess( process: Pick, @@ -133,7 +153,14 @@ const DeploymentsView = ({ return (
-
+
+ + + router.push( + pathname + '?' + createQueryString('archived', el.target.checked ? 'true' : ''), + ) + } + > + + Show Past Executions{' '} + + + + +
{ - return getDeployedProcesses(activeEnvironment.spaceId); + return getDeployedProcesses(activeEnvironment.spaceId, withArchived); })(), ]); @@ -33,7 +39,11 @@ async function Executions({ environmentId }: { environmentId: string }) { return { id: p.id, name: p.name, - versions: p.versions.map((v) => ({ id: v.id, name: v.name })), + versions: p.versions.map((v) => ({ + id: v.id, + name: v.name, + deployed: v.deployments.some((d) => !d.removeTime), + })), instances: p.versions.flatMap((v) => v.deployments.flatMap((d) => d.instances.map((i) => i.id)), ), @@ -52,11 +62,16 @@ async function Executions({ environmentId }: { environmentId: string }) { export default async function ExecutionsPage(props: { params: Promise<{ environmentId: string }>; + searchParams: Promise<{ archived: string }>; }) { const params = await props.params; + const searchParams = await props.searchParams; return ( - + ); } diff --git a/src/management-system-v2/lib/data/deployment.ts b/src/management-system-v2/lib/data/deployment.ts index 6729a4c38..7b77a7ceb 100644 --- a/src/management-system-v2/lib/data/deployment.ts +++ b/src/management-system-v2/lib/data/deployment.ts @@ -7,7 +7,7 @@ import { SuccessType, UserErrorType, userError } from '../user-error'; import Ability from '../ability/abilityHelper'; import { cacheLife, cacheTag, revalidateTag } from 'next/cache'; -export async function getDeployedProcesses(environmentId: string) { +export async function getDeployedProcesses(environmentId: string, withArchived = false) { const { ability } = await getCurrentEnvironment(environmentId); if (!ability.can('view', 'Execution')) @@ -17,32 +17,17 @@ export async function getDeployedProcesses(environmentId: string) { 'use cache'; cacheLife({ revalidate: 10, expire: 15 }); cacheTag(`space/${environmentId}/deployments`); - const deploymentIsNotDeleted = { AND: [{ removeTime: null }, { toRemove: false }] }; return await db.process.findMany({ where: { - // get all processes in the current environment that have at least one version that is - // currently deployed - AND: [ - { environmentId }, - { NOT: { folderId: null } }, - { - versions: { - some: { deployments: { some: deploymentIsNotDeleted } }, - }, - }, - ], + AND: [{ environmentId }], }, select: { id: true, name: true, versions: { - // only include deployed versions in the output - where: { deployments: { some: deploymentIsNotDeleted } }, include: { deployments: { - // only include deployments in the output that are still actively deployed - where: deploymentIsNotDeleted, include: { instances: { select: { id: true } } }, }, }, @@ -51,7 +36,27 @@ export async function getDeployedProcesses(environmentId: string) { }); } - return ability.filter('view', 'Process', await getFromDBOrCache(environmentId)); + let deployedProcesses = await getFromDBOrCache(environmentId); + + deployedProcesses = deployedProcesses + .filter((p) => { + if (withArchived) return true; + + return p.versions.some((v) => v.deployments.some((d) => !d.removeTime)); + }) + .map((p) => { + return { + ...p, + versions: p.versions + .map((v) => ({ + ...v, + deployments: withArchived ? v.deployments : v.deployments.filter((d) => !d.removeTime), + })) + .filter((v) => !!v.deployments.length), + }; + }); + + return ability.filter('view', 'Process', deployedProcesses); } export async function getProcessDeployments( @@ -59,6 +64,7 @@ export async function getProcessDeployments( processId: string, ability?: Ability, showArchivedProcesses = false, + showArchivedDeployments = false, ) { if (!ability) ({ ability } = await getCurrentEnvironment(spaceId)); @@ -70,22 +76,15 @@ export async function getProcessDeployments( cacheLife({ revalidate: 10, expire: 15 }); cacheTag(`deployments/process/${processId}`); const deployments = await db.processDeployment.findMany({ - where: { - AND: [ - { - version: { - AND: [ - { processId }, - ...(!showArchivedProcesses ? [{ NOT: { process: { folderId: null } } }] : []), - ], - }, - }, - { removeTime: null }, - { toRemove: false }, - ], - }, include: { - version: { select: { id: true, processId: true, name: true } }, + version: { + select: { + id: true, + processId: true, + name: true, + process: { select: { folderId: true } }, + }, + }, instances: { select: { id: true } }, engine: { include: { @@ -107,7 +106,16 @@ export async function getProcessDeployments( })); } - return getFromDBOrCache(processId); + let deployments = await getFromDBOrCache(processId); + + deployments = deployments.filter((d) => { + if (!showArchivedProcesses && d.version.process.folderId === null) return false; + if (!showArchivedDeployments && d.removeTime !== null) return false; + + return true; + }); + + return deployments; } export type StoredDeployment = SuccessType< From 6d0fb62959010a0ede4fe82aec71f42e4bcbd529 Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Wed, 24 Jun 2026 14:38:09 +0200 Subject: [PATCH 2/8] Show all deployable versions in the instance view even when some of them have not been deployed yet --- .../[processId]/instance-helpers.ts | 9 +- .../[processId]/process-deployment-view.tsx | 85 ++++++++++--------- .../lib/data/processes.tsx | 31 ++++++- 3 files changed, 82 insertions(+), 43 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers.ts b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers.ts index afa2fe193..e9d858e16 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers.ts +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers.ts @@ -1,6 +1,7 @@ import { StoredDeployment } from '@/lib/data/deployment'; import { ExtendedInstanceInfo } from '@/lib/data/instance'; import { InstanceInfo } from '@/lib/engines/deployment'; +import { Version } from '@prisma/client'; import { convertISODurationToMiliseconds } from '@proceed/bpmn-helper/src/getters'; import type { ElementLike } from 'diagram-js/lib/core/Types'; @@ -179,15 +180,15 @@ export function getVersionInstances(instances: ExtendedInstanceInfo[], version?: return instances.filter((instance) => instance.processVersion === version); } -export function getLatestDeployment(deployments: StoredDeployment[]) { - return deployments.reduce( +export function getLatestVersion(versions: Version[]) { + return versions.reduce( (latest, curr) => { - if (!latest || latest.deployTime.getTime() > curr.deployTime.getTime()) { + if (!latest || latest.createdOn.getTime() < curr.createdOn.getTime()) { return curr; } return latest; }, - undefined as undefined | StoredDeployment, + undefined as undefined | Version, ); } diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx index 72dfe53c6..674115527 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx @@ -26,7 +26,7 @@ import { ColorOptions, colorOptions } from './instance-coloring'; import { RemoveReadOnly, truthyFilter } from '@/lib/typescript-utils'; import type { ElementLike } from 'diagram-js/lib/core/Types'; import { wrapServerCall } from '@/lib/wrap-server-call'; -import { getLatestDeployment, getVersionInstances, getYoungestInstance } from './instance-helpers'; +import { getLatestVersion, getVersionInstances, getYoungestInstance } from './instance-helpers'; import useColors from './use-colors'; import useTokens from './use-tokens'; @@ -53,8 +53,8 @@ import { useQuery } from '@tanstack/react-query'; import { getProcessDeployments } from '@/lib/data/deployment'; import { isSuccessResponse, isUserErrorResponse, userError } from '@/lib/user-error'; import { getInstance } from '@/lib/data/instance'; -import { asyncMap, pick } from '@/lib/helpers/javascriptHelpers'; -import { getProcessBPMN } from '@/lib/data/processes'; +import { asyncFilter, asyncMap, pick } from '@/lib/helpers/javascriptHelpers'; +import { getVersions } from '@/lib/data/processes'; import { enableInstanceCSVExport } from 'FeatureFlags'; import jsonToCsvExport from 'json-to-csv-export'; @@ -80,6 +80,29 @@ export default function ProcessDeploymentView({ processId }: { processId: string const canvasRef = useRef(null); const [infoPanelOpen, setInfoPanelOpen] = useState(false); + const { data: versions } = useQuery({ + queryFn: async () => { + let versions = await getVersions(spaceId, processId); + if (isUserErrorResponse(versions)) return []; + + // filter out all versions that are not executable + versions = await asyncFilter(versions, async (version) => { + const bpmnObj = await toBpmnObject(version.bpmn); + const processes = getElementsByTagName(bpmnObj, 'bpmn:Process'); + if (!processes.length) return false; + return processes[0].isExecutable; + }); + + versions.sort((a, b) => { + return b.createdOn.getTime() - a.createdOn.getTime(); + }); + + return versions; + }, + queryKey: ['processVersions', spaceId, processId], + refetchInterval: 1000, + }); + // get information where the process is deployed and which instances exist const { data: deployments, refetch: refetchDeployments } = useQuery({ queryFn: async () => { @@ -130,8 +153,8 @@ export default function ProcessDeploymentView({ processId }: { processId: string const { selectedVersion, versionInstances, currentVersion } = useMemo(() => { let selectedVersion, versionInstances, currentVersion; - if (deployments?.length) { - selectedVersion = deployments.find((d) => d.versionId === selectedVersionId)?.version; + if (versions?.length) { + selectedVersion = versions.find((v) => v.id === selectedVersionId); // sort instances newest first const rawInstances = getVersionInstances(knownInstances, selectedVersionId); @@ -143,13 +166,13 @@ export default function ProcessDeploymentView({ processId }: { processId: string ? versionInstances.find((i) => i.processInstanceId === selectedInstanceId) : undefined; - let currentVersionId = getLatestDeployment(deployments)!.versionId; + let currentVersionId = getLatestVersion(versions)!.id; if (selectedInstance) { currentVersionId = selectedInstance.processVersion; } else if (selectedVersionId) { currentVersionId = selectedVersionId; } - currentVersion = deployments.find((d) => d.versionId === currentVersionId)!.version; + currentVersion = versions.find((v) => v.id === currentVersionId); } return { @@ -157,7 +180,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string versionInstances, currentVersion, }; - }, [deployments, knownInstances, selectedVersionId, selectedInstanceId]); + }, [versions, knownInstances, selectedVersionId, selectedInstanceId]); const { data: currentInstance, refetch: refetchCurrentInstance } = useQuery({ queryKey: ['processDeployments', spaceId, processId, 'instance', selectedInstanceId], @@ -179,9 +202,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string const { data: selectedBpmn } = useQuery({ queryFn: async () => { - const bpmn = await getProcessBPMN(processId, spaceId, currentVersion?.id); - if (isUserErrorResponse(bpmn)) return undefined; - return { bpmn }; + return { bpmn: currentVersion?.bpmn || '' }; }, queryKey: ['space', spaceId, 'process', processId, 'version', currentVersion?.id || '', 'bpmn'], }); @@ -315,20 +336,11 @@ export default function ProcessDeploymentView({ processId }: { processId: string }, ] : []), - ...deployments - .map(({ version }) => ({ - label: version.name, - key: `${version.id}`, - disabled: false, - })) - .filter((el, index, arr) => { - // make the entries unique so every version is only displayed once - for (let i = 0; i < index; ++i) { - if (arr[i].key === el.key) return false; - } - - return true; - }), + ...(versions?.map((v) => ({ + key: v.id, + label: v.name, + disabled: false, + })) || []), ], selectable: true, onSelect: ({ key }) => { @@ -379,14 +391,14 @@ export default function ProcessDeploymentView({ processId }: { processId: string setStartingInstance(true); await wrapServerCall({ fn: async () => { - const latestDeployment = getLatestDeployment(deployments); - if (!latestDeployment) { + const latestVersion = getLatestVersion(versions || []); + if (!latestVersion) { return userError( 'The current process does not seem to be deployed anymore.', ); } - const { versionId } = latestDeployment; + const { id: versionId } = latestVersion; let startForm = await getProcessStartForm(spaceId, processId, versionId); @@ -460,7 +472,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string onClick={async () => { setTogglingActivation(true); const nextState = !isProcessActivated; - const versionId = getLatestDeployment(deployments)!.versionId; + const versionId = getLatestVersion(versions || [])!.id; await wrapServerCall({ fn: () => changeDeploymentActivation(processId, spaceId, versionId, nextState), @@ -650,10 +662,12 @@ export default function ProcessDeploymentView({ processId }: { processId: string // start the instance with the initial variable values from the start form await wrapServerCall({ fn: async () => { - const deployment = getLatestDeployment(deployments); + const version = getLatestVersion(versions || []); - if (!deployment) { - return userError('The current process does not seem to be deployed.'); + if (!version) { + return userError( + 'The current process does not seem to have versions to execute.', + ); } const mappedVariables: Record = {}; @@ -663,12 +677,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string ([key, value]) => (mappedVariables[key] = { value }), ); - return startInstance( - spaceId, - deployment.processId, - deployment.version.id, - mappedVariables, - ); + return startInstance(spaceId, version.processId, version.id, mappedVariables); }, onSuccess: async (instanceId) => { await refetchDeployments(); diff --git a/src/management-system-v2/lib/data/processes.tsx b/src/management-system-v2/lib/data/processes.tsx index d97581c03..ae4db2ded 100644 --- a/src/management-system-v2/lib/data/processes.tsx +++ b/src/management-system-v2/lib/data/processes.tsx @@ -38,7 +38,7 @@ import { // Antd uses barrel files, which next optimizes away. That requires us to import // antd components directly from their files in this server actions file. import { Process, ProcessMetadata } from './process-schema'; -import { revalidatePath } from 'next/cache'; +import { cacheLife, cacheTag, revalidatePath, updateTag } from 'next/cache'; import { getUsersFavourites } from './users'; import { checkIfProcessAlreadyExistsForAUserInASpaceByName, @@ -79,6 +79,7 @@ import { asyncMap } from '../helpers/javascriptHelpers'; import { isActive } from '@/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers'; import { InstanceInfo } from '../engines/deployment'; import { getMSConfig } from '../ms-config/ms-config'; +import { retrieveFile } from './file-manager/file-manager'; // Import necessary functions from processModule @@ -626,6 +627,32 @@ export const copyProcesses = async ( return copiedProcesses; }; +export const getVersions = async (spaceId: string, processId: string) => { + const error = await checkValidity(processId, 'view', spaceId); + + if (error) return error; + + async function getFromDBOrCache(processId: string) { + 'use cache'; + cacheLife({ revalidate: 60, expire: 120 }); + cacheTag(`/process/${processId}/versions`); + const versions = await db.version.findMany({ + where: { processId }, + }); + + return asyncMap(versions, async (version) => { + const bpmn = ((await retrieveFile(version.bpmnFilePath, false)) as Buffer).toString('utf8'); + + return { + ...version, + bpmn, + }; + }); + } + + return await getFromDBOrCache(processId); +}; + /** * Function that checks if a process' latest version is unchanged from the version it is based on * @@ -715,6 +742,8 @@ export const createVersion = async ( await updateProcessVersionBasedOn({ ...process, bpmn }, versionId); + updateTag(`/process/${processId}/versions`); + return versionId; }; From 3a99074e6dffea7c255ce086115c5c8133c5c3fd Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Wed, 24 Jun 2026 15:44:59 +0200 Subject: [PATCH 3/8] Allowing users to start the currently selected version in the instance view instead of just the latest version If the version is not yet deployed it is deployed automatically --- .../[processId]/process-deployment-view.tsx | 22 +++++++---------- .../lib/data/deployment.ts | 4 +++- .../executions/deployment-server-actions.ts | 10 ++++++-- .../lib/executions/instance-server-actions.ts | 24 ++++++++++++++++--- .../mcp-tools/startProcess.ts | 12 ---------- 5 files changed, 41 insertions(+), 31 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx index 674115527..b230ca28e 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx @@ -391,14 +391,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string setStartingInstance(true); await wrapServerCall({ fn: async () => { - const latestVersion = getLatestVersion(versions || []); - if (!latestVersion) { - return userError( - 'The current process does not seem to be deployed anymore.', - ); - } - - const { id: versionId } = latestVersion; + const { id: versionId } = currentVersion!; let startForm = await getProcessStartForm(spaceId, processId, versionId); @@ -472,7 +465,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string onClick={async () => { setTogglingActivation(true); const nextState = !isProcessActivated; - const versionId = getLatestVersion(versions || [])!.id; + const versionId = currentVersion!.id; await wrapServerCall({ fn: () => changeDeploymentActivation(processId, spaceId, versionId, nextState), @@ -662,9 +655,7 @@ export default function ProcessDeploymentView({ processId }: { processId: string // start the instance with the initial variable values from the start form await wrapServerCall({ fn: async () => { - const version = getLatestVersion(versions || []); - - if (!version) { + if (!currentVersion) { return userError( 'The current process does not seem to have versions to execute.', ); @@ -677,7 +668,12 @@ export default function ProcessDeploymentView({ processId }: { processId: string ([key, value]) => (mappedVariables[key] = { value }), ); - return startInstance(spaceId, version.processId, version.id, mappedVariables); + return startInstance( + spaceId, + currentVersion.processId, + currentVersion.id, + mappedVariables, + ); }, onSuccess: async (instanceId) => { await refetchDeployments(); diff --git a/src/management-system-v2/lib/data/deployment.ts b/src/management-system-v2/lib/data/deployment.ts index 7b77a7ceb..c9d34be10 100644 --- a/src/management-system-v2/lib/data/deployment.ts +++ b/src/management-system-v2/lib/data/deployment.ts @@ -135,12 +135,14 @@ export async function addDeployment( const data = DeploymentInputSchema.parse(input); - await db.processDeployment.createMany({ + const res = await db.processDeployment.createManyAndReturn({ data: data.engineIds.map((engineId) => ({ ...data, engineIds: undefined, engineId })), }); revalidateTag(`space/${spaceId}/deployments`, 'max'); revalidateTag(`deployments/process/${processId}`, 'max'); + + return res; } export async function updateDeployment( diff --git a/src/management-system-v2/lib/executions/deployment-server-actions.ts b/src/management-system-v2/lib/executions/deployment-server-actions.ts index f52bce828..05f7345af 100644 --- a/src/management-system-v2/lib/executions/deployment-server-actions.ts +++ b/src/management-system-v2/lib/executions/deployment-server-actions.ts @@ -40,7 +40,6 @@ import Ability from '../ability/abilityHelper'; import { EngineConnection } from '@prisma/client'; import { revalidateTag } from 'next/cache'; import { isActive } from '@/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/instance-helpers'; -import { getDBInstance } from '../data/instance'; export async function deployProcess( definitionId: string, @@ -113,7 +112,7 @@ export async function deployProcess( ability, ); - await addDeployment( + const newDeployments = await addDeployment( spaceId, definitionId, { @@ -126,6 +125,8 @@ export async function deployProcess( ability, ); + if (isUserErrorResponse(newDeployments)) return newDeployments; + // deactivate the process on all engines that have a deployment but which were not target of the // new deployment await Promise.allSettled( @@ -140,6 +141,11 @@ export async function deployProcess( } }), ); + + return newDeployments.map((d) => ({ + ...d, + engine: deployedTo.find((e) => e.id === d.engineId)!, + })); } catch (e) { const message = getErrorMessage(e); return userError(message); diff --git a/src/management-system-v2/lib/executions/instance-server-actions.ts b/src/management-system-v2/lib/executions/instance-server-actions.ts index 25a9252f6..8d89433b7 100644 --- a/src/management-system-v2/lib/executions/instance-server-actions.ts +++ b/src/management-system-v2/lib/executions/instance-server-actions.ts @@ -3,6 +3,7 @@ import { asyncForEach, asyncMap } from '@/lib/helpers/javascriptHelpers'; import { getCurrentEnvironment, getCurrentUser } from '@/components/auth'; import { + UserError, UserErrorType, getErrorMessage, isUserErrorResponse, @@ -37,6 +38,7 @@ import { truthyFilter } from '@/lib/typescript-utils'; import { getInstanceFile, saveInstanceArtifact } from '../data/file-manager-facade'; import { getProcessVersion } from '../data/db/process'; import Ability from '../ability/abilityHelper'; +import { deployProcess } from './deployment-server-actions'; export async function getProcessStartForm( spaceId: string, @@ -68,7 +70,8 @@ export async function startInstance( if (!ability.can('create', 'Execution')) return userError('Invalid Permissions', UserErrorType.PermissionError); - const deployments = await getProcessDeployments(spaceId, definitionId, ability); + let deployments: { id: string; versionId: string; engine: Engine }[] | { error: UserError } = + await getProcessDeployments(spaceId, definitionId, ability); if (isUserErrorResponse(deployments)) return deployments; if (!userId) ({ userId } = await getCurrentUser()); @@ -77,8 +80,23 @@ export async function startInstance( return d.versionId === versionId; }); - // TODO: automatically deploy the version if possible - if (!versionDeployments.length) return userError('This process version is not deployed.'); + if (!versionDeployments.length) { + const res = await deployProcess( + definitionId, + versionId, + spaceId, + 'dynamic', + undefined, + ability, + userId, + ); + + if (isUserErrorResponse(res)) return res; + + if (!res) return userError('Failed to start an instance'); + + deployments = res.map((d) => ({ ...d })); + } for (const deployment of deployments) { const { engine } = deployment; diff --git a/src/management-system-v2/mcp-tools/startProcess.ts b/src/management-system-v2/mcp-tools/startProcess.ts index 072599c3d..f284cec58 100644 --- a/src/management-system-v2/mcp-tools/startProcess.ts +++ b/src/management-system-v2/mcp-tools/startProcess.ts @@ -70,18 +70,6 @@ export default async function startProcess({ // we don't need to check if the variables that are required at startup are set since the engine // will do that for us and return an error if they aren't - const deployment = await deployProcess( - processId, - process.version.id, - environmentId, - 'dynamic', - undefined, - ability, - userId, - ); - - if (isUserErrorResponse(deployment)) return deployment.error.message; - const instanceId = await startInstance( environmentId, processId, From 9b0894958cae5005f1fe6af49f88c730c068deee Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Wed, 24 Jun 2026 16:11:46 +0200 Subject: [PATCH 4/8] Allow users to inspect user tasks of instances started from archived deployments --- src/management-system-v2/lib/data/user-tasks.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/management-system-v2/lib/data/user-tasks.ts b/src/management-system-v2/lib/data/user-tasks.ts index f871d882b..9bda2f625 100644 --- a/src/management-system-v2/lib/data/user-tasks.ts +++ b/src/management-system-v2/lib/data/user-tasks.ts @@ -34,10 +34,7 @@ export async function getUserTasks(spaceId: string, ability?: Ability) { { instance: { deployment: { - AND: [ - { version: { process: { environmentId } } }, - { removeTime: null, toRemove: false }, - ], + AND: [{ version: { process: { environmentId } } }], }, }, }, From a4fd69b5256c9e9a71ef412c7dd6a77d1d41aade Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Wed, 24 Jun 2026 17:23:23 +0200 Subject: [PATCH 5/8] Fixed: if a url contains a query the selection highlighting and automatic expansion in the left sidebar menu do not work anymore --- .../(dashboard)/[environmentId]/layout.tsx | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx index 2f6633ce3..036a80c4b 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/layout.tsx @@ -149,7 +149,7 @@ const DashboardLayout = async ( key: 'start', label: Start, icon: , - selectedRegex: '/start($|/)', + selectedRegex: '/start($|/|\\?)', }); const automationSettings = await getSpaceSettingsValues( @@ -170,7 +170,7 @@ const DashboardLayout = async ( msConfig.PROCEED_PUBLIC_PROCESS_AUTOMATION_TASK_EDITOR_ACTIVE && automationSettings.task_editor?.active !== false ) { - childRegex = '/tasks($|/)'; + childRegex = '/tasks($|/|\\?)'; children.push({ key: 'task-editor', label: Task Editor, @@ -203,7 +203,7 @@ const DashboardLayout = async ( /> ), - selectedRegex: '/tasklist($|/)', + selectedRegex: '/tasklist($|/|\\?)', openRegex: childRegex, children: children.length ? children : undefined, }); @@ -216,13 +216,13 @@ const DashboardLayout = async ( ); if (documentationSettings.active !== false) { - const processRegex = '/processes($|/)'; + const processRegex = '/processes($|/|\\?)'; let children: ExtendedMenuItems = [ documentationSettings.list?.active !== false && { key: 'processes-list', label: Process List, icon: , - selectedRegex: '/processes/list($|/)', + selectedRegex: '/processes/list($|/|\\?)', }, documentationSettings.editor?.active !== false && ability.can('manage', 'Process') && { @@ -231,7 +231,7 @@ const DashboardLayout = async ( Process Editor ), icon: , - selectedRegex: '/processes/editor($|/)', + selectedRegex: '/processes/editor($|/|\\?)', }, ].filter(truthyFilter); @@ -257,7 +257,7 @@ const DashboardLayout = async ( ability.can('view', 'Execution') && automationSettings.dashboard?.active !== false ) { - const dashboardRegex = '/executions-dashboard($|/)'; + const dashboardRegex = '/executions-dashboard($|/|\\?)'; childRegex = !childRegex ? dashboardRegex : `(${childRegex})|(${dashboardRegex})`; children.push({ key: 'dashboard', @@ -267,7 +267,7 @@ const DashboardLayout = async ( }); } if (ability.can('view', 'Execution') && automationSettings.executions?.active !== false) { - const executionsRegex = '/executions($|/)'; + const executionsRegex = '/executions($|/|\\?)'; childRegex = !childRegex ? executionsRegex : `(${childRegex})|(${executionsRegex})`; children.push({ key: 'executions', @@ -277,7 +277,7 @@ const DashboardLayout = async ( }); } if (ability.can('view', 'Machine') && automationSettings.machines?.active !== false) { - const machinesRegex = '/engines($|/)'; + const machinesRegex = '/engines($|/|\\?)'; childRegex = !childRegex ? machinesRegex : `(${childRegex})|(${machinesRegex})`; children.push({ key: 'machines', @@ -313,7 +313,7 @@ const DashboardLayout = async ( ), - selectedRegex: `/machine-config(?!/${userId}|/${activeEnvironment.spaceId})($|/)`, + selectedRegex: `/machine-config(?!/${userId}|/${activeEnvironment.spaceId})($|/|\\?)`, }); } @@ -329,7 +329,7 @@ const DashboardLayout = async ( let childRegex = ''; if (can('update', 'Environment') || can('delete', 'Environment')) { - const settingsRegex = '/settings($|/)'; + const settingsRegex = '/settings($|/|\\?)'; childRegex = !childRegex ? settingsRegex : `(${childRegex})|(${settingsRegex})`; children.push({ key: 'organization-settings', @@ -343,7 +343,7 @@ const DashboardLayout = async ( activeEnvironment.isOrganization && (can('update', 'Environment') || can('delete', 'Environment')) ) { - const managementRegex = '/management($|/)'; + const managementRegex = '/management($|/|\\?)'; childRegex = !childRegex ? managementRegex : `(${childRegex})|(${managementRegex})`; children.push({ key: 'organization-management', @@ -354,7 +354,7 @@ const DashboardLayout = async ( } // Data view under Organization - const dataRegex = `/machine-config/${activeEnvironment.spaceId}($|/)`; + const dataRegex = `/machine-config/${activeEnvironment.spaceId}($|/|\\?)`; childRegex = !childRegex ? dataRegex : `(${childRegex})|(${dataRegex})`; children.push({ key: 'organization-data', @@ -368,7 +368,7 @@ const DashboardLayout = async ( }); if (can('manage', 'User')) { - const userRegex = '/iam/users($|/)'; + const userRegex = '/iam/users($|/|\\?)'; childRegex = !childRegex ? userRegex : `(${childRegex})|(${userRegex})`; children.push({ key: 'users', @@ -379,7 +379,7 @@ const DashboardLayout = async ( } if (can('admin', 'All')) { - const rolesRegex = '/iam/roles($|/)'; + const rolesRegex = '/iam/roles($|/|\\?)'; childRegex = !childRegex ? rolesRegex : `(${childRegex})|(${rolesRegex})`; children.push({ key: 'roles', @@ -400,8 +400,8 @@ const DashboardLayout = async ( } if (msConfig.PROCEED_PUBLIC_IAM_ACTIVE) { - const profileRegex = '/profile($|/)'; - const spacesRegex = '/spaces($|/)'; + const profileRegex = '/profile($|/|\\?)'; + const spacesRegex = '/spaces($|/|\\?)'; // Only match if the url contains the source=personal query parameter const personalDataRegex = `/machine-config/${userId}\\?source=personal($|&)`; const regex = `(${profileRegex})|(${spacesRegex})|(${personalDataRegex})`; @@ -449,7 +449,7 @@ const DashboardLayout = async ( } if (!activeEnvironment.isOrganization) { - const settingsRegex = '/settings($|/)'; + const settingsRegex = '/settings($|/|\\?)'; // Match only the base path without query parameters, or with query params that don't include source=personal const personalHomeDataRegex = `/machine-config/${activeEnvironment.spaceId}(?!\\?source=personal)($|\\?)`; const regex = `(${settingsRegex})|(${personalHomeDataRegex})`; From efa0c76b54bbcb34091957f8d4818ac1a0f74e9a Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Fri, 26 Jun 2026 10:22:33 +0200 Subject: [PATCH 6/8] Show loading indicator while toggling between showing archived and not showing archived or the other way around --- .../executions/deployments-view.tsx | 31 ++++++++++++------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx index 2a0216400..a4d1d2b5c 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/deployments-view.tsx @@ -1,6 +1,6 @@ 'use client'; -import { App, Button, Checkbox, Tooltip, Typography } from 'antd'; +import { App, Button, Checkbox, Space, Spin, Tooltip, Typography } from 'antd'; import { QuestionCircleOutlined } from '@ant-design/icons'; import { useCallback, useEffect, useState, useTransition } from 'react'; import DeploymentsModal from './deployments-modal'; @@ -53,6 +53,7 @@ const DeploymentsView = ({ transformData: (matches) => matches.map((match) => match.item), }); + const [togglingShowArchived, startTogglingShowArchived] = useTransition(); const query = useSearchParams(); // Get a new searchParams string by merging the current // searchParams with a provided key/value pair @@ -145,7 +146,8 @@ const DeploymentsView = ({ setInitialLoading(false); }, []); - const loading = initialLoading || checkingProcessVersion || removingDeployment; + const loading = + initialLoading || checkingProcessVersion || removingDeployment || togglingShowArchived; const tableProps: { loading: boolean; pagination?: false } = { loading }; @@ -171,21 +173,28 @@ const DeploymentsView = ({ Deploy Process - - router.push( - pathname + '?' + createQueryString('archived', el.target.checked ? 'true' : ''), - ) - } - > + + {togglingShowArchived ? ( + + ) : ( + + startTogglingShowArchived(() => { + router.push( + pathname + '?' + createQueryString('archived', el.target.checked ? 'true' : ''), + ); + }) + } + /> + )} Show Past Executions{' '} - +
Date: Fri, 26 Jun 2026 10:43:43 +0200 Subject: [PATCH 7/8] Fixed: the executions table shows processes that have never been deployed before --- src/management-system-v2/lib/data/deployment.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/management-system-v2/lib/data/deployment.ts b/src/management-system-v2/lib/data/deployment.ts index c9d34be10..6e6c2af7d 100644 --- a/src/management-system-v2/lib/data/deployment.ts +++ b/src/management-system-v2/lib/data/deployment.ts @@ -40,9 +40,11 @@ export async function getDeployedProcesses(environmentId: string, withArchived = deployedProcesses = deployedProcesses .filter((p) => { - if (withArchived) return true; + if (withArchived) { + return p.versions.some((v) => !!v.deployments.length); + } - return p.versions.some((v) => v.deployments.some((d) => !d.removeTime)); + return p.versions.some((v) => v.deployments.filter((d) => !d.removeTime).length); }) .map((p) => { return { From 6fe57679f9abcaabe0cfbaa8f2a0070de91aff5c Mon Sep 17 00:00:00 2001 From: Janis Joderi Shoferi Date: Fri, 26 Jun 2026 10:44:10 +0200 Subject: [PATCH 8/8] Caching executable property of process versions so they are not recomputed once per second --- .../executions/[processId]/process-deployment-view.tsx | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx index b230ca28e..541e02de0 100644 --- a/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx +++ b/src/management-system-v2/app/(dashboard)/[environmentId]/(automation)/executions/[processId]/process-deployment-view.tsx @@ -80,6 +80,8 @@ export default function ProcessDeploymentView({ processId }: { processId: string const canvasRef = useRef(null); const [infoPanelOpen, setInfoPanelOpen] = useState(false); + const isExecutableVersionMap = useRef>({}); + const { data: versions } = useQuery({ queryFn: async () => { let versions = await getVersions(spaceId, processId); @@ -87,9 +89,14 @@ export default function ProcessDeploymentView({ processId }: { processId: string // filter out all versions that are not executable versions = await asyncFilter(versions, async (version) => { + if (version.id in isExecutableVersionMap.current) { + return isExecutableVersionMap.current[version.id]; + } + const bpmnObj = await toBpmnObject(version.bpmn); const processes = getElementsByTagName(bpmnObj, 'bpmn:Process'); if (!processes.length) return false; + isExecutableVersionMap.current[version.id] = processes[0].isExecutable; return processes[0].isExecutable; });