-
Notifications
You must be signed in to change notification settings - Fork 12
feat(identity): onboard Users service with getById #616
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
0e6cf02
53b1bf8
b17f2fb
fbcdd80
8b57cf5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| /** | ||
| * Identity models barrel export. | ||
| */ | ||
|
|
||
| export * from './users.types'; | ||
| export * from './users.models'; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import { UserType, UserCategory } from './users.types'; | ||
|
|
||
| /** | ||
| * User field mappings (API field name → SDK field name). | ||
| * | ||
| * Semantic renames only — the API already returns camelCase, so no case | ||
| * conversion is involved. | ||
| */ | ||
| export const UserMap: { [key: string]: string } = { | ||
| creationTime: 'createdTime', | ||
| lastModificationTime: 'lastModifiedTime', | ||
| groupIDs: 'groupIds', | ||
| }; | ||
|
|
||
| /** | ||
| * Maps numeric user type codes (from API) to {@link UserType} enum values. | ||
| */ | ||
| export const UserTypeMap: { [key: number]: UserType } = { | ||
| 0: UserType.User, | ||
| 1: UserType.Robot, | ||
| 2: UserType.DirectoryUser, | ||
| 3: UserType.DirectoryGroup, | ||
| 4: UserType.RobotAccount, | ||
| 5: UserType.Application, | ||
| }; | ||
|
|
||
| /** | ||
| * Maps numeric user category codes (from API) to {@link UserCategory} enum values. | ||
| */ | ||
| export const UserCategoryMap: { [key: number]: UserCategory } = { | ||
| 0: UserCategory.Local, | ||
| 1: UserCategory.LinkedLocal, | ||
| 2: UserCategory.Directory, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| /** | ||
| * Internal-only types for the Identity Users service. | ||
| * | ||
| * NOT exported from the public barrel (`src/models/identity/index.ts`). | ||
| */ | ||
|
|
||
| /** | ||
| * Raw user shape as returned by the Identity user management API. | ||
| * | ||
| * Mirrors the API contract exactly: it uses the API's field names | ||
| * (`creationTime`, `groupIDs`, …) and numeric `type`/`category` codes — the | ||
| * Swagger spec declares string enums for these, but the live API returns | ||
| * numbers. The SDK renames/maps them before returning to consumers. | ||
| */ | ||
| export interface RawUserEntry { | ||
| id: string; | ||
| userName: string; | ||
| email: string; | ||
| emailConfirmed: boolean; | ||
| name: string | null; | ||
| surname: string | null; | ||
| displayName: string | null; | ||
| /** Renamed to `createdTime` in the SDK response. */ | ||
| creationTime: string; | ||
| /** Renamed to `lastModifiedTime` in the SDK response. */ | ||
| lastModificationTime: string | null; | ||
| lastLoginTime: string | null; | ||
| /** Renamed to `groupIds` in the SDK response. */ | ||
| groupIDs: string[]; | ||
| isActive: boolean; | ||
| bypassBasicAuthRestriction: boolean; | ||
| /** Numeric code mapped to the {@link UserType} enum in the SDK response. */ | ||
| type: number; | ||
| /** Numeric code mapped to the {@link UserCategory} enum in the SDK response. */ | ||
| category: number; | ||
| invitationAccepted: boolean; | ||
| // Internal field the SDK strips before returning to consumers: | ||
| /** Internal platform synchronization id — dropped from the SDK response. */ | ||
| legacyId?: number; | ||
| } | ||
|
|
||
| /** | ||
| * Fields stripped from each {@link RawUserEntry} before it is returned to the | ||
| * SDK consumer. | ||
| */ | ||
| export const INTERNAL_USER_FIELDS = [ | ||
| 'legacyId', | ||
| ] as const satisfies ReadonlyArray<keyof RawUserEntry>; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| /** | ||
| * Identity Users service model — public response shapes and the ServiceModel | ||
| * interface that drives generated API documentation. | ||
| */ | ||
|
|
||
| import type { RawUserGetResponse } from './users.types'; | ||
|
|
||
| /** | ||
| * User returned by the Users service. | ||
| */ | ||
| export interface UserGetResponse extends RawUserGetResponse {} | ||
|
|
||
| /** | ||
| * Service model for managing users in a UiPath organization. | ||
| * | ||
| * Provides organization-level user administration. | ||
| * | ||
| * ### Usage | ||
| * | ||
| * Prerequisites: Initialize the SDK first - see [Getting Started](/uipath-typescript/getting-started/#import-initialize) | ||
| * | ||
| * ```typescript | ||
| * import { Users } from '@uipath/uipath-typescript/users'; | ||
| * | ||
| * const users = new Users(sdk); | ||
| * const user = await users.getById('<userId>'); | ||
| * ``` | ||
| */ | ||
| export interface UserServiceModel { | ||
| /** | ||
| * Gets a user by ID. | ||
| * | ||
| * Returns the full user details including profile fields, group membership, | ||
| * activity flags and invitation state. | ||
| * | ||
| * @param userId - User GUID | ||
| * @returns Promise resolving to a {@link UserGetResponse} with the user's profile fields, group membership, activity flags and invitation state | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const user = await users.getById('<userId>'); | ||
| * console.log(`${user.displayName} (${user.email}) — active: ${user.isActive}`); | ||
| * ``` | ||
| */ | ||
| getById(userId: string): Promise<UserGetResponse>; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| /** | ||
| * Identity user management types — response shapes and enums. | ||
| */ | ||
|
|
||
| /** | ||
| * Defines how a user was created and how it is supposed to be used. | ||
| */ | ||
| export enum UserType { | ||
| /** Standard interactive user. */ | ||
| User = 'user', | ||
| /** Robot user driving unattended automation. */ | ||
| Robot = 'robot', | ||
| /** User provisioned from an external directory (e.g. Azure AD). */ | ||
| DirectoryUser = 'directoryUser', | ||
| /** Group provisioned from an external directory. */ | ||
| DirectoryGroup = 'directoryGroup', | ||
| /** Robot account (non-interactive machine identity). */ | ||
| RobotAccount = 'robotAccount', | ||
| /** External application identity. */ | ||
| Application = 'application', | ||
| } | ||
|
|
||
| /** | ||
| * Discriminates a user by how they relate to directory provisioning. | ||
| */ | ||
| export enum UserCategory { | ||
| /** Local account managed in UiPath. */ | ||
| Local = 'local', | ||
| /** Local account linked to a directory identity. */ | ||
| LinkedLocal = 'linkedLocal', | ||
| /** Account provisioned from an external directory. */ | ||
| Directory = 'directory', | ||
| } | ||
|
|
||
| /** | ||
| * User as returned by the Identity user management API. | ||
| * | ||
| * Field selection: `legacyId` (an internal platform synchronization field) is returned | ||
| * by the API but dropped from the SDK because it has no use for an application developer. | ||
| */ | ||
| export interface RawUserGetResponse { | ||
| /** User GUID. */ | ||
| id: string; | ||
| /** The username. */ | ||
| userName: string; | ||
| /** Email address. Empty string when the user has no email. */ | ||
| email: string; | ||
| /** Whether the email address has been confirmed. */ | ||
| emailConfirmed: boolean; | ||
| /** First name. */ | ||
| name: string | null; | ||
| /** Last name. */ | ||
| surname: string | null; | ||
| /** Display name. */ | ||
| displayName: string | null; | ||
| /** When the user was created. */ | ||
| createdTime: string; | ||
| /** When the user was last modified. */ | ||
| lastModifiedTime: string | null; | ||
| /** When the user last logged in. `null` if the user has never logged in. */ | ||
| lastLoginTime: string | null; | ||
| /** GUIDs of the groups the user is a member of. */ | ||
| groupIds: string[]; | ||
| /** Whether the user is active. */ | ||
| isActive: boolean; | ||
| /** Whether this user bypasses the basic authentication restriction. */ | ||
| bypassBasicAuthRestriction: boolean; | ||
| /** How the user was created and is supposed to be used. */ | ||
| type: UserType; | ||
| /** How the user relates to directory provisioning. */ | ||
| category: UserCategory; | ||
| /** Whether the user has accepted their invitation. */ | ||
| invitationAccepted: boolean; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| /** | ||
| * Identity Users Service Module | ||
| * | ||
| * Provides organization-level user administration via the UiPath Identity API: | ||
| * - `Users` — retrieve users in the organization | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * import { UiPath } from '@uipath/uipath-typescript/core'; | ||
| * import { Users } from '@uipath/uipath-typescript/users'; | ||
| * | ||
| * const sdk = new UiPath(config); | ||
| * await sdk.initialize(); | ||
| * | ||
| * const users = new Users(sdk); | ||
| * const user = await users.getById('<userId>'); | ||
| * ``` | ||
| * | ||
| * @module | ||
| */ | ||
|
|
||
| export { UserService as Users } from './users'; | ||
|
|
||
| // Models (types, enums, response shapes) | ||
| export * from '../../models/identity'; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,49 @@ | ||||||||||||||||||
| /** | ||||||||||||||||||
| * UserService — organization-level user administration via the Identity API. | ||||||||||||||||||
| */ | ||||||||||||||||||
|
|
||||||||||||||||||
| import { track } from '../../core/telemetry'; | ||||||||||||||||||
| import { BaseService } from '../base'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import type { RawUserGetResponse } from '../../models/identity/users.types'; | ||||||||||||||||||
| import type { UserGetResponse, UserServiceModel } from '../../models/identity/users.models'; | ||||||||||||||||||
| import { | ||||||||||||||||||
| INTERNAL_USER_FIELDS, | ||||||||||||||||||
| type RawUserEntry, | ||||||||||||||||||
| } from '../../models/identity/users.internal-types'; | ||||||||||||||||||
| import { UserCategoryMap, UserMap, UserTypeMap } from '../../models/identity/users.constants'; | ||||||||||||||||||
|
|
||||||||||||||||||
| import { IDENTITY_USER_ENDPOINTS } from '../../utils/constants/endpoints'; | ||||||||||||||||||
| import { applyDataTransforms, transformData } from '../../utils/transform'; | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Service for managing users in a UiPath organization. | ||||||||||||||||||
| * | ||||||||||||||||||
| * All operations route at the **organization** level — no tenant or folder | ||||||||||||||||||
| * context is involved. | ||||||||||||||||||
| */ | ||||||||||||||||||
| export class UserService extends BaseService implements UserServiceModel { | ||||||||||||||||||
| @track('Users.GetById') | ||||||||||||||||||
| async getById(userId: string): Promise<UserGetResponse> { | ||||||||||||||||||
| const response = await this.get<RawUserEntry>(IDENTITY_USER_ENDPOINTS.BY_ID(userId)); | ||||||||||||||||||
| return this.transformUser(response.data); | ||||||||||||||||||
| } | ||||||||||||||||||
|
|
||||||||||||||||||
| /** | ||||||||||||||||||
| * Strips internal fields from a raw user entry and applies the SDK field | ||||||||||||||||||
| * renames and numeric → enum value mappings before returning it to the consumer. | ||||||||||||||||||
| */ | ||||||||||||||||||
| private transformUser(user: RawUserEntry): RawUserGetResponse { | ||||||||||||||||||
| const stripped: RawUserEntry = { ...user }; | ||||||||||||||||||
| for (const field of INTERNAL_USER_FIELDS) { | ||||||||||||||||||
| delete stripped[field]; | ||||||||||||||||||
| } | ||||||||||||||||||
| const renamed = transformData(stripped, UserMap) as unknown as RawUserGetResponse; | ||||||||||||||||||
| return applyDataTransforms(renamed, { | ||||||||||||||||||
| field: 'type', | ||||||||||||||||||
| valueMap: UserTypeMap, | ||||||||||||||||||
| transform: (data) => | ||||||||||||||||||
| applyDataTransforms(data, { field: 'category', valueMap: UserCategoryMap }), | ||||||||||||||||||
| }); | ||||||||||||||||||
|
Comment on lines
+42
to
+47
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Non-standard use of the The conventional pattern throughout the SDK is two sequential calls:
Suggested change
|
||||||||||||||||||
| } | ||||||||||||||||||
| } | ||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The JSDoc says "User as returned by the Identity user management API" but
RawUserGetResponseis the SDK-facing shape (after field renames and numeric→enum mapping), not the raw API wire format. The actual API response shape isRawUserEntryinusers.internal-types.ts— which hascreationTime,groupIDs, and numerictype/categorycodes.Per CLAUDE.md:
Raw{Entity}GetResponsetypes describe the shape before method attachment, not the literal API wire format. A contributor implementing a future method (e.g.getAll) who reads this comment could incorrectly assume the API returnscreatedTime/UserTypevalues and skip the transform step.