Skip to content

Commit e0d05e3

Browse files
Sarath1018claude
andauthored
feat(feedback): add Feedback.getById method (#405)
* feat(feedback): add Feedback.getById method Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * docs(feedback): improve getById JSDoc examples and param description Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: pass folderkey as header * fix: set folderkey as options * fix: integration test * fix: add validation for folderid --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3cf5b25 commit e0d05e3

9 files changed

Lines changed: 170 additions & 2 deletions

File tree

docs/oauth-scopes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ The `ConversationalAgents` scope is required for real-time WebSocket sessions (`
153153
| Method | OAuth Scope |
154154
|--------|-------------|
155155
| `getAll()` | `Traces.Api` |
156+
| `getById()` | `Traces.Api` |
156157

157158
## Processes
158159

src/models/agents/feedback/feedback.internal-types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ export interface RawFeedbackGetResponse {
1414
metadata?: string;
1515
isPositive: boolean;
1616
feedbackCategories: FeedbackCategory[];
17+
folderKey?: string;
1718
userEmail?: string;
1819
status: FeedbackStatus;
1920
createdAt: string;

src/models/agents/feedback/feedback.models.ts

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type {
22
FeedbackGetResponse,
33
FeedbackGetAllOptions,
4+
FeedbackOptions,
45
} from './feedback.types';
56
import type { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '../../../utils/pagination';
67

@@ -66,4 +67,26 @@ export interface FeedbackServiceModel {
6667
? PaginatedResponse<FeedbackGetResponse>
6768
: NonPaginatedResponse<FeedbackGetResponse>
6869
>;
70+
71+
/**
72+
* Gets a single feedback entry by its feedback ID.
73+
*
74+
* @param id - Feedback ID (GUID) of the feedback entry
75+
* @param options - Required options including folderKey for folder-level authorization {@link FeedbackOptions}
76+
* @returns Promise resolving to {@link FeedbackGetResponse}
77+
* @example
78+
* ```typescript
79+
* import { Feedback } from '@uipath/uipath-typescript/feedback';
80+
*
81+
* const feedback = new Feedback(sdk);
82+
*
83+
* // First, get feedback entries to obtain the ID and folder key
84+
* const allFeedback = await feedback.getAll({ pageSize: 10 });
85+
* const feedbackId = allFeedback.items[0].id;
86+
* const folderKey = allFeedback.items[0].folderKey;
87+
* const item = await feedback.getById(feedbackId, { folderKey });
88+
* console.log(item.isPositive, item.comment, item.status);
89+
* ```
90+
*/
91+
getById(id: string, options: FeedbackOptions): Promise<FeedbackGetResponse>;
6992
}

src/models/agents/feedback/feedback.types.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ export interface FeedbackGetResponse {
5353
isPositive: boolean;
5454
/** Categories associated with this feedback entry */
5555
feedbackCategories: FeedbackCategory[];
56+
/** Folder key (GUID) of the folder the feedback belongs to */
57+
folderKey?: string;
5658
/** Email address of the user who submitted the feedback */
5759
userEmail?: string;
5860
/** Current status of the feedback in the review workflow */
@@ -63,6 +65,14 @@ export interface FeedbackGetResponse {
6365
updatedTime: string;
6466
}
6567

68+
/**
69+
* Options shared across feedback operations
70+
*/
71+
export interface FeedbackOptions {
72+
/** Folder key for authorization */
73+
folderKey: string;
74+
}
75+
6676
/**
6777
* Options for retrieving multiple feedback entries
6878
*/

src/services/agents/feedback/feedback.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { BaseService } from '../../base';
22
import {
33
FeedbackGetResponse,
44
FeedbackGetAllOptions,
5+
FeedbackOptions,
56
} from '../../../models/agents/feedback/feedback.types';
67
import { FeedbackServiceModel } from '../../../models/agents/feedback/feedback.models';
78
import { FeedbackMap } from '../../../models/agents/feedback/feedback.constants';
@@ -13,6 +14,9 @@ import { PaginatedResponse, NonPaginatedResponse, HasPaginationOptions } from '.
1314
import { PaginationHelpers } from '../../../utils/pagination/helpers';
1415
import { PaginationType } from '../../../utils/pagination/internal-types';
1516
import { track } from '../../../core/telemetry';
17+
import { ValidationError } from '../../../core/errors';
18+
import { createHeaders } from '../../../utils/http/headers';
19+
import { FOLDER_KEY } from '../../../utils/constants/headers';
1620

1721
/**
1822
* Service for interacting with UiPath Agent Feedback API
@@ -81,4 +85,36 @@ export class FeedbackService extends BaseService implements FeedbackServiceModel
8185
excludeFromPrefix: Object.keys(options || {}),
8286
}, options);
8387
}
88+
89+
/**
90+
* Gets a single feedback entry by its feedback ID.
91+
*
92+
* @param id - Feedback ID (GUID) of the feedback entry
93+
* @param options - Required options including folderKey for folder-level authorization {@link FeedbackOptions}
94+
* @returns Promise resolving to {@link FeedbackGetResponse}
95+
* @example
96+
* ```typescript
97+
* import { Feedback } from '@uipath/uipath-typescript/feedback';
98+
*
99+
* const feedback = new Feedback(sdk);
100+
*
101+
* // First, get feedback entries to obtain the ID and folder key
102+
* const allFeedback = await feedback.getAll({ pageSize: 10 });
103+
* const feedbackId = allFeedback.items[0].id;
104+
* const folderKey = allFeedback.items[0].folderKey;
105+
* const item = await feedback.getById(feedbackId, { folderKey });
106+
* console.log(item.isPositive, item.comment, item.status);
107+
* ```
108+
*/
109+
@track('Feedback.GetById')
110+
async getById(id: string, options: FeedbackOptions): Promise<FeedbackGetResponse> {
111+
if (!id) throw new ValidationError({ message: 'Feedback ID is required for getById' });
112+
if (!options?.folderKey) throw new ValidationError({ message: 'folderKey is required for getById' });
113+
114+
const response = await this.get<RawFeedbackGetResponse>(
115+
FEEDBACK_ENDPOINTS.GET_BY_ID(id),
116+
{ headers: createHeaders({ [FOLDER_KEY]: options?.folderKey }) }
117+
);
118+
return transformData(response.data, FeedbackMap) as unknown as FeedbackGetResponse;
119+
}
84120
}

src/utils/constants/endpoints/feedback.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@ import { LLMOPS_BASE } from './base';
55
*/
66
export const FEEDBACK_ENDPOINTS = {
77
GET_ALL: `${LLMOPS_BASE}/api/Feedback`,
8+
GET_BY_ID: (id: string) => `${LLMOPS_BASE}/api/Feedback/${id}`,
89
} as const;

tests/integration/shared/agents/feedback.integration.test.ts

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
import { describe, it, expect, beforeEach } from 'vitest';
1+
import { describe, it, expect, beforeEach, beforeAll } from 'vitest';
22
import { getServices, setupUnifiedTests, InitMode } from '../../config/unified-setup';
33
import { Feedback } from '../../../../src/services/agents/feedback';
4-
import { FeedbackStatus } from '../../../../src/models/agents/feedback/feedback.types';
4+
import { FeedbackStatus, FeedbackGetResponse } from '../../../../src/models/agents/feedback/feedback.types';
55

66
const modes: InitMode[] = ['v1'];
77

@@ -61,4 +61,51 @@ describe.each(modes)('Agent Feedback - Integration Tests [%s]', (mode) => {
6161
expect(item.updatedTime).toBeDefined();
6262
});
6363
});
64+
65+
describe('getById', () => {
66+
let existingFeedbackId!: string;
67+
let existingFolderKey!: string;
68+
69+
beforeAll(async () => {
70+
feedback = getServices().feedback!;
71+
const result = await feedback.getAll({ pageSize: 1 });
72+
if (result.items.length === 0) {
73+
throw new Error('No feedback available for getById tests — create at least one feedback entry first');
74+
}
75+
existingFeedbackId = result.items[0].id;
76+
if (!result.items[0].folderKey) {
77+
throw new Error('Feedback entry missing folderKey — cannot run getById tests');
78+
}
79+
existingFolderKey = result.items[0].folderKey;
80+
});
81+
82+
it('should retrieve feedback by ID', async () => {
83+
const result = await feedback.getById(existingFeedbackId, { folderKey: existingFolderKey });
84+
85+
expect(result).toBeDefined();
86+
expect(result.id).toBe(existingFeedbackId);
87+
});
88+
89+
it('should have expected fields on the retrieved feedback', async () => {
90+
const result: FeedbackGetResponse = await feedback.getById(existingFeedbackId, { folderKey: existingFolderKey });
91+
92+
expect(result.id).toBeDefined();
93+
expect(result.traceId).toBeDefined();
94+
expect(result.spanId).toBeDefined();
95+
expect(typeof result.isPositive).toBe('boolean');
96+
expect(Array.isArray(result.feedbackCategories)).toBe(true);
97+
expect(result.status).toBeDefined();
98+
expect(result.createdTime).toBeDefined();
99+
expect(result.updatedTime).toBeDefined();
100+
});
101+
102+
it('should transform API fields — camelCase fields present, raw fields absent', async () => {
103+
const result = await feedback.getById(existingFeedbackId, { folderKey: existingFolderKey });
104+
105+
expect(result.createdTime).toBeDefined();
106+
expect(result.updatedTime).toBeDefined();
107+
expect((result as any).createdAt).toBeUndefined();
108+
expect((result as any).updatedAt).toBeUndefined();
109+
});
110+
});
64111
});

tests/unit/services/agents/feedback.test.ts

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,50 @@ describe('FeedbackService Unit Tests', () => {
9292
);
9393
});
9494
});
95+
96+
describe('getById', () => {
97+
it('should get feedback by ID successfully', async () => {
98+
const mockResponse = createMockFeedback();
99+
mockApiClient.get.mockResolvedValue(mockResponse);
100+
101+
const result = await feedbackService.getById(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID, { folderKey: FEEDBACK_TEST_CONSTANTS.FOLDER_KEY });
102+
103+
expect(result).toBeDefined();
104+
expect(result.id).toBe(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID);
105+
expect(mockApiClient.get).toHaveBeenCalledWith(
106+
FEEDBACK_ENDPOINTS.GET_BY_ID(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID),
107+
expect.any(Object)
108+
);
109+
});
110+
111+
it('should transform createdAt and updatedAt to createdTime and updatedTime', async () => {
112+
mockApiClient.get.mockResolvedValue(createMockFeedback());
113+
114+
const result = await feedbackService.getById(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID, { folderKey: FEEDBACK_TEST_CONSTANTS.FOLDER_KEY });
115+
116+
expect(result.createdTime).toBe(CONVERSATIONAL_AGENT_TEST_CONSTANTS.CREATED_AT);
117+
expect(result.updatedTime).toBe(CONVERSATIONAL_AGENT_TEST_CONSTANTS.UPDATED_AT);
118+
expect((result as any).createdAt).toBeUndefined();
119+
expect((result as any).updatedAt).toBeUndefined();
120+
});
121+
122+
it('should throw ValidationError when ID is empty', async () => {
123+
await expect(feedbackService.getById('', { folderKey: FEEDBACK_TEST_CONSTANTS.FOLDER_KEY })).rejects.toThrow('Feedback ID is required');
124+
expect(mockApiClient.get).not.toHaveBeenCalled();
125+
});
126+
127+
it('should throw ValidationError when folderKey is empty', async () => {
128+
await expect(feedbackService.getById(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID, { folderKey: '' })).rejects.toThrow('folderKey is required');
129+
expect(mockApiClient.get).not.toHaveBeenCalled();
130+
});
131+
132+
it('should throw error when feedback not found', async () => {
133+
const error = new Error(FEEDBACK_TEST_CONSTANTS.ERROR_FEEDBACK_NOT_FOUND);
134+
mockApiClient.get.mockRejectedValue(error);
135+
136+
await expect(feedbackService.getById(FEEDBACK_TEST_CONSTANTS.FEEDBACK_ID, { folderKey: FEEDBACK_TEST_CONSTANTS.FOLDER_KEY })).rejects.toThrow(
137+
FEEDBACK_TEST_CONSTANTS.ERROR_FEEDBACK_NOT_FOUND
138+
);
139+
});
140+
});
95141
});

tests/utils/constants/feedback.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export const FEEDBACK_TEST_CONSTANTS = {
77
FEEDBACK_ID: 'feedback-1',
88
FEEDBACK_ID_2: 'feedback-2',
99

10+
// Folder key for authorization
11+
FOLDER_KEY: '00000000-0000-0000-0000-000000000001',
12+
1013
// Agent identifiers (string UUID for feedback API)
1114
AGENT_UUID: 'agent-789',
1215

0 commit comments

Comments
 (0)