Skip to content

Commit 819c0cc

Browse files
committed
improvement(logs): added filtering by trigger type
1 parent 38afdeb commit 819c0cc

7 files changed

Lines changed: 194 additions & 2 deletions

File tree

apps/sim/app/api/logs/route.test.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,7 @@ describe('Workflow Logs API Route', () => {
175175
id: 'workflowLogs.id',
176176
workflowId: 'workflowLogs.workflowId',
177177
level: 'workflowLogs.level',
178+
trigger: 'workflowLogs.trigger',
178179
createdAt: 'workflowLogs.createdAt',
179180
message: 'workflowLogs.message',
180181
executionId: 'workflowLogs.executionId',
@@ -470,5 +471,61 @@ describe('Workflow Logs API Route', () => {
470471
expect(data.data).toHaveLength(2)
471472
expect(data.data.every((log: any) => log.executionId === 'exec-1')).toBe(true)
472473
})
474+
475+
it('should filter logs by single trigger type', async () => {
476+
const apiLogs = mockWorkflowLogs.filter((log) => log.trigger === 'api')
477+
setupDatabaseMock({ logs: apiLogs })
478+
479+
const url = new URL('http://localhost:3000/api/logs?triggers=api')
480+
const req = new Request(url.toString())
481+
482+
const { GET } = await import('./route')
483+
const response = await GET(req as any)
484+
const data = await response.json()
485+
486+
expect(response.status).toBe(200)
487+
expect(data.data).toHaveLength(1)
488+
expect(data.data[0].trigger).toBe('api')
489+
})
490+
491+
it('should filter logs by multiple trigger types', async () => {
492+
const manualAndApiLogs = mockWorkflowLogs.filter(
493+
(log) => log.trigger === 'manual' || log.trigger === 'api'
494+
)
495+
setupDatabaseMock({ logs: manualAndApiLogs })
496+
497+
const url = new URL('http://localhost:3000/api/logs?triggers=manual,api')
498+
const req = new Request(url.toString())
499+
500+
const { GET } = await import('./route')
501+
const response = await GET(req as any)
502+
const data = await response.json()
503+
504+
expect(response.status).toBe(200)
505+
expect(data.data).toHaveLength(3)
506+
expect(data.data.every((log: any) => ['manual', 'api'].includes(log.trigger))).toBe(true)
507+
})
508+
509+
it('should combine trigger filter with other filters', async () => {
510+
const filteredLogs = mockWorkflowLogs.filter(
511+
(log) => log.trigger === 'manual' && log.level === 'info' && log.workflowId === 'workflow-1'
512+
)
513+
setupDatabaseMock({ logs: filteredLogs })
514+
515+
const url = new URL(
516+
'http://localhost:3000/api/logs?triggers=manual&level=info&workflowIds=workflow-1'
517+
)
518+
const req = new Request(url.toString())
519+
520+
const { GET } = await import('./route')
521+
const response = await GET(req as any)
522+
const data = await response.json()
523+
524+
expect(response.status).toBe(200)
525+
expect(data.data).toHaveLength(1)
526+
expect(data.data[0].trigger).toBe('manual')
527+
expect(data.data[0].level).toBe('info')
528+
expect(data.data[0].workflowId).toBe('workflow-1')
529+
})
473530
})
474531
})

apps/sim/app/api/logs/route.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const QueryParamsSchema = z.object({
1717
offset: z.coerce.number().optional().default(0),
1818
level: z.string().optional(),
1919
workflowIds: z.string().optional(), // Comma-separated list of workflow IDs
20+
triggers: z.string().optional(), // Comma-separated list of trigger types
2021
startDate: z.string().optional(),
2122
endDate: z.string().optional(),
2223
search: z.string().optional(),
@@ -79,6 +80,18 @@ export async function GET(request: NextRequest) {
7980
conditions = and(conditions, eq(workflowLogs.level, params.level))
8081
}
8182

83+
if (params.triggers) {
84+
const triggerTypes = params.triggers.split(',').map((trigger) => trigger.trim())
85+
if (triggerTypes.length === 1) {
86+
conditions = and(conditions, eq(workflowLogs.trigger, triggerTypes[0]))
87+
} else {
88+
conditions = and(
89+
conditions,
90+
or(...triggerTypes.map((trigger) => eq(workflowLogs.trigger, trigger)))
91+
)
92+
}
93+
}
94+
8295
if (params.startDate) {
8396
const startDate = new Date(params.startDate)
8497
conditions = and(conditions, gte(workflowLogs.createdAt, startDate))
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Check, ChevronDown } from 'lucide-react'
2+
import { Button } from '@/components/ui/button'
3+
import {
4+
DropdownMenu,
5+
DropdownMenuContent,
6+
DropdownMenuItem,
7+
DropdownMenuSeparator,
8+
DropdownMenuTrigger,
9+
} from '@/components/ui/dropdown-menu'
10+
import { useFilterStore } from '@/app/w/logs/stores/store'
11+
import type { TriggerType } from '../../../stores/types'
12+
13+
export default function Trigger() {
14+
const { triggers, toggleTrigger, setTriggers } = useFilterStore()
15+
const triggerOptions: { value: TriggerType; label: string; color?: string }[] = [
16+
{ value: 'manual', label: 'Manual', color: 'bg-secondary' },
17+
{ value: 'api', label: 'API', color: 'bg-blue-500' },
18+
{ value: 'webhook', label: 'Webhook', color: 'bg-orange-500' },
19+
{ value: 'schedule', label: 'Schedule', color: 'bg-green-500' },
20+
{ value: 'chat', label: 'Chat', color: 'bg-purple-500' },
21+
]
22+
23+
// Get display text for the dropdown button
24+
const getSelectedTriggersText = () => {
25+
if (triggers.length === 0) return 'All triggers'
26+
if (triggers.length === 1) {
27+
const selected = triggerOptions.find((t) => t.value === triggers[0])
28+
return selected ? selected.label : 'All triggers'
29+
}
30+
return `${triggers.length} triggers selected`
31+
}
32+
33+
// Check if a trigger is selected
34+
const isTriggerSelected = (trigger: TriggerType) => {
35+
return triggers.includes(trigger)
36+
}
37+
38+
// Clear all selections
39+
const clearSelections = () => {
40+
setTriggers([])
41+
}
42+
43+
return (
44+
<DropdownMenu>
45+
<DropdownMenuTrigger asChild>
46+
<Button variant='outline' size='sm' className='w-full justify-between font-normal text-sm'>
47+
{getSelectedTriggersText()}
48+
<ChevronDown className='ml-2 h-4 w-4 text-muted-foreground' />
49+
</Button>
50+
</DropdownMenuTrigger>
51+
<DropdownMenuContent align='start' className='w-[180px]'>
52+
<DropdownMenuItem
53+
key='all'
54+
onSelect={(e) => {
55+
e.preventDefault()
56+
clearSelections()
57+
}}
58+
className='flex cursor-pointer items-center justify-between p-2 text-sm'
59+
>
60+
<span>All triggers</span>
61+
{triggers.length === 0 && <Check className='h-4 w-4 text-primary' />}
62+
</DropdownMenuItem>
63+
64+
<DropdownMenuSeparator />
65+
66+
{triggerOptions.map((triggerItem) => (
67+
<DropdownMenuItem
68+
key={triggerItem.value}
69+
onSelect={(e) => {
70+
e.preventDefault()
71+
toggleTrigger(triggerItem.value)
72+
}}
73+
className='flex cursor-pointer items-center justify-between p-2 text-sm'
74+
>
75+
<div className='flex items-center'>
76+
{triggerItem.color && (
77+
<div className={`mr-2 h-2 w-2 rounded-full ${triggerItem.color}`} />
78+
)}
79+
{triggerItem.label}
80+
</div>
81+
{isTriggerSelected(triggerItem.value) && <Check className='h-4 w-4 text-primary' />}
82+
</DropdownMenuItem>
83+
))}
84+
</DropdownMenuContent>
85+
</DropdownMenu>
86+
)
87+
}

apps/sim/app/w/logs/components/filters/filters.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { useUserSubscription } from '@/hooks/use-user-subscription'
77
import FilterSection from './components/filter-section'
88
import Level from './components/level'
99
import Timeline from './components/timeline'
10+
import Trigger from './components/trigger'
1011
import Workflow from './components/workflow'
1112

1213
/**
@@ -58,6 +59,9 @@ export function Filters() {
5859
{/* Level Filter */}
5960
<FilterSection title='Level' defaultOpen={true} content={<Level />} />
6061

62+
{/* Trigger Filter */}
63+
<FilterSection title='Trigger' defaultOpen={true} content={<Trigger />} />
64+
6165
{/* Workflow Filter */}
6266
<FilterSection title='Workflow' defaultOpen={true} content={<Workflow />} />
6367
</div>

apps/sim/app/w/logs/logs.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ export default function Logs() {
7373
level,
7474
workflowIds,
7575
searchQuery,
76+
triggers,
7677
} = useFilterStore()
7778

7879
const [selectedLog, setSelectedLog] = useState<WorkflowLog | null>(null)
@@ -225,6 +226,7 @@ export default function Logs() {
225226
level,
226227
workflowIds,
227228
searchQuery,
229+
triggers,
228230
setPage,
229231
setHasMore,
230232
setLoading,

apps/sim/app/w/logs/stores/store.ts

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import { create } from 'zustand'
2-
import type { FilterState } from './types'
2+
import type { FilterState, TriggerType } from './types'
33

44
export const useFilterStore = create<FilterState>((set, get) => ({
55
logs: [],
66
timeRange: 'All time',
77
level: 'all',
88
workflowIds: [],
99
searchQuery: '',
10+
triggers: [],
1011
loading: true,
1112
error: null,
1213
page: 1,
@@ -57,6 +58,25 @@ export const useFilterStore = create<FilterState>((set, get) => ({
5758
get().resetPagination()
5859
},
5960

61+
setTriggers: (triggers: TriggerType[]) => {
62+
set({ triggers })
63+
get().resetPagination()
64+
},
65+
66+
toggleTrigger: (trigger: TriggerType) => {
67+
const currentTriggers = [...get().triggers]
68+
const index = currentTriggers.indexOf(trigger)
69+
70+
if (index === -1) {
71+
currentTriggers.push(trigger)
72+
} else {
73+
currentTriggers.splice(index, 1)
74+
}
75+
76+
set({ triggers: currentTriggers })
77+
get().resetPagination()
78+
},
79+
6080
setLoading: (loading) => set({ loading }),
6181

6282
setError: (error) => set({ error }),
@@ -71,7 +91,7 @@ export const useFilterStore = create<FilterState>((set, get) => ({
7191

7292
// Build query parameters for server-side filtering
7393
buildQueryParams: (page: number, limit: number) => {
74-
const { timeRange, level, workflowIds, searchQuery } = get()
94+
const { timeRange, level, workflowIds, searchQuery, triggers } = get()
7595
const params = new URLSearchParams()
7696

7797
params.set('includeWorkflow', 'true')
@@ -83,6 +103,11 @@ export const useFilterStore = create<FilterState>((set, get) => ({
83103
params.set('level', level)
84104
}
85105

106+
// Add trigger filter
107+
if (triggers.length > 0) {
108+
params.set('triggers', triggers.join(','))
109+
}
110+
86111
// Add workflow filter
87112
if (workflowIds.length > 0) {
88113
params.set('workflowIds', workflowIds.join(','))

apps/sim/app/w/logs/stores/types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ export interface LogsResponse {
8383

8484
export type TimeRange = 'Past 30 minutes' | 'Past hour' | 'Past 24 hours' | 'All time'
8585
export type LogLevel = 'error' | 'info' | 'all'
86+
export type TriggerType = 'chat' | 'api' | 'webhook' | 'manual' | 'schedule' | 'all'
8687

8788
export interface FilterState {
8889
// Original logs from API
@@ -93,6 +94,7 @@ export interface FilterState {
9394
level: LogLevel
9495
workflowIds: string[]
9596
searchQuery: string
97+
triggers: TriggerType[]
9698

9799
// Loading state
98100
loading: boolean
@@ -110,6 +112,8 @@ export interface FilterState {
110112
setWorkflowIds: (workflowIds: string[]) => void
111113
toggleWorkflowId: (workflowId: string) => void
112114
setSearchQuery: (query: string) => void
115+
setTriggers: (triggers: TriggerType[]) => void
116+
toggleTrigger: (trigger: TriggerType) => void
113117
setLoading: (loading: boolean) => void
114118
setError: (error: string | null) => void
115119
setPage: (page: number) => void

0 commit comments

Comments
 (0)