Skip to content

Commit 389671b

Browse files
feat(telemetry): add org/tenant id and CloudUserId attributes, bump to 1.0.0-beta.3 (#494)
* fix: rename telemetry constants as per review * test: extract shared JWT test helpers into tests/utils/jwt Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * chore: move SDK-side changes to a separate PR Keep this PR scoped to the @uipath/core-telemetry package (constant renames, CloudUserId support, beta.3 bump). The SDK-side consumption (token-manager wiring, JWT util) moves to a dedicated PR since it requires the published beta.3 package. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> * fix(telemetry): keep name fields alongside new id fields in context Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent b3a00c4 commit 389671b

7 files changed

Lines changed: 100 additions & 10 deletions

File tree

packages/telemetry/package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

packages/telemetry/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@uipath/core-telemetry",
3-
"version": "1.0.0-beta.2",
3+
"version": "1.0.0-beta.3",
44
"description": "Shared typescript telemetry package. Sends Application Insights custom events through OpenTelemetry's batched log pipeline.",
55
"license": "MIT",
66
"type": "module",

packages/telemetry/src/client.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,13 @@ type Logger = ReturnType<LoggerProvider['getLogger']>;
1212
import {
1313
APP_NAME,
1414
CLOUD_CLIENT_ID,
15+
CLOUD_ORGANIZATION_ID,
1516
CLOUD_ORGANIZATION_NAME,
1617
CLOUD_REDIRECT_URI,
18+
CLOUD_TENANT_ID,
1719
CLOUD_TENANT_NAME,
1820
CLOUD_URL,
21+
CLOUD_USER_ID,
1922
CONNECTION_STRING,
2023
SERVICE,
2124
UNKNOWN,
@@ -186,6 +189,7 @@ export class TelemetryClient {
186189
private logProvider?: LoggerProvider;
187190
private logger?: Logger;
188191
private telemetryContext?: TelemetryContext;
192+
private cloudUserId?: string;
189193

190194
public initialize(options: TelemetryClientInitOptions): void {
191195
if (this.isInitialized) {
@@ -239,6 +243,17 @@ export class TelemetryClient {
239243
return this.options?.defaultEventName;
240244
}
241245

246+
/**
247+
* Sets the authenticated user's id, reported as `CloudUserId` on every
248+
* subsequently emitted event. Empty values are ignored and the
249+
* previously set user id, if any, is kept.
250+
*/
251+
public setUserId(userId: string): void {
252+
if (userId) {
253+
this.cloudUserId = userId;
254+
}
255+
}
256+
242257
private setupTelemetryProvider(connectionString: string): void {
243258
// `setupTelemetryProvider` is only called from `initialize` after
244259
// `this.options` has been assigned, so the non-null assertion is safe.
@@ -276,6 +291,9 @@ export class TelemetryClient {
276291
[CLOUD_URL]: this.createCloudUrl(),
277292
[CLOUD_ORGANIZATION_NAME]: this.telemetryContext?.orgName ?? UNKNOWN,
278293
[CLOUD_TENANT_NAME]: this.telemetryContext?.tenantName ?? UNKNOWN,
294+
[CLOUD_ORGANIZATION_ID]: this.telemetryContext?.orgId ?? UNKNOWN,
295+
[CLOUD_TENANT_ID]: this.telemetryContext?.tenantId ?? UNKNOWN,
296+
[CLOUD_USER_ID]: this.cloudUserId ?? UNKNOWN,
279297
[CLOUD_REDIRECT_URI]: this.telemetryContext?.redirectUri ?? UNKNOWN,
280298
[CLOUD_CLIENT_ID]: this.telemetryContext?.clientId ?? UNKNOWN,
281299
...extraAttributes,
@@ -284,13 +302,14 @@ export class TelemetryClient {
284302

285303
private createCloudUrl(): string {
286304
const baseUrl = this.telemetryContext?.baseUrl;
287-
const orgId = this.telemetryContext?.orgName;
288-
const tenantId = this.telemetryContext?.tenantName;
305+
// Prefer the org/tenant ids; fall back to the names when absent.
306+
const org = this.telemetryContext?.orgId || this.telemetryContext?.orgName;
307+
const tenant = this.telemetryContext?.tenantId || this.telemetryContext?.tenantName;
289308

290-
if (!baseUrl || !orgId || !tenantId) {
309+
if (!baseUrl || !org || !tenant) {
291310
return UNKNOWN;
292311
}
293312

294-
return `${baseUrl}/${orgId}/${tenantId}`;
313+
return `${baseUrl}/${org}/${tenant}`;
295314
}
296315
}

packages/telemetry/src/constants.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ export const VERSION = 'Version';
1414
export const SERVICE = 'Service';
1515
export const CLOUD_ORGANIZATION_NAME = 'CloudOrganizationName';
1616
export const CLOUD_TENANT_NAME = 'CloudTenantName';
17+
export const CLOUD_ORGANIZATION_ID = 'CloudOrganizationId';
18+
export const CLOUD_TENANT_ID = 'CloudTenantId';
19+
export const CLOUD_USER_ID = 'CloudUserId';
1720
export const CLOUD_URL = 'CloudUrl';
1821
export const CLOUD_CLIENT_ID = 'CloudClientId';
1922
export const CLOUD_REDIRECT_URI = 'CloudRedirectUri';

packages/telemetry/src/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ export interface TelemetryContext {
1616
baseUrl?: string;
1717
orgName?: string;
1818
tenantName?: string;
19+
orgId?: string;
20+
tenantId?: string;
1921
clientId?: string;
2022
redirectUri?: string;
2123
}

packages/telemetry/tests/client.test.ts

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ const VALID_OPTIONS: TelemetryClientInitOptions = {
3030
const VALID_CONNECTION_STRING =
3131
'InstrumentationKey=abc;IngestionEndpoint=https://example.com';
3232

33+
const TEST_USER_ID = 'user-guid-1234';
34+
3335
/**
3436
* `TelemetryClient.initialize` reads `CONNECTION_STRING` from `./constants`
3537
* at call time. Each test that needs to drive a specific value re-mocks
@@ -166,6 +168,8 @@ describe('TelemetryClient.track', () => {
166168
baseUrl: 'https://example.com',
167169
orgName: 'org',
168170
tenantName: 'tenant',
171+
orgId: 'org-guid',
172+
tenantId: 'tenant-guid',
169173
clientId: 'client-1',
170174
redirectUri: 'https://example.com/cb',
171175
},
@@ -180,16 +184,36 @@ describe('TelemetryClient.track', () => {
180184
ApplicationName: 'TestService',
181185
Version: '1.2.3',
182186
Service: 'Service.Method',
183-
CloudUrl: 'https://example.com/org/tenant',
187+
CloudUrl: 'https://example.com/org-guid/tenant-guid',
184188
CloudOrganizationName: 'org',
185189
CloudTenantName: 'tenant',
190+
CloudOrganizationId: 'org-guid',
191+
CloudTenantId: 'tenant-guid',
186192
CloudClientId: 'client-1',
187193
CloudRedirectUri: 'https://example.com/cb',
188194
custom: 'value',
189195
});
190196
expect(typeof logRecord.timestamp).toBe('number');
191197
});
192198

199+
it('builds CloudUrl from org/tenant names when the ids are absent', async () => {
200+
const client = await clientWithConnectionString(VALID_CONNECTION_STRING);
201+
202+
client.initialize({
203+
...VALID_OPTIONS,
204+
context: {
205+
baseUrl: 'https://example.com',
206+
orgName: 'org',
207+
tenantName: 'tenant',
208+
},
209+
});
210+
211+
client.track('Some.Event');
212+
213+
const [logRecord] = mocks.emit.mock.calls[0];
214+
expect(logRecord.attributes.CloudUrl).toBe('https://example.com/org/tenant');
215+
});
216+
193217
it('falls back to the eventName as the body when no display name is given', async () => {
194218
const client = await clientWithConnectionString(VALID_CONNECTION_STRING);
195219
client.initialize(VALID_OPTIONS);
@@ -210,9 +234,12 @@ describe('TelemetryClient.track', () => {
210234
const [logRecord] = mocks.emit.mock.calls[0];
211235
expect(logRecord.attributes.CloudOrganizationName).toBe('');
212236
expect(logRecord.attributes.CloudTenantName).toBe('');
237+
expect(logRecord.attributes.CloudOrganizationId).toBe('');
238+
expect(logRecord.attributes.CloudTenantId).toBe('');
213239
expect(logRecord.attributes.CloudUrl).toBe('');
214240
expect(logRecord.attributes.CloudClientId).toBe('');
215241
expect(logRecord.attributes.CloudRedirectUri).toBe('');
242+
expect(logRecord.attributes.CloudUserId).toBe('');
216243
});
217244

218245
it('reports the event name as the Service attribute and respects the explicit display name', async () => {
@@ -227,6 +254,45 @@ describe('TelemetryClient.track', () => {
227254
});
228255
});
229256

257+
describe('TelemetryClient.setUserId', () => {
258+
it('populates CloudUserId on subsequent events', async () => {
259+
const client = await clientWithConnectionString(VALID_CONNECTION_STRING);
260+
client.initialize(VALID_OPTIONS);
261+
262+
client.setUserId(TEST_USER_ID);
263+
client.track('Some.Event');
264+
265+
const [logRecord] = mocks.emit.mock.calls[0];
266+
expect(logRecord.attributes.CloudUserId).toBe(TEST_USER_ID);
267+
});
268+
269+
it('reports CloudUserId as UNKNOWN on events emitted before a user id is set', async () => {
270+
const client = await clientWithConnectionString(VALID_CONNECTION_STRING);
271+
client.initialize(VALID_OPTIONS);
272+
273+
client.track('Before.UserId');
274+
client.setUserId(TEST_USER_ID);
275+
client.track('After.UserId');
276+
277+
const [beforeRecord] = mocks.emit.mock.calls[0];
278+
const [afterRecord] = mocks.emit.mock.calls[1];
279+
expect(beforeRecord.attributes.CloudUserId).toBe('');
280+
expect(afterRecord.attributes.CloudUserId).toBe(TEST_USER_ID);
281+
});
282+
283+
it('keeps the previously set user id when called with an empty value', async () => {
284+
const client = await clientWithConnectionString(VALID_CONNECTION_STRING);
285+
client.initialize(VALID_OPTIONS);
286+
287+
client.setUserId(TEST_USER_ID);
288+
client.setUserId('');
289+
client.track('Some.Event');
290+
291+
const [logRecord] = mocks.emit.mock.calls[0];
292+
expect(logRecord.attributes.CloudUserId).toBe(TEST_USER_ID);
293+
});
294+
});
295+
230296
describe('TelemetryClient.getDefaultEventName', () => {
231297
it('returns undefined before initialize', () => {
232298
const client = new TelemetryClient();

tests/integration/shared/action-center/tasks.integration.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ describe.each(modes)('Action Center Tasks - Integration Tests [%s]', (mode) => {
2424
const config = getTestConfig();
2525

2626
const folderId = config.folderId ? Number(config.folderId) : undefined;
27-
const result = await tasks.getAll({ folderId });
27+
const result = await tasks.getAll({ folderId, pageSize: 5 });
2828

2929
expect(result).toBeDefined();
3030
expect(result.items).toBeDefined();
@@ -144,7 +144,7 @@ describe.each(modes)('Action Center Tasks - Integration Tests [%s]', (mode) => {
144144
const { tasks } = getServices();
145145

146146
// Find an existing App task
147-
const allTasks = await tasks.getAll({ filter: "Type eq 'AppTask' and Status eq 'Unassigned' and IsDeleted eq false" });
147+
const allTasks = await tasks.getAll({ filter: "Type eq 'AppTask' and Status eq 'Unassigned' and IsDeleted eq false", pageSize: 5 });
148148

149149
if (allTasks.items.length === 0) {
150150
throw new Error('No App task available in the test environment');

0 commit comments

Comments
 (0)