Skip to content
Closed
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
26 changes: 8 additions & 18 deletions samples/process-app-v0/src/components/ProcessInstances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -217,7 +217,7 @@ export const ProcessInstances = () => {

// Fetch BPMN and execution history (with individual error handling)
let bpmnXml: BpmnXmlString | null = null;
let executionHistory: ProcessInstanceExecutionHistoryResponse[] = [];
let executionHistory: ProcessInstanceExecutionHistoryResponse | null = null;

try {
bpmnXml = await sdk.maestro.processes.instances.getBpmn(instance.instanceId, instance.folderKey);
Expand All @@ -242,24 +242,14 @@ export const ProcessInstances = () => {
if (match && match[1]) {
activityType = match[1].replace(/([A-Z])/g, ' $1').trim(); // Add spaces between camelCase

// If it's a user task, look for the action center task link
if (activityType.toLowerCase() === 'user task' && executionHistory.length > 0) {
// Find the history entry with matching elementId
console.log('Looking for elementId:', elementId);
console.log('Execution History:', executionHistory);
const taskEntry = executionHistory.find((entry: ProcessInstanceExecutionHistoryResponse) => {
// Parse attributes JSON if it's a string
const attributes = typeof entry.attributes === 'string'
? JSON.parse(entry.attributes)
: entry.attributes;
return attributes?.elementId === elementId;
});
// If it's a user task, look for the action center task link in element executions
if (activityType.toLowerCase() === 'user task' && executionHistory?.elementExecutions?.length) {
const taskEntry = executionHistory.elementExecutions.find(
(execution) => execution.elementId === elementId
);

if (taskEntry) {
const attributes = typeof taskEntry.attributes === 'string'
? JSON.parse(taskEntry.attributes)
: taskEntry.attributes;
taskLink = attributes?.actionCenterTaskLink;
if (taskEntry?.externalLink) {
taskLink = taskEntry.externalLink;
console.log('Found task link:', taskLink);
}
}
Expand Down
26 changes: 8 additions & 18 deletions samples/process-app-v1/src/components/ProcessInstances.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,7 +237,7 @@ export const ProcessInstances = () => {

// Fetch BPMN and execution history (with individual error handling)
let bpmnXml: BpmnXmlString | null = null;
let executionHistory: ProcessInstanceExecutionHistoryResponse[] = [];
let executionHistory: ProcessInstanceExecutionHistoryResponse | null = null;

try {
// Use modular ProcessInstances service directly
Expand All @@ -264,24 +264,14 @@ export const ProcessInstances = () => {
if (match && match[1]) {
activityType = match[1].replace(/([A-Z])/g, ' $1').trim(); // Add spaces between camelCase

// If it's a user task, look for the action center task link
if (activityType.toLowerCase() === 'user task' && executionHistory.length > 0) {
// Find the history entry with matching elementId
console.log('Looking for elementId:', elementId);
console.log('Execution History:', executionHistory);
const taskEntry = executionHistory.find((entry: ProcessInstanceExecutionHistoryResponse) => {
// Parse attributes JSON if it's a string
const attributes = typeof entry.attributes === 'string'
? JSON.parse(entry.attributes)
: entry.attributes;
return attributes?.elementId === elementId;
});
// If it's a user task, look for the action center task link in element executions
if (activityType.toLowerCase() === 'user task' && executionHistory?.elementExecutions?.length) {
const taskEntry = executionHistory.elementExecutions.find(
(execution) => execution.elementId === elementId
);

if (taskEntry) {
const attributes = typeof taskEntry.attributes === 'string'
? JSON.parse(taskEntry.attributes)
: taskEntry.attributes;
taskLink = attributes?.actionCenterTaskLink;
if (taskEntry?.externalLink) {
taskLink = taskEntry.externalLink;
console.log('Found task link:', taskLink);
}
}
Expand Down
3 changes: 2 additions & 1 deletion src/models/maestro/process-instances.constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,6 @@ export const ProcessInstanceMap: { [key: string]: string } = {
* Maps fields for Process Instance Execution History to ensure consistent naming
*/
export const ProcessInstanceExecutionHistoryMap: { [key: string]: string } = {
startTime: 'startedTime'
startedTimeUtc: 'startedTime',
completedTimeUtc: 'completedTime',
};
19 changes: 9 additions & 10 deletions src/models/maestro/process-instances.models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ export interface ProcessInstancesServiceModel {
getById(id: string, folderKey: string): Promise<ProcessInstanceGetResponse>;

/**
* Get execution history (spans) for a process instance
* Get execution history for a process instance
* @param instanceId The ID of the instance to get history for
* @returns Promise resolving to execution history
* {@link ProcessInstanceExecutionHistoryResponse}
Expand All @@ -105,15 +105,14 @@ export interface ProcessInstancesServiceModel {
* <instanceId>
* );
*
* // Analyze execution timeline
* history.forEach(span => {
* console.log(`Activity: ${span.name}`);
* console.log(`Start: ${span.startTime}`);
* console.log(`Duration: ${span.duration}ms`);
* // Analyze element executions
* history.elementExecutions.forEach(execution => {
* console.log(`Element: ${execution.elementName}`);
* console.log(`Status: ${execution.status}`);
* });
* ```
*/
getExecutionHistory(instanceId: string): Promise<ProcessInstanceExecutionHistoryResponse[]>;
getExecutionHistory(instanceId: string): Promise<ProcessInstanceExecutionHistoryResponse>;

/**
* Get BPMN XML file for a process instance
Expand Down Expand Up @@ -291,11 +290,11 @@ export interface ProcessInstanceMethods {
getIncidents(): Promise<ProcessIncidentGetResponse[]>;

/**
* Gets execution history (spans) for this process instance
* Gets execution history for this process instance
*
* @returns Promise resolving to execution history
*/
getExecutionHistory(): Promise<ProcessInstanceExecutionHistoryResponse[]>;
getExecutionHistory(): Promise<ProcessInstanceExecutionHistoryResponse>;

/**
* Gets BPMN XML file for this process instance
Expand Down Expand Up @@ -353,7 +352,7 @@ function createProcessInstanceMethods(instanceData: RawProcessInstanceGetRespons
return service.getIncidents(instanceData.instanceId, instanceData.folderKey);
},

async getExecutionHistory(): Promise<ProcessInstanceExecutionHistoryResponse[]> {
async getExecutionHistory(): Promise<ProcessInstanceExecutionHistoryResponse> {
if (!instanceData.instanceId) throw new Error('Process instance ID is undefined');

return service.getExecutionHistory(instanceData.instanceId);
Expand Down
51 changes: 41 additions & 10 deletions src/models/maestro/process-instances.types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,17 +62,48 @@ export interface ProcessInstanceOperationResponse {
* Response for process instance execution history
*/
export interface ProcessInstanceExecutionHistoryResponse {
id: string;
traceId: string;
parentId: string | null;
name: string;
instanceId: string;
processKey: string;
packageKey: string;
packageId: string;
packageVersion: string;
folderKey: string;
instanceDisplayName: string;
source: string;
/** Instance status (e.g., "Completed", "Faulted", "Running", "Pausing", "Canceling") */
status: string;
creationUserKey: string | null;
startedTime: string;
completedTime: string | null;
elementExecutions: ProcessElementExecutionMetadata[];
}

/**
* Element execution metadata for process instances
*/
export interface ProcessElementExecutionMetadata {
elementId: string;
elementName: string;
parentElementId: string | null;
/** Element status (e.g., "Completed", "Faulted", "Running") */
status: string;
startedTime: string;
endTime: string | null;
attributes: string | null;
createdTime: string;
updatedTime?: string;
expiredTime: string | null;
// TO Do: Add status and attributes interface
completedTime: string | null;
processKey: string;
/** External reference link, eg link to the HITL task in Action Center */
externalLink: string;
elementRuns: ProcessElementRunMetadata[];
}

/**
* Element run metadata for process instances
*/
export interface ProcessElementRunMetadata {
status: string;
startedTime: string;
completedTime: string | null;
elementRunId: string;
parentElementRunId: string | null;
}

/**
Expand Down
31 changes: 24 additions & 7 deletions src/services/maestro/processes/process-instances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,16 +117,33 @@ export class ProcessInstancesService extends BaseService implements ProcessInsta
}

/**
* Get execution history (spans) for a process instance
* Get execution history for a process instance
* @param instanceId The ID of the instance to get history for
* @returns Promise<ProcessInstanceExecutionHistoryResponse[]>
* @returns Promise<ProcessInstanceExecutionHistoryResponse>
*/
@track('ProcessInstances.GetExecutionHistory')
async getExecutionHistory(instanceId: string): Promise<ProcessInstanceExecutionHistoryResponse[]> {
const response = await this.get<ProcessInstanceExecutionHistoryResponse[]>(MAESTRO_ENDPOINTS.INSTANCES.GET_EXECUTION_HISTORY(instanceId));
return response.data.map(historyItem =>
transformData(historyItem, ProcessInstanceExecutionHistoryMap)
);
async getExecutionHistory(instanceId: string): Promise<ProcessInstanceExecutionHistoryResponse> {
const response = await this.get<any>(MAESTRO_ENDPOINTS.INSTANCES.GET_EXECUTION_HISTORY(instanceId));

// Transform the main response
const transformedResponse = transformData(response.data, ProcessInstanceExecutionHistoryMap);

// Transform each element execution and its nested element runs
if (transformedResponse.elementExecutions && Array.isArray(transformedResponse.elementExecutions)) {
transformedResponse.elementExecutions = transformedResponse.elementExecutions.map((execution: any) => {
const transformedExecution = transformData(execution, ProcessInstanceExecutionHistoryMap);

if (transformedExecution.elementRuns && Array.isArray(transformedExecution.elementRuns)) {
transformedExecution.elementRuns = transformedExecution.elementRuns.map((run: any) =>
transformData(run, ProcessInstanceExecutionHistoryMap)
);
}

return transformedExecution;
});
}

return transformedResponse as ProcessInstanceExecutionHistoryResponse;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/utils/constants/endpoints/maestro.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ export const MAESTRO_ENDPOINTS = {
INSTANCES: {
GET_ALL: `${PIMS_BASE}/api/v1/instances`,
GET_BY_ID: (instanceId: string) => `${PIMS_BASE}/api/v1/instances/${instanceId}`,
GET_EXECUTION_HISTORY: (instanceId: string) => `${PIMS_BASE}/api/v1/spans/${instanceId}`,
GET_EXECUTION_HISTORY: (instanceId: string) => `${PIMS_BASE}/api/v1/element-executions/process-instances/${instanceId}`,
GET_BPMN: (instanceId: string) => `${PIMS_BASE}/api/v1/instances/${instanceId}/bpmn`,
GET_VARIABLES: (instanceId: string) => `${PIMS_BASE}/api/v1/instances/${instanceId}/variables`,
CANCEL: (instanceId: string) => `${PIMS_BASE}/api/v1/instances/${instanceId}/cancel`,
Expand Down
4 changes: 3 additions & 1 deletion tests/integration/config/unified-setup.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { UiPath } from '../../../src/core';
import { Entities, ChoiceSets } from '../../../src/services/data-fabric';
import { Tasks } from '../../../src/services/action-center';
import { Assets, Buckets, Queues, Processes } from '../../../src/services/orchestrator';
import { Assets, Buckets, Jobs, Queues, Processes } from '../../../src/services/orchestrator';
import {
MaestroProcessesService,
ProcessInstancesService,
Expand Down Expand Up @@ -34,6 +34,7 @@ export interface TestServices {
assets: Assets;
buckets: Buckets;
queues: Queues;
jobs?: Jobs;
processes: Processes;
maestroProcesses: MaestroProcessesService;
processInstances: ProcessInstancesService;
Expand Down Expand Up @@ -111,6 +112,7 @@ function createV1Services(config: IntegrationConfig): TestServices {
assets: new Assets(sdk),
buckets: new Buckets(sdk),
queues: new Queues(sdk),
jobs: new Jobs(sdk),
processes: new Processes(sdk),
maestroProcesses: new MaestroProcessesService(sdk),
processInstances: new ProcessInstancesService(sdk),
Expand Down
120 changes: 120 additions & 0 deletions tests/integration/shared/orchestrator/jobs.integration.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
import { describe, it, expect } from 'vitest';
import { getServices, getTestConfig, setupUnifiedTests, InitMode } from '../../config/unified-setup';

const modes: InitMode[] = ['v1'];

describe.each(modes)('Orchestrator Jobs - Integration Tests [%s]', (mode) => {
setupUnifiedTests(mode);

describe('getAll', () => {
it('should retrieve all jobs', async () => {
const { jobs } = getServices();
const config = getTestConfig();

if (!jobs) {
throw new Error('Jobs service not available in test services');
}

const folderId = config.folderId ? Number(config.folderId) : undefined;

if (!folderId) {
console.log('INTEGRATION_TEST_FOLDER_ID not configured, running without folder filter.');
}

const result = await jobs.getAll({
folderId,
pageSize: 100,
});

expect(result).toBeDefined();
expect(result.items).toBeDefined();
expect(Array.isArray(result.items)).toBe(true);
});

it('should retrieve jobs with pagination options', async () => {
const { jobs } = getServices();
const config = getTestConfig();

if (!jobs) {
throw new Error('Jobs service not available in test services');
}

const folderId = config.folderId ? Number(config.folderId) : undefined;

if (!folderId) {
console.log('INTEGRATION_TEST_FOLDER_ID not configured, running without folder filter.');
}

const result = await jobs.getAll({
folderId,
pageSize: 10,
});

expect(result).toBeDefined();
expect(result.items).toBeDefined();
expect(Array.isArray(result.items)).toBe(true);
expect(result.items.length).toBeLessThanOrEqual(10);
});

it('should retrieve jobs with filter', async () => {
const { jobs } = getServices();
const config = getTestConfig();

if (!jobs) {
throw new Error('Jobs service not available in test services');
}

const folderId = config.folderId ? Number(config.folderId) : undefined;

if (!folderId) {
console.log('INTEGRATION_TEST_FOLDER_ID not configured, running without folder filter.');
}

const result = await jobs.getAll({
folderId,
pageSize: 5,
filter: "State eq 'Successful'",
});

expect(result).toBeDefined();
expect(result.items).toBeDefined();
expect(Array.isArray(result.items)).toBe(true);
});
});

describe('Job structure validation', () => {
it('should have expected fields in job objects', async () => {
const { jobs } = getServices();
const config = getTestConfig();

if (!jobs) {
throw new Error('Jobs service not available in test services');
}

const folderId = config.folderId ? Number(config.folderId) : undefined;

if (!folderId) {
console.log('INTEGRATION_TEST_FOLDER_ID not configured, running without folder filter.');
}

const result = await jobs.getAll({
folderId,
pageSize: 1,
});

if (result.items.length === 0) {
throw new Error('No jobs available to validate structure');
}

const job = result.items[0];

expect(job).toBeDefined();
expect(job.id).toBeDefined();
expect(job.key).toBeDefined();
expect(job.state).toBeDefined();
expect(typeof job.id).toBe('number');
expect(typeof job.key).toBe('string');
expect(typeof job.state).toBe('string');
});
});
});
Loading
Loading