Skip to content

Commit ab0de5f

Browse files
ninja-shreyashclaudegithub-actions[bot]
authored
feat(insights): add getElementStats to MaestroProcesses and Cases [PLT-102911] (#416)
* feat(insights): add getElementCountByStatus to ProcessInstances and CaseInstances Add Insights RTM endpoint for retrieving per-element execution counts and duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements. - Use Date input for startTime/endTime, convert internally via .getTime() - CaseInstancesService delegates to ProcessInstancesService - Extract getElementCountByStatusImpl to avoid double @track - Add JSDoc, type annotations, and trailing newlines PLT-102911 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: sync CaseInstances.getElementCountByStatus JSDoc with ServiceModel Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: add @internal to getElementCountByStatusImpl and sync ProcessInstances JSDoc Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(tests): reduce duplication in ElementCountByStatus tests - Extract mock response data to MAESTRO_TEST_CONSTANTS - Remove redundant error/empty tests from case-instances (delegation-only) - Add shared expectValidElementCountByStatus helper for integration tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tests): add error test for CaseInstances.getElementCountByStatus Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(tests): extract shared testGetElementCountByStatus integration helper Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: extract fetchElementCountByStatus to shared insights helper Removes ProcessInstancesService dependency from CaseInstancesService. Both services now call the shared function directly via their own BaseService.post method. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tests): update stale test descriptions and remove dead test - Fix CaseInstances test descriptions referencing old delegation pattern - Remove isCaseManagement absence test (field was never set) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(insights): move getElementCountByStatus to MaestroProcesses and Cases Move getElementCountByStatus from ProcessInstances/CaseInstances to MaestroProcesses/Cases, aligning with other Insights aggregate methods (getTopRunCount, getTopFaultedCount, etc.). - Use positional params instead of options object (all required) - Add bound methods on process/case objects (startTime, endTime, version) - Replace fetchElementCountByStatus with buildElementCountByStatusBody - Add CaseMethods interface and createCaseWithMethods factory for Cases - Update tests: comprehensive in processes, minimal in cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor: rename version param to packageVersion Aligns with the packageVersions field on process/case objects. The API body field remains "version" — mapped in buildElementCountByStatusBody. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: sync service class JSDoc with model interfaces Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: address PR review — move integration tests, add model tests - Move getElementCountByStatus integration tests from instance files to processes/cases integration files - Update helper to match MaestroProcesses/Cases getAll() signature - Fix orphaned extractCaseName JSDoc in cases.ts - Add model tests for bound methods (processes.test.ts, cases.test.ts) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tests): update stale test descriptions and remove dead test - Skip integration tests (insightsrtm_ requires OAuth, not PAT) - Remove out-of-scope getIncidents model tests - Keep only success test for cases (minimal pattern) - Fix stale @returns JSDoc on CasesServiceModel.getAll Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs: fix oauth-scopes formatting for getElementCountByStatus in Cases and Processes Co-authored-by: Shreyash <ninja-shreyash@users.noreply.github.com> * refactor: rename getElementCountByStatus to getElementStats Rename public method and type for brevity: - getElementCountByStatus() → getElementStats() - ElementCountByStatus → ElementStats - Internal names unchanged (endpoint constant, body builder, API path) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com> Co-authored-by: Shreyash <ninja-shreyash@users.noreply.github.com>
1 parent a039836 commit ab0de5f

21 files changed

Lines changed: 621 additions & 18 deletions

File tree

docs/oauth-scopes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,7 @@ This page lists the specific OAuth scopes required in external app for each SDK
7878
| `getTopElementFailedCount()` | `Insights.RealTimeData Insights OR.Folders.Read` |
7979
| `getInstanceStatusTimeline()` | `Insights.RealTimeData Insights OR.Folders.Read` |
8080
| `getTopExecutionDuration()` | `Insights.RealTimeData Insights OR.Folders.Read` |
81+
| `getElementStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
8182

8283
## Maestro Process Instances
8384

@@ -103,6 +104,7 @@ This page lists the specific OAuth scopes required in external app for each SDK
103104
| `getTopElementFailedCount()` | `Insights.RealTimeData Insights OR.Folders.Read` |
104105
| `getInstanceStatusTimeline()` | `Insights.RealTimeData Insights OR.Folders.Read` |
105106
| `getTopExecutionDuration()` | `Insights.RealTimeData Insights OR.Folders.Read` |
107+
| `getElementStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
106108

107109
## Maestro Case Instances
108110

src/models/maestro/cases.models.ts

Lines changed: 84 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
*/
55

66
import { CaseGetAllResponse, CaseGetTopRunCountResponse, CaseGetTopFaultedCountResponse, CaseGetTopDurationResponse } from './cases.types';
7-
import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, ElementGetTopFailedCountResponse } from './insights.types';
7+
import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, ElementGetTopFailedCountResponse, ElementStats } from './insights.types';
88

99
/**
1010
* Service for managing UiPath Maestro Cases
@@ -24,8 +24,7 @@ import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, Eleme
2424
*/
2525
export interface CasesServiceModel {
2626
/**
27-
* @returns Promise resolving to array of Case objects
28-
* {@link CaseGetAllResponse}
27+
* @returns Promise resolving to an array of {@link CaseGetAllWithMethodsResponse}
2928
* @example
3029
* ```typescript
3130
* // Get all case management processes
@@ -39,7 +38,7 @@ export interface CasesServiceModel {
3938
* }
4039
* ```
4140
*/
42-
getAll(): Promise<CaseGetAllResponse[]>;
41+
getAll(): Promise<CaseGetAllWithMethodsResponse[]>;
4342

4443
/**
4544
* Get the top 5 case processes ranked by run count within a time range.
@@ -242,4 +241,85 @@ export interface CasesServiceModel {
242241
* ```
243242
*/
244243
getTopExecutionDuration(startTime: Date, endTime: Date, options?: TopQueryOptions): Promise<CaseGetTopDurationResponse[]>;
244+
245+
/**
246+
* Get element stats for case instances
247+
*
248+
* Returns per-element execution counts (success, fail, terminated, paused, in-progress) and
249+
* duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements within a case.
250+
*
251+
* @param processKey - Process key to filter by
252+
* @param packageId - Package identifier
253+
* @param startTime - Start of the time range to query
254+
* @param endTime - End of the time range to query
255+
* @param packageVersion - Package version to filter by
256+
* @returns Promise resolving to an array of {@link ElementStats}
257+
* @example
258+
* ```typescript
259+
* // Get element metrics for a case
260+
* const elements = await cases.getElementStats(
261+
* '<processKey>',
262+
* '<packageId>',
263+
* new Date('2026-04-01'),
264+
* new Date(),
265+
* '1.0.1'
266+
* );
267+
*
268+
* // Find elements with failures
269+
* const failedElements = elements.filter(e => e.failCount > 0);
270+
* for (const element of failedElements) {
271+
* console.log(`Failed element: ${element.elementId}, failures: ${element.failCount}`);
272+
* }
273+
* ```
274+
*/
275+
getElementStats(processKey: string, packageId: string, startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
276+
}
277+
278+
// Method interface that will be added to case objects
279+
export interface CaseMethods {
280+
/**
281+
* Get element stats for this case
282+
*
283+
* @param startTime - Start of the time range to query
284+
* @param endTime - End of the time range to query
285+
* @param packageVersion - Package version to filter by
286+
* @returns Promise resolving to an array of {@link ElementStats}
287+
*/
288+
getElementStats(startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
289+
}
290+
291+
// Combined type for case data with methods
292+
export type CaseGetAllWithMethodsResponse = CaseGetAllResponse & CaseMethods;
293+
294+
/**
295+
* Creates methods for a case object
296+
*
297+
* @param caseData - The case data (response from API)
298+
* @param service - The cases service instance
299+
* @returns Object containing case methods
300+
*/
301+
function createCaseMethods(caseData: CaseGetAllResponse, service: CasesServiceModel): CaseMethods {
302+
return {
303+
getElementStats(startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]> {
304+
if (!caseData.processKey) throw new Error('Process key is undefined');
305+
if (!caseData.packageId) throw new Error('Package ID is undefined');
306+
307+
return service.getElementStats(caseData.processKey, caseData.packageId, startTime, endTime, packageVersion);
308+
}
309+
};
310+
}
311+
312+
/**
313+
* Creates an actionable case by combining API case data with operational methods.
314+
*
315+
* @param caseData - The case data from API
316+
* @param service - The cases service instance
317+
* @returns A case object with added methods
318+
*/
319+
export function createCaseWithMethods(
320+
caseData: CaseGetAllResponse,
321+
service: CasesServiceModel
322+
): CaseGetAllWithMethodsResponse {
323+
const methods = createCaseMethods(caseData, service);
324+
return Object.assign({}, caseData, methods) as CaseGetAllWithMethodsResponse;
245325
}

src/models/maestro/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,4 +21,4 @@ export * from './case-instances.types';
2121
export * from './case-instances.models';
2222

2323
// Insights types (shared across process and case services)
24-
export * from './insights.types';
24+
export * from './insights.types';

src/models/maestro/insights.types.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,3 +117,34 @@ export interface GetTopDurationResponse extends GetTopBaseResponse {
117117
/** Total execution duration in milliseconds */
118118
duration: number;
119119
}
120+
121+
/**
122+
* Element count by status for a BPMN element within a process or case
123+
*/
124+
export interface ElementStats {
125+
/** BPMN element identifier */
126+
elementId: string;
127+
/** Number of successful executions */
128+
successCount: number;
129+
/** Number of failed executions */
130+
failCount: number;
131+
/** Number of terminated executions */
132+
terminatedCount: number;
133+
/** Number of paused executions */
134+
pausedCount: number;
135+
/** Number of in-progress executions */
136+
inProgressCount: number;
137+
/** Minimum duration in milliseconds */
138+
minDurationMs: number;
139+
/** Maximum duration in milliseconds */
140+
maxDurationMs: number;
141+
/** Average duration in milliseconds */
142+
avgDurationMs: number;
143+
/** 50th percentile (median) duration in milliseconds */
144+
p50DurationMs: number;
145+
/** 95th percentile duration in milliseconds */
146+
p95DurationMs: number;
147+
/** 99th percentile duration in milliseconds */
148+
p99DurationMs: number;
149+
}
150+

src/models/maestro/processes.models.ts

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { RawMaestroProcessGetAllResponse, ProcessGetTopRunCountResponse, ProcessGetTopFaultedCountResponse, ProcessGetTopDurationResponse } from './processes.types';
77
import { ProcessIncidentGetResponse } from './process-incidents.types';
8-
import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, ElementGetTopFailedCountResponse } from './insights.types';
8+
import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, ElementGetTopFailedCountResponse, ElementStats } from './insights.types';
99

1010
/**
1111
* Service for managing UiPath Maestro Processes
@@ -269,6 +269,39 @@ export interface MaestroProcessesServiceModel {
269269
* ```
270270
*/
271271
getTopExecutionDuration(startTime: Date, endTime: Date, options?: TopQueryOptions): Promise<ProcessGetTopDurationResponse[]>;
272+
273+
/**
274+
* Get element stats for process instances
275+
*
276+
* Returns per-element execution counts (success, fail, terminated, paused, in-progress) and
277+
* duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements within a process.
278+
*
279+
* @param processKey - Process key to filter by
280+
* @param packageId - Package identifier
281+
* @param startTime - Start of the time range to query
282+
* @param endTime - End of the time range to query
283+
* @param packageVersion - Package version to filter by
284+
* @returns Promise resolving to an array of {@link ElementStats}
285+
* @example
286+
* ```typescript
287+
* // Get element metrics for a process
288+
* const elements = await maestroProcesses.getElementStats(
289+
* '<processKey>',
290+
* '<packageId>',
291+
* new Date('2026-04-01'),
292+
* new Date(),
293+
* '1.0.1'
294+
* );
295+
*
296+
* // Analyze element performance
297+
* for (const element of elements) {
298+
* console.log(`Element: ${element.elementId}`);
299+
* console.log(` Success: ${element.successCount}, Failed: ${element.failCount}`);
300+
* console.log(` Avg duration: ${element.avgDurationMs}ms, P95: ${element.p95DurationMs}ms`);
301+
* }
302+
* ```
303+
*/
304+
getElementStats(processKey: string, packageId: string, startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
272305
}
273306

274307
// Method interface that will be added to process objects
@@ -279,6 +312,16 @@ export interface ProcessMethods {
279312
* @returns Promise resolving to array of process incidents
280313
*/
281314
getIncidents(): Promise<ProcessIncidentGetResponse[]>;
315+
316+
/**
317+
* Get element stats for this process
318+
*
319+
* @param startTime - Start of the time range to query
320+
* @param endTime - End of the time range to query
321+
* @param packageVersion - Package version to filter by
322+
* @returns Promise resolving to an array of {@link ElementStats}
323+
*/
324+
getElementStats(startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
282325
}
283326

284327
// Combined type for process data with methods
@@ -298,6 +341,12 @@ function createProcessMethods(processData: RawMaestroProcessGetAllResponse, serv
298341
if (!processData.folderKey) throw new Error('Folder key is undefined');
299342

300343
return service.getIncidents(processData.processKey, processData.folderKey);
344+
},
345+
getElementStats(startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]> {
346+
if (!processData.processKey) throw new Error('Process key is undefined');
347+
if (!processData.packageId) throw new Error('Package ID is undefined');
348+
349+
return service.getElementStats(processData.processKey, processData.packageId, startTime, endTime, packageVersion);
301350
}
302351
};
303352
}

src/services/maestro/cases/case-instances.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ import {
1616
SlaSummaryResponse,
1717
CaseInstanceSlaSummaryOptions,
1818
CaseInstanceStageSLAResponse,
19-
CaseInstanceStageSLAOptions,
19+
CaseInstanceStageSLAOptions
2020
} from '../../../models/maestro';
2121
import { TaskGetResponse } from '../../../models/action-center';
2222
import {

src/services/maestro/cases/cases.ts

Lines changed: 46 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,11 @@
1-
import { CaseGetAllResponse, CaseGetTopRunCountResponse, CaseGetTopFaultedCountResponse, CaseGetTopDurationResponse, GetTopRunCountResponse, GetTopDurationResponse, ElementGetTopFailedCountResponse, InstanceStatusTimelineResponse } from '../../../models/maestro';
1+
import { CaseGetAllResponse, CaseGetTopRunCountResponse, CaseGetTopFaultedCountResponse, CaseGetTopDurationResponse, GetTopRunCountResponse, GetTopDurationResponse, ElementGetTopFailedCountResponse, InstanceStatusTimelineResponse, ElementStats } from '../../../models/maestro';
22
import type { RawElementGetTopFailedCountResponse } from '../../../models/maestro/insights.internal-types';
33
import type { TimelineOptions, TopQueryOptions } from '../../../models/maestro';
44
import { ProcessType } from '../../../models/maestro/cases.internal-types';
55
import { MAESTRO_ENDPOINTS } from '../../../utils/constants/endpoints';
66
import type { CasesServiceModel } from '../../../models/maestro/cases.models';
7-
import { buildInsightsTopBody, fetchInstanceStatusTimeline } from '../insights';
7+
import { createCaseWithMethods, CaseGetAllWithMethodsResponse } from '../../../models/maestro/cases.models';
8+
import { buildInsightsTopBody, fetchInstanceStatusTimeline, buildElementCountByStatusBody } from '../insights';
89
import { BaseService } from '../../base';
910
import { track } from '../../../core/telemetry';
1011
import { createParams } from '../../../utils/http/params';
@@ -15,7 +16,7 @@ import { createParams } from '../../../utils/http/params';
1516
export class CasesService extends BaseService implements CasesServiceModel {
1617
/**
1718
* Get all case management processes with their instance statistics
18-
* @returns Promise resolving to array of Case objects
19+
* @returns Promise resolving to an array of {@link CaseGetAllWithMethodsResponse}
1920
*
2021
* @example
2122
* ```typescript
@@ -33,7 +34,7 @@ export class CasesService extends BaseService implements CasesServiceModel {
3334
* ```
3435
*/
3536
@track('Cases.GetAll')
36-
async getAll(): Promise<CaseGetAllResponse[]> {
37+
async getAll(): Promise<CaseGetAllWithMethodsResponse[]> {
3738
const params = createParams({
3839
processType: ProcessType.CaseManagement
3940
});
@@ -45,10 +46,10 @@ export class CasesService extends BaseService implements CasesServiceModel {
4546

4647
// Extract processes array from response data and add name field
4748
const cases = response.data?.processes || [];
48-
return cases.map(caseItem => ({
49+
return cases.map(caseItem => createCaseWithMethods({
4950
...caseItem,
5051
name: this.extractCaseName(caseItem.packageId)
51-
}));
52+
}, this));
5253
}
5354

5455
/**
@@ -294,6 +295,45 @@ export class CasesService extends BaseService implements CasesServiceModel {
294295
return (data ?? []).map(process => ({ ...process, name: this.extractCaseName(process.packageId) }));
295296
}
296297

298+
/**
299+
* Get element stats for case instances
300+
*
301+
* Returns per-element execution counts (success, fail, terminated, paused, in-progress) and
302+
* duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements within a case.
303+
*
304+
* @param processKey - Process key to filter by
305+
* @param packageId - Package identifier
306+
* @param startTime - Start of the time range to query
307+
* @param endTime - End of the time range to query
308+
* @param packageVersion - Package version to filter by
309+
* @returns Promise resolving to an array of {@link ElementStats}
310+
* @example
311+
* ```typescript
312+
* // Get element metrics for a case
313+
* const elements = await cases.getElementStats(
314+
* '<processKey>',
315+
* '<packageId>',
316+
* new Date('2026-04-01'),
317+
* new Date(),
318+
* '1.0.1'
319+
* );
320+
*
321+
* // Find elements with failures
322+
* const failedElements = elements.filter(e => e.failCount > 0);
323+
* for (const element of failedElements) {
324+
* console.log(`Failed element: ${element.elementId}, failures: ${element.failCount}`);
325+
* }
326+
* ```
327+
*/
328+
@track('Cases.GetElementStats')
329+
async getElementStats(processKey: string, packageId: string, startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]> {
330+
const { data } = await this.post<ElementStats[]>(
331+
MAESTRO_ENDPOINTS.INSIGHTS.ELEMENT_COUNT_BY_STATUS,
332+
buildElementCountByStatusBody(processKey, packageId, startTime, endTime, packageVersion)
333+
);
334+
return data ?? [];
335+
}
336+
297337
/**
298338
* Extract a readable case name from the packageId
299339
* @param packageId - The full package identifier

src/services/maestro/insights.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,3 +60,26 @@ export async function fetchInstanceStatusTimeline(
6060

6161
return response.data ?? [];
6262
}
63+
64+
/**
65+
* Builds the request body for the ElementCountByStatus endpoint.
66+
*
67+
* @param processKey - Process key to filter by
68+
* @param packageId - Package identifier
69+
* @param startTime - Start of the time range to query
70+
* @param endTime - End of the time range to query
71+
* @param packageVersion - Package version to filter by
72+
* @returns Request body for the ElementCountByStatus endpoint
73+
* @internal
74+
*/
75+
export function buildElementCountByStatusBody(processKey: string, packageId: string, startTime: Date, endTime: Date, packageVersion: string) {
76+
return {
77+
commonParams: {
78+
processKey,
79+
packageId,
80+
startTime: startTime.getTime(),
81+
endTime: endTime.getTime(),
82+
version: packageVersion
83+
}
84+
};
85+
}

src/services/maestro/processes/process-instances.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ export class ProcessInstancesService extends BaseService implements ProcessInsta
203203
};
204204
}
205205

206+
206207
/**
207208
* Parses BPMN XML to extract variable metadata from uipath:inputOutput elements
208209
* @private

0 commit comments

Comments
 (0)