Skip to content

Commit 6238f14

Browse files
feat(credential-groups): allowlist integrations by workspace
1 parent de0f118 commit 6238f14

57 files changed

Lines changed: 2115 additions & 663 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/app/api/organizations/[id]/connected-accounts/workspace-access/route.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,5 +36,5 @@ export const PUT = defineInternalJsonRoute({
3636
errorPolicy: internalOrchestrationErrorPolicy,
3737
mapInput: ({ params, body }) => ({ organizationId: params.id, ...body }),
3838
useCase: updateOrganizationAccountWorkspaceAccess,
39-
present: ({ revision, workspaceIds }) => ({ revision, workspaceIds }),
39+
present: ({ revision, grants }) => ({ revision, grants }),
4040
})

‎apps/sim/app/o/[organizationId]/settings/navigation.test.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ describe('organization settings navigation', () => {
3333

3434
it('uses Sources for administration when Search is available', () => {
3535
expect(organizationSettingsNavigation(true, enterprise, available)).toEqual(
36-
ORGANIZATION_SETTINGS_ITEMS.filter(({ id }) => id !== 'connected-accounts')
36+
ORGANIZATION_SETTINGS_ITEMS
3737
)
3838
expect(
3939
organizationSettingsNavigation(true, enterprise, available).find(

‎apps/sim/app/o/[organizationId]/settings/navigation.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,8 +71,7 @@ export function organizationSettingsNavigation(
7171
) {
7272
return ORGANIZATION_SETTINGS_ITEMS.filter(
7373
(item) =>
74-
(item.id !== 'connected-accounts' ||
75-
(availability.connectedAccounts && !availability.search)) &&
74+
(item.id !== 'connected-accounts' || availability.connectedAccounts) &&
7675
((item.id !== 'search-mcp' && item.id !== 'search-slack' && item.id !== 'integrations') ||
7776
availability.search) &&
7877
resolveOrganizationSectionAccess({

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.test.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ describe('workspace SettingsSidebar organization rollout', () => {
212212
expect(workspaceLink('billing')).toHaveTextContent('Subscription')
213213
expect(workspaceLink('usage')).toHaveTextContent('Usage tracking')
214214
expect(workspaceLink('sso')).toHaveTextContent('Single sign-on')
215-
expect(workspaceLink('connected-accounts')).toHaveTextContent('Connected accounts')
215+
expect(workspaceLink('connected-accounts')).toHaveTextContent('Credential Groups')
216216
expect(container.querySelector('a[href^="/o/"]')).toBeNull()
217217
expectWorkspaceLinks()
218218
}
@@ -240,7 +240,10 @@ describe('workspace SettingsSidebar organization rollout', () => {
240240
expect(links).toHaveLength(1)
241241
expect(links[0]).toHaveAttribute('href', '/o/host-org/settings/members')
242242
expect(links[0]).toHaveTextContent('Organization')
243-
for (const section of ['organization', 'billing', 'usage', 'sso', 'connected-accounts']) {
243+
if (role === 'admin')
244+
expect(workspaceLink('connected-accounts')).toHaveTextContent('Credential Groups')
245+
else expect(workspaceLink('connected-accounts')).toBeNull()
246+
for (const section of ['organization', 'billing', 'usage', 'sso']) {
244247
expect(workspaceLink(section)).toBeNull()
245248
}
246249
expectWorkspaceLinks()

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/settings-sidebar/settings-sidebar.tsx‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,8 +156,7 @@ export function SettingsSidebar({
156156
return Boolean(
157157
hostContext.hostOrganizationId &&
158158
isOrgAdminOrOwner &&
159-
hostContext.features?.credentialGroups &&
160-
!hostContext.features?.organizationSearch
159+
hostContext.features?.credentialGroups
161160
)
162161
}
163162
if (

‎apps/sim/components/settings/navigation.ts‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -509,11 +509,11 @@ export const SETTINGS_SECTION_REGISTRY: readonly SettingsSectionRegistryEntry[]
509509
},
510510
},
511511
{
512-
label: 'Connected accounts',
512+
label: 'Credential Groups',
513513
icon: GridOffset,
514514
unified: {
515515
id: 'connected-accounts',
516-
description: 'Manage accounts shared with your organization’s workflows.',
516+
description: 'Manage integrations and workspace access for workflows and Chat.',
517517
group: 'organization',
518518
order: 1,
519519
organizationSection: 'connected-accounts',
@@ -913,8 +913,8 @@ export const ORGANIZATION_SETTINGS_ITEMS: SettingsNavigationItem<OrganizationSet
913913
if (id === 'connected-accounts') {
914914
return {
915915
id,
916-
label: 'Connected accounts',
917-
description: 'Manage accounts shared with your organization’s workflows.',
916+
label: 'Credential Groups',
917+
description: 'Manage integrations and workspace access for workflows and Chat.',
918918
icon: GridOffset,
919919
group,
920920
}

‎apps/sim/ee/credential-groups/components/organization-account-providers.test.tsx‎

Lines changed: 36 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ vi.mock('@/ee/credential-groups/components/organization-account-people', () => (
5252
OrganizationAccountPeople: () => null,
5353
}))
5454
vi.mock('@/ee/credential-groups/components/organization-account-workspace-access', () => ({
55-
OrganizationAccountWorkspaceAccess: () => null,
55+
OrganizationAccountWorkspaceAccess: () => <div data-testid='workspace-access'>Workspaces</div>,
5656
}))
5757

5858
import { OrganizationAccountProviders } from '@/ee/credential-groups/components/organization-account-providers'
@@ -167,6 +167,33 @@ describe('organization provider configuration UI', () => {
167167
})
168168
}
169169

170+
it('keeps workspace allowlists in the Access tab and preserves the integration controls', async () => {
171+
mocks.accounts.mockReturnValue({
172+
data: {
173+
canManage: true,
174+
credentialGroup: { ...group, options: [gmail] },
175+
availableProviders: ['gmail'],
176+
},
177+
})
178+
await act(async () =>
179+
root.render(
180+
<NuqsTestingAdapter hasMemory>
181+
<OrganizationConnectedAccounts organizationId='org-1' />
182+
</NuqsTestingAdapter>
183+
)
184+
)
185+
expect(container.textContent).toContain('Update configurations')
186+
expect(container.querySelector('[data-testid="workspace-access"]')).toBeNull()
187+
await clickButton('Access')
188+
expect(container.querySelector('[data-testid="workspace-access"]')).not.toBeNull()
189+
expect(container.textContent).not.toContain('Update configurations')
190+
expect(container.querySelector('[role="combobox"]')).toBeNull()
191+
await clickButton('Integrations')
192+
expect(container.textContent).toContain('Update configurations')
193+
expect(container.querySelector('[data-testid="workspace-access"]')).toBeNull()
194+
expect(mocks.update).not.toHaveBeenCalled()
195+
})
196+
170197
it('shows only added providers and searches the remaining catalog', async () => {
171198
await render([], [gmail])
172199
expect(container.textContent).toContain('Gmail')
@@ -175,7 +202,7 @@ describe('organization provider configuration UI', () => {
175202
expect(container.textContent).not.toMatch(/Ready|Setup required/)
176203
expect(container.textContent).not.toContain('Fireflies')
177204
expect(container.querySelector('[role="radio"]')).toBeNull()
178-
await clickButton('Add provider')
205+
await clickButton('Add integration')
179206
expect(document.querySelector('[aria-label="Add Gmail"]')).toBeNull()
180207
const search = document.querySelector('[aria-label="Search providers"]')
181208
await act(async () => {
@@ -191,7 +218,7 @@ describe('organization provider configuration UI', () => {
191218

192219
it('adds Fireflies directly without an empty configuration modal', async () => {
193220
await render([])
194-
await clickButton('Add provider')
221+
await clickButton('Add integration')
195222
await clickButton('Add Fireflies')
196223
expect(mocks.add).toHaveBeenCalledWith(
197224
{ organizationId: 'org-1', connectorId: 'fireflies' },
@@ -208,7 +235,7 @@ describe('organization provider configuration UI', () => {
208235

209236
it('adds Gmail directly without opening indexing configuration', async () => {
210237
await render([])
211-
await clickButton('Add provider')
238+
await clickButton('Add integration')
212239
await clickButton('Add Gmail')
213240
expect(mocks.update).toHaveBeenCalledWith(
214241
{
@@ -341,7 +368,7 @@ describe('organization provider configuration UI', () => {
341368
it('surfaces an add failure in the catalog and does not open configuration', async () => {
342369
mocks.add.mockImplementation(() => {})
343370
await render([])
344-
await clickButton('Add provider')
371+
await clickButton('Add integration')
345372
await clickButton('Add Fireflies')
346373
mocks.addError = new Error('Could not add Fireflies')
347374
await render([])
@@ -373,7 +400,7 @@ describe('organization provider configuration UI', () => {
373400
it('returns an unfinished Databricks entry to the catalog until its configuration is saved', async () => {
374401
await render([provider])
375402
expect(container.textContent).not.toContain('Databricks')
376-
await clickButton('Add provider')
403+
await clickButton('Add integration')
377404
await clickButton('Add Databricks')
378405
expect(mocks.add).not.toHaveBeenCalled()
379406
expect(mocks.addAsync).not.toHaveBeenCalled()
@@ -393,7 +420,7 @@ describe('organization provider configuration UI', () => {
393420

394421
it('cancels Databricks setup without adding a provider', async () => {
395422
await render([])
396-
await clickButton('Add provider')
423+
await clickButton('Add integration')
397424
await clickButton('Add Databricks')
398425
expect(mocks.setup).toHaveBeenCalledWith('org-1', false)
399426
expect(document.querySelector('[role="dialog"]')?.textContent).toContain('Add Databricks')
@@ -413,7 +440,7 @@ describe('organization provider configuration UI', () => {
413440

414441
it('adds Databricks with its complete configuration in one organization-scoped request', async () => {
415442
await render([])
416-
await clickButton('Add provider')
443+
await clickButton('Add integration')
417444
await clickButton('Add Databricks')
418445
expect(mocks.addAsync).not.toHaveBeenCalled()
419446
await fill('Name', ' Analytics ')
@@ -437,7 +464,7 @@ describe('organization provider configuration UI', () => {
437464
it('keeps Databricks configuration open after validation fails and allows correction', async () => {
438465
mocks.addAsync.mockRejectedValueOnce(new Error('Enter a valid Databricks MCP URL'))
439466
await render([])
440-
await clickButton('Add provider')
467+
await clickButton('Add integration')
441468
await clickButton('Add Databricks')
442469
await fill('MCP URL', 'https://invalid.example.com/mcp')
443470
await fill('OAuth Client ID', 'client-1')

‎apps/sim/ee/credential-groups/components/organization-account-providers.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ export function OrganizationAccountProviders({
159159
return (
160160
<div className='flex flex-col gap-7'>
161161
<SettingsSection
162-
label='Providers'
162+
label='Integrations'
163163
action={
164164
<div className='flex flex-wrap gap-2'>
165165
{options.length > 0 && (
@@ -177,7 +177,7 @@ export function OrganizationAccountProviders({
177177
setCatalogOpen(true)
178178
}}
179179
>
180-
Add provider
180+
Add integration
181181
</Chip>
182182
</div>
183183
}
@@ -223,7 +223,7 @@ export function OrganizationAccountProviders({
223223
))}
224224
{!rows.length && (
225225
<SettingsEmptyState variant='inline'>
226-
Add a provider to start connecting accounts.
226+
Add an integration to start connecting accounts.
227227
</SettingsEmptyState>
228228
)}
229229
</div>

0 commit comments

Comments
 (0)