From d770d20328341f155a221023869096b95dfe5273 Mon Sep 17 00:00:00 2001 From: Jim Kalafut Date: Thu, 3 Sep 2026 10:21:09 -0700 Subject: [PATCH 1/9] feat(ui,clerk-js,shared): add a role mapping step to the Directory Sync wizard Organization admins setting up Directory Sync in can now map directory groups from their identity provider to organization roles. A new "Roles" step follows "Test" in the ConfigureDirectorySync wizard. The Test step's Complete button is now Continue, and the wizard finishes on the Roles step. On the Roles step admins can: - Assign an organization role to each group the IdP has pushed. A group set to "Unassigned" has no mapping. - Order the mappings by dragging, or with the arrow keys on the drag handle. A member in several mapped groups gets the role of the highest-priority group. - The "Everyone else" row, which shows the default role given to members in no mapped group. It is read-only. - Turn role sync on or off. Both changes ask for confirmation first: turning it on overwrites existing member roles, including ones assigned manually. Turning it off keeps current roles and the saved mappings. Edits stay local until the step is saved. Mappings are sent only if they changed, and the enabled flag is updated separately. --- .changeset/directory-sync-role-mapping.md | 8 + .../src/core/resources/DirectorySync.ts | 77 +++ .../resources/__tests__/DirectorySync.test.ts | 114 +++- packages/localizations/src/ar-SA.ts | 32 ++ packages/localizations/src/be-BY.ts | 32 ++ packages/localizations/src/bg-BG.ts | 32 ++ packages/localizations/src/bn-IN.ts | 32 ++ packages/localizations/src/ca-ES.ts | 32 ++ packages/localizations/src/cs-CZ.ts | 32 ++ packages/localizations/src/da-DK.ts | 32 ++ packages/localizations/src/de-DE.ts | 32 ++ packages/localizations/src/el-GR.ts | 32 ++ packages/localizations/src/en-GB.ts | 32 ++ packages/localizations/src/en-US.ts | 35 ++ packages/localizations/src/es-CR.ts | 32 ++ packages/localizations/src/es-ES.ts | 32 ++ packages/localizations/src/es-MX.ts | 32 ++ packages/localizations/src/es-UY.ts | 32 ++ packages/localizations/src/fa-IR.ts | 32 ++ packages/localizations/src/fi-FI.ts | 32 ++ packages/localizations/src/fr-FR.ts | 32 ++ packages/localizations/src/he-IL.ts | 32 ++ packages/localizations/src/hi-IN.ts | 32 ++ packages/localizations/src/hr-HR.ts | 32 ++ packages/localizations/src/hu-HU.ts | 32 ++ packages/localizations/src/id-ID.ts | 32 ++ packages/localizations/src/is-IS.ts | 32 ++ packages/localizations/src/it-IT.ts | 32 ++ packages/localizations/src/ja-JP.ts | 32 ++ packages/localizations/src/kk-KZ.ts | 32 ++ packages/localizations/src/ko-KR.ts | 32 ++ packages/localizations/src/mn-MN.ts | 32 ++ packages/localizations/src/ms-MY.ts | 32 ++ packages/localizations/src/nb-NO.ts | 32 ++ packages/localizations/src/nl-BE.ts | 32 ++ packages/localizations/src/nl-NL.ts | 32 ++ packages/localizations/src/pl-PL.ts | 32 ++ packages/localizations/src/pt-BR.ts | 32 ++ packages/localizations/src/pt-PT.ts | 32 ++ packages/localizations/src/ro-RO.ts | 32 ++ packages/localizations/src/ru-RU.ts | 32 ++ packages/localizations/src/sk-SK.ts | 32 ++ packages/localizations/src/sr-RS.ts | 32 ++ packages/localizations/src/sv-SE.ts | 32 ++ packages/localizations/src/ta-IN.ts | 32 ++ packages/localizations/src/te-IN.ts | 32 ++ packages/localizations/src/th-TH.ts | 32 ++ packages/localizations/src/tr-TR.ts | 32 ++ packages/localizations/src/uk-UA.ts | 32 ++ packages/localizations/src/vi-VN.ts | 32 ++ packages/localizations/src/zh-CN.ts | 32 ++ packages/localizations/src/zh-TW.ts | 32 ++ packages/shared/src/react/hooks/index.ts | 6 + .../useOrganizationDirectorySync.shared.ts | 25 + ...nizationDirectorySyncGroupRoleMappings.tsx | 134 +++++ packages/shared/src/react/stable-keys.ts | 2 + packages/shared/src/types/directorySync.ts | 96 +++- packages/shared/src/types/localization.ts | 32 ++ .../ConfigureDirectorySync.tsx | 2 +- .../ConfigureDirectorySyncContext.tsx | 130 +++++ .../ConfigureDirectorySyncWizard.tsx | 20 +- .../ConfigureDirectorySync/RoleSyncDialog.tsx | 85 +++ .../ConfigureDirectorySyncWizard.test.tsx | 2 + .../__tests__/RoleMappingStep.test.tsx | 293 ++++++++++ .../ConfigureDirectorySync/roleMapping.ts | 29 + .../steps/RoleMappingStep.tsx | 527 ++++++++++++++++++ .../steps/TestSyncStep.tsx | 24 +- .../ConfigureSSO/RemoveDomainDialog.tsx | 1 + .../OrganizationProfile/MemberListTable.tsx | 20 +- .../OrganizationSecurityPage.tsx | 1 + .../src/customizables/elementDescriptors.ts | 11 + packages/ui/src/elements/Switch.tsx | 10 +- packages/ui/src/hooks/useFetchRoles.ts | 2 +- packages/ui/src/icons/drag.svg | 8 + packages/ui/src/icons/globe.svg | 3 + packages/ui/src/icons/index.ts | 2 + packages/ui/src/internal/appearance.ts | 11 + 77 files changed, 3215 insertions(+), 31 deletions(-) create mode 100644 .changeset/directory-sync-role-mapping.md create mode 100644 packages/shared/src/react/hooks/useOrganizationDirectorySyncGroupRoleMappings.tsx create mode 100644 packages/ui/src/components/ConfigureDirectorySync/RoleSyncDialog.tsx create mode 100644 packages/ui/src/components/ConfigureDirectorySync/__tests__/RoleMappingStep.test.tsx create mode 100644 packages/ui/src/components/ConfigureDirectorySync/roleMapping.ts create mode 100644 packages/ui/src/components/ConfigureDirectorySync/steps/RoleMappingStep.tsx create mode 100644 packages/ui/src/icons/drag.svg create mode 100644 packages/ui/src/icons/globe.svg diff --git a/.changeset/directory-sync-role-mapping.md b/.changeset/directory-sync-role-mapping.md new file mode 100644 index 00000000000..9ba10948b10 --- /dev/null +++ b/.changeset/directory-sync-role-mapping.md @@ -0,0 +1,8 @@ +--- +'@clerk/clerk-js': minor +'@clerk/localizations': minor +'@clerk/shared': minor +'@clerk/ui': minor +--- + +Add a role mapping step to the self-serve Directory Sync setup in ``. Organization admins can now assign an organization role to each directory group their identity provider pushes, set which group wins when a member belongs to several, and turn role syncing on or off. Members in no mapped group keep the organization's default role. diff --git a/packages/clerk-js/src/core/resources/DirectorySync.ts b/packages/clerk-js/src/core/resources/DirectorySync.ts index 597a0c885c7..09235670cf2 100644 --- a/packages/clerk-js/src/core/resources/DirectorySync.ts +++ b/packages/clerk-js/src/core/resources/DirectorySync.ts @@ -2,6 +2,11 @@ import type { ClerkPaginatedResponse, DeletedObjectJSON, DeletedObjectResource, + DirectorySyncGroupRoleMappingJSON, + DirectorySyncGroupRoleMappingsJSON, + DirectorySyncGroupRoleMappingsResource, + DirectorySyncGroupsPage, + DirectorySyncGroupsPageJSON, DirectorySyncJSON, DirectorySyncJSONSnapshot, DirectorySyncProvider, @@ -10,7 +15,9 @@ import type { DirectorySyncStatusResource, DirectorySyncUserJSON, DirectorySyncUserResource, + GetDirectorySyncGroupsParams, GetDirectorySyncUsersParams, + ReplaceDirectorySyncGroupRoleMappingsParams, SetDirectorySyncCredentialsParams, UpdateDirectorySyncParams, } from '@clerk/shared/types'; @@ -19,6 +26,7 @@ import { convertPageToOffsetSearchParams } from '../../utils/convertPageToOffset import { unixEpochToDate } from '../../utils/date'; import { BaseResource } from './Base'; import { DeletedObject } from './DeletedObject'; +import { Role } from './Role'; export class DirectorySync extends BaseResource implements DirectorySyncResource { id!: string; @@ -50,6 +58,9 @@ export class DirectorySync extends BaseResource implements DirectorySyncResource if (params.enabled !== undefined) { body.enabled = params.enabled; } + if (params.groupRoleMappingEnabled !== undefined) { + body.group_role_mapping_enabled = params.groupRoleMappingEnabled; + } if (params.attributeMapping !== undefined) { body.attribute_mapping = JSON.stringify(params.attributeMapping); } @@ -125,6 +136,57 @@ export class DirectorySync extends BaseResource implements DirectorySyncResource }; }; + getGroups = async (params?: GetDirectorySyncGroupsParams): Promise => { + const search: Record = {}; + if (params?.limit !== undefined) { + search.limit = String(params.limit); + } + if (params?.startingAfter) { + search.starting_after = params.startingAfter; + } + + const res = await BaseResource._fetch({ + path: `${this.directoryPath}/groups`, + method: 'GET', + search, + }); + + const payload = res?.response as unknown as DirectorySyncGroupsPageJSON | undefined; + + return { + data: (payload?.data ?? []).map(group => ({ + id: group.id, + displayName: group.display_name, + updatedAt: group.updated_at ? unixEpochToDate(group.updated_at) : null, + })), + startingAfter: payload?.cursor?.starting_after ?? null, + hasNextPage: payload?.cursor?.has_next_page ?? false, + }; + }; + + getGroupRoleMappings = async (): Promise => { + const res = await BaseResource._fetch({ + path: `${this.directoryPath}/group_role_mappings`, + method: 'GET', + }); + + return toGroupRoleMappings(res?.response as unknown as DirectorySyncGroupRoleMappingsJSON | undefined); + }; + + replaceGroupRoleMappings = async ( + params: ReplaceDirectorySyncGroupRoleMappingsParams, + ): Promise => { + const res = await BaseResource._fetch({ + path: `${this.directoryPath}/group_role_mappings`, + method: 'PUT', + body: { + mappings: JSON.stringify(params.mappings.map(m => ({ directory_group_id: m.directoryGroupId, role: m.role }))), + } as any, + }); + + return toGroupRoleMappings(res?.response as unknown as DirectorySyncGroupRoleMappingsJSON | undefined); + }; + getUsers = async ( params?: GetDirectorySyncUsersParams, ): Promise> => { @@ -183,6 +245,21 @@ export class DirectorySync extends BaseResource implements DirectorySyncResource } } +const toGroupRoleMapping = (json: DirectorySyncGroupRoleMappingJSON) => ({ + id: json.id, + directoryGroupId: json.directory_group_id, + directoryGroupDisplayName: json.directory_group_display_name, + role: json.role ? new Role(json.role) : null, + precedence: json.precedence, +}); + +const toGroupRoleMappings = ( + json: DirectorySyncGroupRoleMappingsJSON | undefined, +): DirectorySyncGroupRoleMappingsResource => ({ + data: (json?.data ?? []).map(toGroupRoleMapping), + defaultRole: json?.default_role ? new Role(json.default_role) : null, +}); + export class DirectorySyncUser extends BaseResource implements DirectorySyncUserResource { id!: string; userId!: string; diff --git a/packages/clerk-js/src/core/resources/__tests__/DirectorySync.test.ts b/packages/clerk-js/src/core/resources/__tests__/DirectorySync.test.ts index 81b451f1514..dd4f3f42f13 100644 --- a/packages/clerk-js/src/core/resources/__tests__/DirectorySync.test.ts +++ b/packages/clerk-js/src/core/resources/__tests__/DirectorySync.test.ts @@ -1,4 +1,4 @@ -import type { DirectorySyncJSON } from '@clerk/shared/types'; +import type { DirectorySyncJSON, RoleJSON } from '@clerk/shared/types'; import { describe, expect, it, vi } from 'vitest'; import { BaseResource, DirectorySync } from '../internal'; @@ -20,6 +20,17 @@ const directoryJSON: DirectorySyncJSON = { updated_at: 1700000000000, }; +const roleJSON = (key: string, name: string): RoleJSON => ({ + object: 'role', + id: `role_${key}`, + key, + name, + description: '', + permissions: [], + created_at: 1700000000000, + updated_at: 1700000000000, +}); + function createDirectorySync(): DirectorySync { return new DirectorySync(directoryJSON, ORG_ID); } @@ -258,4 +269,105 @@ describe('DirectorySync', () => { expect(result.data[0].identifier).toBe('ada@example.com'); expect(result.data[0].active).toBe(true); }); + + it('turns group role mapping on through update', async () => { + // @ts-ignore + BaseResource._fetch = vi + .fn() + .mockReturnValue(Promise.resolve({ response: { ...directoryJSON, group_role_mapping_enabled: true } })); + + const result = await createDirectorySync().update({ groupRoleMappingEnabled: true }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'PATCH', + path: DIRECTORY_PATH, + body: { group_role_mapping_enabled: true }, + }); + expect(result.groupRoleMappingEnabled).toBe(true); + }); + + it('lists directory groups with cursor pagination', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue( + Promise.resolve({ + response: { + data: [{ object: 'directory_group', id: 'dirgrp_1', display_name: 'Engineering', updated_at: 1700000000000 }], + cursor: { starting_after: 'dirgrp_1', ending_before: null, has_next_page: true }, + }, + }), + ); + + const page = await createDirectorySync().getGroups({ limit: 1, startingAfter: 'dirgrp_0' }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'GET', + path: `${DIRECTORY_PATH}/groups`, + search: { limit: '1', starting_after: 'dirgrp_0' }, + }); + expect(page.data).toEqual([{ id: 'dirgrp_1', displayName: 'Engineering', updatedAt: new Date(1700000000000) }]); + expect(page.startingAfter).toBe('dirgrp_1'); + expect(page.hasNextPage).toBe(true); + }); + + const mappingsResponse = { + data: [ + { + object: 'directory_group_role_mapping', + id: 'dgrm_1', + directory_id: 'scimdir_1', + directory_group_id: 'dirgrp_1', + directory_group_display_name: 'Engineering', + role: roleJSON('org:admin', 'Admin'), + precedence: 1, + created_at: 1700000000000, + updated_at: 1700000000000, + }, + ], + total_count: 1, + default_role: roleJSON('org:member', 'Member'), + }; + + it('reads the group role mappings and the default role', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: mappingsResponse })); + + const result = await createDirectorySync().getGroupRoleMappings(); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ method: 'GET', path: `${DIRECTORY_PATH}/group_role_mappings` }); + expect(result.data).toHaveLength(1); + expect(result.data[0]).toMatchObject({ + directoryGroupId: 'dirgrp_1', + directoryGroupDisplayName: 'Engineering', + precedence: 1, + role: { key: 'org:admin', name: 'Admin' }, + }); + expect(result.defaultRole).toMatchObject({ key: 'org:member', name: 'Member' }); + }); + + it('replaces the group role mappings, serializing them as JSON in priority order', async () => { + // @ts-ignore + BaseResource._fetch = vi.fn().mockReturnValue(Promise.resolve({ response: mappingsResponse })); + + await createDirectorySync().replaceGroupRoleMappings({ + mappings: [ + { directoryGroupId: 'dirgrp_2', role: 'org:member' }, + { directoryGroupId: 'dirgrp_1', role: 'org:admin' }, + ], + }); + + // @ts-ignore + expect(BaseResource._fetch).toHaveBeenCalledWith({ + method: 'PUT', + path: `${DIRECTORY_PATH}/group_role_mappings`, + body: { + mappings: JSON.stringify([ + { directory_group_id: 'dirgrp_2', role: 'org:member' }, + { directory_group_id: 'dirgrp_1', role: 'org:admin' }, + ]), + }, + }); + }); }); diff --git a/packages/localizations/src/ar-SA.ts b/packages/localizations/src/ar-SA.ts index 0665f4b68d0..3b25dc8ea9e 100644 --- a/packages/localizations/src/ar-SA.ts +++ b/packages/localizations/src/ar-SA.ts @@ -302,9 +302,41 @@ export const arSA: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/be-BY.ts b/packages/localizations/src/be-BY.ts index e4ad0e6c004..635acfdab16 100644 --- a/packages/localizations/src/be-BY.ts +++ b/packages/localizations/src/be-BY.ts @@ -302,9 +302,41 @@ export const beBY: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/bg-BG.ts b/packages/localizations/src/bg-BG.ts index 717ee166f6e..6f0ddc36934 100644 --- a/packages/localizations/src/bg-BG.ts +++ b/packages/localizations/src/bg-BG.ts @@ -303,9 +303,41 @@ export const bgBG: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/bn-IN.ts b/packages/localizations/src/bn-IN.ts index 022665b1f17..fb21697d340 100644 --- a/packages/localizations/src/bn-IN.ts +++ b/packages/localizations/src/bn-IN.ts @@ -308,9 +308,41 @@ export const bnIN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ca-ES.ts b/packages/localizations/src/ca-ES.ts index 6fecadbf40b..8aef41818a9 100644 --- a/packages/localizations/src/ca-ES.ts +++ b/packages/localizations/src/ca-ES.ts @@ -309,9 +309,41 @@ export const caES: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/cs-CZ.ts b/packages/localizations/src/cs-CZ.ts index 2050a63cde1..a4a0b36f8fc 100644 --- a/packages/localizations/src/cs-CZ.ts +++ b/packages/localizations/src/cs-CZ.ts @@ -306,9 +306,41 @@ export const csCZ: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/da-DK.ts b/packages/localizations/src/da-DK.ts index 863123bccb2..04666c33d94 100644 --- a/packages/localizations/src/da-DK.ts +++ b/packages/localizations/src/da-DK.ts @@ -302,9 +302,41 @@ export const daDK: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/de-DE.ts b/packages/localizations/src/de-DE.ts index 3dae5675b44..da33bf85efb 100644 --- a/packages/localizations/src/de-DE.ts +++ b/packages/localizations/src/de-DE.ts @@ -308,9 +308,41 @@ export const deDE: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/el-GR.ts b/packages/localizations/src/el-GR.ts index cbaa83a14eb..a7603a71d35 100644 --- a/packages/localizations/src/el-GR.ts +++ b/packages/localizations/src/el-GR.ts @@ -302,9 +302,41 @@ export const elGR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/en-GB.ts b/packages/localizations/src/en-GB.ts index 8cf3fa19f17..a42632f498c 100644 --- a/packages/localizations/src/en-GB.ts +++ b/packages/localizations/src/en-GB.ts @@ -302,9 +302,41 @@ export const enGB: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/en-US.ts b/packages/localizations/src/en-US.ts index 34ba72cb85c..631f94934e0 100644 --- a/packages/localizations/src/en-US.ts +++ b/packages/localizations/src/en-US.ts @@ -304,9 +304,44 @@ export const enUS: LocalizationResource = { google: 'Google Workspace', okta: 'Okta Workforce', }, + roleMappingStep: { + actionHint__reorder: 'Drag to reorder, or use the arrow keys', + actionLabel__reorder: 'Reorder {{group}} mapping', + columns: { + directoryGroup: 'Directory group', + priority: 'Priority', + role: 'Role', + }, + disableDialog: { + cancelButton: 'Cancel', + confirmButton: 'Disable role sync', + subtitle: + "Members keep their current roles, but new members won't get a role from their directory group. Your mappings are saved, and turning role sync back on reapplies them to every member, overwriting any manual role changes.", + title: 'Disabling role sync', + }, + empty__noGroups: { + subtitle: 'Push groups from {{provider}} and they will appear here, ready to map.', + title: 'No groups provisioned yet', + }, + enableDialog: { + cancelButton: 'Cancel', + confirmButton: 'Enable role sync', + subtitle: + "Members' organization roles will be overwritten by the role mapped to their directory group, including roles that were assigned manually.", + title: 'Enabling role sync', + }, + error__loadMappings: 'Could not load role mappings', + everyoneElse: 'Everyone else', + formFieldLabel__syncRoles: 'Sync roles', + roleOption__unassigned: 'Unassigned', + subtitle: + 'Members get a role from the directory group they belong to. Members belonging to multiple groups are assigned the highest priority role.', + title: 'Role mapping', + }, stepper: { attributes: 'Attributes', configure: 'Configure', + roles: 'Roles', test: 'Test', }, testStep: { diff --git a/packages/localizations/src/es-CR.ts b/packages/localizations/src/es-CR.ts index 881651ad605..fc30a90a668 100644 --- a/packages/localizations/src/es-CR.ts +++ b/packages/localizations/src/es-CR.ts @@ -302,9 +302,41 @@ export const esCR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/es-ES.ts b/packages/localizations/src/es-ES.ts index 352b491767c..5f40dc73d40 100644 --- a/packages/localizations/src/es-ES.ts +++ b/packages/localizations/src/es-ES.ts @@ -308,9 +308,41 @@ export const esES: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/es-MX.ts b/packages/localizations/src/es-MX.ts index fb8f136a774..03498878e39 100644 --- a/packages/localizations/src/es-MX.ts +++ b/packages/localizations/src/es-MX.ts @@ -303,9 +303,41 @@ export const esMX: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/es-UY.ts b/packages/localizations/src/es-UY.ts index 4bc80046ddc..d832f97de4a 100644 --- a/packages/localizations/src/es-UY.ts +++ b/packages/localizations/src/es-UY.ts @@ -302,9 +302,41 @@ export const esUY: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/fa-IR.ts b/packages/localizations/src/fa-IR.ts index 1cec93c532a..00992af9fcb 100644 --- a/packages/localizations/src/fa-IR.ts +++ b/packages/localizations/src/fa-IR.ts @@ -307,9 +307,41 @@ export const faIR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/fi-FI.ts b/packages/localizations/src/fi-FI.ts index af968a9348d..03af3ef6cc7 100644 --- a/packages/localizations/src/fi-FI.ts +++ b/packages/localizations/src/fi-FI.ts @@ -308,9 +308,41 @@ export const fiFI: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/fr-FR.ts b/packages/localizations/src/fr-FR.ts index f10b2f0e17e..76ee2a1559e 100644 --- a/packages/localizations/src/fr-FR.ts +++ b/packages/localizations/src/fr-FR.ts @@ -310,9 +310,41 @@ export const frFR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/he-IL.ts b/packages/localizations/src/he-IL.ts index 9238c575468..179284b7301 100644 --- a/packages/localizations/src/he-IL.ts +++ b/packages/localizations/src/he-IL.ts @@ -302,9 +302,41 @@ export const heIL: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/hi-IN.ts b/packages/localizations/src/hi-IN.ts index 50faad7f25b..6175fb4cbf9 100644 --- a/packages/localizations/src/hi-IN.ts +++ b/packages/localizations/src/hi-IN.ts @@ -308,9 +308,41 @@ export const hiIN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/hr-HR.ts b/packages/localizations/src/hr-HR.ts index d4f315fc89c..72cc5ecb1e2 100644 --- a/packages/localizations/src/hr-HR.ts +++ b/packages/localizations/src/hr-HR.ts @@ -309,9 +309,41 @@ export const hrHR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/hu-HU.ts b/packages/localizations/src/hu-HU.ts index 87f03437062..688d4b49b69 100644 --- a/packages/localizations/src/hu-HU.ts +++ b/packages/localizations/src/hu-HU.ts @@ -309,9 +309,41 @@ export const huHU: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/id-ID.ts b/packages/localizations/src/id-ID.ts index 4a56e4e5711..4559485892f 100644 --- a/packages/localizations/src/id-ID.ts +++ b/packages/localizations/src/id-ID.ts @@ -302,9 +302,41 @@ export const idID: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/is-IS.ts b/packages/localizations/src/is-IS.ts index 78dc41a170e..9583bb296a6 100644 --- a/packages/localizations/src/is-IS.ts +++ b/packages/localizations/src/is-IS.ts @@ -308,9 +308,41 @@ export const isIS: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/it-IT.ts b/packages/localizations/src/it-IT.ts index bb419bfd479..65bcac55361 100644 --- a/packages/localizations/src/it-IT.ts +++ b/packages/localizations/src/it-IT.ts @@ -308,9 +308,41 @@ export const itIT: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ja-JP.ts b/packages/localizations/src/ja-JP.ts index 4f1fe4d90ce..6f3414c7b85 100644 --- a/packages/localizations/src/ja-JP.ts +++ b/packages/localizations/src/ja-JP.ts @@ -309,9 +309,41 @@ export const jaJP: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/kk-KZ.ts b/packages/localizations/src/kk-KZ.ts index f669637237f..e3864ebc49f 100644 --- a/packages/localizations/src/kk-KZ.ts +++ b/packages/localizations/src/kk-KZ.ts @@ -302,9 +302,41 @@ export const kkKZ: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ko-KR.ts b/packages/localizations/src/ko-KR.ts index 88e554eaefa..729da35194f 100644 --- a/packages/localizations/src/ko-KR.ts +++ b/packages/localizations/src/ko-KR.ts @@ -306,9 +306,41 @@ export const koKR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/mn-MN.ts b/packages/localizations/src/mn-MN.ts index 6aa4840b5a8..2ae6e37018f 100644 --- a/packages/localizations/src/mn-MN.ts +++ b/packages/localizations/src/mn-MN.ts @@ -302,9 +302,41 @@ export const mnMN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ms-MY.ts b/packages/localizations/src/ms-MY.ts index 7c53a9ae9c3..6a944c1a38c 100644 --- a/packages/localizations/src/ms-MY.ts +++ b/packages/localizations/src/ms-MY.ts @@ -310,9 +310,41 @@ export const msMY: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/nb-NO.ts b/packages/localizations/src/nb-NO.ts index c31ca1ffa75..a3ae72c1fee 100644 --- a/packages/localizations/src/nb-NO.ts +++ b/packages/localizations/src/nb-NO.ts @@ -309,9 +309,41 @@ export const nbNO: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/nl-BE.ts b/packages/localizations/src/nl-BE.ts index 69a5f136b18..645b79e8ad2 100644 --- a/packages/localizations/src/nl-BE.ts +++ b/packages/localizations/src/nl-BE.ts @@ -302,9 +302,41 @@ export const nlBE: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/nl-NL.ts b/packages/localizations/src/nl-NL.ts index 27da0499ce9..7623193b103 100644 --- a/packages/localizations/src/nl-NL.ts +++ b/packages/localizations/src/nl-NL.ts @@ -302,9 +302,41 @@ export const nlNL: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/pl-PL.ts b/packages/localizations/src/pl-PL.ts index 04af979284e..a2870dccb37 100644 --- a/packages/localizations/src/pl-PL.ts +++ b/packages/localizations/src/pl-PL.ts @@ -302,9 +302,41 @@ export const plPL: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/pt-BR.ts b/packages/localizations/src/pt-BR.ts index 99b368031c2..6e0873a4d11 100644 --- a/packages/localizations/src/pt-BR.ts +++ b/packages/localizations/src/pt-BR.ts @@ -308,9 +308,41 @@ export const ptBR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/pt-PT.ts b/packages/localizations/src/pt-PT.ts index 48d6a572489..ee33333de49 100644 --- a/packages/localizations/src/pt-PT.ts +++ b/packages/localizations/src/pt-PT.ts @@ -310,9 +310,41 @@ export const ptPT: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ro-RO.ts b/packages/localizations/src/ro-RO.ts index be1e270ce09..ab90bea2337 100644 --- a/packages/localizations/src/ro-RO.ts +++ b/packages/localizations/src/ro-RO.ts @@ -308,9 +308,41 @@ export const roRO: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ru-RU.ts b/packages/localizations/src/ru-RU.ts index 4403998a770..87f93a8ba9d 100644 --- a/packages/localizations/src/ru-RU.ts +++ b/packages/localizations/src/ru-RU.ts @@ -302,9 +302,41 @@ export const ruRU: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/sk-SK.ts b/packages/localizations/src/sk-SK.ts index c73b35e563c..9ef966b6160 100644 --- a/packages/localizations/src/sk-SK.ts +++ b/packages/localizations/src/sk-SK.ts @@ -302,9 +302,41 @@ export const skSK: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/sr-RS.ts b/packages/localizations/src/sr-RS.ts index e43def61292..5524a84bdde 100644 --- a/packages/localizations/src/sr-RS.ts +++ b/packages/localizations/src/sr-RS.ts @@ -302,9 +302,41 @@ export const srRS: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/sv-SE.ts b/packages/localizations/src/sv-SE.ts index 31dfc362a9e..b802583bccd 100644 --- a/packages/localizations/src/sv-SE.ts +++ b/packages/localizations/src/sv-SE.ts @@ -302,9 +302,41 @@ export const svSE: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/ta-IN.ts b/packages/localizations/src/ta-IN.ts index 71ba727aadc..c27248bc3d1 100644 --- a/packages/localizations/src/ta-IN.ts +++ b/packages/localizations/src/ta-IN.ts @@ -310,9 +310,41 @@ export const taIN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/te-IN.ts b/packages/localizations/src/te-IN.ts index e5bf22ca4cd..2dd8f332c4f 100644 --- a/packages/localizations/src/te-IN.ts +++ b/packages/localizations/src/te-IN.ts @@ -309,9 +309,41 @@ export const teIN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/th-TH.ts b/packages/localizations/src/th-TH.ts index 25185bfbb26..8bda70c167e 100644 --- a/packages/localizations/src/th-TH.ts +++ b/packages/localizations/src/th-TH.ts @@ -306,9 +306,41 @@ export const thTH: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/tr-TR.ts b/packages/localizations/src/tr-TR.ts index addbef60fef..a52cf945581 100644 --- a/packages/localizations/src/tr-TR.ts +++ b/packages/localizations/src/tr-TR.ts @@ -302,9 +302,41 @@ export const trTR: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/uk-UA.ts b/packages/localizations/src/uk-UA.ts index f714296df0c..11aafe6430e 100644 --- a/packages/localizations/src/uk-UA.ts +++ b/packages/localizations/src/uk-UA.ts @@ -302,9 +302,41 @@ export const ukUA: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/vi-VN.ts b/packages/localizations/src/vi-VN.ts index f4af25fadb9..12d4f7271c3 100644 --- a/packages/localizations/src/vi-VN.ts +++ b/packages/localizations/src/vi-VN.ts @@ -308,9 +308,41 @@ export const viVN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/zh-CN.ts b/packages/localizations/src/zh-CN.ts index 429fba2d4d1..c7a10e3e8da 100644 --- a/packages/localizations/src/zh-CN.ts +++ b/packages/localizations/src/zh-CN.ts @@ -302,9 +302,41 @@ export const zhCN: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/localizations/src/zh-TW.ts b/packages/localizations/src/zh-TW.ts index 0a59569f066..02959f97a62 100644 --- a/packages/localizations/src/zh-TW.ts +++ b/packages/localizations/src/zh-TW.ts @@ -305,9 +305,41 @@ export const zhTW: LocalizationResource = { google: undefined, okta: undefined, }, + roleMappingStep: { + actionHint__reorder: undefined, + actionLabel__reorder: undefined, + columns: { + directoryGroup: undefined, + priority: undefined, + role: undefined, + }, + disableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + empty__noGroups: { + subtitle: undefined, + title: undefined, + }, + enableDialog: { + cancelButton: undefined, + confirmButton: undefined, + subtitle: undefined, + title: undefined, + }, + error__loadMappings: undefined, + everyoneElse: undefined, + formFieldLabel__syncRoles: undefined, + roleOption__unassigned: undefined, + subtitle: undefined, + title: undefined, + }, stepper: { attributes: undefined, configure: undefined, + roles: undefined, test: undefined, }, testStep: { diff --git a/packages/shared/src/react/hooks/index.ts b/packages/shared/src/react/hooks/index.ts index abe5b258c87..16adaa48d8c 100644 --- a/packages/shared/src/react/hooks/index.ts +++ b/packages/shared/src/react/hooks/index.ts @@ -60,6 +60,12 @@ export type { UseOrganizationDirectorySyncUsersReturn, } from './useOrganizationDirectorySyncUsers'; export { __internal_useOrganizationDirectorySyncStatus } from './useOrganizationDirectorySyncStatus'; +export { __internal_useOrganizationDirectorySyncGroupRoleMappings } from './useOrganizationDirectorySyncGroupRoleMappings'; +export type { + DirectorySyncGroupRoleMappingsData, + UseOrganizationDirectorySyncGroupRoleMappingsParams, + UseOrganizationDirectorySyncGroupRoleMappingsReturn, +} from './useOrganizationDirectorySyncGroupRoleMappings'; export type { UseOrganizationDirectorySyncStatusParams, UseOrganizationDirectorySyncStatusReturn, diff --git a/packages/shared/src/react/hooks/useOrganizationDirectorySync.shared.ts b/packages/shared/src/react/hooks/useOrganizationDirectorySync.shared.ts index 9f85edf1910..486b2a1b057 100644 --- a/packages/shared/src/react/hooks/useOrganizationDirectorySync.shared.ts +++ b/packages/shared/src/react/hooks/useOrganizationDirectorySync.shared.ts @@ -79,3 +79,28 @@ export function useOrganizationDirectorySyncStatusCacheKeys(params: { }); }, [organizationId, enterpriseConnectionId, directoryId]); } + +/** + * @internal + */ +export function useOrganizationDirectorySyncGroupRoleMappingsCacheKeys(params: { + organizationId: string | null; + enterpriseConnectionId: string | null; + directoryId: string | null; +}) { + const { organizationId, enterpriseConnectionId, directoryId } = params; + return useMemo(() => { + return createCacheKeys({ + stablePrefix: INTERNAL_STABLE_KEYS.ORGANIZATION_DIRECTORY_SYNC_GROUP_ROLE_MAPPINGS_KEY, + authenticated: Boolean(organizationId), + tracked: { + organizationId: organizationId ?? null, + enterpriseConnectionId: enterpriseConnectionId ?? null, + directoryId: directoryId ?? null, + }, + untracked: { + args: {}, + }, + }); + }, [organizationId, enterpriseConnectionId, directoryId]); +} diff --git a/packages/shared/src/react/hooks/useOrganizationDirectorySyncGroupRoleMappings.tsx b/packages/shared/src/react/hooks/useOrganizationDirectorySyncGroupRoleMappings.tsx new file mode 100644 index 00000000000..b50cc8f9d84 --- /dev/null +++ b/packages/shared/src/react/hooks/useOrganizationDirectorySyncGroupRoleMappings.tsx @@ -0,0 +1,134 @@ +import { useCallback } from 'react'; + +import type { + DirectorySyncGroupResource, + DirectorySyncGroupRoleMappingResource, + DirectorySyncResource, + ReplaceDirectorySyncGroupRoleMappingsParams, + RoleResource, +} from '../../types'; +import { useClerkInstanceContext } from '../contexts'; +import { useClerkQueryClient } from '../query/use-clerk-query-client'; +import { useClerkQuery } from '../query/useQuery'; +import { useOrganizationBase } from './base/useOrganizationBase'; +import { useClearQueriesOnSignOut } from './useClearQueriesOnSignOut'; +import { useOrganizationDirectorySyncGroupRoleMappingsCacheKeys } from './useOrganizationDirectorySync.shared'; + +const GROUPS_PAGE_SIZE = 500; + +export type UseOrganizationDirectorySyncGroupRoleMappingsParams = { + /** The directory to read, e.g. `data` from `useOrganizationDirectorySync`. Nothing is fetched while `null` or `undefined`. */ + directory: DirectorySyncResource | null | undefined; + /** + * If `false`, nothing is fetched. + * + * @default true + */ + enabled?: boolean; +}; + +export type DirectorySyncGroupRoleMappingsData = { + /** Every group the identity provider has pushed. */ + groups: DirectorySyncGroupResource[]; + /** The mappings in priority order. */ + mappings: DirectorySyncGroupRoleMappingResource[]; + /** The role members in no mapped group receive. */ + defaultRole: RoleResource | null; +}; + +export type UseOrganizationDirectorySyncGroupRoleMappingsReturn = { + /** `undefined` while loading and while the hook is disabled. */ + data: DirectorySyncGroupRoleMappingsData | undefined; + error: Error | null; + isLoading: boolean; + isFetching: boolean; + /** Replaces every mapping and caches the result. */ + replaceGroupRoleMappings: ( + params: ReplaceDirectorySyncGroupRoleMappingsParams, + ) => Promise; + revalidate: () => Promise; +}; + +const fetchAllGroups = async (directory: DirectorySyncResource): Promise => { + const groups: DirectorySyncGroupResource[] = []; + let startingAfter: string | undefined; + do { + const page = await directory.getGroups({ limit: GROUPS_PAGE_SIZE, startingAfter }); + groups.push(...page.data); + startingAfter = page.hasNextPage && page.startingAfter ? page.startingAfter : undefined; + } while (startingAfter); + return groups; +}; + +/** + * The groups a Directory Sync directory has received, its group role mappings, and the organization's + * default role, loaded together because the mapping editor needs all three. + * + * @internal + */ +function useOrganizationDirectorySyncGroupRoleMappings( + params: UseOrganizationDirectorySyncGroupRoleMappingsParams, +): UseOrganizationDirectorySyncGroupRoleMappingsReturn { + const { directory, enabled = true } = params; + + const clerk = useClerkInstanceContext(); + const organization = useOrganizationBase(); + const [queryClient] = useClerkQueryClient(); + + const { queryKey, invalidationKey, stableKey, authenticated } = + useOrganizationDirectorySyncGroupRoleMappingsCacheKeys({ + organizationId: organization?.id ?? null, + enterpriseConnectionId: directory?.enterpriseConnectionId ?? null, + directoryId: directory?.id ?? null, + }); + + useClearQueriesOnSignOut({ + isSignedOut: organization === null, + authenticated, + stableKeys: stableKey, + }); + + const belongsToActiveOrganization = Boolean(organization) && directory?.organizationId === organization?.id; + const queryEnabled = enabled && clerk.loaded && belongsToActiveOrganization && Boolean(directory); + + const query = useClerkQuery({ + queryKey, + queryFn: async (): Promise => { + if (!directory) { + throw new Error('directory is required to fetch group role mappings'); + } + const [groups, mappings] = await Promise.all([fetchAllGroups(directory), directory.getGroupRoleMappings()]); + return { groups, mappings: mappings.data, defaultRole: mappings.defaultRole }; + }, + enabled: queryEnabled, + }); + + const revalidate = useCallback(async () => { + await queryClient.invalidateQueries({ queryKey: invalidationKey }); + }, [queryClient, invalidationKey]); + + const replaceGroupRoleMappings = useCallback( + async (replaceParams: ReplaceDirectorySyncGroupRoleMappingsParams) => { + if (!directory) { + return undefined; + } + const replaced = await directory.replaceGroupRoleMappings(replaceParams); + queryClient.setQueryData(queryKey, previous => + previous ? { ...previous, mappings: replaced.data, defaultRole: replaced.defaultRole } : previous, + ); + return replaced.data; + }, + [directory, queryClient, queryKey], + ); + + return { + data: queryEnabled ? query.data : undefined, + error: query.error ?? null, + isLoading: query.isLoading, + isFetching: query.isFetching, + replaceGroupRoleMappings, + revalidate, + }; +} + +export { useOrganizationDirectorySyncGroupRoleMappings as __internal_useOrganizationDirectorySyncGroupRoleMappings }; diff --git a/packages/shared/src/react/stable-keys.ts b/packages/shared/src/react/stable-keys.ts index 0df670488f0..cb673d6fce3 100644 --- a/packages/shared/src/react/stable-keys.ts +++ b/packages/shared/src/react/stable-keys.ts @@ -86,6 +86,7 @@ const ORGANIZATION_DOMAINS_KEY = 'organizationDomains'; const ORGANIZATION_DIRECTORY_SYNC_KEY = 'organizationDirectorySync'; const ORGANIZATION_DIRECTORY_SYNC_USERS_KEY = 'organizationDirectorySyncUsers'; const ORGANIZATION_DIRECTORY_SYNC_STATUS_KEY = 'organizationDirectorySyncStatus'; +const ORGANIZATION_DIRECTORY_SYNC_GROUP_ROLE_MAPPINGS_KEY = 'organizationDirectorySyncGroupRoleMappings'; const ORGANIZATION_SSO_BYPASS_ALLOWLIST_KEY = 'organizationSSOBypassAllowlist'; const CREDIT_HISTORY_KEY = 'billing-credit-history'; @@ -103,6 +104,7 @@ export const INTERNAL_STABLE_KEYS = { ORGANIZATION_DIRECTORY_SYNC_KEY, ORGANIZATION_DIRECTORY_SYNC_USERS_KEY, ORGANIZATION_DIRECTORY_SYNC_STATUS_KEY, + ORGANIZATION_DIRECTORY_SYNC_GROUP_ROLE_MAPPINGS_KEY, ORGANIZATION_SSO_BYPASS_ALLOWLIST_KEY, } as const; diff --git a/packages/shared/src/types/directorySync.ts b/packages/shared/src/types/directorySync.ts index e8340764fcd..d7ac19e8f77 100644 --- a/packages/shared/src/types/directorySync.ts +++ b/packages/shared/src/types/directorySync.ts @@ -1,7 +1,8 @@ import type { DeletedObjectResource } from './deletedObject'; -import type { ClerkResourceJSON } from './json'; +import type { ClerkResourceJSON, RoleJSON } from './json'; import type { ClerkPaginatedResponse } from './pagination'; import type { ClerkResource } from './resource'; +import type { RoleResource } from './role'; /** * The SCIM provider backing a Directory Sync directory. Derived server-side @@ -107,6 +108,21 @@ export interface DirectorySyncResource extends ClerkResource { * completes. */ getSyncStatus: () => Promise; + /** + * Gets a page of the groups the identity provider has pushed into the directory. + */ + getGroups: (params?: GetDirectorySyncGroupsParams) => Promise; + /** + * Gets the group role mappings in priority order, together with the organization's default role. + */ + getGroupRoleMappings: () => Promise; + /** + * Replaces every group role mapping. The list order is the priority, and a group left out is unmapped. + * Members' roles are reassigned in the background. + */ + replaceGroupRoleMappings: ( + params: ReplaceDirectorySyncGroupRoleMappingsParams, + ) => Promise; __internal_toSnapshot: () => DirectorySyncJSONSnapshot; } @@ -148,6 +164,8 @@ export type UpdateDirectorySyncParams = { enabled?: boolean; /** Partial attribute mapping to merge into the stored one; `null` values remove keys. */ attributeMapping?: Record; + /** Turns group role mapping on (`true`) or off (`false`). */ + groupRoleMappingEnabled?: boolean; }; export type CreateDirectorySyncParams = { @@ -193,3 +211,79 @@ export type SetDirectorySyncCredentialsParams = { /** The directory administrator the service account impersonates when reading the directory. */ subjectEmail: string; }; + +export interface DirectorySyncGroupJSON { + object: 'directory_group'; + id: string; + display_name: string; + updated_at: number; +} + +export interface DirectorySyncGroupResource { + id: string; + /** The group's name as the identity provider pushed it. */ + displayName: string; + updatedAt: Date | null; +} + +export interface DirectorySyncGroupsPageJSON { + data: DirectorySyncGroupJSON[]; + cursor: { + starting_after: string | null; + ending_before: string | null; + has_next_page: boolean; + }; +} + +export interface DirectorySyncGroupsPage { + data: DirectorySyncGroupResource[]; + /** Pass as `startingAfter` to fetch the next page; `null` on the last page. */ + startingAfter: string | null; + hasNextPage: boolean; +} + +export type GetDirectorySyncGroupsParams = { + /** At most 500. */ + limit?: number; + startingAfter?: string; +}; + +export interface DirectorySyncGroupRoleMappingJSON { + object: 'directory_group_role_mapping'; + id: string; + directory_id: string; + directory_group_id: string; + directory_group_display_name: string; + role?: RoleJSON | null; + precedence: number; + created_at: number; + updated_at: number; +} + +export interface DirectorySyncGroupRoleMappingResource { + id: string; + directoryGroupId: string; + directoryGroupDisplayName: string; + /** `null` if the mapped role no longer exists. */ + role: RoleResource | null; + /** 1 is the highest priority. */ + precedence: number; +} + +export interface DirectorySyncGroupRoleMappingsJSON { + data: DirectorySyncGroupRoleMappingJSON[]; + total_count: number; + default_role: RoleJSON | null; +} + +export interface DirectorySyncGroupRoleMappingsResource { + /** The mappings in priority order. */ + data: DirectorySyncGroupRoleMappingResource[]; + /** The role members in no mapped group receive. It cannot be changed through the directory. */ + defaultRole: RoleResource | null; +} + +export type ReplaceDirectorySyncGroupRoleMappingsParams = { + /** In priority order. `role` is an organization role key, such as `org:admin`. At most 100 entries. */ + mappings: { directoryGroupId: string; role: string }[]; +}; diff --git a/packages/shared/src/types/localization.ts b/packages/shared/src/types/localization.ts index 912ac54e607..c3caaafaef6 100644 --- a/packages/shared/src/types/localization.ts +++ b/packages/shared/src/types/localization.ts @@ -1596,6 +1596,7 @@ export type __internal_LocalizationResource = { configure: LocalizationValue; attributes: LocalizationValue; test: LocalizationValue; + roles: LocalizationValue; }; providers: { okta: LocalizationValue; @@ -1690,6 +1691,37 @@ export type __internal_LocalizationResource = { error__loadUsers: LocalizationValue; actionLabel__complete: LocalizationValue; }; + roleMappingStep: { + title: LocalizationValue; + subtitle: LocalizationValue; + formFieldLabel__syncRoles: LocalizationValue; + columns: { + priority: LocalizationValue; + directoryGroup: LocalizationValue; + role: LocalizationValue; + }; + roleOption__unassigned: LocalizationValue; + everyoneElse: LocalizationValue; + actionLabel__reorder: LocalizationValue<'group'>; + actionHint__reorder: LocalizationValue; + empty__noGroups: { + title: LocalizationValue; + subtitle: LocalizationValue<'provider'>; + }; + error__loadMappings: LocalizationValue; + enableDialog: { + title: LocalizationValue; + subtitle: LocalizationValue; + cancelButton: LocalizationValue; + confirmButton: LocalizationValue; + }; + disableDialog: { + title: LocalizationValue; + subtitle: LocalizationValue; + cancelButton: LocalizationValue; + confirmButton: LocalizationValue; + }; + }; }; configureSSO: { connectionScopeBanner: { diff --git a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySync.tsx b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySync.tsx index 50dd4efdd78..51f01e63b98 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySync.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySync.tsx @@ -36,7 +36,7 @@ const AuthenticatedContent = withCoreUserGuard(() => { > - + diff --git a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx index 04a17a2cda2..7ee5c978479 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncContext.tsx @@ -1,5 +1,6 @@ import { __internal_useOrganizationDirectorySync, + __internal_useOrganizationDirectorySyncGroupRoleMappings, __internal_useOrganizationEnterpriseConnections, } from '@clerk/shared/react'; import type { @@ -7,12 +8,38 @@ import type { DirectorySyncResource, EnterpriseConnectionResource, SetDirectorySyncCredentialsParams, + UpdateDirectorySyncParams, } from '@clerk/shared/types'; import React, { type PropsWithChildren } from 'react'; +import { useFetchRoles } from '../../hooks/useFetchRoles'; import { sortEnterpriseConnections } from '../ConfigureSSO/domain/organizationEnterpriseConnection'; import type { DirectorySyncProviderMeta } from './providerMeta'; import { DIRECTORY_SYNC_PROVIDERS, directorySyncProviderForConnection } from './providerMeta'; +import type { GroupRoleMapping, RoleOption, UnmappedGroup } from './roleMapping'; +import { moveItem, NO_ROLE_KEY } from './roleMapping'; + +/** + * Group → role mapping edits for the wizard's role mapping step. Edits stay + * local until `save`; `mappings` is ordered by priority (index 0 wins). + */ +export interface RoleMappingView { + isLoading: boolean; + error: Error | null; + enabled: boolean; + setEnabled: (enabled: boolean) => void; + roles: RoleOption[]; + mappings: GroupRoleMapping[]; + unmappedGroups: UnmappedGroup[]; + /** Binds a group to a role; a new mapping is appended at the lowest priority. `NO_ROLE_KEY` unmaps it. */ + setRole: (group: UnmappedGroup, roleKey: string) => void; + /** Reorders a mapping by position within `mappings`. */ + move: (from: number, to: number) => void; + /** The role members in no mapped group receive. */ + defaultRole: RoleOption | null; + /** Writes any changed mappings and the enabled flag. */ + save: () => Promise; +} /** * Shared state for the ConfigureDirectorySync wizard, persisted across steps. @@ -39,6 +66,8 @@ export interface ConfigureDirectorySyncData { setCredentials: (params: SetDirectorySyncCredentialsParams) => Promise; /** Starts a sync for a pull directory. */ syncDirectory: () => Promise; + roleMapping: RoleMappingView; + contentRef?: React.RefObject; onExit?: () => void; } @@ -46,6 +75,7 @@ const ConfigureDirectorySyncContext = React.createContext; onExit?: () => void; }>; @@ -55,6 +85,7 @@ type RevealedToken = { }; export const ConfigureDirectorySyncProvider = ({ + contentRef, onExit, children, }: ConfigureDirectorySyncProviderProps): JSX.Element => { @@ -105,6 +136,8 @@ export const ConfigureDirectorySyncProvider = ({ const provider = directory?.provider ?? (connection ? directorySyncProviderForConnection(connection.provider) : undefined); + const roleMapping = useRoleMapping(directory, updateDirectorySync); + const value: ConfigureDirectorySyncData = { isLoading: isLoadingConnections || (Boolean(enterpriseConnectionId) && isLoadingDirectory), connection, @@ -117,12 +150,109 @@ export const ConfigureDirectorySyncProvider = ({ setDirectoryEnabled, setCredentials: setDirectorySyncCredentials, syncDirectory, + roleMapping, + contentRef, onExit, }; return {children}; }; +const sameMappings = (a: GroupRoleMapping[], b: GroupRoleMapping[]): boolean => + a.length === b.length && a.every((m, i) => m.groupId === b[i].groupId && m.roleKey === b[i].roleKey); + +const useRoleMapping = ( + directory: DirectorySyncResource | null | undefined, + updateDirectorySync: (params: UpdateDirectorySyncParams) => Promise, +): RoleMappingView => { + const { + data, + error, + isLoading: isLoadingMappings, + replaceGroupRoleMappings, + } = __internal_useOrganizationDirectorySyncGroupRoleMappings({ directory }); + const { options: roleOptions, isLoading: isLoadingRoles } = useFetchRoles(); + + const savedMappings = React.useMemo( + () => + (data?.mappings ?? []).flatMap(m => + m.role ? [{ groupId: m.directoryGroupId, groupName: m.directoryGroupDisplayName, roleKey: m.role.key }] : [], + ), + [data?.mappings], + ); + const savedEnabled = directory?.groupRoleMappingEnabled ?? false; + + const [draftMappings, setDraftMappings] = React.useState(null); + const [draftEnabled, setDraftEnabled] = React.useState(null); + const mappings = draftMappings ?? savedMappings; + const enabled = draftEnabled ?? savedEnabled; + + const unmappedGroups = React.useMemo( + () => + (data?.groups ?? []) + .filter(g => !mappings.some(m => m.groupId === g.id)) + .map(g => ({ id: g.id, name: g.displayName })) + .sort((a, b) => a.name.localeCompare(b.name)), + [data?.groups, mappings], + ); + + const setRole = React.useCallback( + (group: UnmappedGroup, roleKey: string) => { + setDraftMappings(prev => { + const current = prev ?? savedMappings; + if (roleKey === NO_ROLE_KEY) { + return current.filter(m => m.groupId !== group.id); + } + return current.some(m => m.groupId === group.id) + ? current.map(m => (m.groupId === group.id ? { ...m, roleKey } : m)) + : [...current, { groupId: group.id, groupName: group.name, roleKey }]; + }); + }, + [savedMappings], + ); + + const move = React.useCallback( + (from: number, to: number) => { + setDraftMappings(prev => moveItem(prev ?? savedMappings, from, to)); + }, + [savedMappings], + ); + + const save = React.useCallback(async () => { + if (draftMappings && !sameMappings(draftMappings, savedMappings)) { + await replaceGroupRoleMappings({ + mappings: draftMappings.map(m => ({ directoryGroupId: m.groupId, role: m.roleKey })), + }); + } + setDraftMappings(null); + if (draftEnabled !== null && draftEnabled !== savedEnabled) { + await updateDirectorySync({ groupRoleMappingEnabled: draftEnabled }); + } + setDraftEnabled(null); + }, [draftMappings, savedMappings, draftEnabled, savedEnabled, replaceGroupRoleMappings, updateDirectorySync]); + + const defaultRole = data?.defaultRole + ? { + value: data.defaultRole.key, + label: roleOptions?.find(r => r.value === data.defaultRole?.key)?.label ?? data.defaultRole.name, + } + : null; + + return { + isLoading: isLoadingMappings || isLoadingRoles, + error, + enabled, + setEnabled: setDraftEnabled, + roles: roleOptions ?? [], + mappings, + unmappedGroups, + setRole, + move, + defaultRole, + save, + }; +}; + export const useConfigureDirectorySync = (): ConfigureDirectorySyncData => { const ctx = React.useContext(ConfigureDirectorySyncContext); if (!ctx) { diff --git a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncWizard.tsx b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncWizard.tsx index 438ea05b842..61b2cf7cfa5 100644 --- a/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncWizard.tsx +++ b/packages/ui/src/components/ConfigureDirectorySync/ConfigureDirectorySyncWizard.tsx @@ -10,10 +10,12 @@ import { Wizard, type WizardStepConfig } from '../ConfigureSSO/elements/Wizard'; import { ConfigureDirectorySyncProvider, useConfigureDirectorySync } from './ConfigureDirectorySyncContext'; import { AttributeMappingStep } from './steps/AttributeMappingStep'; import { ConfigureStep } from './steps/ConfigureStep'; +import { RoleMappingStep } from './steps/RoleMappingStep'; import { TestSyncStep } from './steps/TestSyncStep'; export type ConfigureDirectorySyncWizardProps = { title?: React.ReactNode; + contentRef?: React.RefObject; onExit?: () => void; }; @@ -23,7 +25,10 @@ export type ConfigureDirectorySyncWizardProps = { * organization enterprise connection and its SCIM directory. */ export const ConfigureDirectorySyncWizard = (props: ConfigureDirectorySyncWizardProps): JSX.Element => ( - + ); @@ -50,6 +55,11 @@ const WizardInternal = ({ title }: ConfigureDirectorySyncWizardProps): JSX.Eleme label: localizationKeys('configureDirectorySync.stepper.test'), isReachable: () => hasSsoConnection && hasDirectory, }, + { + id: 'roles', + label: localizationKeys('configureDirectorySync.stepper.roles'), + isReachable: () => hasSsoConnection && hasDirectory, + }, ], [hasSsoConnection, hasDirectory], ); @@ -88,6 +98,14 @@ const WizardInternal = ({ title }: ConfigureDirectorySyncWizardProps): JSX.Eleme + + + + + + + + ); }; diff --git a/packages/ui/src/components/ConfigureDirectorySync/RoleSyncDialog.tsx b/packages/ui/src/components/ConfigureDirectorySync/RoleSyncDialog.tsx new file mode 100644 index 00000000000..228394da2f1 --- /dev/null +++ b/packages/ui/src/components/ConfigureDirectorySync/RoleSyncDialog.tsx @@ -0,0 +1,85 @@ +import { Button, descriptors, Flex, localizationKeys } from '@/customizables'; +import { Card } from '@/elements/Card'; +import { withCardStateProvider } from '@/elements/contexts'; +import { Header } from '@/elements/Header'; +import { Modal } from '@/elements/Modal'; + +type RoleSyncDialogProps = { + action: 'enable' | 'disable'; + isOpen: boolean; + onClose: () => void; + onConfirm: () => void; + contentRef?: React.RefObject; +}; + +export const RoleSyncDialog = (props: RoleSyncDialogProps): JSX.Element | null => { + if (!props.isOpen) { + return null; + } + + return ( + ({ + alignItems: 'center', + position: 'absolute', + inset: 0, + width: 'auto', + height: 'auto', + backgroundColor: 'inherit', + backdropFilter: `blur(${t.sizes.$2})`, + })} + > + + + ); +}; + +const RoleSyncDialogContent = withCardStateProvider(({ action, onClose, onConfirm }: RoleSyncDialogProps) => { + const dialogKey = action === 'enable' ? 'enableDialog' : 'disableDialog'; + + return ( + ({ borderRadius: t.radii.$md })} + > + ({ textAlign: 'start', padding: t.sizes.$5, gap: t.space.$4 })}> + + + + + + + ({ fontSize: t.fontSizes.$md, fontVariantNumeric: 'tabular-nums' })} + > + {index + 1} + + + + + + + + roleMapping.setRole(group, key)} + triggerSx={selectTriggerSx} + /> + + + ); +}; + +const UnmappedRow = ({ group }: { group: UnmappedGroup }): JSX.Element => { + const { roleMapping } = useConfigureDirectorySync(); + const roleOptions = useGroupRoleOptions(); + + return ( + + + + + + + + roleMapping.setRole(group, key)} + triggerSx={selectTriggerSx} + /> + + + ); +}; + +const EveryoneElseRow = (): JSX.Element => { + const { roleMapping } = useConfigureDirectorySync(); + const { defaultRole } = roleMapping; + + return ( + ({ backgroundColor: t.colors.$neutralAlpha25 })} + > + + + + + + + + + {}} + isDisabled + triggerSx={selectTriggerSx} + /> + + + ); +}; + +const MappingTable = (): JSX.Element => { + const { roleMapping } = useConfigureDirectorySync(); + const { mappings, unmappedGroups } = roleMapping; + + const [dragIndex, setDragIndex] = React.useState(null); + const [overIndex, setOverIndex] = React.useState(null); + + const endDrag = () => { + setDragIndex(null); + setOverIndex(null); + }; + + return ( + ({ + flex: '0 1 auto', + minHeight: 0, + overflowY: 'auto', + borderWidth: t.borderWidths.$normal, + borderStyle: t.borderStyles.$solid, + borderColor: t.colors.$borderAlpha150, + borderRadius: t.radii.$lg, + })} + > + ({ + borderWidth: 0, + borderRadius: 0, + 'thead th': { + position: 'sticky', + top: 0, + zIndex: 1, + backgroundColor: t.colors.$colorBackground, + boxShadow: `inset 0 -1px 0 0 ${t.colors.$borderAlpha150}`, + }, + 'thead::after': { display: 'none' }, + tbody: { borderTopWidth: 0 }, + 'tbody > tr:first-of-type > td': { borderTopWidth: 0 }, + 'tr > th': { paddingBlock: t.space.$3, paddingInline: t.space.$4 }, + 'tr > th:first-of-type': { paddingInlineStart: t.space.$8 }, + 'tr > td': { paddingBlock: t.space.$3, verticalAlign: 'middle' }, + 'tr > td:first-of-type': { paddingInlineStart: t.space.$8, paddingInlineEnd: 0 }, + })} + > + + + + + + + + + {mappings.map((mapping, index) => ( + { + if (dragIndex !== null) { + roleMapping.move(dragIndex, to); + } + endDrag(); + }} + onDragEnd={endDrag} + /> + ))} + {unmappedGroups.map(group => ( + + ))} + + +
+ + + + + + +
+
+ ); +}; + +const EmptyGroups = (): JSX.Element => { + const { providerMeta } = useConfigureDirectorySync(); + const { t } = useLocalizations(); + const providerName = t((providerMeta ?? DIRECTORY_SYNC_PROVIDERS.custom).name); + + return ( + ({ + gap: t.space.$1, + padding: t.space.$8, + borderRadius: t.radii.$md, + borderWidth: t.borderWidths.$normal, + borderStyle: 'dashed', + borderColor: t.colors.$borderAlpha150, + textAlign: 'center', + })} + > + ({ fontSize: t.fontSizes.$sm, fontWeight: t.fontWeights.$medium })} + localizationKey={localizationKeys('configureDirectorySync.roleMappingStep.empty__noGroups.title')} + /> + ({ fontSize: t.fontSizes.$sm })} + localizationKey={localizationKeys('configureDirectorySync.roleMappingStep.empty__noGroups.subtitle', { + provider: providerName, + })} + /> + + ); +}; + +const RoleMappingContent = (): JSX.Element => { + const { roleMapping } = useConfigureDirectorySync(); + const { isLoading, error, mappings, unmappedGroups } = roleMapping; + + if (isLoading) { + return ( + ({ padding: t.space.$8 })} + > + + + ); + } + + if (error) { + return ( + + ); + } + + return mappings.length === 0 && unmappedGroups.length === 0 ? : ; +}; + +export const RoleMappingStep = (): JSX.Element => { + const { goPrev } = useWizard(); + const { roleMapping, contentRef, onExit } = useConfigureDirectorySync(); + const card = useCardState(); + const [pendingAction, setPendingAction] = React.useState<'enable' | 'disable' | null>(null); + + const complete = async () => { + if (card.isLoading) { + return; + } + card.setError(undefined); + card.setLoading(); + try { + await roleMapping.save(); + onExit?.(); + } catch (err) { + handleError(err as Error, [], card.setError); + } finally { + card.setIdle(); + } + }; + + return ( + <> + + ({ flexShrink: 0, height: `calc(${t.fontSizes.$lg} * ${t.lineHeights.$medium})` })} + > + setPendingAction(enabled ? 'enable' : 'disable')} + /> + + + + setPendingAction(null)} + onConfirm={() => roleMapping.setEnabled(pendingAction === 'enable')} + contentRef={contentRef} + /> + + + ({ gap: t.space.$4, minHeight: 0 })} + > + {card.error && ( + + )} + + + + + + goPrev()} /> +