From 8aa8d33c537cd4b66b93ac435779afad092066ea Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 13 Mar 2026 18:01:32 -0700 Subject: [PATCH 1/2] fix(notifications): polish modal styling, credential display, and trigger filters - Show credential display name instead of raw account ID in Slack account selector - Fix label styling to use default Label component (text-primary) for consistency - Fix modal body spacing with proper top padding after tab bar - Replace list-card skeleton with form-field skeleton matching actual layout - Replace custom "Select a Slack account first" box with disabled Combobox (dependsOn pattern) - Use proper Label component in WorkflowSelector with consistent gap spacing - Add overflow badge pattern (slice + +N) to level and trigger filter badges - Use dynamic trigger options from getTriggerOptions() instead of hardcoded CORE_TRIGGER_TYPES - Relax API validation to accept integration trigger types (z.string instead of z.enum) - Deduplicate account rows from credential leftJoin in accounts API - Extract getTriggerOptions() to module-level constants to avoid per-render calls Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/sim/app/api/auth/accounts/route.ts | 15 ++- .../notifications/[notificationId]/route.ts | 3 +- .../workspaces/[id]/notifications/route.ts | 5 +- .../slack-channel-selector.tsx | 16 +-- .../workflow-selector/workflow-selector.tsx | 10 +- .../notifications/notifications.tsx | 103 ++++++++++-------- 6 files changed, 81 insertions(+), 71 deletions(-) diff --git a/apps/sim/app/api/auth/accounts/route.ts b/apps/sim/app/api/auth/accounts/route.ts index aebb5d6a28..c48102fd09 100644 --- a/apps/sim/app/api/auth/accounts/route.ts +++ b/apps/sim/app/api/auth/accounts/route.ts @@ -1,5 +1,5 @@ import { db } from '@sim/db' -import { account } from '@sim/db/schema' +import { account, credential } from '@sim/db/schema' import { createLogger } from '@sim/logger' import { and, desc, eq } from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' @@ -28,16 +28,25 @@ export async function GET(request: NextRequest) { id: account.id, accountId: account.accountId, providerId: account.providerId, + credentialDisplayName: credential.displayName, }) .from(account) + .leftJoin(credential, eq(credential.accountId, account.id)) .where(and(...whereConditions)) .orderBy(desc(account.updatedAt)) - const accountsWithDisplayName = accounts.map((acc) => ({ + const seen = new Map() + for (const acc of accounts) { + if (!seen.has(acc.id)) { + seen.set(acc.id, acc) + } + } + + const accountsWithDisplayName = Array.from(seen.values()).map((acc) => ({ id: acc.id, accountId: acc.accountId, providerId: acc.providerId, - displayName: acc.accountId || acc.providerId, + displayName: acc.credentialDisplayName || acc.providerId, })) return NextResponse.json({ accounts: accountsWithDisplayName }) diff --git a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts index ddc2730018..96acb82811 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts @@ -8,13 +8,12 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' -import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types' import { MAX_EMAIL_RECIPIENTS, MAX_WORKFLOW_IDS } from '../constants' const logger = createLogger('WorkspaceNotificationAPI') const levelFilterSchema = z.array(z.enum(['info', 'error'])) -const triggerFilterSchema = z.array(z.enum(CORE_TRIGGER_TYPES)) +const triggerFilterSchema = z.array(z.string().min(1)) const alertRuleSchema = z.enum([ 'consecutive_failures', diff --git a/apps/sim/app/api/workspaces/[id]/notifications/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/route.ts index 6fc8f4866c..4243dc35ce 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/route.ts @@ -9,14 +9,13 @@ import { AuditAction, AuditResourceType, recordAudit } from '@/lib/audit/log' import { getSession } from '@/lib/auth' import { encryptSecret } from '@/lib/core/security/encryption' import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils' -import { CORE_TRIGGER_TYPES } from '@/stores/logs/filters/types' import { MAX_EMAIL_RECIPIENTS, MAX_NOTIFICATIONS_PER_TYPE, MAX_WORKFLOW_IDS } from './constants' const logger = createLogger('WorkspaceNotificationsAPI') const notificationTypeSchema = z.enum(['webhook', 'email', 'slack']) const levelFilterSchema = z.array(z.enum(['info', 'error'])) -const triggerFilterSchema = z.array(z.enum(CORE_TRIGGER_TYPES)) +const triggerFilterSchema = z.array(z.string().min(1)) const alertRuleSchema = z.enum([ 'consecutive_failures', @@ -82,7 +81,7 @@ const createNotificationSchema = z workflowIds: z.array(z.string()).max(MAX_WORKFLOW_IDS).default([]), allWorkflows: z.boolean().default(false), levelFilter: levelFilterSchema.default(['info', 'error']), - triggerFilter: triggerFilterSchema.default([...CORE_TRIGGER_TYPES]), + triggerFilter: triggerFilterSchema, includeFinalOutput: z.boolean().default(false), includeTraceSpans: z.boolean().default(false), includeRateLimits: z.boolean().default(false), diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx index c551fd456c..de9903c7ce 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx @@ -79,14 +79,6 @@ export function SlackChannelSelector({ const selectedChannel = channels.find((c) => c.id === value) - if (!accountId) { - return ( -
-

Select a Slack account first

-
- ) - } - const handleChange = (channelId: string) => { const channel = channels.find((c) => c.id === channelId) onChange(channelId, channel?.name || '') @@ -99,9 +91,13 @@ export function SlackChannelSelector({ value={value} onChange={handleChange} placeholder={ - channels.length === 0 && !isLoading ? 'No channels available' : 'Select channel...' + !accountId + ? 'Select an account first...' + : channels.length === 0 && !isLoading + ? 'No channels available' + : 'Select channel...' } - disabled={disabled || channels.length === 0} + disabled={disabled || !accountId || channels.length === 0} isLoading={isLoading} error={fetchError} searchable diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/workflow-selector/workflow-selector.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/workflow-selector/workflow-selector.tsx index 35f40657e0..1596e2f088 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/workflow-selector/workflow-selector.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/workflow-selector/workflow-selector.tsx @@ -2,7 +2,7 @@ import { useMemo } from 'react' import { X } from 'lucide-react' -import { Badge, Combobox, type ComboboxOption } from '@/components/emcn' +import { Badge, Combobox, type ComboboxOption, Label } from '@/components/emcn' import { Skeleton } from '@/components/ui' import { useWorkflows } from '@/hooks/queries/workflows' @@ -103,16 +103,16 @@ export function WorkflowSelector({ if (isLoading) { return ( -
- Workflows +
+
) } return ( -
- Workflows +
+ t.value) + type NotificationType = 'webhook' | 'email' | 'slack' type LogLevel = 'info' | 'error' type AlertRule = @@ -137,7 +140,7 @@ export const NotificationSettings = memo(function NotificationSettings({ workflowIds: [] as string[], allWorkflows: true, levelFilter: ['info', 'error'] as LogLevel[], - triggerFilter: [...CORE_TRIGGER_TYPES] as CoreTriggerType[], + triggerFilter: ALL_TRIGGER_VALUES, includeFinalOutput: false, includeTraceSpans: false, includeRateLimits: false, @@ -205,7 +208,7 @@ export const NotificationSettings = memo(function NotificationSettings({ workflowIds: [], allWorkflows: true, levelFilter: ['info', 'error'], - triggerFilter: [...CORE_TRIGGER_TYPES], + triggerFilter: ALL_TRIGGER_VALUES, includeFinalOutput: false, includeTraceSpans: false, includeRateLimits: false, @@ -477,7 +480,7 @@ export const NotificationSettings = memo(function NotificationSettings({ workflowIds: subscription.workflowIds || [], allWorkflows: subscription.allWorkflows, levelFilter: subscription.levelFilter as LogLevel[], - triggerFilter: subscription.triggerFilter as CoreTriggerType[], + triggerFilter: subscription.triggerFilter, includeFinalOutput: subscription.includeFinalOutput, includeTraceSpans: subscription.includeTraceSpans, includeRateLimits: subscription.includeRateLimits, @@ -626,7 +629,7 @@ export const NotificationSettings = memo(function NotificationSettings({ {activeTab === 'webhook' && ( <>
- +
- + - + addEmail(value)} @@ -673,7 +676,7 @@ export const NotificationSettings = memo(function NotificationSettings({ {activeTab === 'slack' && ( <>
- + {isLoadingSlackAccounts ? ( ) : slackAccounts.length === 0 ? ( @@ -719,7 +722,7 @@ export const NotificationSettings = memo(function NotificationSettings({
{slackAccounts.length > 0 && (
- + - + ({ label: level.charAt(0).toUpperCase() + level.slice(1), @@ -755,8 +758,8 @@ export const NotificationSettings = memo(function NotificationSettings({ placeholder='Select log levels...' overlayContent={ formData.levelFilter.length > 0 ? ( -
- {formData.levelFilter.map((level) => ( +
+ {formData.levelFilter.slice(0, 2).map((level) => ( ))} + {formData.levelFilter.length > 2 && ( + + +{formData.levelFilter.length - 2} + + )}
) : null } @@ -786,23 +797,23 @@ export const NotificationSettings = memo(function NotificationSettings({
- + ({ - label: trigger.charAt(0).toUpperCase() + trigger.slice(1), - value: trigger, + options={TRIGGER_OPTIONS.map((t) => ({ + label: t.label, + value: t.value, }))} multiSelect multiSelectValues={formData.triggerFilter} onMultiSelectChange={(values) => { - setFormData({ ...formData, triggerFilter: values as CoreTriggerType[] }) + setFormData({ ...formData, triggerFilter: values }) setFormErrors({ ...formErrors, triggerFilter: '' }) }} placeholder='Select trigger types...' overlayContent={ formData.triggerFilter.length > 0 ? (
- {formData.triggerFilter.map((trigger) => ( + {formData.triggerFilter.slice(0, 6).map((trigger) => ( ))} + {formData.triggerFilter.length > 6 && ( + + +{formData.triggerFilter.length - 6} + + )}
) : null } @@ -832,7 +851,7 @@ export const NotificationSettings = memo(function NotificationSettings({
- +
- + ({ value: rule.value, @@ -929,7 +948,7 @@ export const NotificationSettings = memo(function NotificationSettings({ {formData.alertRule === 'consecutive_failures' && (
- +
- +
- + - +
- +
- + - + - +
- +
- +
{isLoading ? ( -
- {[1, 2].map((i) => ( -
-
-
- -
- - -
-
-
- - - -
-
+
+ {[120, 80, 100, 90].map((labelWidth, i) => ( +
+ +
))}
@@ -1213,7 +1220,7 @@ export const NotificationSettings = memo(function NotificationSettings({ Slack - + {renderTabContent()} {renderTabContent()} {renderTabContent()} From 2d4f25e8d19b8fc5426be22b0997ff671272ff0f Mon Sep 17 00:00:00 2001 From: waleed Date: Fri, 13 Mar 2026 18:06:48 -0700 Subject: [PATCH 2/2] fix(notifications): address PR review feedback - Restore accountId in displayName fallback chain (credentialDisplayName || accountId || providerId) - Add .default([]) to triggerFilter in create schema to preserve backward compatibility - Treat empty triggerFilter as "match all" in notification matching logic - Remove unreachable overflow badge for levelFilter (only 2 possible values) Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/sim/app/api/auth/accounts/route.ts | 2 +- .../app/api/workspaces/[id]/notifications/route.ts | 2 +- .../components/notifications/notifications.tsx | 12 ++---------- apps/sim/lib/logs/events.ts | 3 ++- 4 files changed, 6 insertions(+), 13 deletions(-) diff --git a/apps/sim/app/api/auth/accounts/route.ts b/apps/sim/app/api/auth/accounts/route.ts index c48102fd09..67847afbfa 100644 --- a/apps/sim/app/api/auth/accounts/route.ts +++ b/apps/sim/app/api/auth/accounts/route.ts @@ -46,7 +46,7 @@ export async function GET(request: NextRequest) { id: acc.id, accountId: acc.accountId, providerId: acc.providerId, - displayName: acc.credentialDisplayName || acc.providerId, + displayName: acc.credentialDisplayName || acc.accountId || acc.providerId, })) return NextResponse.json({ accounts: accountsWithDisplayName }) diff --git a/apps/sim/app/api/workspaces/[id]/notifications/route.ts b/apps/sim/app/api/workspaces/[id]/notifications/route.ts index 4243dc35ce..6c46cef900 100644 --- a/apps/sim/app/api/workspaces/[id]/notifications/route.ts +++ b/apps/sim/app/api/workspaces/[id]/notifications/route.ts @@ -81,7 +81,7 @@ const createNotificationSchema = z workflowIds: z.array(z.string()).max(MAX_WORKFLOW_IDS).default([]), allWorkflows: z.boolean().default(false), levelFilter: levelFilterSchema.default(['info', 'error']), - triggerFilter: triggerFilterSchema, + triggerFilter: triggerFilterSchema.default([]), includeFinalOutput: z.boolean().default(false), includeTraceSpans: z.boolean().default(false), includeRateLimits: z.boolean().default(false), diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx index 690a19a625..5370e31e75 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx @@ -758,8 +758,8 @@ export const NotificationSettings = memo(function NotificationSettings({ placeholder='Select log levels...' overlayContent={ formData.levelFilter.length > 0 ? ( -
- {formData.levelFilter.slice(0, 2).map((level) => ( +
+ {formData.levelFilter.map((level) => ( ))} - {formData.levelFilter.length > 2 && ( - - +{formData.levelFilter.length - 2} - - )}
) : null } diff --git a/apps/sim/lib/logs/events.ts b/apps/sim/lib/logs/events.ts index 17ee7bd678..bbf17b2320 100644 --- a/apps/sim/lib/logs/events.ts +++ b/apps/sim/lib/logs/events.ts @@ -76,7 +76,8 @@ export async function emitWorkflowExecutionCompleted(log: WorkflowExecutionLog): for (const subscription of subscriptions) { const levelMatches = subscription.levelFilter.includes(log.level) - const triggerMatches = subscription.triggerFilter.includes(log.trigger) + const triggerMatches = + subscription.triggerFilter.length === 0 || subscription.triggerFilter.includes(log.trigger) if (!levelMatches || !triggerMatches) { logger.debug(`Skipping subscription ${subscription.id} due to filter mismatch`)