11import { FolderScopedService } from '../../folder-scoped' ;
2- import { RawJobGetResponse , JobGetAllOptions } from '../../../models/orchestrator/jobs.types' ;
2+ import { RawJobGetResponse , JobGetAllOptions , JobStopOptions , JobStopData } from '../../../models/orchestrator/jobs.types' ;
33import { RawJobOutputFields } from '../../../models/orchestrator/jobs.internal-types' ;
44import { JobServiceModel , JobGetResponse , createJobWithMethods } from '../../../models/orchestrator/jobs.models' ;
55import { pascalToCamelCaseKeys , transformData } from '../../../utils/transform' ;
@@ -11,12 +11,17 @@ import { ValidationError, ServerError } from '../../../core/errors';
1111import { ErrorFactory } from '../../../core/errors/error-factory' ;
1212import { errorResponseParser } from '../../../core/errors/parser' ;
1313import { createHeaders } from '../../../utils/http/headers' ;
14- import { FOLDER_ID } from '../../../utils/constants/headers' ;
14+ import { FOLDER_ID , RESPONSE_TYPES } from '../../../utils/constants/headers' ;
1515import { PaginatedResponse , NonPaginatedResponse , HasPaginationOptions } from '../../../utils/pagination' ;
1616import { PaginationHelpers } from '../../../utils/pagination/helpers' ;
1717import { PaginationType } from '../../../utils/pagination/internal-types' ;
1818import { track } from '../../../core/telemetry' ;
1919import type { IUiPath } from '../../../core/types' ;
20+ import { OperationResponse , CollectionResponse } from '../../../models/common/types' ;
21+ import { StopStrategy } from '../../../models/orchestrator/processes.types' ;
22+
23+ /** Maximum number of job keys to resolve in a single OData filter query */
24+ const JOB_KEY_RESOLUTION_CHUNK_SIZE = 50 ;
2025
2126/**
2227 * Service for interacting with UiPath Orchestrator Jobs API
@@ -160,6 +165,37 @@ export class JobService extends FolderScopedService implements JobServiceModel {
160165 return null ;
161166 }
162167
168+ /**
169+ * Stops one or more jobs by their UUID keys.
170+ *
171+ * Resolves job UUID keys to integer IDs, then calls the StopJobs OData action.
172+ * Keys are resolved in chunks of 50 to avoid URL length limits.
173+ *
174+ * @param jobKeys - Array of job UUID keys to stop
175+ * @param folderId - The folder ID where the jobs reside (required by the API)
176+ * @param options - Optional stop options including strategy
177+ * @returns Promise resolving to an OperationResponse with the resolved job IDs
178+ */
179+ @track ( 'Jobs.Stop' )
180+ async stop (
181+ jobKeys : string [ ] ,
182+ folderId : number ,
183+ options ?: JobStopOptions
184+ ) : Promise < OperationResponse < JobStopData > > {
185+ if ( jobKeys . length === 0 ) {
186+ return { success : true , data : { jobIds : [ ] } } ;
187+ }
188+
189+ const headers = createHeaders ( { [ FOLDER_ID ] : folderId } ) ;
190+ const strategy = options ?. strategy ?? StopStrategy . SoftStop ;
191+
192+ const jobIds = await this . resolveJobKeys ( jobKeys , headers ) ;
193+
194+ await this . stopJobsByIds ( jobIds , strategy , headers ) ;
195+
196+ return { success : true , data : { jobIds } } ;
197+ }
198+
163199 /**
164200 * Fetches a job by its Key (GUID) using the GetByKey endpoint.
165201 * Only selects fields needed for output extraction.
@@ -222,4 +258,56 @@ export class JobService extends FolderScopedService implements JobServiceModel {
222258 throw new ServerError ( { message : 'Failed to parse job output file as JSON' } ) ;
223259 }
224260 }
261+
262+ /**
263+ * Resolves job UUID keys to integer IDs via OData filter queries.
264+ * Chunks keys into batches to avoid URL length limits.
265+ */
266+ private async resolveJobKeys (
267+ jobKeys : string [ ] ,
268+ headers : Record < string , string >
269+ ) : Promise < number [ ] > {
270+ const uniqueKeys = [ ...new Set ( jobKeys ) ] ;
271+ const keyToIdMap = new Map < string , number > ( ) ;
272+
273+ for ( let i = 0 ; i < uniqueKeys . length ; i += JOB_KEY_RESOLUTION_CHUNK_SIZE ) {
274+ const chunk = uniqueKeys . slice ( i , i + JOB_KEY_RESOLUTION_CHUNK_SIZE ) ;
275+ const filterValues = chunk . map ( ( key ) => `'${ key } '` ) . join ( ',' ) ;
276+ const filter = `Key in (${ filterValues } )` ;
277+
278+ const response = await this . get < CollectionResponse < { Key : string ; Id : number } > > (
279+ JOB_ENDPOINTS . GET_ALL ,
280+ {
281+ params : { $filter : filter , $select : 'Id,Key' } ,
282+ headers,
283+ }
284+ ) ;
285+
286+ for ( const job of response . data . value ) {
287+ keyToIdMap . set ( job . Key , job . Id ) ;
288+ }
289+ }
290+
291+ const missingKeys = uniqueKeys . filter ( ( key ) => ! keyToIdMap . has ( key ) ) ;
292+ if ( missingKeys . length > 0 ) {
293+ throw new Error ( `Jobs not found for keys: ${ missingKeys . join ( ', ' ) } ` ) ;
294+ }
295+
296+ return jobKeys . map ( ( key ) => keyToIdMap . get ( key ) ! ) ;
297+ }
298+
299+ /**
300+ * Calls the StopJobs OData action with resolved integer IDs.
301+ */
302+ private async stopJobsByIds (
303+ jobIds : number [ ] ,
304+ strategy : StopStrategy ,
305+ headers : Record < string , string >
306+ ) : Promise < void > {
307+ await this . post (
308+ JOB_ENDPOINTS . STOP ,
309+ { jobIds, strategy } ,
310+ { headers, responseType : RESPONSE_TYPES . TEXT }
311+ ) ;
312+ }
225313}
0 commit comments