Skip to content

feat(jobs): onboard getOutput method [PLT-100430]#320

Open
ninja-shreyash wants to merge 8 commits intomainfrom
feat/sdk-plt-100430
Open

feat(jobs): onboard getOutput method [PLT-100430]#320
ninja-shreyash wants to merge 8 commits intomainfrom
feat/sdk-plt-100430

Conversation

@ninja-shreyash
Copy link
Copy Markdown
Contributor

@ninja-shreyash ninja-shreyash commented Mar 24, 2026

Method Added

Layer Method Signature
Service jobs.getOutput() getOutput(options: JobGetOutputOptions): Promise<Record<string, unknown> | null>

Endpoint Called

Method HTTP Endpoint OAuth Scope
getOutput() GET /orchestrator_/odata/Jobs (with $filter=Key eq <jobKey>) OR.Jobs or OR.Jobs.Read
getOutput() GET /orchestrator_/odata/Attachments({key}) (for file-based output) OR.Jobs or OR.Jobs.Read
  • Extends FolderScopedService — sets X-UIPATH-OrganizationUnitId header when folderId is provided
  • getOutput() is a composite method: fetches job by Key filter → resolves output via inline OutputArguments JSON or blob download from Attachments API
  • folderId is optional (verified against Orchestrator Swagger)

Example Usage

import { UiPath } from '@uipath/uipath-typescript/core';
import { Jobs } from '@uipath/uipath-typescript/jobs';

const sdk = new UiPath(config);
const jobs = new Jobs(sdk);

// Get output from a completed job
const output = await jobs.getOutput({ jobKey: '<jobKey>' });

if (output) {
  console.log('Job output:', output);
}

// With folder context
const output2 = await jobs.getOutput({ jobKey: '<jobKey>', folderId: 123 });

API Response vs SDK Response

Composition flow

1. Fetch job by Key filter → GET /odata/Jobs?$filter=Key eq <key>&$select=OutputArguments,OutputFile&$top=1
2. If OutputArguments exists → JSON.parse → return parsed object
3. If OutputFile exists → GET /odata/Attachments({key}) → fetch blob from presigned URI → JSON.parse → return
4. Otherwise → return null

Internal types (not exported)

Type Purpose
RawBlobFileAccess Presigned URI + headers for blob download
RawAttachmentResponse Raw response from Attachments API

Files

Area Files
Endpoint src/utils/constants/endpoints/orchestrator.ts
Types src/models/orchestrator/jobs.types.ts
Internal Types src/models/orchestrator/jobs.internal-types.ts
Models src/models/orchestrator/jobs.models.ts
Service src/services/orchestrator/jobs/jobs.ts
Unit tests tests/unit/services/orchestrator/jobs.test.ts (16 tests)
Integration tests tests/integration/shared/orchestrator/jobs.integration.test.ts (6 tests)
Test utils tests/utils/constants/jobs.ts
Docs docs/oauth-scopes.md
Agent docs agent_docs/rules.md (v0/v1 rule clarification)
Skills .claude/skills/sdk-verify/SKILL.md, .claude/skills/onboard-api/references/e2e-testing.md (cleanup policy fix)

Refs PLT-100430

🤖 Auto-generated using onboarding skills

@ninja-shreyash ninja-shreyash mentioned this pull request Mar 24, 2026
6 tasks
@ninja-shreyash ninja-shreyash changed the base branch from sv/onboard-claude-files to main March 24, 2026 11:29
@ninja-shreyash ninja-shreyash requested a review from a team March 24, 2026 11:29
@ninja-shreyash ninja-shreyash marked this pull request as draft March 25, 2026 11:42
@ninja-shreyash ninja-shreyash marked this pull request as ready for review March 26, 2026 08:43
@ninja-shreyash
Copy link
Copy Markdown
Contributor Author

@claude review

@ninja-shreyash

This comment was marked as resolved.

@ninja-shreyash

This comment was marked as resolved.

@UiPath UiPath deleted a comment from claude bot Mar 30, 2026
@ninja-shreyash
Copy link
Copy Markdown
Contributor Author

Resolved all AI review comments

@ninja-shreyash
Copy link
Copy Markdown
Contributor Author

@claude review. Dont repeat the previous comments since I have already resolved what needed to be resolved.

@Sarath1018

This comment was marked as resolved.

@UiPath UiPath deleted a comment from claude bot Mar 31, 2026
@ninja-shreyash
Copy link
Copy Markdown
Contributor Author

Review

All CI checks pass and the overall structure is solid. A few issues to address before merge:

High — likely silent bug

OData $filter GUID missing quotes (jobs.ts:135)

// current
$filter: `Key eq ${jobKey}`,

// OData requires guid literal syntax
$filter: `Key eq guid'${jobKey}'`,

The commit message says "fix: filter by guid key for job" but the filter has no quoting. OData GUID literals require guid'' prefix — without it, Orchestrator will likely return 0 results, causing getOutput to silently return null for every call. Please validate this against the live API.

Medium — breaks JSDoc example

JobState not exported from the jobs subpath (jobs.models.ts:88)

// This example uses JobState.Successful, but JobState is not re-exported
// from @uipath/uipath-typescript/jobs — users will get a runtime error
const completedJob = allJobs.items.find(j => j.state === JobState.Successful);

Either re-export JobState from src/services/orchestrator/jobs/index.ts, or replace with the string literal 'Successful' in the example.

Low — test quality

Weak integration test assertion (jobs.integration.test.ts:514)

// This is always true — doesn't verify anything
expect(output === null || typeof output === 'object').toBe(true);

Consider renaming this test to "should handle job with or without output" to reflect that it's a smoke test, or remove it since the first getOutput test already covers the success path.

Missing unit test for NetworkError path — the downloadOutputFile branch that throws NetworkError when blobResponse.ok is false has no unit test coverage.

  1. This was flagged by claude review as well. I was validated and it works fine.
  2. Fixed for better visibility of JobState enum.
  3. Fixed

ninja-shreyash and others added 6 commits April 1, 2026 14:51
- Add blobResponse.ok check before JSON.parse in downloadOutputFile
- Wrap JSON.parse in try/catch for both inline and file output paths
- Make folderId optional in JobGetOutputOptions (verified against Swagger)
- Remove folderId validation block (createHeaders filters undefined)
- Fix JSDoc: .data → .items, add JobState enum, fix param descriptions
- Replace (job: any) with Record<string, unknown> in transform
- Remove unused JOB_ENDPOINTS.GET_BY_ID constant
- Integration tests: console.warn+return → throw/continue pattern
- Unit tests: move vi.unstubAllGlobals() to afterEach
- Add blobResponse.ok to fetch mocks, add folderId-optional test
- Clarify v0/v1 init mode rule in rules.md
- Fix e2e-testing.md cleanup policy precedence

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sonarqubecloud
Copy link
Copy Markdown

sonarqubecloud bot commented Apr 1, 2026

* Methods available on job response objects.
* These are bound to the job data and delegate to the service.
*/
export interface JobMethods {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit : put JobMethods after all methods are defined and above createJobMethods

* }
* ```
*/
getOutput(options: JobGetOutputOptions): Promise<Record<string, unknown> | null>;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JobGetOutputOptions only contains one field which is jobKey.
jobKey itself is not an optional parameter. Shouldn't it be : getOutput(jobKey: string)
with no options?

*/
private async fetchJobByKey(
jobKey: string
): Promise<{ OutputArguments: string | null; OutputFile: string | null } | null> {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

create an interface for the return type in internal-types

* Service for interacting with UiPath Orchestrator Jobs API
*/
export class JobService extends FolderScopedService implements JobServiceModel {
private attachmentService: AttachmentService;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit : add comments like this -

* Creates an instance of the Case Instances service.

try {
return JSON.parse(job.OutputArguments) as Record<string, unknown>;
} catch {
throw new ValidationError({ message: `Failed to parse job output arguments as JSON` });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this isn't really a validation error. Validation errors are on user inputs.

/**
* The unique key (GUID) of the job to retrieve output from
*/
jobKey: string;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if jobkey is not optional and required then it doesnt needs to be in options.
we should have getOutput(jobKey: string)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the skill as well

Comment on lines +169 to +172
const blobHeaders: Record<string, string> =
blobAccess.headers && 'keys' in blobAccess.headers
? arrayDictionaryToRecord(blobAccess.headers as unknown as { keys: string[]; values: string[] })
: (blobAccess.headers as Record<string, string>) ?? {};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this needed here? isn't blobAccess.headers, already typed to be Record<string, string> in the attachment service?

*
* @example
* ```typescript
* const allJobs = await jobs.getAll({ folderId: <folderId> });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i cant see folderid in options

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

export type JobGetAllOptions = RequestOptions & PaginationOptions & {

* @example
* ```typescript
* // Get output using bound method
* const allJobs = await jobs.getAll({ folderId: <folderId> });
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove fro, here also

Comment on lines +21 to +24
* const completedJob = allJobs.items.find(j => j.state === JobState.Successful);
*
* if (completedJob) {
* const output = await completedJob.getOutput();
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these dont get reflected in doc, instead in job getall method add this comment

@Raina451
Copy link
Copy Markdown
Collaborator

Raina451 commented Apr 1, 2026

i can see that outputarguments are coming as null in job getall call , but the outputarguments do exisit for that job, if you call get job by key.

image

you can reproduce the same with jobkey 4d75a122-ab90-4f6e-b64f-9e6daa610b1d for this job:
https://staging.uipath.com/devcon25/Default/orchestrator_/jobs(sidepanel:sidepanel/jobs/4d75a122-ab90-4f6e-b64f-9e6daa610b1d/details)?tid=639453&fid=2042297&isExpanded=true

can you please check this?

}
});

it('should handle job with or without output', async () => {
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

does this test add any value over the one above?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't there be tests/unit/models/orchestrator/jobs.test.ts to test the bound method as well.

Comment on lines +167 to +172
const rawJob = {
Id: 123,
Key: JOB_TEST_CONSTANTS.JOB_KEY,
State: 'Successful',
CreationTime: '2026-01-01T00:00:00Z',
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const rawJob = createMockRawJob(); ?

Comment on lines +182 to +184
const getOutputOptions = {
jobKey: JOB_TEST_CONSTANTS.JOB_KEY,
};
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Type request objects in tests.

});

// Mock getValidToken on the ApiClient
mockApiClient.getValidToken = vi.fn().mockResolvedValue(TEST_CONSTANTS.DEFAULT_ACCESS_TOKEN);
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this inline and not part of setup?

@vnaren23
Copy link
Copy Markdown
Collaborator

vnaren23 commented Apr 1, 2026

i can see that outputarguments are coming as null in job getall call , but the outputarguments do exisit for that job, if you call get job by key.

image

you can reproduce the same with jobkey 4d75a122-ab90-4f6e-b64f-9e6daa610b1d for this job:
https://staging.uipath.com/devcon25/Default/orchestrator_/jobs(sidepanel:sidepanel/jobs/4d75a122-ab90-4f6e-b64f-9e6daa610b1d/details)?tid=639453&fid=2042297&isExpanded=true

can you please check this?

Output arguments won't be returned in getAll call. That's intentional. Each job can have huge outputs and returning that in a getAll call is not performant/not needed.
We are explicitly adding getOutput as a separate method to address this

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants