Skip to content

Commit aa695d1

Browse files
waleedlatif1claude
andcommitted
fix(scim): submit only the changed setting; move settings option lists to a constants module
Also stubs the SCIM section in the SSO settings test, which has no QueryClient. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
1 parent 16fc76e commit aa695d1

3 files changed

Lines changed: 64 additions & 49 deletions

File tree

apps/sim/ee/scim/components/scim-section.tsx

Lines changed: 11 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,15 @@ import {
3434
usePermissionGroups,
3535
} from '@/ee/access-control/hooks/permission-groups'
3636
import { SettingRow } from '@/ee/components/setting-row'
37+
import {
38+
CREDENTIAL_EXPIRY_OPTIONS,
39+
type CredentialExpiry,
40+
type MappingTargetKind,
41+
PERMISSION_OPTIONS,
42+
SETTING_TOGGLES,
43+
TARGET_KIND_OPTIONS,
44+
type WorkspacePermission,
45+
} from '@/ee/scim/constants'
3746
import {
3847
useConfigureScimConnection,
3948
useDeleteScimGroupMapping,
@@ -50,51 +59,6 @@ interface ScimSectionProps {
5059
organizationId: string
5160
}
5261

53-
type MappingTargetKind = ScimGroupMappingView['targetKind']
54-
type WorkspacePermission = NonNullable<ScimGroupMappingView['permissionType']>
55-
56-
const TARGET_KIND_OPTIONS = [
57-
{ value: 'permission_group', label: 'Permission group' },
58-
{ value: 'workspace', label: 'Workspace' },
59-
{ value: 'org_role', label: 'Organization admin' },
60-
] as const
61-
62-
const PERMISSION_OPTIONS = [
63-
{ value: 'read', label: 'Read' },
64-
{ value: 'write', label: 'Write' },
65-
{ value: 'admin', label: 'Admin' },
66-
] as const
67-
68-
const SETTING_TOGGLES = [
69-
{
70-
key: 'lockManualMembership',
71-
label: 'Lock managed membership',
72-
description:
73-
'Refuse invitations, role changes, and manual grants for members the directory provisions. The next sync would revert them anyway.',
74-
},
75-
{
76-
key: 'disableJit',
77-
label: 'Disable just-in-time provisioning',
78-
description:
79-
'Refuse membership for someone signing in with SSO who the directory never provisioned. The directory becomes the only way in.',
80-
},
81-
{
82-
key: 'autoMapPermissionGroupsByName',
83-
label: 'Match permission groups by name',
84-
description:
85-
'When a pushed group has the same name as one of your permission groups, map them automatically. Nothing is created.',
86-
},
87-
] as const
88-
89-
/** Credential lifetimes offered at issue time; `never` matches what Okta and Entra expect by default. */
90-
const CREDENTIAL_EXPIRY_OPTIONS = [
91-
{ value: 'never', label: 'Never expires' },
92-
{ value: '90', label: 'Expires in 90 days' },
93-
{ value: '365', label: 'Expires in 1 year' },
94-
] as const
95-
96-
type CredentialExpiry = (typeof CREDENTIAL_EXPIRY_OPTIONS)[number]['value']
97-
9862
const RELATIVE_TIME = new Intl.RelativeTimeFormat('en', { numeric: 'auto' })
9963

10064
/** Renders "3 minutes ago" for the activity list and credential rows. */
@@ -367,10 +331,8 @@ function ConnectionDetails({ organizationId, connection }: ConnectionDetailsProp
367331

368332
async function handleToggleSetting(key: (typeof SETTING_TOGGLES)[number]['key'], value: boolean) {
369333
try {
370-
await configure.mutateAsync({
371-
organizationId,
372-
settings: { ...connection.settings, [key]: value },
373-
})
334+
/** Only the changed key is sent; the server merges it, so a concurrent edit elsewhere is not reverted. */
335+
await configure.mutateAsync({ organizationId, settings: { [key]: value } })
374336
} catch (error) {
375337
toast.error(getErrorMessage(error, 'Failed to update setting'))
376338
}

apps/sim/ee/scim/constants.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import type { ScimGroupMappingView } from '@/lib/api/contracts/organization-scim'
2+
3+
/** Option lists and setting descriptions for the directory provisioning settings section. */
4+
5+
export type MappingTargetKind = ScimGroupMappingView['targetKind']
6+
export type WorkspacePermission = NonNullable<ScimGroupMappingView['permissionType']>
7+
8+
export const TARGET_KIND_OPTIONS = [
9+
{ value: 'permission_group', label: 'Permission group' },
10+
{ value: 'workspace', label: 'Workspace' },
11+
{ value: 'org_role', label: 'Organization admin' },
12+
] as const
13+
14+
export const PERMISSION_OPTIONS = [
15+
{ value: 'read', label: 'Read' },
16+
{ value: 'write', label: 'Write' },
17+
{ value: 'admin', label: 'Admin' },
18+
] as const
19+
20+
export const SETTING_TOGGLES = [
21+
{
22+
key: 'lockManualMembership',
23+
label: 'Lock managed membership',
24+
description:
25+
'Refuse invitations, role changes, and manual grants for members the directory provisions. The next sync would revert them anyway.',
26+
},
27+
{
28+
key: 'disableJit',
29+
label: 'Disable just-in-time provisioning',
30+
description:
31+
'Refuse membership for someone signing in with SSO who the directory never provisioned. The directory becomes the only way in.',
32+
},
33+
{
34+
key: 'autoMapPermissionGroupsByName',
35+
label: 'Match permission groups by name',
36+
description:
37+
'When a pushed group has the same name as one of your permission groups, map them automatically. Nothing is created.',
38+
},
39+
] as const
40+
41+
/** Credential lifetimes offered at issue time; `never` matches what Okta and Entra expect by default. */
42+
export const CREDENTIAL_EXPIRY_OPTIONS = [
43+
{ value: 'never', label: 'Never expires' },
44+
{ value: '90', label: 'Expires in 90 days' },
45+
{ value: '365', label: 'Expires in 1 year' },
46+
] as const
47+
48+
export type CredentialExpiry = (typeof CREDENTIAL_EXPIRY_OPTIONS)[number]['value']

apps/sim/ee/sso/components/sso-settings.test.tsx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ vi.mock('@/ee/sso/components/verified-domains-section', () => ({
9090
VerifiedDomainsSection: () => <div />,
9191
}))
9292

93+
/** Directory provisioning has its own React Query hooks and its own tests; here it is a sibling section. */
94+
vi.mock('@/ee/scim/components/scim-section', () => ({
95+
ScimSection: () => <div />,
96+
}))
97+
9398
// Surface the real Save/Update action so submit paths are reachable from tests.
9499
vi.mock('@/components/settings/save-discard-actions', () => ({
95100
saveDiscardActions: ({ saveLabel, onSave }: { saveLabel?: string; onSave?: () => void }) => [

0 commit comments

Comments
 (0)