Skip to content

Commit d42ffff

Browse files
committed
add warnings for UI indication for credential sets
1 parent 70928c0 commit d42ffff

File tree

18 files changed

+393
-242
lines changed

18 files changed

+393
-242
lines changed

apps/sim/app/api/v1/admin/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -640,6 +640,7 @@ export interface AdminDeployResult {
640640
isDeployed: boolean
641641
version: number
642642
deployedAt: string
643+
warnings?: string[]
643644
}
644645

645646
export interface AdminUndeployResult {

apps/sim/app/api/v1/admin/workflows/[id]/deploy/route.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { db, workflow, workflowDeploymentVersion } from '@sim/db'
22
import { createLogger } from '@sim/logger'
33
import { and, eq } from 'drizzle-orm'
44
import { generateRequestId } from '@/lib/core/utils/request'
5+
import { removeMcpToolsForWorkflow, syncMcpToolsForWorkflow } from '@/lib/mcp/workflow-mcp-sync'
56
import {
67
cleanupWebhooksForWorkflow,
78
restorePreviousVersionWebhooks,
@@ -163,10 +164,14 @@ export const POST = withAdminAuthParams<RouteParams>(async (request, context) =>
163164
`[${requestId}] Admin API: Deployed workflow ${workflowId} as v${deployResult.version}`
164165
)
165166

167+
// Sync MCP tools with the latest parameter schema
168+
await syncMcpToolsForWorkflow({ workflowId, requestId, context: 'deploy' })
169+
166170
const response: AdminDeployResult = {
167171
isDeployed: true,
168172
version: deployResult.version!,
169173
deployedAt: deployResult.deployedAt!.toISOString(),
174+
warnings: triggerSaveResult.warnings,
170175
}
171176

172177
return singleResponse(response)
@@ -202,6 +207,8 @@ export const DELETE = withAdminAuthParams<RouteParams>(async (request, context)
202207
return internalErrorResponse(result.error || 'Failed to undeploy workflow')
203208
}
204209

210+
await removeMcpToolsForWorkflow(workflowId, requestId)
211+
205212
logger.info(`Admin API: Undeployed workflow ${workflowId}`)
206213

207214
const response: AdminUndeployResult = {

apps/sim/app/api/v1/admin/workflows/[id]/versions/[versionId]/activate/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,7 @@ export const POST = withAdminAuthParams<RouteParams>(async (request, context) =>
192192
success: true,
193193
version: versionNum,
194194
deployedAt: result.deployedAt!.toISOString(),
195+
warnings: triggerSaveResult.warnings,
195196
})
196197
} catch (error) {
197198
logger.error(

apps/sim/app/api/webhooks/[id]/route.ts

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -241,20 +241,18 @@ export async function DELETE(
241241
}
242242

243243
const foundWebhook = webhookData.webhook
244-
const providerConfig = foundWebhook.providerConfig as Record<string, unknown> | null
245-
const credentialSetId = providerConfig?.credentialSetId as string | undefined
246-
const blockId = providerConfig?.blockId as string | undefined
244+
const credentialSetId = foundWebhook.credentialSetId as string | undefined
245+
const blockId = foundWebhook.blockId as string | undefined
247246

248247
if (credentialSetId && blockId) {
249248
const allCredentialSetWebhooks = await db
250249
.select()
251250
.from(webhook)
252251
.where(and(eq(webhook.workflowId, webhookData.workflow.id), eq(webhook.blockId, blockId)))
253252

254-
const webhooksToDelete = allCredentialSetWebhooks.filter((w) => {
255-
const config = w.providerConfig as Record<string, unknown> | null
256-
return config?.credentialSetId === credentialSetId
257-
})
253+
const webhooksToDelete = allCredentialSetWebhooks.filter(
254+
(w) => w.credentialSetId === credentialSetId
255+
)
258256

259257
for (const w of webhooksToDelete) {
260258
await cleanupExternalWebhook(w, webhookData.workflow, requestId)

apps/sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
273273
nextRunAt: scheduleInfo.nextRunAt,
274274
}
275275
: undefined,
276+
warnings: triggerSaveResult.warnings,
276277
})
277278
} catch (error: any) {
278279
logger.error(`[${requestId}] Error deploying workflow: ${id}`, {

apps/sim/app/api/workflows/[id]/deployments/[version]/activate/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,11 @@ export async function POST(
178178
context: 'activate',
179179
})
180180

181-
return createSuccessResponse({ success: true, deployedAt: result.deployedAt })
181+
return createSuccessResponse({
182+
success: true,
183+
deployedAt: result.deployedAt,
184+
warnings: triggerSaveResult.warnings,
185+
})
182186
} catch (error: any) {
183187
logger.error(`[${requestId}] Error activating deployment for workflow: ${id}`, error)
184188
return createErrorResponse(error.message || 'Failed to activate deployment', 500)

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/deploy/components/deploy-modal/deploy-modal.tsx

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export function DeployModal({
9595
const [activeTab, setActiveTab] = useState<TabView>('general')
9696
const [chatSubmitting, setChatSubmitting] = useState(false)
9797
const [apiDeployError, setApiDeployError] = useState<string | null>(null)
98+
const [apiDeployWarnings, setApiDeployWarnings] = useState<string[]>([])
9899
const [isChatFormValid, setIsChatFormValid] = useState(false)
99100
const [selectedStreamingOutputs, setSelectedStreamingOutputs] = useState<string[]>([])
100101

@@ -227,6 +228,7 @@ export function DeployModal({
227228
if (open && workflowId) {
228229
setActiveTab('general')
229230
setApiDeployError(null)
231+
setApiDeployWarnings([])
230232
}
231233
}, [open, workflowId])
232234

@@ -282,9 +284,13 @@ export function DeployModal({
282284
if (!workflowId) return
283285

284286
setApiDeployError(null)
287+
setApiDeployWarnings([])
285288

286289
try {
287-
await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
290+
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
291+
if (result.warnings && result.warnings.length > 0) {
292+
setApiDeployWarnings(result.warnings)
293+
}
288294
await refetchDeployedState()
289295
} catch (error: unknown) {
290296
logger.error('Error deploying workflow:', { error })
@@ -297,8 +303,13 @@ export function DeployModal({
297303
async (version: number) => {
298304
if (!workflowId) return
299305

306+
setApiDeployWarnings([])
307+
300308
try {
301-
await activateVersionMutation.mutateAsync({ workflowId, version })
309+
const result = await activateVersionMutation.mutateAsync({ workflowId, version })
310+
if (result.warnings && result.warnings.length > 0) {
311+
setApiDeployWarnings(result.warnings)
312+
}
302313
await refetchDeployedState()
303314
} catch (error) {
304315
logger.error('Error promoting version:', { error })
@@ -324,9 +335,13 @@ export function DeployModal({
324335
if (!workflowId) return
325336

326337
setApiDeployError(null)
338+
setApiDeployWarnings([])
327339

328340
try {
329-
await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
341+
const result = await deployMutation.mutateAsync({ workflowId, deployChatEnabled: false })
342+
if (result.warnings && result.warnings.length > 0) {
343+
setApiDeployWarnings(result.warnings)
344+
}
330345
await refetchDeployedState()
331346
} catch (error: unknown) {
332347
logger.error('Error redeploying workflow:', { error })
@@ -338,6 +353,7 @@ export function DeployModal({
338353
const handleCloseModal = useCallback(() => {
339354
setChatSubmitting(false)
340355
setApiDeployError(null)
356+
setApiDeployWarnings([])
341357
onOpenChange(false)
342358
}, [onOpenChange])
343359

@@ -479,6 +495,14 @@ export function DeployModal({
479495
<div>{apiDeployError}</div>
480496
</div>
481497
)}
498+
{apiDeployWarnings.length > 0 && (
499+
<div className='mb-3 rounded-[4px] border border-amber-500/30 bg-amber-500/10 p-3 text-amber-700 dark:text-amber-400 text-sm'>
500+
<div className='font-semibold'>Deployment Warning</div>
501+
{apiDeployWarnings.map((warning, index) => (
502+
<div key={index}>{warning}</div>
503+
))}
504+
</div>
505+
)}
482506
<ModalTabsContent value='general'>
483507
<GeneralDeploy
484508
workflowId={workflowId}

apps/sim/hooks/queries/deployments.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ interface DeployWorkflowResult {
237237
isDeployed: boolean
238238
deployedAt?: string
239239
apiKey?: string
240+
warnings?: string[]
240241
}
241242

242243
/**
@@ -272,6 +273,7 @@ export function useDeployWorkflow() {
272273
isDeployed: data.isDeployed ?? false,
273274
deployedAt: data.deployedAt,
274275
apiKey: data.apiKey,
276+
warnings: data.warnings,
275277
}
276278
},
277279
onSuccess: (data, variables) => {
@@ -360,6 +362,7 @@ interface ActivateVersionVariables {
360362
interface ActivateVersionResult {
361363
deployedAt?: string
362364
apiKey?: string
365+
warnings?: string[]
363366
}
364367

365368
/**

apps/sim/lib/webhooks/deploy.ts

Lines changed: 40 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,12 @@ interface TriggerSaveError {
3232
interface TriggerSaveResult {
3333
success: boolean
3434
error?: TriggerSaveError
35+
warnings?: string[]
36+
}
37+
38+
interface CredentialSetSyncResult {
39+
error: TriggerSaveError | null
40+
warnings: string[]
3541
}
3642

3743
interface SaveTriggerWebhooksInput {
@@ -258,7 +264,7 @@ async function syncCredentialSetWebhooks(params: {
258264
providerConfig: Record<string, unknown>
259265
requestId: string
260266
deploymentVersionId?: string
261-
}): Promise<TriggerSaveError | null> {
267+
}): Promise<CredentialSetSyncResult> {
262268
const {
263269
workflowId,
264270
blockId,
@@ -271,7 +277,7 @@ async function syncCredentialSetWebhooks(params: {
271277

272278
const credentialSetId = providerConfig.credentialSetId as string | undefined
273279
if (!credentialSetId) {
274-
return null
280+
return { error: null, warnings: [] }
275281
}
276282

277283
const oauthProviderId = getProviderIdFromServiceId(provider)
@@ -290,10 +296,23 @@ async function syncCredentialSetWebhooks(params: {
290296
deploymentVersionId,
291297
})
292298

299+
const warnings: string[] = []
300+
301+
if (syncResult.failed.length > 0) {
302+
const failedCount = syncResult.failed.length
303+
const totalCount = syncResult.webhooks.length + failedCount
304+
warnings.push(
305+
`${failedCount} of ${totalCount} credentials in the set failed to sync for ${provider}. Some team members may not receive triggers.`
306+
)
307+
}
308+
293309
if (syncResult.webhooks.length === 0) {
294310
return {
295-
message: `No valid credentials found in credential set for ${provider}. Please connect accounts and try again.`,
296-
status: 400,
311+
error: {
312+
message: `No valid credentials found in credential set for ${provider}. Please connect accounts and try again.`,
313+
status: 400,
314+
},
315+
warnings,
297316
}
298317
}
299318

@@ -307,16 +326,19 @@ async function syncCredentialSetWebhooks(params: {
307326
if (!success) {
308327
await db.delete(webhook).where(eq(webhook.id, wh.id))
309328
return {
310-
message: `Failed to configure ${provider} polling. Please check account permissions.`,
311-
status: 500,
329+
error: {
330+
message: `Failed to configure ${provider} polling. Please check account permissions.`,
331+
status: 500,
332+
},
333+
warnings,
312334
}
313335
}
314336
}
315337
}
316338
}
317339
}
318340

319-
return null
341+
return { error: null, warnings }
320342
}
321343

322344
/**
@@ -503,14 +525,16 @@ export async function saveTriggerWebhooksForDeploy({
503525
await db.delete(webhook).where(inArray(webhook.id, idsToDelete))
504526
}
505527

528+
const collectedWarnings: string[] = []
529+
506530
for (const block of blocksNeedingCredentialSetSync) {
507531
const config = webhookConfigs.get(block.id)
508532
if (!config) continue
509533

510534
const { provider, providerConfig, triggerPath } = config
511535

512536
try {
513-
const credentialSetError = await syncCredentialSetWebhooks({
537+
const syncResult = await syncCredentialSetWebhooks({
514538
workflowId,
515539
blockId: block.id,
516540
provider,
@@ -520,9 +544,13 @@ export async function saveTriggerWebhooksForDeploy({
520544
deploymentVersionId,
521545
})
522546

523-
if (credentialSetError) {
547+
if (syncResult.warnings.length > 0) {
548+
collectedWarnings.push(...syncResult.warnings)
549+
}
550+
551+
if (syncResult.error) {
524552
await restorePreviousSubscriptions()
525-
return { success: false, error: credentialSetError }
553+
return { success: false, error: syncResult.error, warnings: collectedWarnings }
526554
}
527555
} catch (error: any) {
528556
logger.error(`[${requestId}] Failed to create webhook for ${block.id}`, error)
@@ -533,6 +561,7 @@ export async function saveTriggerWebhooksForDeploy({
533561
message: error?.message || 'Failed to save trigger configuration',
534562
status: 500,
535563
},
564+
warnings: collectedWarnings,
536565
}
537566
}
538567
}
@@ -708,7 +737,7 @@ export async function saveTriggerWebhooksForDeploy({
708737
}
709738
}
710739

711-
return { success: true }
740+
return { success: true, warnings: collectedWarnings.length > 0 ? collectedWarnings : undefined }
712741
}
713742

714743
/**

apps/sim/lib/webhooks/gmail-polling-service.ts

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,7 @@ export async function pollGmailWebhooks() {
161161
const metadata = webhookData.providerConfig as any
162162
const credentialId: string | undefined = metadata?.credentialId
163163
const userId: string | undefined = metadata?.userId
164-
const credentialSetId: string | undefined = metadata?.credentialSetId
164+
const credentialSetId: string | undefined = webhookData.credentialSetId ?? undefined
165165

166166
if (!credentialId && !userId) {
167167
logger.error(`[${requestId}] Missing credential info for webhook ${webhookId}`)
@@ -697,7 +697,6 @@ async function processEmails(
697697
method: 'POST',
698698
headers: {
699699
'Content-Type': 'application/json',
700-
'X-Webhook-Secret': webhookData.secret || '',
701700
'User-Agent': 'Sim/1.0',
702701
},
703702
body: JSON.stringify(payload),
@@ -766,17 +765,21 @@ async function markEmailAsRead(accessToken: string, messageId: string) {
766765
}
767766

768767
async function updateWebhookLastChecked(webhookId: string, timestamp: string, historyId?: string) {
769-
const result = await db.select().from(webhook).where(eq(webhook.id, webhookId))
770-
const existingConfig = (result[0]?.providerConfig as Record<string, any>) || {}
771-
await db
772-
.update(webhook)
773-
.set({
774-
providerConfig: {
775-
...existingConfig,
776-
lastCheckedTimestamp: timestamp,
777-
...(historyId ? { historyId } : {}),
778-
} as any,
779-
updatedAt: new Date(),
780-
})
781-
.where(eq(webhook.id, webhookId))
768+
try {
769+
const result = await db.select().from(webhook).where(eq(webhook.id, webhookId))
770+
const existingConfig = (result[0]?.providerConfig as Record<string, any>) || {}
771+
await db
772+
.update(webhook)
773+
.set({
774+
providerConfig: {
775+
...existingConfig,
776+
lastCheckedTimestamp: timestamp,
777+
...(historyId ? { historyId } : {}),
778+
} as any,
779+
updatedAt: new Date(),
780+
})
781+
.where(eq(webhook.id, webhookId))
782+
} catch (error) {
783+
logger.error(`Error updating webhook ${webhookId} last checked timestamp:`, error)
784+
}
782785
}

0 commit comments

Comments
 (0)