Skip to content

Commit 0e97b53

Browse files
chore: address comments
1 parent da60623 commit 0e97b53

6 files changed

Lines changed: 71 additions & 28 deletions

File tree

src/models/orchestrator/jobs.models.ts

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -144,20 +144,18 @@ export interface JobServiceModel {
144144
*
145145
* @example
146146
* ```typescript
147-
* import { Jobs } from '@uipath/uipath-typescript/jobs';
148-
*
149-
* const jobs = new Jobs(sdk);
150-
*
151147
* // Stop a single job with default soft stop
152-
* const result = await jobs.stop(
153-
* ['c80c3b30-f010-4eb8-82d4-b67bc615e137'],
154-
* 123
155-
* );
148+
* const result = await jobs.stop([<jobKey>], <folderId>);
149+
* ```
150+
*
151+
* @example
152+
* ```typescript
153+
* import { StopStrategy } from '@uipath/uipath-typescript/jobs';
156154
*
157155
* // Force-kill multiple jobs
158-
* const killResult = await jobs.stop(
159-
* ['c80c3b30-f010-4eb8-82d4-b67bc615e137', '24ef1040-454d-4184-b994-c641ee32318d'],
160-
* 123,
156+
* const result = await jobs.stop(
157+
* [<jobKey1>, <jobKey2>],
158+
* <folderId>,
161159
* { strategy: StopStrategy.Kill }
162160
* );
163161
* ```

src/services/orchestrator/jobs/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,4 @@ export { JobService as Jobs, JobService } from './jobs';
2323
export * from '../../../models/orchestrator/jobs.types';
2424
export * from '../../../models/orchestrator/jobs.models';
2525
export { JobState } from '../../../models/common/types';
26+
export { StopStrategy } from '../../../models/orchestrator/processes.types';

src/services/orchestrator/jobs/jobs.ts

Lines changed: 26 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,31 @@ export class JobService extends FolderScopedService implements JobServiceModel {
219219
/**
220220
* Stops one or more jobs by their UUID keys.
221221
*
222-
* Resolves job UUID keys to integer IDs, then calls the StopJobs OData action.
223-
* Keys are resolved in chunks of 50 to avoid URL length limits.
222+
* Resolves the provided job UUID keys to integer IDs, then sends a stop request to the Orchestrator.
223+
* Keys are processed in chunks of 50 to avoid URL length limits. Throws if any keys cannot be resolved.
224224
*
225-
* @param jobKeys - Array of job UUID keys to stop
226-
* @param folderId - The folder ID where the jobs reside (required by the API)
227-
* @param options - Optional stop options including strategy
228-
* @returns Promise resolving to an OperationResponse with the resolved job IDs
225+
* @param jobKeys - Array of job UUID keys to stop (e.g., from {@link JobGetResponse}.key)
226+
* @param folderId - The folder ID where the jobs reside (required)
227+
* @param options - Optional {@link JobStopOptions} including stop strategy
228+
* @returns Promise resolving to an {@link OperationResponse}<{@link JobStopData}> with the resolved job IDs
229+
*
230+
* @example
231+
* ```typescript
232+
* // Stop a single job with default soft stop
233+
* const result = await jobs.stop([<jobKey>], <folderId>);
234+
* ```
235+
*
236+
* @example
237+
* ```typescript
238+
* import { StopStrategy } from '@uipath/uipath-typescript/jobs';
239+
*
240+
* // Force-kill multiple jobs
241+
* const result = await jobs.stop(
242+
* [<jobKey1>, <jobKey2>],
243+
* <folderId>,
244+
* { strategy: StopStrategy.Kill }
245+
* );
246+
* ```
229247
*/
230248
@track('Jobs.Stop')
231249
async stop(
@@ -308,7 +326,7 @@ export class JobService extends FolderScopedService implements JobServiceModel {
308326
const response = await this.get<CollectionResponse<{ Key: string; Id: number }>>(
309327
JOB_ENDPOINTS.GET_ALL,
310328
{
311-
params: { $filter: filter, $select: 'Id,Key' },
329+
params: { $filter: filter, $select: 'Id,Key', $top: chunk.length },
312330
headers,
313331
}
314332
);
@@ -320,7 +338,7 @@ export class JobService extends FolderScopedService implements JobServiceModel {
320338

321339
const missingKeys = uniqueKeys.filter((key) => !keyToIdMap.has(key));
322340
if (missingKeys.length > 0) {
323-
throw new Error(`Jobs not found for keys: ${missingKeys.join(', ')}`);
341+
throw new ValidationError({ message: `Jobs not found for keys: ${missingKeys.join(', ')}` });
324342
}
325343

326344
return jobKeys.map((key) => keyToIdMap.get(key)!);

tests/integration/shared/orchestrator/jobs.integration.test.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -193,8 +193,7 @@ describe.each(modes)('Orchestrator Jobs - Integration Tests [%s]', (mode) => {
193193
const { jobs, folderId } = getJobsService();
194194

195195
if (!folderId) {
196-
console.warn('INTEGRATION_TEST_FOLDER_ID not configured, skipping stop test.');
197-
return;
196+
throw new Error('INTEGRATION_TEST_FOLDER_ID not configured — cannot run stop test.');
198197
}
199198

200199
// Find a running job to stop
@@ -223,14 +222,10 @@ describe.each(modes)('Orchestrator Jobs - Integration Tests [%s]', (mode) => {
223222
});
224223

225224
it('should return empty result when called with empty array', async () => {
226-
const { jobs, folderId } = getJobsService();
227-
228-
if (!folderId) {
229-
console.warn('INTEGRATION_TEST_FOLDER_ID not configured, skipping stop test.');
230-
return;
231-
}
225+
const { jobs } = getJobsService();
232226

233-
const result = await jobs.stop([], folderId);
227+
// folderId is unused for empty-array inputs — stop() returns early before reading it
228+
const result = await jobs.stop([], 0);
234229

235230
expect(result).toBeDefined();
236231
expect(result.success).toBe(true);

tests/unit/services/orchestrator/jobs.test.ts

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -519,6 +519,7 @@ describe('JobService Unit Tests', () => {
519519
params: {
520520
$filter: `Key in ('${JOB_TEST_CONSTANTS.JOB_KEY}')`,
521521
$select: 'Id,Key',
522+
$top: 1,
522523
},
523524
})
524525
);
@@ -604,6 +605,33 @@ describe('JobService Unit Tests', () => {
604605
expect(keyMatches).toHaveLength(2); // One key, two quotes
605606
});
606607

608+
it('should resolve keys in multiple chunks when count exceeds chunk size', async () => {
609+
const chunkSize = JOB_TEST_CONSTANTS.KEY_RESOLUTION_CHUNK_SIZE;
610+
const keys = Array.from(
611+
{ length: chunkSize + 1 },
612+
(_, i) => `${i.toString().padStart(8, '0')}-bbbb-cccc-dddd-eeeeeeeeeeee`
613+
);
614+
const ids = keys.map((_, i) => i + 1);
615+
616+
// Chunk 1: first 50 keys
617+
mockApiClient.get.mockResolvedValueOnce({
618+
value: keys.slice(0, chunkSize).map((k, i) => ({ Key: k, Id: ids[i] })),
619+
});
620+
// Chunk 2: remaining 1 key
621+
mockApiClient.get.mockResolvedValueOnce({
622+
value: [{ Key: keys[chunkSize], Id: ids[chunkSize] }],
623+
});
624+
mockApiClient.post.mockResolvedValueOnce(undefined);
625+
626+
const result = await jobService.stop(keys, TEST_CONSTANTS.FOLDER_ID);
627+
628+
expect(mockApiClient.get).toHaveBeenCalledTimes(2);
629+
expect(mockApiClient.get.mock.calls[0][1].params.$top).toBe(chunkSize);
630+
expect(mockApiClient.get.mock.calls[1][1].params.$top).toBe(1);
631+
expect(result.data.jobIds).toHaveLength(chunkSize + 1);
632+
expect(result.data.jobIds).toEqual(ids);
633+
});
634+
607635
it('should propagate resolution API errors', async () => {
608636
const error = createMockError(JOB_TEST_CONSTANTS.ERROR_JOB_NOT_FOUND);
609637
mockApiClient.get.mockRejectedValueOnce(error);

tests/utils/constants/jobs.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,7 @@ export const JOB_TEST_CONSTANTS = {
3232
// Error Messages
3333
ERROR_JOB_NOT_FOUND: 'Job not found',
3434
ERROR_JOBS_NOT_FOUND_FOR_KEYS: 'Jobs not found for keys',
35+
36+
// Stop resolution
37+
KEY_RESOLUTION_CHUNK_SIZE: 50,
3538
} as const;

0 commit comments

Comments
 (0)