Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 3 additions & 0 deletions FeatureFlags.js
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,9 @@ module.exports = {
// Whether the Chatbot UserInterface and its functionality should be enabled
enableChatbot: false,

// CSV export buttons for process instances
enableInstanceCSVExport: false,

//feature to use GCP_bucket / fs depending on deployment env to store blobs
// -----------------------------------------------------------------------------
// Chopping Block
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
import { DeployedProcessInfo, InstanceInfo } from '@/lib/engines/deployment';
import { getEnv, getSpaceUsers, getUser } from '@/lib/data/db/machine-config';
import { DeployedProcessInfo, InstanceInfo, VersionInfo } from '@/lib/engines/deployment';
import { asyncMap } from '@/lib/helpers/javascriptHelpers';
import { truthyFilter } from '@/lib/typescript-utils';
import {
getDefinitionsInfos,
getDefinitionsName,
getElementById,
toBpmnObject,
} from '@proceed/bpmn-helper';
import { convertISODurationToMiliseconds } from '@proceed/bpmn-helper/src/getters';
import type { ElementLike } from 'diagram-js/lib/core/Types';
import jsonToCsvExport from 'json-to-csv-export';

export type ElementStatus =
| 'PAUSED'
Expand Down Expand Up @@ -160,3 +170,153 @@ export function getYoungestInstance<T extends InstanceInfo[]>(instances: T) {
}
return instances[firstInstance];
}

export async function exportInstanceData(
selectedInstances: (InstanceInfo | undefined)[],
versionInfo: VersionInfo[],
spaceId: string,
) {
const objectOrderTemplate = {
ProcessId: null,
ProcessName: null,
ProcessShortName: null,
ProcessVersionId: null,
ProcessVersionName: null,
ProcessVersionDescription: null,
ProcessVersionCreatedOn: null,
ProcessVersionBasedOn: null,
ProcessInstanceId: null,
ProcessInstanceInitiatorId: null,
ProcessInstanceInitiatorFullName: null,
ProcessInstanceInitiatorUsername: null,
ProcessInstanceInitiatorSpaceId: null,
ProcessInstanceInitiatorSpaceName: null,
InstanceStartTime: null,
ProcessStepId: null,
ProcessStepName: null,
ProcessStepType: null,
ProcessStepStatus: null,
ProcessStepStartTime: null,
ProcessStepEndTime: null,
PreviousProcessStepId: null,
ProcessStepTokenId: null,
ActualPerformerId: null,
ActualPerformerName: null,
ActualPerformerUsername: null,
ProcessEngineId: null,
ProcessEngineName: null, //tofind
Log: null,
};

const spaceUsers = await getSpaceUsers(spaceId);
const space = await getEnv(spaceId);

// pasting metadata from VersionInfo
const instancesWithVersionData = (
await asyncMap(selectedInstances, async (instance) => {
if (!instance) return undefined;
const correspondingVersion = versionInfo.find((e) => e.versionId == instance.processVersion);
if (!correspondingVersion) return undefined;
const bpmnObj = await toBpmnObject(correspondingVersion.bpmn);
const initiator = spaceUsers.find((user) => user.id == instance.processInitiator);
const definitionInfos = await getDefinitionsInfos(bpmnObj);

return {
...instance,
ProcessName: definitionInfos.name,
ProcessShortName: definitionInfos.userDefinedId,
ProcessVersionName: correspondingVersion.versionName,
ProcessVersionDescription: correspondingVersion.versionDescription,
ProcessVersionCreatedOn: correspondingVersion.deploymentDate,
ProcessVersionBasedOn: correspondingVersion.basedOnVersion,
ProcessInstanceInitiatorFullName:
initiator && !initiator.isGuest
? `${initiator.firstName} ${initiator.lastName}`
: 'Guest',
ProcessInstanceInitiatorUsername:
initiator && !initiator.isGuest ? initiator.username : 'Guest',
ProcessInstanceInitiatorSpaceName: space.isOrganization ? space.name : 'no organization',
ProcessEngineId: instance.log[0].machine.id,
correspondingVersion,
};
})
).filter(truthyFilter);

// retrieve and flatten event data
const instanceEvents = (
await asyncMap(instancesWithVersionData, async (instance) => {
const bpmnObj = await toBpmnObject(instance.correspondingVersion?.bpmn || '');

return instance
? instance.log.map((eventEntry) => {
const eventElement = getElementById(bpmnObj, eventEntry.flowElementId) as {
$type?: string;
name?: string;
outgoing?: any;
incoming?: any;
};
console.log(eventElement);
console.log(bpmnObj);
Comment on lines +258 to +259
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These logs seem to have been left from some debugging attempt.

const ActualPerformerId = eventEntry.actualOwner?.[0];
const user = spaceUsers.find((user) => user.id == ActualPerformerId);
return {
...instance,
...eventEntry,
ProcessStepName: eventElement?.name,
ProcessStepType: eventElement?.$type?.split(':')[1],
ActualPerformerId,
ActualPerformerName: user ? `${user.firstName} ${user.lastName}` : undefined,
ActualPerformerUsername: user ? user.username : undefined,
Log: JSON.stringify(eventEntry.variableChanges),
PreviousProcessStepId: eventElement.incoming?.map((flow: any) => flow.sourceRef.id),
};
})
: [];
})
).flat();

// renaming
const keyMap: Record<string, string> = {
processId: 'ProcessId',
processVersion: 'ProcessVersionId',
processInstanceId: 'ProcessInstanceId',
processInitiator: 'ProcessInstanceInitiatorId',
spaceIdOfProcessInitiator: 'ProcessInstanceInitiatorSpaceId',
globalStartTime: 'InstanceStartTime',
flowElementId: 'ProcessStepId',
executionState: 'ProcessStepStatus',
startTime: 'ProcessStepStartTime',
endTime: 'ProcessStepEndTime',
tokenId: 'ProcessStepTokenId',
};

const renamedInstanceEvents: Record<string, any>[] = instanceEvents.map((instance) =>
Object.fromEntries(Object.entries(instance).map(([k, v]) => [keyMap[k] ?? k, v])),
);

// converting dates
const datedInstanceEvents = renamedInstanceEvents.map((instance) => ({
...instance,
InstanceStartTime: new Date(instance.InstanceStartTime).toISOString(),
ProcessStepEndTime: new Date(instance.ProcessStepEndTime).toISOString(),
ProcessStepStartTime: new Date(instance.ProcessStepStartTime).toISOString(),
ProcessVersionCreatedOn: new Date(instance.ProcessVersionCreatedOn).toISOString(),
}));

// reordering
const structuredInstanceEvents = datedInstanceEvents.map((instance) =>
Object.entries(instance).reduce(
(acc, [key, value]) => {
if (key in acc) {
acc[key] = value;
}
return acc;
},
{ ...objectOrderTemplate } as Record<string, any>,
),
);

return jsonToCsvExport({
data: structuredInstanceEvents,
});
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import {
CaretRightOutlined,
PauseOutlined,
StopOutlined,
ExportOutlined,
} from '@ant-design/icons';
import { useCallback, useEffect, useMemo, useRef, useState } from 'react';
import contentStyles from './content.module.scss';
Expand All @@ -25,7 +26,12 @@ import { RemoveReadOnly } from '@/lib/typescript-utils';
import type { ElementLike } from 'diagram-js/lib/core/Types';
import { wrapServerCall } from '@/lib/wrap-server-call';
import useDeployment from '../deployment-hook';
import { getLatestDeployment, getVersionInstances, getYoungestInstance } from './instance-helpers';
import {
exportInstanceData,
getLatestDeployment,
getVersionInstances,
getYoungestInstance,
} from './instance-helpers';

import useColors from './use-colors';
import useTokens from './use-tokens';
Expand All @@ -44,6 +50,7 @@ import { useEnvironment } from '@/components/auth-can';

import { GrDocumentUser } from 'react-icons/gr';
import { handleOpenDocumentation } from '../../../processes/processes-helper';
import { enableInstanceCSVExport } from 'FeatureFlags';

export default function ProcessDeploymentView({
processId,
Expand Down Expand Up @@ -491,6 +498,46 @@ export default function ProcessDeploymentView({

<Space style={{ alignItems: 'start' }}>
<ToolbarGroup>
{enableInstanceCSVExport && (
<>
<Tooltip title={'Export data of this selected instance as a csv file'}>
<Button
onClick={() =>
exportInstanceData([selectedInstance], deploymentInfo.versions, spaceId)
}
>
<div
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
>
<ExportOutlined style={{ fontSize: '18px' }} />
<span style={{ fontSize: '8px', fontWeight: 'bold', lineHeight: 1 }}>
THIS
</span>
</div>
</Button>
</Tooltip>
<Tooltip title={'Export data of all instances to this process as a csv file'}>
<Button
onClick={() =>
exportInstanceData(
deploymentInfo.instances,
deploymentInfo.versions,
spaceId,
)
}
>
<div
style={{ display: 'flex', flexDirection: 'column', alignItems: 'center' }}
>
<ExportOutlined style={{ fontSize: '18px' }} />
<span style={{ fontSize: '8px', fontWeight: 'bold', lineHeight: 1 }}>
ALL
</span>
</div>
</Button>
</Tooltip>
</>
)}
{selectedInstance && (
<Tooltip title="View Instance Documentation">
<Button
Expand Down
20 changes: 15 additions & 5 deletions src/management-system-v2/lib/data/db/machine-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ import jsonata from 'jsonata';
import { possiblyNumber } from '@/lib/utils';
import { z, ZodError } from 'zod';
import { getUserById } from './iam/users';
import { getMembers } from './iam/memberships';
import { getMembers, getUsersInSpace } from './iam/memberships';
import { Membership } from '@prisma/client';
import { truthyFilter } from '@/lib/typescript-utils';
import {
Expand All @@ -63,6 +63,7 @@ import {
import { defaultOrganizationConfigurationTemplate } from '@/app/(dashboard)/[environmentId]/machine-config/templates/configuration-template-organization';
import { getRoles, getUserRoles } from '../roles';
import { getRolesWithMembers } from './iam/roles';
import { getEnvironmentById } from './iam/environments';

const IntSchema = z.number().int();
type Int = z.infer<typeof IntSchema>;
Expand Down Expand Up @@ -4949,10 +4950,6 @@ export async function syncSpaceConfigs() {
}
}

export async function getUser(userId: string) {
return await getUserById(userId);
}

async function checkSiblingNames(siblings: string[], name: string) {
for (const id of siblings) {
let childParameterResult = await db.configParameter.findUnique({
Expand All @@ -4963,3 +4960,16 @@ async function checkSiblingNames(siblings: string[], name: string) {
}
return false;
}

// fetch helpers TO BE REMOVED IN THE FUTURE
export async function getUser(userId: string) {
return await getUserById(userId);
}

export async function getSpaceUsers(envId: string) {
return await getUsersInSpace(envId);
}

export async function getEnv(envId: string) {
return await getEnvironmentById(envId);
}
2 changes: 2 additions & 0 deletions src/management-system-v2/lib/engines/deployment.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,7 +265,9 @@ export type VersionInfo = {
versionId: string;
versionName: string;
versionDescription: string;
basedOnVersion?: string;
};

export type InstanceInfo = {
processId: string;
processInstanceId: string;
Expand Down
3 changes: 2 additions & 1 deletion src/management-system-v2/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,8 @@
"mqtt": "^5.10.1",
"bcryptjs": "3.0.2",
"sharp": "0.34.3",
"jsonata": "2.1.0"
"jsonata": "2.1.0",
"json-to-csv-export": "3.1.3"
},
"devDependencies": {
"@tanstack/eslint-plugin-query": "5.91.2",
Expand Down
5 changes: 5 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -11364,6 +11364,11 @@ json-stringify-safe@~5.0.1:
resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb"
integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA==

json-to-csv-export@3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/json-to-csv-export/-/json-to-csv-export-3.1.3.tgz#1865587962df9ded2e68107fd460f8b59ef47dd9"
integrity sha512-bSJLOcd+8MOXlQ/ERV2h95Msoj9WZ8PATF4XwQJC0MDasMl+KkDNJEqH9xaBmSxvRq7p5bi6Jj+2+AYDF6hBvw==

json2mq@^0.2.0:
version "0.2.0"
resolved "https://registry.yarnpkg.com/json2mq/-/json2mq-0.2.0.tgz#b637bd3ba9eabe122c83e9720483aeb10d2c904a"
Expand Down
Loading