Skip to content

Commit 04cd837

Browse files
fix(notifs): inactivity polling filters, consolidate trigger types, minor consistency issue with filter parsing (#2452)
* fix(notifs-slac): display name for account * fix inactivity polling check * consolidate trigger types * remove redundant defaults * fix
1 parent c23130a commit 04cd837

File tree

11 files changed

+159
-60
lines changed

11 files changed

+159
-60
lines changed

apps/sim/app/api/auth/accounts/route.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,17 @@ export async function GET(request: NextRequest) {
3232
.from(account)
3333
.where(and(...whereConditions))
3434

35-
return NextResponse.json({ accounts })
35+
// Use the user's email as the display name (consistent with credential selector)
36+
const userEmail = session.user.email
37+
38+
const accountsWithDisplayName = accounts.map((acc) => ({
39+
id: acc.id,
40+
accountId: acc.accountId,
41+
providerId: acc.providerId,
42+
displayName: userEmail || acc.providerId,
43+
}))
44+
45+
return NextResponse.json({ accounts: accountsWithDisplayName })
3646
} catch (error) {
3747
logger.error('Failed to fetch accounts', { error })
3848
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { processInputFileFields } from '@/lib/execution/files'
1111
import { preprocessExecution } from '@/lib/execution/preprocessing'
1212
import { createLogger } from '@/lib/logs/console/logger'
1313
import { LoggingSession } from '@/lib/logs/execution/logging-session'
14+
import { ALL_TRIGGER_TYPES } from '@/lib/logs/types'
1415
import { executeWorkflowCore } from '@/lib/workflows/executor/execution-core'
1516
import { type ExecutionEvent, encodeSSEEvent } from '@/lib/workflows/executor/execution-events'
1617
import { PauseResumeManager } from '@/lib/workflows/executor/human-in-the-loop-manager'
@@ -30,7 +31,7 @@ const logger = createLogger('WorkflowExecuteAPI')
3031

3132
const ExecuteWorkflowSchema = z.object({
3233
selectedOutputs: z.array(z.string()).optional().default([]),
33-
triggerType: z.enum(['api', 'webhook', 'schedule', 'manual', 'chat']).optional(),
34+
triggerType: z.enum(ALL_TRIGGER_TYPES).optional(),
3435
stream: z.boolean().optional(),
3536
useDraftState: z.boolean().optional(),
3637
input: z.any().optional(),

apps/sim/app/api/workspaces/[id]/notifications/[notificationId]/route.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@ import { z } from 'zod'
66
import { getSession } from '@/lib/auth'
77
import { encryptSecret } from '@/lib/core/security/encryption'
88
import { createLogger } from '@/lib/logs/console/logger'
9+
import { ALL_TRIGGER_TYPES } from '@/lib/logs/types'
910
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1011
import { MAX_EMAIL_RECIPIENTS, MAX_WORKFLOW_IDS } from '../constants'
1112

1213
const logger = createLogger('WorkspaceNotificationAPI')
1314

1415
const levelFilterSchema = z.array(z.enum(['info', 'error']))
15-
const triggerFilterSchema = z.array(z.enum(['api', 'webhook', 'schedule', 'manual', 'chat']))
16+
const triggerFilterSchema = z.array(z.enum(ALL_TRIGGER_TYPES))
1617

1718
const alertRuleSchema = z.enum([
1819
'consecutive_failures',

apps/sim/app/api/workspaces/[id]/notifications/route.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,14 +7,15 @@ import { z } from 'zod'
77
import { getSession } from '@/lib/auth'
88
import { encryptSecret } from '@/lib/core/security/encryption'
99
import { createLogger } from '@/lib/logs/console/logger'
10+
import { ALL_TRIGGER_TYPES } from '@/lib/logs/types'
1011
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
1112
import { MAX_EMAIL_RECIPIENTS, MAX_NOTIFICATIONS_PER_TYPE, MAX_WORKFLOW_IDS } from './constants'
1213

1314
const logger = createLogger('WorkspaceNotificationsAPI')
1415

1516
const notificationTypeSchema = z.enum(['webhook', 'email', 'slack'])
1617
const levelFilterSchema = z.array(z.enum(['info', 'error']))
17-
const triggerFilterSchema = z.array(z.enum(['api', 'webhook', 'schedule', 'manual', 'chat']))
18+
const triggerFilterSchema = z.array(z.enum(ALL_TRIGGER_TYPES))
1819

1920
const alertRuleSchema = z.enum([
2021
'consecutive_failures',
@@ -80,7 +81,7 @@ const createNotificationSchema = z
8081
workflowIds: z.array(z.string()).max(MAX_WORKFLOW_IDS).default([]),
8182
allWorkflows: z.boolean().default(false),
8283
levelFilter: levelFilterSchema.default(['info', 'error']),
83-
triggerFilter: triggerFilterSchema.default(['api', 'webhook', 'schedule', 'manual', 'chat']),
84+
triggerFilter: triggerFilterSchema.default([...ALL_TRIGGER_TYPES]),
8485
includeFinalOutput: z.boolean().default(false),
8586
includeTraceSpans: z.boolean().default(false),
8687
includeRateLimits: z.boolean().default(false),

apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/components/slack-channel-selector/slack-channel-selector.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,8 @@ export function SlackChannelSelector({
104104
disabled={disabled || channels.length === 0}
105105
isLoading={isLoading}
106106
error={fetchError}
107+
searchable
108+
searchPlaceholder='Search channels...'
107109
/>
108110
{selectedChannel && !fetchError && (
109111
<p className='text-[12px] text-[var(--text-muted)]'>

apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/notifications/notifications.tsx

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import { SlackIcon } from '@/components/icons'
2222
import { Skeleton } from '@/components/ui'
2323
import { cn } from '@/lib/core/utils/cn'
2424
import { createLogger } from '@/lib/logs/console/logger'
25+
import { ALL_TRIGGER_TYPES, type TriggerType } from '@/lib/logs/types'
2526
import { quickValidateEmail } from '@/lib/messaging/email/validation'
2627
import {
2728
type NotificationSubscription,
@@ -43,7 +44,6 @@ const PRIMARY_BUTTON_STYLES =
4344

4445
type NotificationType = 'webhook' | 'email' | 'slack'
4546
type LogLevel = 'info' | 'error'
46-
type TriggerType = 'api' | 'webhook' | 'schedule' | 'manual' | 'chat'
4747
type AlertRule =
4848
| 'none'
4949
| 'consecutive_failures'
@@ -84,7 +84,6 @@ interface NotificationSettingsProps {
8484
}
8585

8686
const LOG_LEVELS: LogLevel[] = ['info', 'error']
87-
const TRIGGER_TYPES: TriggerType[] = ['api', 'webhook', 'schedule', 'manual', 'chat']
8887

8988
function formatAlertConfigLabel(config: {
9089
rule: AlertRule
@@ -137,7 +136,7 @@ export function NotificationSettings({
137136
workflowIds: [] as string[],
138137
allWorkflows: true,
139138
levelFilter: ['info', 'error'] as LogLevel[],
140-
triggerFilter: ['api', 'webhook', 'schedule', 'manual', 'chat'] as TriggerType[],
139+
triggerFilter: [...ALL_TRIGGER_TYPES] as TriggerType[],
141140
includeFinalOutput: false,
142141
includeTraceSpans: false,
143142
includeRateLimits: false,
@@ -207,7 +206,7 @@ export function NotificationSettings({
207206
workflowIds: [],
208207
allWorkflows: true,
209208
levelFilter: ['info', 'error'],
210-
triggerFilter: ['api', 'webhook', 'schedule', 'manual', 'chat'],
209+
triggerFilter: [...ALL_TRIGGER_TYPES],
211210
includeFinalOutput: false,
212211
includeTraceSpans: false,
213212
includeRateLimits: false,
@@ -768,7 +767,7 @@ export function NotificationSettings({
768767
<Combobox
769768
options={slackAccounts.map((acc) => ({
770769
value: acc.id,
771-
label: acc.accountId,
770+
label: acc.displayName || 'Slack Workspace',
772771
}))}
773772
value={formData.slackAccountId}
774773
onChange={(value) => {
@@ -859,7 +858,7 @@ export function NotificationSettings({
859858
<div className='flex flex-col gap-[8px]'>
860859
<Label className='text-[var(--text-secondary)]'>Trigger Type Filters</Label>
861860
<Combobox
862-
options={TRIGGER_TYPES.map((trigger) => ({
861+
options={ALL_TRIGGER_TYPES.map((trigger) => ({
863862
label: trigger.charAt(0).toUpperCase() + trigger.slice(1),
864863
value: trigger,
865864
}))}

apps/sim/hooks/use-slack-accounts.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ interface SlackAccount {
44
id: string
55
accountId: string
66
providerId: string
7+
displayName?: string
78
}
89

910
interface UseSlackAccountsResult {

apps/sim/lib/logs/events.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -81,8 +81,8 @@ export async function emitWorkflowExecutionCompleted(log: WorkflowExecutionLog):
8181
)
8282

8383
for (const subscription of subscriptions) {
84-
const levelMatches = subscription.levelFilter?.includes(log.level) ?? true
85-
const triggerMatches = subscription.triggerFilter?.includes(log.trigger) ?? true
84+
const levelMatches = subscription.levelFilter.includes(log.level)
85+
const triggerMatches = subscription.triggerFilter.includes(log.trigger)
8686

8787
if (!levelMatches || !triggerMatches) {
8888
logger.debug(`Skipping subscription ${subscription.id} due to filter mismatch`)
@@ -98,6 +98,7 @@ export async function emitWorkflowExecutionCompleted(log: WorkflowExecutionLog):
9898
status: log.level === 'error' ? 'error' : 'success',
9999
durationMs: log.totalDurationMs || 0,
100100
cost: (log.cost as { total?: number })?.total || 0,
101+
triggerFilter: subscription.triggerFilter,
101102
}
102103

103104
const shouldAlert = await shouldTriggerAlert(alertConfig, context, subscription.lastAlertAt)

apps/sim/lib/logs/types.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,8 +51,11 @@ export interface ExecutionEnvironment {
5151
workspaceId: string
5252
}
5353

54+
export const ALL_TRIGGER_TYPES = ['api', 'webhook', 'schedule', 'manual', 'chat'] as const
55+
export type TriggerType = (typeof ALL_TRIGGER_TYPES)[number]
56+
5457
export interface ExecutionTrigger {
55-
type: 'api' | 'webhook' | 'schedule' | 'manual' | 'chat' | string
58+
type: TriggerType | string
5659
source: string
5760
data?: Record<string, unknown>
5861
timestamp: string

apps/sim/lib/notifications/alert-rules.ts

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { db } from '@sim/db'
22
import { workflowExecutionLogs } from '@sim/db/schema'
3-
import { and, avg, count, desc, eq, gte } from 'drizzle-orm'
3+
import { and, avg, count, desc, eq, gte, inArray } from 'drizzle-orm'
44
import { createLogger } from '@/lib/logs/console/logger'
55

66
const logger = createLogger('AlertRules')
@@ -135,25 +135,29 @@ export function isInCooldown(lastAlertAt: Date | null): boolean {
135135
return new Date() < cooldownEnd
136136
}
137137

138-
/**
139-
* Context passed to alert check functions
140-
*/
141138
export interface AlertCheckContext {
142139
workflowId: string
143140
executionId: string
144141
status: 'success' | 'error'
145142
durationMs: number
146143
cost: number
144+
triggerFilter: string[]
147145
}
148146

149-
/**
150-
* Check if consecutive failures threshold is met
151-
*/
152-
async function checkConsecutiveFailures(workflowId: string, threshold: number): Promise<boolean> {
147+
async function checkConsecutiveFailures(
148+
workflowId: string,
149+
threshold: number,
150+
triggerFilter: string[]
151+
): Promise<boolean> {
153152
const recentLogs = await db
154153
.select({ level: workflowExecutionLogs.level })
155154
.from(workflowExecutionLogs)
156-
.where(eq(workflowExecutionLogs.workflowId, workflowId))
155+
.where(
156+
and(
157+
eq(workflowExecutionLogs.workflowId, workflowId),
158+
inArray(workflowExecutionLogs.trigger, triggerFilter)
159+
)
160+
)
157161
.orderBy(desc(workflowExecutionLogs.createdAt))
158162
.limit(threshold)
159163

@@ -162,13 +166,11 @@ async function checkConsecutiveFailures(workflowId: string, threshold: number):
162166
return recentLogs.every((log) => log.level === 'error')
163167
}
164168

165-
/**
166-
* Check if failure rate exceeds threshold
167-
*/
168169
async function checkFailureRate(
169170
workflowId: string,
170171
ratePercent: number,
171-
windowHours: number
172+
windowHours: number,
173+
triggerFilter: string[]
172174
): Promise<boolean> {
173175
const windowStart = new Date(Date.now() - windowHours * 60 * 60 * 1000)
174176

@@ -181,7 +183,8 @@ async function checkFailureRate(
181183
.where(
182184
and(
183185
eq(workflowExecutionLogs.workflowId, workflowId),
184-
gte(workflowExecutionLogs.createdAt, windowStart)
186+
gte(workflowExecutionLogs.createdAt, windowStart),
187+
inArray(workflowExecutionLogs.trigger, triggerFilter)
185188
)
186189
)
187190
.orderBy(workflowExecutionLogs.createdAt)
@@ -206,14 +209,12 @@ function checkLatencyThreshold(durationMs: number, thresholdMs: number): boolean
206209
return durationMs > thresholdMs
207210
}
208211

209-
/**
210-
* Check if execution duration is significantly above average
211-
*/
212212
async function checkLatencySpike(
213213
workflowId: string,
214214
currentDurationMs: number,
215215
spikePercent: number,
216-
windowHours: number
216+
windowHours: number,
217+
triggerFilter: string[]
217218
): Promise<boolean> {
218219
const windowStart = new Date(Date.now() - windowHours * 60 * 60 * 1000)
219220

@@ -226,7 +227,8 @@ async function checkLatencySpike(
226227
.where(
227228
and(
228229
eq(workflowExecutionLogs.workflowId, workflowId),
229-
gte(workflowExecutionLogs.createdAt, windowStart)
230+
gte(workflowExecutionLogs.createdAt, windowStart),
231+
inArray(workflowExecutionLogs.trigger, triggerFilter)
230232
)
231233
)
232234

@@ -248,13 +250,11 @@ function checkCostThreshold(cost: number, thresholdDollars: number): boolean {
248250
return cost > thresholdDollars
249251
}
250252

251-
/**
252-
* Check if error count exceeds threshold within window
253-
*/
254253
async function checkErrorCount(
255254
workflowId: string,
256255
threshold: number,
257-
windowHours: number
256+
windowHours: number,
257+
triggerFilter: string[]
258258
): Promise<boolean> {
259259
const windowStart = new Date(Date.now() - windowHours * 60 * 60 * 1000)
260260

@@ -265,17 +265,15 @@ async function checkErrorCount(
265265
and(
266266
eq(workflowExecutionLogs.workflowId, workflowId),
267267
eq(workflowExecutionLogs.level, 'error'),
268-
gte(workflowExecutionLogs.createdAt, windowStart)
268+
gte(workflowExecutionLogs.createdAt, windowStart),
269+
inArray(workflowExecutionLogs.trigger, triggerFilter)
269270
)
270271
)
271272

272273
const errorCount = result[0]?.count || 0
273274
return errorCount >= threshold
274275
}
275276

276-
/**
277-
* Evaluates if an alert should be triggered based on the configuration
278-
*/
279277
export async function shouldTriggerAlert(
280278
config: AlertConfig,
281279
context: AlertCheckContext,
@@ -287,16 +285,21 @@ export async function shouldTriggerAlert(
287285
}
288286

289287
const { rule } = config
290-
const { workflowId, status, durationMs, cost } = context
288+
const { workflowId, status, durationMs, cost, triggerFilter } = context
291289

292290
switch (rule) {
293291
case 'consecutive_failures':
294292
if (status !== 'error') return false
295-
return checkConsecutiveFailures(workflowId, config.consecutiveFailures!)
293+
return checkConsecutiveFailures(workflowId, config.consecutiveFailures!, triggerFilter)
296294

297295
case 'failure_rate':
298296
if (status !== 'error') return false
299-
return checkFailureRate(workflowId, config.failureRatePercent!, config.windowHours!)
297+
return checkFailureRate(
298+
workflowId,
299+
config.failureRatePercent!,
300+
config.windowHours!,
301+
triggerFilter
302+
)
300303

301304
case 'latency_threshold':
302305
return checkLatencyThreshold(durationMs, config.durationThresholdMs!)
@@ -306,19 +309,24 @@ export async function shouldTriggerAlert(
306309
workflowId,
307310
durationMs,
308311
config.latencySpikePercent!,
309-
config.windowHours!
312+
config.windowHours!,
313+
triggerFilter
310314
)
311315

312316
case 'cost_threshold':
313317
return checkCostThreshold(cost, config.costThresholdDollars!)
314318

315319
case 'no_activity':
316-
// no_activity alerts are handled by the hourly polling job, not execution events
317320
return false
318321

319322
case 'error_count':
320323
if (status !== 'error') return false
321-
return checkErrorCount(workflowId, config.errorCountThreshold!, config.windowHours!)
324+
return checkErrorCount(
325+
workflowId,
326+
config.errorCountThreshold!,
327+
config.windowHours!,
328+
triggerFilter
329+
)
322330

323331
default:
324332
logger.warn(`Unknown alert rule: ${rule}`)

0 commit comments

Comments
 (0)