Skip to content

Commit b9ff93a

Browse files
feat(insights): add getInstanceStats to MaestroProcesses and Cases [PLT-103276] (#478)
* feat(insights): add getInstanceCountByStatus to MaestroProcesses and Cases [PLT-103276] Add InstanceCountByStatus endpoint returning instance counts broken down by status (running, completed, faulted, etc.) and average duration. - Rename buildElementCountByStatusBody to buildInsightsCommonBody (reused) - Add InstanceCountByStatusResponse type - Add service method, bound method, and JSDoc on both services - Comprehensive tests in processes, minimal in cases Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(insights): rename getInstanceCountByStatus to getInstanceStats - Rename method: getInstanceCountByStatus → getInstanceStats - Rename type: InstanceCountByStatusResponse → InstanceStats - Extract DurationStats interface (shared by ElementStats + InstanceStats) - Add p50/p95/p99 + min/max duration percentiles to InstanceStats - Rename SDK count fields (countOf* → *Count) with InstanceStatsMap transform - API names (countOf*, endpoint constants) unchanged Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix: remove stale PIMS scope and align integration tests with ElementStats pattern - Remove spurious PIMS scope from getElementStats in oauth-scopes.md - Extract testGetInstanceStats + expectValidInstanceStats helpers - Simplify cases/processes integration tests to use the helper (matches getElementStats pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * fix(tests): add getInstanceStats to beforeEach mock setup Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * docs(insights): improve JSDoc examples for getElementStats/getInstanceStats - Show how to obtain processKey, packageId, packageVersion via getAll() - Include bound method usage in the same @example block (matches getStagesSlaSummary pattern) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * refactor(insights): use ProcessStatsRequest object for getElementStats/getInstanceStats 5 positional params at the service layer hurt readability. Bundle them into a single ProcessStatsRequest type. Bound entity methods stay positional (3 params each — the entity auto-fills processKey and packageId). - Add ProcessStatsRequest type in insights.types.ts (shared by both methods on MaestroProcesses and Cases) - buildInsightsCommonBody now takes the request object - Service-level getElementStats/getInstanceStats: (request: ProcessStatsRequest) - Bound process/case methods unchanged signature: (startTime, endTime, packageVersion) - Establish Request/Options two-suffix convention in agent_docs/conventions.md: Options = optional-fields bag (existing), Request = required-fields bag, used only when 4+ required values cluster semantically Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * refactor(insights): rename ProcessStatsRequest to MaestroProcessStatsRequest Disambiguate from Orchestrator's Process resource, which lives in the same public type namespace. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(insights): assert raw countOf* keys absent in getInstanceStats result Matches the transform-completeness pattern already used by getTopRunCount (processes.test.ts:322). Cases test stays differential — comprehensive absence checks live on the processes side only. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * test(maestro): drop dead inline mock overrides in bound-method tests beforeEach already registers getElementStats and getInstanceStats as vi.fn(). The inline mockResolvedValue reassignments shadowed those without changing behavior — none of the tests assert on the return value, so the resolved value was never observed. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 3f62cfc commit b9ff93a

19 files changed

Lines changed: 715 additions & 157 deletions

File tree

agent_docs/conventions.md

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,15 @@
3232
- **Final response type**: `type {Entity}GetResponse = Raw{Entity}GetResponse & {Entity}Methods` — defined in `*.models.ts`, combining raw data with bound methods.
3333
- **Options types**: `{Entity}GetAllOptions`, `{Entity}GetByIdOptions`, `{Entity}{Operation}Options` (e.g., `TaskAssignmentOptions`, `ProcessInstanceOperationOptions`). Compose with `RequestOptions & PaginationOptions & { ... }` for list methods.
3434
- **Common base types**: `BaseOptions` (expand, select), `RequestOptions` (extends BaseOptions with filter, orderby), `OperationResponse<TData>` (success + data) — all from `src/models/common/types.ts`.
35-
- **Use "Options" not "Request"** for parameter types — the entire SDK uses `{Entity}{Operation}Options`.
36-
- **Required parameters are always positional; Options objects are reserved for optional parameters only.** Required values (IDs, keys, data) are positional arguments. Options objects are always the last parameter, always marked `?`, and contain only optional fields. E.g., `getOutput(jobKey: string)` not `getOutput(options: { jobKey: string })`, `close(instanceId, folderKey, options?)` not `close(options: { instanceId, folderKey })`.
35+
- **Parameter type naming — two parallel suffixes:**
36+
- `{Entity}{Operation}Options` — bag of **optional** fields. Always the last parameter. Always marked `?`. Contains only optional fields.
37+
- `{Entity}{Operation}Request` — bag of **required** fields. Pairs naturally with the existing `{Entity}{Operation}Response`. Convention param name is `request`. Use this **only** when a method has **4+ required values that semantically cluster** (e.g., a process scope + time range). Below that threshold, keep required params positional.
38+
- **NEVER** use `Request` as the suffix for an optional-fields bag — that slot is `Options`. The two suffixes are not interchangeable.
39+
- **Required parameters: positional by default, `Request` object only at 4+.**
40+
- 1–3 required params → positional. E.g., `getOutput(jobKey: string)` not `getOutput(options: { jobKey: string })`; `close(instanceId, folderKey, options?)` not `close(options: { instanceId, folderKey })`.
41+
- 4+ required params that cluster semantically → single `{Entity}{Operation}Request` object. E.g., `getElementStats(request: ProcessStatsRequest)` where `ProcessStatsRequest` bundles `processKey`, `packageId`, `packageVersion`, `startTime`, `endTime`.
42+
- **Shared `Request` type across methods is allowed** when the exact same shape is used by 2+ methods with no natural method-specific anchor — name it after the concept it represents (e.g., `ProcessStatsRequest` is shared by `getElementStats` and `getInstanceStats`). Same rule that already applies to shared response types.
43+
- **The same ≤3 / 4+ rule applies to bound entity methods independently.** Bound methods often have fewer required params than their service-level counterparts because the entity auto-fills some fields (e.g., `processKey`, `packageId`). Count what's left after the entity-supplied fields. E.g., service-level `getElementStats` has 5 required → uses `ProcessStatsRequest`. Bound `process.getElementStats(packageVersion, startTime, endTime)` has 3 required → stays positional. The factory delegate constructs the `Request` from the positional args plus the entity's fields before calling the service.
3744
- **NEVER** duplicate fields across option types — extend existing ones. If `CaseInstanceOperationOptions` already has `comment`, extend it instead of re-declaring. When the shape is identical, use `extends` (e.g., `export interface EntityUpdateRecordByIdOptions extends EntityGetRecordByIdOptions {}`).
3845
- **Use `Omit` only when a field must not appear in a type at all.** When a field has a server-side default and is simply not required by callers, mark it `?` optional instead. Using `Omit` implies the field would cause an error if included — it misleads callers into thinking they cannot pass the field. Example: if `assignmentCriteria` defaults to `SingleUser` on the server, the options type should declare it as `assignmentCriteria?: TaskAssignmentCriteria`, not `Omit<BaseOptions, 'assignmentCriteria'>`.
3946
- **Always use `type` for response types** (intersections, unions, composed types). The only place `interface extends` is required is single-type aliases (`type X = Y`), which break TypeDoc — use `export interface EntityUpdateRecordResponse extends EntityRecord {}` instead.

docs/oauth-scopes.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ This page lists the specific OAuth scopes required in external app for each SDK
8080
| `getInstanceStatusTimeline()` | `Insights.RealTimeData Insights OR.Folders.Read` |
8181
| `getTopExecutionDuration()` | `Insights.RealTimeData Insights OR.Folders.Read` |
8282
| `getElementStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
83+
| `getInstanceStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
8384

8485
## Maestro Process Instances
8586

@@ -106,6 +107,7 @@ This page lists the specific OAuth scopes required in external app for each SDK
106107
| `getInstanceStatusTimeline()` | `Insights.RealTimeData Insights OR.Folders.Read` |
107108
| `getTopExecutionDuration()` | `Insights.RealTimeData Insights OR.Folders.Read` |
108109
| `getElementStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
110+
| `getInstanceStats()` | `Insights.RealTimeData Insights OR.Folders.Read` |
109111

110112
## Maestro Case Instances
111113

src/models/maestro/cases.models.ts

Lines changed: 87 additions & 16 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, ElementStats } from './insights.types';
7+
import { TopQueryOptions, InstanceStatusTimelineResponse, TimelineOptions, ElementGetTopFailedCountResponse, ElementStats, InstanceStats, MaestroProcessStatsRequest } from './insights.types';
88

99
/**
1010
* Service for managing UiPath Maestro Cases
@@ -248,31 +248,74 @@ export interface CasesServiceModel {
248248
* Returns per-element execution counts (success, fail, terminated, paused, in-progress) and
249249
* duration percentile metrics (min, max, avg, p50, p95, p99) for BPMN elements within a case.
250250
*
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
251+
* @param request - Process scope + time range to aggregate over
256252
* @returns Promise resolving to an array of {@link ElementStats}
257253
* @example
258254
* ```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-
* );
255+
* // First, list cases to find the processKey, packageId, and available versions
256+
* const allCases = await cases.getAll();
257+
* const caseItem = allCases[0];
258+
*
259+
* // Get element metrics for that case
260+
* const elements = await cases.getElementStats({
261+
* processKey: caseItem.processKey,
262+
* packageId: caseItem.packageId,
263+
* packageVersion: caseItem.packageVersions[0],
264+
* startTime: new Date('2026-04-01'),
265+
* endTime: new Date(),
266+
* });
267267
*
268268
* // Find elements with failures
269269
* const failedElements = elements.filter(e => e.failCount > 0);
270270
* for (const element of failedElements) {
271271
* console.log(`Failed element: ${element.elementId}, failures: ${element.failCount}`);
272272
* }
273+
*
274+
* // Using bound method on a case — auto-fills processKey and packageId
275+
* const boundElements = await caseItem.getElementStats(
276+
* new Date('2026-04-01'),
277+
* new Date(),
278+
* caseItem.packageVersions[0]
279+
* );
273280
* ```
274281
*/
275-
getElementStats(processKey: string, packageId: string, startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
282+
getElementStats(request: MaestroProcessStatsRequest): Promise<ElementStats[]>;
283+
284+
/**
285+
* Get instance stats for a case.
286+
*
287+
* Returns total instance counts broken down by status (running, completed, faulted, etc.)
288+
* and the average execution duration for all instances of a case within a time range.
289+
*
290+
* @param request - Process scope + time range to aggregate over
291+
* @returns Promise resolving to {@link InstanceStats}
292+
* @example
293+
* ```typescript
294+
* // First, list cases to find the processKey, packageId, and available versions
295+
* const allCases = await cases.getAll();
296+
* const caseItem = allCases[0];
297+
*
298+
* // Get instance status breakdown for that case
299+
* const counts = await cases.getInstanceStats({
300+
* processKey: caseItem.processKey,
301+
* packageId: caseItem.packageId,
302+
* packageVersion: caseItem.packageVersions[0],
303+
* startTime: new Date('2026-04-01'),
304+
* endTime: new Date(),
305+
* });
306+
*
307+
* console.log(`Total: ${counts.totalCount}`);
308+
* console.log(`Completed: ${counts.completedCount}, Faulted: ${counts.faultedCount}`);
309+
*
310+
* // Using bound method on a case — auto-fills processKey and packageId
311+
* const boundCounts = await caseItem.getInstanceStats(
312+
* new Date('2026-04-01'),
313+
* new Date(),
314+
* caseItem.packageVersions[0]
315+
* );
316+
* ```
317+
*/
318+
getInstanceStats(request: MaestroProcessStatsRequest): Promise<InstanceStats>;
276319
}
277320

278321
// Method interface that will be added to case objects
@@ -286,6 +329,16 @@ export interface CaseMethods {
286329
* @returns Promise resolving to an array of {@link ElementStats}
287330
*/
288331
getElementStats(startTime: Date, endTime: Date, packageVersion: string): Promise<ElementStats[]>;
332+
333+
/**
334+
* Get instance stats for this case
335+
*
336+
* @param startTime - Start of the time range to query
337+
* @param endTime - End of the time range to query
338+
* @param packageVersion - Package version to filter by
339+
* @returns Promise resolving to {@link InstanceStats}
340+
*/
341+
getInstanceStats(startTime: Date, endTime: Date, packageVersion: string): Promise<InstanceStats>;
289342
}
290343

291344
// Combined type for case data with methods
@@ -304,7 +357,25 @@ function createCaseMethods(caseData: CaseGetAllResponse, service: CasesServiceMo
304357
if (!caseData.processKey) throw new Error('Process key is undefined');
305358
if (!caseData.packageId) throw new Error('Package ID is undefined');
306359

307-
return service.getElementStats(caseData.processKey, caseData.packageId, startTime, endTime, packageVersion);
360+
return service.getElementStats({
361+
processKey: caseData.processKey,
362+
packageId: caseData.packageId,
363+
packageVersion,
364+
startTime,
365+
endTime,
366+
});
367+
},
368+
getInstanceStats(startTime: Date, endTime: Date, packageVersion: string): Promise<InstanceStats> {
369+
if (!caseData.processKey) throw new Error('Process key is undefined');
370+
if (!caseData.packageId) throw new Error('Package ID is undefined');
371+
372+
return service.getInstanceStats({
373+
processKey: caseData.processKey,
374+
packageId: caseData.packageId,
375+
packageVersion,
376+
startTime,
377+
endTime,
378+
});
308379
}
309380
};
310381
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
/**
2+
* Maps API field names (countOf*) to SDK field names (*Count) for InstanceStats,
3+
* aligning naming with ElementStats and other count-suffixed conventions.
4+
*/
5+
export const InstanceStatsMap: { [key: string]: string } = {
6+
countOfAllInstances: 'totalCount',
7+
countOfRunning: 'runningCount',
8+
countOfTransitioning: 'transitioningCount',
9+
countOfPaused: 'pausedCount',
10+
countOfFaulted: 'faultedCount',
11+
countOfCompleted: 'completedCount',
12+
countOfCancelled: 'cancelledCount',
13+
countOfDeleted: 'deletedCount'
14+
};

src/models/maestro/insights.internal-types.ts

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
import { DurationStats } from './insights.types';
2+
13
/**
24
* Raw API response for TopElementswithFailure endpoint.
35
* The SDK renames `count` to `failedCount` in the public response type.
@@ -12,3 +14,18 @@ export interface RawElementGetTopFailedCountResponse {
1214
/** Number of failed executions */
1315
count: number;
1416
}
17+
18+
/**
19+
* Raw API response for InstanceCountByStatus endpoint.
20+
* The SDK renames `countOf*` fields to `*Count` for consistency with ElementStats.
21+
*/
22+
export interface RawInstanceStats extends DurationStats {
23+
countOfAllInstances: number;
24+
countOfRunning: number;
25+
countOfTransitioning: number;
26+
countOfPaused: number;
27+
countOfFaulted: number;
28+
countOfCompleted: number;
29+
countOfCancelled: number;
30+
countOfDeleted: number;
31+
}

src/models/maestro/insights.types.ts

Lines changed: 67 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -119,21 +119,12 @@ export interface GetTopDurationResponse extends GetTopBaseResponse {
119119
}
120120

121121
/**
122-
* Element count by status for a BPMN element within a process or case
122+
* Duration percentile stats shared by Insights aggregate endpoints (per-element and per-process/case).
123+
*
124+
* For instance-level stats, durations are computed over terminal instances only
125+
* (Completed, Cancelled, Deleted) and default to `0` when no terminal instances exist.
123126
*/
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;
127+
export interface DurationStats {
137128
/** Minimum duration in milliseconds */
138129
minDurationMs: number;
139130
/** Maximum duration in milliseconds */
@@ -148,3 +139,65 @@ export interface ElementStats {
148139
p99DurationMs: number;
149140
}
150141

142+
/**
143+
* Instance count and duration stats aggregated by status for a process or case within a time range.
144+
*
145+
* Duration fields are computed over terminal instances only (Completed, Cancelled, Deleted)
146+
* and default to `0` when no terminal instances exist in the time range.
147+
*/
148+
export interface InstanceStats extends DurationStats {
149+
/** Total number of instances across all statuses */
150+
totalCount: number;
151+
/** Number of currently running instances */
152+
runningCount: number;
153+
/** Number of instances in transitioning state */
154+
transitioningCount: number;
155+
/** Number of paused instances */
156+
pausedCount: number;
157+
/** Number of faulted instances */
158+
faultedCount: number;
159+
/** Number of completed instances */
160+
completedCount: number;
161+
/** Number of cancelled instances */
162+
cancelledCount: number;
163+
/** Number of deleted instances */
164+
deletedCount: number;
165+
}
166+
167+
/**
168+
* Required request parameters for process-scoped insights stats endpoints
169+
* (`getElementStats`, `getInstanceStats`).
170+
*
171+
* Identifies a single process+package+version and the time range to aggregate over.
172+
*/
173+
export interface MaestroProcessStatsRequest {
174+
/** Process key to filter by */
175+
processKey: string;
176+
/** Package identifier */
177+
packageId: string;
178+
/** Package version to filter by */
179+
packageVersion: string;
180+
/** Start of the time range to query */
181+
startTime: Date;
182+
/** End of the time range to query */
183+
endTime: Date;
184+
}
185+
186+
/**
187+
* Per-element execution counts and duration percentiles for a BPMN element within a process or case.
188+
*/
189+
export interface ElementStats extends DurationStats {
190+
/** BPMN element identifier */
191+
elementId: string;
192+
/** Number of successful executions */
193+
successCount: number;
194+
/** Number of failed executions */
195+
failCount: number;
196+
/** Number of terminated executions */
197+
terminatedCount: number;
198+
/** Number of paused executions */
199+
pausedCount: number;
200+
/** Number of in-progress executions */
201+
inProgressCount: number;
202+
}
203+

0 commit comments

Comments
 (0)