Skip to content

Commit a3d7f63

Browse files
fix(self-host): preserve integration availability paths
1 parent 166422a commit a3d7f63

6 files changed

Lines changed: 63 additions & 14 deletions

File tree

apps/sim/app/workspace/[workspaceId]/integrations/integrations.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ function IntegrationItem({
8686
iconVariant='custom'
8787
icon={<IntegrationTile blockType={blockType} icon={Icon} />}
8888
title={name}
89-
description={unavailable ? 'Unavailable in this deployment' : description || undefined}
89+
description={
90+
unavailable
91+
? 'Unavailable in this deployment. Contact your administrator.'
92+
: description || undefined
93+
}
9094
href={`/workspace/${workspaceId}/integrations/${slug}`}
9195
clickLabel={`Open ${name}`}
9296
navigable={!unavailable}

apps/sim/lib/core/config/env-flags.ts

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,15 @@
11
/**
2-
* Environment utility functions for consistent environment detection across the application
2+
* Loaded by `next.config.ts` before the `@/` alias is available, so
3+
* config-boundary dependencies in this module must use relative imports.
34
*/
45

5-
import {
6-
hasEnvCapabilityValue,
7-
inspectCapability,
8-
SANDBOX_CAPABILITY,
9-
} from '@/lib/core/config/env-capabilities'
106
import {
117
ENTERPRISE_FEATURE_LEGACY_DEFAULTS,
128
type EnterpriseFeature,
139
resolveEnterpriseEntitlement,
1410
} from './enterprise-entitlements'
1511
import { env, envBoolean, getEnv, isFalsy, isTruthy } from './env'
12+
import { hasEnvCapabilityValue, inspectCapability, SANDBOX_CAPABILITY } from './env-capabilities'
1613

1714
/**
1815
* Is the application running in production mode

apps/sim/lib/integrations/availability.server.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,15 @@ describe('integration availability', () => {
6464
})
6565
})
6666

67+
it('keeps an ungated service-account path available when OAuth is partial', () => {
68+
expect(availabilityFor('notion_v2', { NOTION_CLIENT_ID: 'client' })).toMatchObject({
69+
state: 'limited',
70+
oauthAvailable: false,
71+
serviceAccountAvailable: true,
72+
missingFields: ['NOTION_CLIENT_SECRET'],
73+
})
74+
})
75+
6776
it('marks an unconfigured OAuth-only integration unavailable', () => {
6877
expect(availabilityFor('x')).toMatchObject({
6978
state: 'unavailable',

apps/sim/lib/integrations/availability.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -132,10 +132,10 @@ function resolveOAuthIntegrationAvailability(
132132
const state: IntegrationAvailabilityState =
133133
oauth.state === 'ready'
134134
? 'ready'
135-
: oauth.state === 'partial' || oauth.state === 'invalid'
136-
? 'misconfigured'
137-
: serviceAccountAvailable
138-
? 'limited'
135+
: serviceAccountAvailable
136+
? 'limited'
137+
: oauth.state === 'partial' || oauth.state === 'invalid'
138+
? 'misconfigured'
139139
: 'unavailable'
140140

141141
return {

apps/sim/lib/integrations/credential-visibility.server.test.ts

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,40 @@ describe('integration credential visibility', () => {
126126
expect(revealed.isCredentialVisible(credential)).toBe(true)
127127
})
128128

129+
it('projects partial OAuth state through a revealed service-account preview', () => {
130+
getIntegrationAvailabilityMock.mockReturnValue([
131+
availability('slack', 'misconfigured', {
132+
oauthAvailable: false,
133+
serviceAccountAvailable: false,
134+
}),
135+
])
136+
const visibility = createIntegrationCredentialVisibility({
137+
allowedIntegrationTypes: new Set(['slack']),
138+
blockVisibility: {
139+
revealed: new Set(['slack_v2']),
140+
disabled: new Set(),
141+
previewTagged: new Set(['slack_v2']),
142+
},
143+
oauthServices: SERVICES,
144+
})
145+
const disabled = createIntegrationCredentialVisibility({
146+
allowedIntegrationTypes: new Set(['slack']),
147+
blockVisibility: {
148+
revealed: new Set(['slack_v2']),
149+
disabled: new Set(['slack_v2']),
150+
previewTagged: new Set(['slack_v2']),
151+
},
152+
oauthServices: SERVICES,
153+
})
154+
155+
const credential = {
156+
providerId: 'slack-custom-bot',
157+
type: 'service_account',
158+
} as const
159+
expect(visibility.isCredentialVisible(credential)).toBe(true)
160+
expect(disabled.isCredentialVisible(credential)).toBe(false)
161+
})
162+
129163
it('leaves non-integration credentials visible', () => {
130164
const visibility = createIntegrationCredentialVisibility({
131165
allowedIntegrationTypes: new Set(),

apps/sim/lib/integrations/credential-visibility.server.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { getServiceAccountGatingBlockType } from '@/lib/credentials/service-acco
33
import {
44
getIntegrationTypesForOAuthServiceId,
55
isOAuthServiceAllowedByIntegrationTypes,
6+
resolveIntegrationAvailabilityStateForVisibility,
67
} from '@/lib/integrations/availability'
78
import {
89
getIntegrationAvailability,
@@ -102,9 +103,13 @@ export function createIntegrationCredentialVisibility({
102103
return owners.some(
103104
(service) =>
104105
isServiceAllowed(service) &&
105-
visibleAvailability(service).some(
106-
(availability) => availability.state !== 'misconfigured'
107-
)
106+
visibleAvailability(service).some((availability) => {
107+
const state = resolveIntegrationAvailabilityStateForVisibility(
108+
availability,
109+
blockVisibility
110+
)
111+
return state === 'ready' || state === 'limited'
112+
})
108113
)
109114
}
110115

0 commit comments

Comments
 (0)