Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
9e2e6dd
Added a refetch loop that automatically pull new deployment informati…
jjoderis May 21, 2026
4ffc26c
Added logic for removal of deployments on engines that are not reacha…
jjoderis May 21, 2026
4debc90
The background refetching loop also updates changed instance data in …
jjoderis May 22, 2026
d8cff98
The deployments list gets information about deployed processes from t…
jjoderis May 22, 2026
9add2c4
The execution dashboard gets its information from the database instea…
jjoderis May 22, 2026
e0c3527
Fixed: Potential error response on engine fetch in dashboard view is …
jjoderis May 26, 2026
379ffbc
The deployment view gets information about the deployed process and i…
jjoderis May 26, 2026
5f61824
Made the refetch loop configurable with environment variables
jjoderis May 27, 2026
a94a129
Removed unused functions
jjoderis May 27, 2026
02484ed
Changed the deployment refetch function to allow callers to wait eith…
jjoderis May 27, 2026
a703f38
Made the instance documentation view get the instance data from the d…
jjoderis May 27, 2026
61d02e8
Made the instance info mcp tool get the instance data from the databa…
jjoderis May 27, 2026
8726d6b
Made the mcp tool that returns all known instances get its data from …
jjoderis May 27, 2026
c149ee6
Fixed: if a process has at least one actively deployed version the de…
jjoderis May 27, 2026
e5c3163
Merge branch 'main' of github.com:PROCEED-Labs/proceed into deploymen…
jjoderis May 27, 2026
249f65d
Merge branch 'main' of github.com:PROCEED-Labs/proceed into deploymen…
jjoderis May 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,19 +1,64 @@
'use client';

import { useMemo, Fragment } from 'react';
import useDeployments from './use-deployments';
import { Card, Col, Row, Skeleton, Statistic } from 'antd';
import { useEnvironment } from '@/components/auth-can';
import { getAvailableSpaceEngines } from '@/lib/data/engines';
import { useQuery } from '@tanstack/react-query';
import { getDeployedProcesses } from '@/lib/data/deployment';
import { isUserErrorResponse } from '@/lib/user-error';
import { asyncMap } from '@/lib/helpers/javascriptHelpers';
import { getInstance } from '@/lib/data/instance';
import { truthyFilter } from '@/lib/typescript-utils';

const DashboardView: React.FC = () => {
const { engines, deployments } = useDeployments(
'definitionId,instances(processInstanceId,instanceState)',
);
const space = useEnvironment();

const { data: engines } = useQuery({
queryFn: async () => {
const res = await getAvailableSpaceEngines(space.spaceId);

if (isUserErrorResponse(res)) return [];

return res;
},
refetchInterval: 1000,
queryKey: ['space', space.spaceId, 'engines'],
});

const { data } = useQuery({
queryFn: async () => {
let deployedProcesses = await getDeployedProcesses(space.spaceId);

if (isUserErrorResponse(deployedProcesses))
return { deployedProcesses: [] as string[], instances: [] };

const instanceIds = new Set<string>();
deployedProcesses.forEach((p) =>
p.versions.forEach((v) =>
v.deployments.forEach((d) => d.instances.forEach((i) => instanceIds.add(i.id))),
),
);

const instances = (
await asyncMap([...instanceIds], async (id) => {
const instance = await getInstance(space.spaceId, id);
if (isUserErrorResponse(instance)) return undefined;
return instance;
})
).filter(truthyFilter);

return { deployedProcesses: deployedProcesses.map((p) => p.id), instances };
},
refetchInterval: 1000,
queryKey: ['space', space.spaceId, 'deployments'],
});

const stats = useMemo(() => {
if (!engines || !deployments) return;
if (!engines || !data) return;
const stats = {
numEngines: 0,
numDeployments: 0,
numDeployments: data.deployedProcesses.length,
numInstances: 0,
numRunningInstances: 0,
numFailedInstances: 0,
Expand All @@ -35,30 +80,25 @@ const DashboardView: React.FC = () => {
const knownDeployments: Record<string, boolean> = {};
const knownInstances: Record<string, string[]> = {};

for (const { definitionId, instances } of deployments) {
if (!knownDeployments[definitionId]) {
stats.numDeployments++;
knownDeployments[definitionId] = true;
}

for (const { processInstanceId, instanceState } of instances) {
if (!knownDeployments[processInstanceId]) {
knownInstances[processInstanceId] = instanceState;
} else {
knownInstances[processInstanceId].push(...instanceState);
}
for (const {
state: { processInstanceId, instanceState },
} of data.instances) {
if (!knownDeployments[processInstanceId]) {
knownInstances[processInstanceId] = instanceState;
} else {
knownInstances[processInstanceId].push(...instanceState);
}
}

for (const instanceState of Object.values(knownInstances)) {
stats.numInstances++;
for (const instanceState of Object.values(knownInstances)) {
stats.numInstances++;

if (instanceState.some((state) => activeStates.includes(state))) {
stats.numRunningInstances++;
} else if (instanceState.some((state) => failedStates.includes(state))) {
stats.numFailedInstances++;
} else {
stats.numCompletedInstances++;
}
if (instanceState.some((state) => activeStates.includes(state))) {
stats.numRunningInstances++;
} else if (instanceState.some((state) => failedStates.includes(state))) {
stats.numFailedInstances++;
} else {
stats.numCompletedInstances++;
}
}

Expand Down Expand Up @@ -90,7 +130,7 @@ const DashboardView: React.FC = () => {
},
],
};
}, [engines, deployments]);
}, [engines, data]);

if (!stats) return <Skeleton active />;

Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,27 @@ import { Alert, Checkbox, Image, Progress, ProgressProps, Space, Typography } fr
import { ClockCircleFilled } from '@ant-design/icons';
import { getPlanDelays, getTimeInfo, statusToType } from './instance-helpers';
import { getMetaDataFromElement } from '@proceed/bpmn-helper';
import { DisplayTable, RelevantInstanceInfo } from './instance-info-panel';
import { DisplayTable } from './instance-info-panel';
import endpointBuilder from '@/lib/engines/endpoints/endpoint-builder';
import { generateDateString, generateDurationString, generateNumberString } from '@/lib/utils';

export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {
import { InstanceInfo } from '@/lib/engines/deployment';
import type { ElementLike } from 'diagram-js/lib/core/Types';

export function ElementStatus({
processId,
element,
instance,
}: {
processId: string;
element: ElementLike;
instance?: InstanceInfo;
}) {
const statusEntries: ReactNode[][] = [];

const isRootElement = info.element && info.element.type === 'bpmn:Process';
const metaData = getMetaDataFromElement(info.element.businessObject);
const token = info.instance?.tokens.find((l) => l.currentFlowElementId == info.element.id);
const logInfo = info.instance?.log.find((logEntry) => logEntry.flowElementId === info.element.id);
const isRootElement = element && element.type === 'bpmn:Process';
const metaData = getMetaDataFromElement(element.businessObject);
const token = instance?.tokens.find((l) => l.currentFlowElementId == element.id);
const logInfo = instance?.log.find((logEntry) => logEntry.flowElementId === element.id);

// Element image
if (metaData.overviewImage)
Expand All @@ -35,7 +45,7 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {
alt="Image linked to the element"
src={endpointBuilder('get', '/resources/process/:definitionId/images/:fileName', {
pathParams: {
definitionId: info.process.definitionId,
definitionId: processId,
fileName: metaData.overviewImage,
},
})}
Expand All @@ -45,14 +55,14 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {

// Element status
let status = undefined;
if (isRootElement && info.instance) {
status = info.instance.instanceState[0];
} else if (info.element && info.instance) {
const elementInfo = info.instance.log.find((l) => l.flowElementId == info.element.id);
if (isRootElement && instance) {
status = instance.instanceState[0];
} else if (element && instance) {
const elementInfo = instance.log.find((l) => l.flowElementId == element.id);
if (elementInfo) {
status = elementInfo.executionState;
} else {
const tokenInfo = info.instance.tokens.find((l) => l.currentFlowElementId == info.element.id);
const tokenInfo = instance.tokens.find((l) => l.currentFlowElementId == element.id);
status = tokenInfo ? tokenInfo.currentFlowNodeState : 'WAITING';
}
}
Expand All @@ -73,15 +83,15 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {
<Checkbox
key="external"
disabled
value={info.element.businessObject && info.element.businessObject.external}
value={element.businessObject && element.businessObject.external}
/>,
]);
}

// Progress
// TODO: editable progress
// see src/management-system/src/frontend/components/deployments/activityInfo/ProgressSetter.vue
if (info.instance && !isRootElement) {
if (instance && !isRootElement) {
let progress:
| { value: number; manual: boolean; milestoneCalculatedProgress?: number }
| undefined = undefined;
Expand Down Expand Up @@ -116,10 +126,10 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {

// User task
// TODO: editable priority
if (info.element.type === 'bpmn:UserTask') {
if (element.type === 'bpmn:UserTask') {
let priority: number | undefined = undefined;

if (info.instance) {
if (instance) {
if (token) priority = token.priority;
else if (logInfo) priority = logInfo.priority;
} else {
Expand All @@ -142,7 +152,7 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {

// Real Costs
// TODO: Set real costs
if (info.instance && !isRootElement) {
if (instance && !isRootElement) {
let costs: string | undefined = undefined;
if (token) costs = token.costsRealSetByOwner;
else if (logInfo) costs = logInfo.costsRealSetByOwner;
Expand All @@ -151,12 +161,12 @@ export function ElementStatus({ info }: { info: RelevantInstanceInfo }) {
}

// Documentation
statusEntries.push(['Documentation:', info.element.businessObject?.documentation?.[0]?.text]);
statusEntries.push(['Documentation:', element.businessObject?.documentation?.[0]?.text]);

// Activity time calculation
const { start, end, duration } = getTimeInfo({
element: info.element,
instance: info.instance,
element: element,
instance: instance,
logInfo,
token,
});
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { DeployedProcessInfo, InstanceInfo } from '@/lib/engines/deployment';
import { StoredDeployment } from '@/lib/data/deployment';
import { InstanceInfo } from '@/lib/engines/deployment';
import { convertISODurationToMiliseconds } from '@proceed/bpmn-helper/src/getters';
import type { ElementLike } from 'diagram-js/lib/core/Types';

Expand Down Expand Up @@ -132,23 +133,21 @@ export function getPlanDelays({
return { plan, delays };
}

export function getVersionInstances(process: DeployedProcessInfo, version?: string) {
const instances = process.instances;

export function getVersionInstances(instances: InstanceInfo[], version?: string) {
if (!version) return instances;
return instances.filter((instance) => instance.processVersion === version);
}

export function getLatestDeployment(process: DeployedProcessInfo) {
let latest = process.versions.length - 1;
for (let i = process.versions.length - 2; i >= 0; i--) {
// TODO: this is actually the last version that was deployed since there is no version creation
// information stored on the engine (do we keep this, store the creation time on the engine or
// parse the creation time from the bpmn?)
if (process.versions[i].deploymentDate > process.versions[latest].deploymentDate) latest = i;
}

return process.versions[latest];
export function getLatestDeployment(deployments: StoredDeployment[]) {
return deployments.reduce(
(latest, curr) => {
if (!latest || latest.deployTime.getTime() > curr.deployTime.getTime()) {
return curr;
}
return latest;
},
undefined as undefined | StoredDeployment,
);
}

export function getYoungestInstance<T extends InstanceInfo[]>(instances: T) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,12 @@
import ResizableElement, { ResizableElementRefType } from '@/components/ResizableElement';
import CollapsibleCard from '@/components/collapsible-card';
import { ReactNode, useRef } from 'react';
import { DeployedProcessInfo, InstanceInfo, VersionInfo } from '@/lib/engines/deployment';
import { InstanceInfo } from '@/lib/engines/deployment';
import { Drawer, Grid, Tabs } from 'antd';
import type { ElementLike } from 'diagram-js/lib/core/Types';
import { ElementStatus } from './element-status';
import InstanceVariables from './instance-variables';

export type RelevantInstanceInfo = {
instance?: InstanceInfo;
process: DeployedProcessInfo;
element: ElementLike;
version: VersionInfo;
};

export function DisplayTable({ data }: { data: ReactNode[][] }) {
// TODO: make this responsive
return (
Expand All @@ -39,29 +32,35 @@ export function DisplayTable({ data }: { data: ReactNode[][] }) {
export default function InstanceInfoPanel({
open,
close,
info,
processId,
version,
instance,
element,
refetch,
}: {
close: () => void;
open: boolean;
info: RelevantInstanceInfo;
processId: string;
version: { bpmn: string };
instance?: InstanceInfo;
element?: ElementLike;
refetch: () => void;
}) {
const resizableElementRef = useRef<ResizableElementRefType>(null);
const breakpoints = Grid.useBreakpoint();

const title = info.element?.businessObject?.name || info.element?.id || 'How to PROCEED?';
const title = element?.businessObject?.name || element?.id || 'How to PROCEED?';

if (breakpoints.xl && !open) return null;

const tabs = info.element ? (
const tabs = element ? (
<Tabs
defaultActiveKey="1"
items={[
{
key: 'Status',
label: 'Status',
children: <ElementStatus info={info} />,
children: <ElementStatus processId={processId} element={element} instance={instance} />,
},
{
key: 'Advanced',
Expand All @@ -81,7 +80,14 @@ export default function InstanceInfoPanel({
{
key: 'Variables',
label: 'Variables',
children: <InstanceVariables refetch={refetch} info={info} />,
children: (
<InstanceVariables
refetch={refetch}
processId={processId}
version={version}
instance={instance}
/>
),
},
{
key: 'Resources',
Expand Down
Loading
Loading