Skip to content

Commit 2276031

Browse files
authored
fix(knowledge): let Slack enter per-member access, and resolve its provider in one place (#7453)
1 parent cc599a4 commit 2276031

4 files changed

Lines changed: 71 additions & 18 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/**
2+
* `supported` is what decides whether the Access field renders at all, and it is
3+
* exactly `connectorMemberGroupProvider(...) !== null`. A connector that declares
4+
* `permissionScopedListing` crawls once per member, so resolving it to `null`
5+
* hides per-member access from the one kind of connector that has it.
6+
*
7+
* @vitest-environment node
8+
*/
9+
import { assert, describe, expect, it, vi } from 'vitest'
10+
11+
vi.mock('@/hooks/queries/credential-groups', () => ({ useCredentialGroups: vi.fn() }))
12+
13+
import { canConnectPersonally } from '@/lib/sim-search/connectors'
14+
import { connectorMemberGroupProvider } from '@/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-member-group-options'
15+
import { getAllConnectorMeta } from '@/connectors/registry'
16+
17+
const permissionScopedOAuthConnectors = Object.entries(getAllConnectorMeta()).filter(([, meta]) =>
18+
canConnectPersonally(meta)
19+
)
20+
21+
describe('connectorMemberGroupProvider', () => {
22+
/** A registry-driven `it.each([])` runs zero cases, so the suite must not be empty. */
23+
it('has permission-scoped OAuth connectors to check', () => {
24+
expect(permissionScopedOAuthConnectors.length).toBeGreaterThan(0)
25+
})
26+
27+
it.each(permissionScopedOAuthConnectors)(
28+
'resolves a credential-group provider for %s',
29+
(_id, meta) => {
30+
expect(connectorMemberGroupProvider(meta)).not.toBeNull()
31+
}
32+
)
33+
34+
it('returns null for a connector that does not crawl per member', () => {
35+
const plain = Object.values(getAllConnectorMeta()).find(
36+
(meta) => meta.auth.mode === 'oauth' && !canConnectPersonally(meta)
37+
)
38+
assert(plain)
39+
expect(connectorMemberGroupProvider(plain)).toBeNull()
40+
})
41+
})

apps/sim/app/workspace/[workspaceId]/knowledge/[id]/hooks/use-connector-member-group-options.ts

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,9 @@
33
import { useMemo } from 'react'
44
import type { ComboboxOption } from '@sim/emcn'
55
import {
6-
type CredentialGroupStandardOAuthProvider,
6+
type CredentialGroupProvider,
7+
findCredentialGroupProviderFromProviderId,
78
getCredentialGroupProviderId,
8-
getCredentialGroupStandardOAuthProviderFromProviderId,
99
isCredentialGroupProvider,
1010
} from '@/lib/credential-groups/providers'
1111
import type { ConnectorMeta } from '@/connectors/types'
@@ -31,15 +31,11 @@ export function decodeConnectorMemberGroupOption(
3131
}
3232

3333
/** The credential-group provider that collects accounts for this connector, if any. */
34-
function connectorMemberGroupProvider(
34+
export function connectorMemberGroupProvider(
3535
connectorConfig: ConnectorMeta
36-
): CredentialGroupStandardOAuthProvider | null {
36+
): CredentialGroupProvider | null {
3737
if (connectorConfig.auth.mode !== 'oauth' || !connectorConfig.permissionScopedListing) return null
38-
try {
39-
return getCredentialGroupStandardOAuthProviderFromProviderId(connectorConfig.auth.provider)
40-
} catch {
41-
return null
42-
}
38+
return findCredentialGroupProviderFromProviderId(connectorConfig.auth.provider)
4339
}
4440

4541
/** The config fields a per-member connector hides: its listing caps, which the server clears. */

apps/sim/lib/credential-groups/providers.ts

Lines changed: 22 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -262,12 +262,31 @@ export function getCredentialGroupProviderId(provider: CredentialGroupProvider):
262262
return getCredentialGroupProviderService(provider).providerId
263263
}
264264

265+
/**
266+
* The credential group provider collecting accounts for an OAuth provider id,
267+
* or `null` when none does.
268+
*
269+
* Every provider counts here, not only the standard OAuth ones: Slack is
270+
* collected through a custom bot app, so resolving against
271+
* {@link CREDENTIAL_GROUP_STANDARD_OAUTH_PROVIDER_IDS} misses it. Callers that
272+
* treat a miss as an ordinary answer take this rather than catching the throw
273+
* from {@link getCredentialGroupProviderFromProviderId}, so the choice of which
274+
* provider set counts is made in one place instead of at each call site.
275+
*/
276+
export function findCredentialGroupProviderFromProviderId(
277+
providerId: string
278+
): CredentialGroupProvider | null {
279+
return (
280+
CREDENTIAL_GROUP_PROVIDER_IDS.find(
281+
(candidate) => getCredentialGroupProviderId(candidate) === providerId
282+
) ?? null
283+
)
284+
}
285+
265286
export function getCredentialGroupProviderFromProviderId(
266287
providerId: string
267288
): CredentialGroupProvider {
268-
const provider = CREDENTIAL_GROUP_PROVIDER_IDS.find(
269-
(candidate) => getCredentialGroupProviderId(candidate) === providerId
270-
)
289+
const provider = findCredentialGroupProviderFromProviderId(providerId)
271290
if (!provider) throw new Error(`Unsupported managed credential provider: ${providerId}`)
272291
return provider
273292
}

apps/sim/lib/knowledge/connectors/member-provisioning.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,7 @@ import {
1717
inviteCredentialGroupEnrollment,
1818
} from '@/lib/credential-groups/enrollments'
1919
import {
20-
type CredentialGroupProvider,
21-
getCredentialGroupProviderFromProviderId,
20+
findCredentialGroupProviderFromProviderId,
2221
getCredentialGroupProviderId,
2322
isCredentialGroupProvider,
2423
isCredentialGroupStandardOAuthProvider,
@@ -115,10 +114,8 @@ export async function provisionKnowledgeConnectorMembersBinding(input: {
115114
throw new OrchestrationError('validation', 'Only an OAuth connector can sync per member')
116115
}
117116
const providerId = connectorMeta.auth.provider
118-
let provider: CredentialGroupProvider
119-
try {
120-
provider = getCredentialGroupProviderFromProviderId(providerId)
121-
} catch {
117+
const provider = findCredentialGroupProviderFromProviderId(providerId)
118+
if (!provider) {
122119
throw new OrchestrationError(
123120
'validation',
124121
`${connectorMeta.name} accounts cannot be collected through a Credential Group yet`

0 commit comments

Comments
 (0)