Skip to content

Commit 472450d

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(oracle-fusion): address SCM review findings
1 parent 6df7cb3 commit 472450d

34 files changed

Lines changed: 456 additions & 101 deletions

‎apps/sim/app/(landing)/integrations/(shell)/[slug]/page.tsx‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,8 @@ function getRelatedSlugs(
108108
const AUTH_STEP: Record<AuthType, (name: string) => string> = {
109109
oauth: (name) =>
110110
`Connect your ${name} account with one-click OAuth, with no credentials to copy.`,
111+
'service-account': (name) =>
112+
`Select or add a reusable ${name} service-account credential in your workspace.`,
111113
'api-key': (name) =>
112114
`Paste your ${name} API key to authenticate. You can find it in your ${name} account settings.`,
113115
none: () => 'No authentication is needed, so the block works as soon as you drop it in.',
@@ -501,9 +503,11 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
501503
that AI agents can use inside Sim&apos;s visual workflow builder.{' '}
502504
{authType === 'oauth'
503505
? `${name} connects with one-click OAuth.`
504-
: authType === 'api-key'
505-
? `${name} connects with an API key.`
506-
: `${name} requires no authentication.`}{' '}
506+
: authType === 'service-account'
507+
? `${name} connects with a reusable service-account credential.`
508+
: authType === 'api-key'
509+
? `${name} connects with an API key.`
510+
: `${name} requires no authentication.`}{' '}
507511
Free to start at sim.ai.
508512
</p>
509513

@@ -656,9 +660,11 @@ export default async function IntegrationPage({ params }: { params: Promise<{ sl
656660
body:
657661
authType === 'oauth'
658662
? `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder, and connect your account with one-click OAuth.`
659-
: authType === 'api-key'
660-
? `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder, and paste in your ${name} API key.`
661-
: `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder. No authentication is needed.`,
663+
: authType === 'service-account'
664+
? `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder, and select or add a reusable service-account credential.`
665+
: authType === 'api-key'
666+
? `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder, and paste in your ${name} API key.`
667+
: `Open your workspace, drag ${articleFor(name)} ${name} block onto the workflow builder. No authentication is needed.`,
662668
},
663669
{
664670
step: '03',

‎apps/sim/app/(landing)/integrations/(shell)/page.tsx‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ const allIntegrations = INTEGRATIONS
2121
const integrationSummaries = allIntegrations.map(toIntegrationSummary)
2222
const INTEGRATION_COUNT = allIntegrations.length
2323
const OAUTH_COUNT = allIntegrations.filter((i) => i.authType === 'oauth').length
24+
const SERVICE_ACCOUNT_COUNT = allIntegrations.filter((i) => i.authType === 'service-account').length
2425
const TRIGGER_INTEGRATION_COUNT = allIntegrations.filter((i) => i.triggerCount > 0).length
2526
const TOTAL_TOOL_COUNT = allIntegrations.reduce((sum, i) => sum + i.operationCount, 0)
2627

@@ -31,7 +32,7 @@ const TOTAL_TOOL_COUNT = allIntegrations.reduce((sum, i) => sum + i.operationCou
3132
const CATALOG_FAQS: FAQItem[] = [
3233
{
3334
question: 'How do integrations work in Sim?',
34-
answer: `Each integration is a block you drag onto Sim's workflow builder. Together, Sim's ${INTEGRATION_COUNT} integrations expose ${TOTAL_TOOL_COUNT}+ tools that AI agents can call. ${OAUTH_COUNT} connect with one-click OAuth, and the rest use an API key or no authentication at all. Wire blocks together, add an AI agent block for reasoning, and run.`,
35+
answer: `Each integration is a block you drag onto Sim's workflow builder. Together, Sim's ${INTEGRATION_COUNT} integrations expose ${TOTAL_TOOL_COUNT}+ tools that AI agents can call. ${OAUTH_COUNT} connect with one-click OAuth, ${SERVICE_ACCOUNT_COUNT} use reusable service-account credentials, and the rest use an API key or no authentication at all. Wire blocks together, add an AI agent block for reasoning, and run.`,
3536
},
3637
{
3738
question: 'Are Sim integrations free to use?',

‎apps/sim/app/api/settings/allowed-integrations/route.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,12 @@ export const GET = withRouteHandler(async () => {
1313
return NextResponse.json({
1414
allowedIntegrations: getAllowedIntegrationsFromEnv(),
1515
integrationAvailability: getIntegrationAvailability().map(
16-
({ type, state, oauthAvailable }) => ({ type, state, oauthAvailable })
16+
({ type, state, oauthAvailable, serviceAccountAvailable }) => ({
17+
type,
18+
state,
19+
oauthAvailable,
20+
serviceAccountAvailable,
21+
})
1722
),
1823
})
1924
})

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -362,7 +362,11 @@ export function Integrations() {
362362
name={integration.name}
363363
description={integration.description}
364364
icon={Icon}
365-
unavailable={integration.authType === 'oauth' && deploymentUnavailable}
365+
unavailable={
366+
(integration.authType === 'oauth' ||
367+
integration.authType === 'service-account') &&
368+
deploymentUnavailable
369+
}
366370
/>
367371
)
368372
})}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import { describe, expect, it } from 'vitest'
2+
import { CONNECT_MODE } from '@/app/workspace/[workspaceId]/integrations/connect-route'
3+
import { resolveIntegrationSearchConnectMode } from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/integration-search-items'
4+
5+
describe('resolveIntegrationSearchConnectMode', () => {
6+
it('opens the service-account picker for a ready service-account-only integration', () => {
7+
expect(
8+
resolveIntegrationSearchConnectMode({
9+
oauthAvailable: false,
10+
serviceAccountAvailable: true,
11+
})
12+
).toBe(CONNECT_MODE.serviceAccount)
13+
})
14+
15+
it('prefers OAuth when both stored-credential paths are available', () => {
16+
expect(
17+
resolveIntegrationSearchConnectMode({
18+
oauthAvailable: true,
19+
serviceAccountAvailable: true,
20+
})
21+
).toBe(CONNECT_MODE.oauth)
22+
})
23+
24+
it('does not request a connection dialog when neither path is available', () => {
25+
expect(
26+
resolveIntegrationSearchConnectMode({
27+
oauthAvailable: false,
28+
serviceAccountAvailable: false,
29+
})
30+
).toBeNull()
31+
})
32+
})

‎apps/sim/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/integration-search-items.ts‎

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type { ComponentType } from 'react'
22
import { blockTypeToIconMap, INTEGRATIONS, resolveCredentialDisplay } from '@/lib/integrations'
3+
import type { IntegrationAvailability } from '@/lib/integrations/availability'
34
import {
45
CONNECT_MODE,
56
CONNECT_QUERY_PARAM,
@@ -10,6 +11,16 @@ import type { WorkspaceCredential } from '@/hooks/queries/credentials'
1011
/** Fallback brand color for credentials whose integration metadata cannot be resolved. */
1112
const FALLBACK_BG_COLOR = '#6B7280'
1213

14+
/** Selects the credential dialog that a catalog search result should open. */
15+
export function resolveIntegrationSearchConnectMode(
16+
availability: Pick<IntegrationAvailability, 'oauthAvailable' | 'serviceAccountAvailable'> | null
17+
): (typeof CONNECT_MODE)[keyof typeof CONNECT_MODE] | null {
18+
if (!availability) return CONNECT_MODE.oauth
19+
if (availability.oauthAvailable) return CONNECT_MODE.oauth
20+
if (availability.serviceAccountAvailable) return CONNECT_MODE.serviceAccount
21+
return null
22+
}
23+
1324
/**
1425
* Module-level base array of resolvable integrations (entries without a
1526
* registered icon are dropped, matching the catalog's `if (!Icon) return null`
@@ -41,9 +52,9 @@ const INTEGRATION_BASES: readonly {
4152

4253
/**
4354
* Builds the full integration catalog as search items for a given workspace.
44-
* OAuth integrations link directly to the detail page with `?connect=oauth` so
45-
* the connect modal auto-opens (via the detail page's `useEffect` on
46-
* `CONNECT_QUERY_PARAM`). Non-OAuth integrations link to the plain detail page.
55+
* Stored-credential integrations link directly to the detail page with a connect mode so the
56+
* matching modal auto-opens (via the detail page's `useEffect` on `CONNECT_QUERY_PARAM`). Other
57+
* integrations link to the plain detail page.
4758
*/
4859
export function buildIntegrationSearchItems(
4960
workspaceId: string,
@@ -53,7 +64,10 @@ export function buildIntegrationSearchItems(
5364
) => (typeof CONNECT_MODE)[keyof typeof CONNECT_MODE] | null = () => CONNECT_MODE.oauth
5465
): IntegrationSearchItem[] {
5566
return INTEGRATION_BASES.filter((base) => isBlockAllowed(base.blockType)).map((base) => {
56-
const connectMode = base.authType === 'oauth' ? getConnectMode(base.blockType) : null
67+
const connectMode =
68+
base.authType === 'oauth' || base.authType === 'service-account'
69+
? getConnectMode(base.blockType)
70+
: null
5771
const connectSuffix = connectMode ? `?${CONNECT_QUERY_PARAM}=${connectMode}` : ''
5872
return {
5973
id: base.id,

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

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,6 @@ import { isChatEnabled, isHosted, isStatusNoticePreviewEnabled } from '@/lib/cor
4242
import { isMacPlatform } from '@/lib/core/utils/platform'
4343
import { buildFolderTree, getFolderPathNames } from '@/lib/folders/tree'
4444
import { captureEvent } from '@/lib/posthog/client'
45-
import { CONNECT_MODE } from '@/app/workspace/[workspaceId]/integrations/connect-route'
4645
import { useRegisterGlobalCommands } from '@/app/workspace/[workspaceId]/providers/global-commands-provider'
4746
import { useWorkspaceHostContext } from '@/app/workspace/[workspaceId]/providers/workspace-host-provider'
4847
import { useUserPermissionsContext } from '@/app/workspace/[workspaceId]/providers/workspace-permissions-provider'
@@ -71,6 +70,7 @@ import {
7170
import {
7271
buildConnectedAccountSearchItems,
7372
buildIntegrationSearchItems,
73+
resolveIntegrationSearchConnectMode,
7474
} from '@/app/workspace/[workspaceId]/w/components/sidebar/components/search-modal/integration-search-items'
7575
import type {
7676
LogItem,
@@ -1102,10 +1102,7 @@ export const Sidebar = memo(function Sidebar({
11021102
? []
11031103
: buildIntegrationSearchItems(workspaceId, isBlockAllowed, (blockType) => {
11041104
const availability = integrationAvailability.get(blockType.toLowerCase())
1105-
if (!availability) return CONNECT_MODE.oauth
1106-
if (availability?.oauthAvailable) return CONNECT_MODE.oauth
1107-
if (availability?.state === 'limited') return CONNECT_MODE.serviceAccount
1108-
return null
1105+
return resolveIntegrationSearchConnectMode(availability ?? null)
11091106
}),
11101107
[workspaceId, permissionConfig.hideIntegrationsTab, isBlockAllowed, integrationAvailability]
11111108
)

‎apps/sim/blocks/blocks.test.ts‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -670,7 +670,13 @@ describe.concurrent('Blocks Module', () => {
670670

671671
describe('AuthMode Validation', () => {
672672
it('should have valid authMode when defined', () => {
673-
const validAuthModes = [AuthMode.OAuth, AuthMode.ApiKey, AuthMode.BotToken, undefined]
673+
const validAuthModes = [
674+
AuthMode.OAuth,
675+
AuthMode.ServiceAccount,
676+
AuthMode.ApiKey,
677+
AuthMode.BotToken,
678+
undefined,
679+
]
674680
const blocks = getAllBlocks()
675681
for (const block of blocks) {
676682
expect(validAuthModes).toContain(block.authMode)

‎apps/sim/blocks/blocks/oracle_fusion_scm.test.ts‎

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
*/
44
import { describe, expect, it } from 'vitest'
55
import { OracleFusionScmBlock, OracleFusionScmBlockMeta } from '@/blocks/blocks/oracle_fusion_scm'
6-
import type { SubBlockConfig } from '@/blocks/types'
6+
import { AuthMode, type SubBlockConfig } from '@/blocks/types'
77
import * as oracleFusionScmTools from '@/tools/oracle_fusion_scm'
88
import type { ToolConfig } from '@/tools/types'
99

@@ -129,6 +129,14 @@ describe('Oracle Fusion SCM block', () => {
129129
})
130130

131131
it('binds all six project selectors to the stored SCM credential', () => {
132+
expect(OracleFusionScmBlock.authMode).toBe(AuthMode.ServiceAccount)
133+
expect(subBlocks.find((subBlock) => subBlock.id === 'credential')).toMatchObject({
134+
type: 'oauth-input',
135+
serviceId: 'oracle_fusion_scm',
136+
credentialKind: 'service-account',
137+
canonicalParamId: 'oauthCredential',
138+
required: true,
139+
})
132140
const selectors = subBlocks.filter((subBlock) => subBlock.type === 'project-selector')
133141
expect(selectors).toHaveLength(6)
134142
for (const selector of selectors) {

‎apps/sim/blocks/blocks/oracle_fusion_scm.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export const OracleFusionScmBlock: BlockConfig = {
4848
docsLink: 'https://docs.sim.ai/integrations/oracle_fusion_scm',
4949
category: 'tools',
5050
integrationType: IntegrationType.Commerce,
51-
authMode: AuthMode.ApiKey,
51+
authMode: AuthMode.ServiceAccount,
5252
bgColor: '#FFFFFF',
5353
icon: OracleIcon,
5454
canvasPresentation: {

0 commit comments

Comments
 (0)