From fdbf8be79b461b8675fd4ba85c244eeef0fec7f7 Mon Sep 17 00:00:00 2001 From: Waleed Date: Tue, 16 Dec 2025 18:18:46 -0800 Subject: [PATCH] fix(logs-search): restored support for log search queries (#2417) --- apps/sim/app/api/logs/route.ts | 120 +++++++++----- .../logs-toolbar/components/search/search.tsx | 154 +++++++----------- .../logs/hooks/use-search-state.ts | 53 +----- apps/sim/lib/logs/query-parser.ts | 8 +- apps/sim/lib/logs/search-suggestions.ts | 33 +--- 5 files changed, 156 insertions(+), 212 deletions(-) diff --git a/apps/sim/app/api/logs/route.ts b/apps/sim/app/api/logs/route.ts index 9651e9a28..333b35974 100644 --- a/apps/sim/app/api/logs/route.ts +++ b/apps/sim/app/api/logs/route.ts @@ -6,7 +6,22 @@ import { workflowDeploymentVersion, workflowExecutionLogs, } from '@sim/db/schema' -import { and, desc, eq, gte, inArray, isNotNull, isNull, lte, or, type SQL, sql } from 'drizzle-orm' +import { + and, + desc, + eq, + gt, + gte, + inArray, + isNotNull, + isNull, + lt, + lte, + ne, + or, + type SQL, + sql, +} from 'drizzle-orm' import { type NextRequest, NextResponse } from 'next/server' import { z } from 'zod' import { getSession } from '@/lib/auth' @@ -22,14 +37,19 @@ const QueryParamsSchema = z.object({ limit: z.coerce.number().optional().default(100), offset: z.coerce.number().optional().default(0), level: z.string().optional(), - workflowIds: z.string().optional(), // Comma-separated list of workflow IDs - folderIds: z.string().optional(), // Comma-separated list of folder IDs - triggers: z.string().optional(), // Comma-separated list of trigger types + workflowIds: z.string().optional(), + folderIds: z.string().optional(), + triggers: z.string().optional(), startDate: z.string().optional(), endDate: z.string().optional(), search: z.string().optional(), workflowName: z.string().optional(), folderName: z.string().optional(), + executionId: z.string().optional(), + costOperator: z.enum(['=', '>', '<', '>=', '<=', '!=']).optional(), + costValue: z.coerce.number().optional(), + durationOperator: z.enum(['=', '>', '<', '>=', '<=', '!=']).optional(), + durationValue: z.coerce.number().optional(), workspaceId: z.string(), }) @@ -49,7 +69,6 @@ export async function GET(request: NextRequest) { const { searchParams } = new URL(request.url) const params = QueryParamsSchema.parse(Object.fromEntries(searchParams.entries())) - // Conditionally select columns based on detail level to optimize performance const selectColumns = params.details === 'full' ? { @@ -63,9 +82,9 @@ export async function GET(request: NextRequest) { startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - executionData: workflowExecutionLogs.executionData, // Large field - only in full mode + executionData: workflowExecutionLogs.executionData, cost: workflowExecutionLogs.cost, - files: workflowExecutionLogs.files, // Large field - only in full mode + files: workflowExecutionLogs.files, createdAt: workflowExecutionLogs.createdAt, workflowName: workflow.name, workflowDescription: workflow.description, @@ -82,7 +101,6 @@ export async function GET(request: NextRequest) { deploymentVersionName: workflowDeploymentVersion.name, } : { - // Basic mode - exclude large fields for better performance id: workflowExecutionLogs.id, workflowId: workflowExecutionLogs.workflowId, executionId: workflowExecutionLogs.executionId, @@ -93,9 +111,9 @@ export async function GET(request: NextRequest) { startedAt: workflowExecutionLogs.startedAt, endedAt: workflowExecutionLogs.endedAt, totalDurationMs: workflowExecutionLogs.totalDurationMs, - executionData: sql`NULL`, // Exclude large execution data in basic mode + executionData: sql`NULL`, cost: workflowExecutionLogs.cost, - files: sql`NULL`, // Exclude files in basic mode + files: sql`NULL`, createdAt: workflowExecutionLogs.createdAt, workflowName: workflow.name, workflowDescription: workflow.description, @@ -109,7 +127,7 @@ export async function GET(request: NextRequest) { pausedTotalPauseCount: pausedExecutions.totalPauseCount, pausedResumedCount: pausedExecutions.resumedCount, deploymentVersion: workflowDeploymentVersion.version, - deploymentVersionName: sql`NULL`, // Only needed in full mode for details panel + deploymentVersionName: sql`NULL`, } const baseQuery = db @@ -139,34 +157,28 @@ export async function GET(request: NextRequest) { ) ) - // Build additional conditions for the query let conditions: SQL | undefined - // Filter by level with support for derived statuses (running, pending) if (params.level && params.level !== 'all') { const levels = params.level.split(',').filter(Boolean) const levelConditions: SQL[] = [] for (const level of levels) { if (level === 'error') { - // Direct database field levelConditions.push(eq(workflowExecutionLogs.level, 'error')) } else if (level === 'info') { - // Completed info logs only (not running, not pending) const condition = and( eq(workflowExecutionLogs.level, 'info'), isNotNull(workflowExecutionLogs.endedAt) ) if (condition) levelConditions.push(condition) } else if (level === 'running') { - // Running logs: info level with no endedAt const condition = and( eq(workflowExecutionLogs.level, 'info'), isNull(workflowExecutionLogs.endedAt) ) if (condition) levelConditions.push(condition) } else if (level === 'pending') { - // Pending logs: info level with pause status indicators const condition = and( eq(workflowExecutionLogs.level, 'info'), or( @@ -189,7 +201,6 @@ export async function GET(request: NextRequest) { } } - // Filter by specific workflow IDs if (params.workflowIds) { const workflowIds = params.workflowIds.split(',').filter(Boolean) if (workflowIds.length > 0) { @@ -197,7 +208,6 @@ export async function GET(request: NextRequest) { } } - // Filter by folder IDs if (params.folderIds) { const folderIds = params.folderIds.split(',').filter(Boolean) if (folderIds.length > 0) { @@ -205,7 +215,6 @@ export async function GET(request: NextRequest) { } } - // Filter by triggers if (params.triggers) { const triggers = params.triggers.split(',').filter(Boolean) if (triggers.length > 0 && !triggers.includes('all')) { @@ -213,7 +222,6 @@ export async function GET(request: NextRequest) { } } - // Filter by date range if (params.startDate) { conditions = and( conditions, @@ -224,33 +232,79 @@ export async function GET(request: NextRequest) { conditions = and(conditions, lte(workflowExecutionLogs.startedAt, new Date(params.endDate))) } - // Filter by search query if (params.search) { const searchTerm = `%${params.search}%` - // With message removed, restrict search to executionId only conditions = and(conditions, sql`${workflowExecutionLogs.executionId} ILIKE ${searchTerm}`) } - // Filter by workflow name (from advanced search input) if (params.workflowName) { const nameTerm = `%${params.workflowName}%` conditions = and(conditions, sql`${workflow.name} ILIKE ${nameTerm}`) } - // Filter by folder name (best-effort text match when present on workflows) if (params.folderName) { const folderTerm = `%${params.folderName}%` conditions = and(conditions, sql`${workflow.name} ILIKE ${folderTerm}`) } - // Execute the query using the optimized join + if (params.executionId) { + conditions = and(conditions, eq(workflowExecutionLogs.executionId, params.executionId)) + } + + if (params.costOperator && params.costValue !== undefined) { + const costField = sql`(${workflowExecutionLogs.cost}->>'total')::numeric` + switch (params.costOperator) { + case '=': + conditions = and(conditions, sql`${costField} = ${params.costValue}`) + break + case '>': + conditions = and(conditions, sql`${costField} > ${params.costValue}`) + break + case '<': + conditions = and(conditions, sql`${costField} < ${params.costValue}`) + break + case '>=': + conditions = and(conditions, sql`${costField} >= ${params.costValue}`) + break + case '<=': + conditions = and(conditions, sql`${costField} <= ${params.costValue}`) + break + case '!=': + conditions = and(conditions, sql`${costField} != ${params.costValue}`) + break + } + } + + if (params.durationOperator && params.durationValue !== undefined) { + const durationField = workflowExecutionLogs.totalDurationMs + switch (params.durationOperator) { + case '=': + conditions = and(conditions, eq(durationField, params.durationValue)) + break + case '>': + conditions = and(conditions, gt(durationField, params.durationValue)) + break + case '<': + conditions = and(conditions, lt(durationField, params.durationValue)) + break + case '>=': + conditions = and(conditions, gte(durationField, params.durationValue)) + break + case '<=': + conditions = and(conditions, lte(durationField, params.durationValue)) + break + case '!=': + conditions = and(conditions, ne(durationField, params.durationValue)) + break + } + } + const logs = await baseQuery .where(conditions) .orderBy(desc(workflowExecutionLogs.startedAt)) .limit(params.limit) .offset(params.offset) - // Get total count for pagination using the same join structure const countQuery = db .select({ count: sql`count(*)` }) .from(workflowExecutionLogs) @@ -279,13 +333,10 @@ export async function GET(request: NextRequest) { const count = countResult[0]?.count || 0 - // Block executions are now extracted from trace spans instead of separate table const blockExecutionsByExecution: Record = {} - // Create clean trace spans from block executions const createTraceSpans = (blockExecutions: any[]) => { return blockExecutions.map((block, index) => { - // For error blocks, include error information in the output let output = block.outputData if (block.status === 'error' && block.errorMessage) { output = { @@ -314,7 +365,6 @@ export async function GET(request: NextRequest) { }) } - // Extract cost information from block executions const extractCostSummary = (blockExecutions: any[]) => { let totalCost = 0 let totalInputCost = 0 @@ -333,7 +383,6 @@ export async function GET(request: NextRequest) { totalPromptTokens += block.cost.tokens?.prompt || 0 totalCompletionTokens += block.cost.tokens?.completion || 0 - // Track per-model costs if (block.cost.model) { if (!models.has(block.cost.model)) { models.set(block.cost.model, { @@ -363,34 +412,29 @@ export async function GET(request: NextRequest) { prompt: totalPromptTokens, completion: totalCompletionTokens, }, - models: Object.fromEntries(models), // Convert Map to object for JSON serialization + models: Object.fromEntries(models), } } - // Transform to clean log format with workflow data included const enhancedLogs = logs.map((log) => { const blockExecutions = blockExecutionsByExecution[log.executionId] || [] - // Only process trace spans and detailed cost in full mode let traceSpans = [] let finalOutput: any let costSummary = (log.cost as any) || { total: 0 } if (params.details === 'full' && log.executionData) { - // Use stored trace spans if available, otherwise create from block executions const storedTraceSpans = (log.executionData as any)?.traceSpans traceSpans = storedTraceSpans && Array.isArray(storedTraceSpans) && storedTraceSpans.length > 0 ? storedTraceSpans : createTraceSpans(blockExecutions) - // Prefer stored cost JSON; otherwise synthesize from blocks costSummary = log.cost && Object.keys(log.cost as any).length > 0 ? (log.cost as any) : extractCostSummary(blockExecutions) - // Include finalOutput if present on executionData try { const fo = (log.executionData as any)?.finalOutput if (fo !== undefined) finalOutput = fo diff --git a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx index 9f14118fa..27d23d797 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx +++ b/apps/sim/app/workspace/[workspaceId]/logs/components/logs-toolbar/components/search/search.tsx @@ -2,11 +2,9 @@ import { useEffect, useMemo, useRef, useState } from 'react' import { Search, X } from 'lucide-react' -import { useParams } from 'next/navigation' -import { Button, Popover, PopoverAnchor, PopoverContent } from '@/components/emcn' +import { Badge, Popover, PopoverAnchor, PopoverContent } from '@/components/emcn' import { cn } from '@/lib/core/utils/cn' -import { createLogger } from '@/lib/logs/console/logger' -import { getIntegrationMetadata } from '@/lib/logs/get-trigger-options' +import { getTriggerOptions } from '@/lib/logs/get-trigger-options' import { type ParsedFilter, parseQuery } from '@/lib/logs/query-parser' import { type FolderData, @@ -18,7 +16,15 @@ import { useSearchState } from '@/app/workspace/[workspaceId]/logs/hooks/use-sea import { useFolderStore } from '@/stores/folders/store' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' -const logger = createLogger('AutocompleteSearch') +function truncateFilterValue(field: string, value: string): string { + if ((field === 'executionId' || field === 'workflowId') && value.length > 12) { + return `...${value.slice(-6)}` + } + if (value.length > 20) { + return `${value.slice(0, 17)}...` + } + return value +} interface AutocompleteSearchProps { value: string @@ -35,11 +41,8 @@ export function AutocompleteSearch({ className, onOpenChange, }: AutocompleteSearchProps) { - const params = useParams() - const workspaceId = params.workspaceId as string const workflows = useWorkflowRegistry((state) => state.workflows) const folders = useFolderStore((state) => state.folders) - const [triggersData, setTriggersData] = useState([]) const workflowsData = useMemo(() => { return Object.values(workflows).map((w) => ({ @@ -56,32 +59,13 @@ export function AutocompleteSearch({ })) }, [folders]) - useEffect(() => { - if (!workspaceId) return - - const fetchTriggers = async () => { - try { - const response = await fetch(`/api/logs/triggers?workspaceId=${workspaceId}`) - if (!response.ok) return - - const data = await response.json() - const triggers: TriggerData[] = data.triggers.map((trigger: string) => { - const metadata = getIntegrationMetadata(trigger) - return { - value: trigger, - label: metadata.label, - color: metadata.color, - } - }) - - setTriggersData(triggers) - } catch (error) { - logger.error('Failed to fetch triggers:', error) - } - } - - fetchTriggers() - }, [workspaceId]) + const triggersData = useMemo(() => { + return getTriggerOptions().map((t) => ({ + value: t.value, + label: t.label, + color: t.color, + })) + }, []) const suggestionEngine = useMemo(() => { return new SearchSuggestions(workflowsData, foldersData, triggersData) @@ -103,7 +87,6 @@ export function AutocompleteSearch({ suggestions, sections, highlightedIndex, - highlightedBadgeIndex, inputRef, dropdownRef, handleInputChange, @@ -122,7 +105,6 @@ export function AutocompleteSearch({ const lastExternalValue = useRef(value) useEffect(() => { - // Only re-initialize if value changed externally (not from user typing) if (value !== lastExternalValue.current) { lastExternalValue.current = value const parsed = parseQuery(value) @@ -130,7 +112,6 @@ export function AutocompleteSearch({ } }, [value, initializeFromQuery]) - // Initial sync on mount useEffect(() => { if (value) { const parsed = parseQuery(value) @@ -189,40 +170,49 @@ export function AutocompleteSearch({
{/* Applied Filter Badges */} {appliedFilters.map((filter, index) => ( - + + ))} {/* Text Search Badge (if present) */} {hasTextSearch && ( - + + "{textSearch}" + + + )} {/* Input - only current typing */} @@ -261,9 +251,8 @@ export function AutocompleteSearch({ sideOffset={4} onOpenAutoFocus={(e) => e.preventDefault()} > -
+
{sections.length > 0 ? ( - // Multi-section layout
{/* Show all results (no header) */} {suggestions[0]?.category === 'show-all' && ( @@ -271,9 +260,9 @@ export function AutocompleteSearch({ key={suggestions[0].id} data-index={0} className={cn( - 'w-full px-3 py-1.5 text-left transition-colors focus:outline-none', - 'hover:bg-[var(--surface-9)] dark:hover:bg-[var(--surface-9)]', - highlightedIndex === 0 && 'bg-[var(--surface-9)] dark:bg-[var(--surface-9)]' + 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', + 'hover:bg-[var(--surface-9)]', + highlightedIndex === 0 && 'bg-[var(--surface-9)]' )} onMouseEnter={() => setHighlightedIndex(0)} onMouseDown={(e) => { @@ -287,7 +276,7 @@ export function AutocompleteSearch({ {sections.map((section) => (
-
+
{section.title}
{section.suggestions.map((suggestion) => { @@ -301,9 +290,9 @@ export function AutocompleteSearch({ key={suggestion.id} data-index={index} className={cn( - 'w-full px-3 py-1.5 text-left transition-colors focus:outline-none', - 'hover:bg-[var(--surface-9)] dark:hover:bg-[var(--surface-9)]', - isHighlighted && 'bg-[var(--surface-9)] dark:bg-[var(--surface-9)]' + 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', + 'hover:bg-[var(--surface-9)]', + isHighlighted && 'bg-[var(--surface-9)]' )} onMouseEnter={() => setHighlightedIndex(index)} onMouseDown={(e) => { @@ -312,19 +301,11 @@ export function AutocompleteSearch({ }} >
-
- {suggestion.category === 'trigger' && suggestion.color && ( -
- )} -
- {suggestion.label} -
+
+ {suggestion.label}
{suggestion.value !== suggestion.label && ( -
+
{suggestion.category === 'workflow' || suggestion.category === 'folder' ? `${suggestion.category}:` @@ -342,7 +323,7 @@ export function AutocompleteSearch({ // Single section layout
{suggestionType === 'filters' && ( -
+
SUGGESTED FILTERS
)} @@ -352,10 +333,9 @@ export function AutocompleteSearch({ key={suggestion.id} data-index={index} className={cn( - 'w-full px-3 py-1.5 text-left transition-colors focus:outline-none', - 'hover:bg-[var(--surface-9)] dark:hover:bg-[var(--surface-9)]', - index === highlightedIndex && - 'bg-[var(--surface-9)] dark:bg-[var(--surface-9)]' + 'w-full rounded-[6px] px-3 py-2 text-left transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--border-focus)]', + 'hover:bg-[var(--surface-9)]', + index === highlightedIndex && 'bg-[var(--surface-9)]' )} onMouseEnter={() => setHighlightedIndex(index)} onMouseDown={(e) => { @@ -364,17 +344,9 @@ export function AutocompleteSearch({ }} >
-
- {suggestion.category === 'trigger' && suggestion.color && ( -
- )} -
{suggestion.label}
-
+
{suggestion.label}
{suggestion.description && ( -
+
{suggestion.value}
)} diff --git a/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-search-state.ts b/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-search-state.ts index 92c6e476c..5d88372ea 100644 --- a/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-search-state.ts +++ b/apps/sim/app/workspace/[workspaceId]/logs/hooks/use-search-state.ts @@ -21,21 +21,15 @@ export function useSearchState({ const [currentInput, setCurrentInput] = useState('') const [textSearch, setTextSearch] = useState('') - // Dropdown state const [isOpen, setIsOpen] = useState(false) const [suggestions, setSuggestions] = useState([]) const [sections, setSections] = useState([]) const [highlightedIndex, setHighlightedIndex] = useState(-1) - // Badge interaction - const [highlightedBadgeIndex, setHighlightedBadgeIndex] = useState(null) - - // Refs const inputRef = useRef(null) const dropdownRef = useRef(null) const debounceRef = useRef(null) - // Update suggestions when input changes const updateSuggestions = useCallback( (input: string) => { const suggestionGroup = getSuggestions(input) @@ -55,13 +49,10 @@ export function useSearchState({ [getSuggestions] ) - // Handle input changes const handleInputChange = useCallback( (value: string) => { setCurrentInput(value) - setHighlightedBadgeIndex(null) // Clear badge highlight on any input - // Debounce suggestion updates if (debounceRef.current) { clearTimeout(debounceRef.current) } @@ -73,11 +64,9 @@ export function useSearchState({ [updateSuggestions, debounceMs] ) - // Handle suggestion selection const handleSuggestionSelect = useCallback( (suggestion: Suggestion) => { if (suggestion.category === 'show-all') { - // Treat as text search setTextSearch(suggestion.value) setCurrentInput('') setIsOpen(false) @@ -85,15 +74,12 @@ export function useSearchState({ return } - // Check if this is a filter-key suggestion (ends with ':') if (suggestion.category === 'filters' && suggestion.value.endsWith(':')) { - // Set input to the filter key and keep dropdown open for values setCurrentInput(suggestion.value) updateSuggestions(suggestion.value) return } - // For filter values, workflows, folders - add as a filter const newFilter: ParsedFilter = { field: suggestion.value.split(':')[0] as any, operator: '=', @@ -110,15 +96,12 @@ export function useSearchState({ setCurrentInput('') setTextSearch('') - // Notify parent onFiltersChange(updatedFilters, '') - // Focus back on input and reopen dropdown with empty suggestions if (inputRef.current) { inputRef.current.focus() } - // Show filter keys dropdown again after selection setTimeout(() => { updateSuggestions('') }, 50) @@ -126,12 +109,10 @@ export function useSearchState({ [appliedFilters, onFiltersChange, updateSuggestions] ) - // Remove a badge const removeBadge = useCallback( (index: number) => { const updatedFilters = appliedFilters.filter((_, i) => i !== index) setAppliedFilters(updatedFilters) - setHighlightedBadgeIndex(null) onFiltersChange(updatedFilters, textSearch) if (inputRef.current) { @@ -141,39 +122,22 @@ export function useSearchState({ [appliedFilters, textSearch, onFiltersChange] ) - // Handle keyboard navigation const handleKeyDown = useCallback( (event: React.KeyboardEvent) => { - // Backspace on empty input - badge deletion if (event.key === 'Backspace' && currentInput === '') { - event.preventDefault() - - if (highlightedBadgeIndex !== null) { - // Delete highlighted badge - removeBadge(highlightedBadgeIndex) - } else if (appliedFilters.length > 0) { - // Highlight last badge - setHighlightedBadgeIndex(appliedFilters.length - 1) + if (appliedFilters.length > 0) { + event.preventDefault() + removeBadge(appliedFilters.length - 1) } return } - // Clear badge highlight on any other key when not in dropdown navigation - if ( - highlightedBadgeIndex !== null && - !['ArrowDown', 'ArrowUp', 'Enter'].includes(event.key) - ) { - setHighlightedBadgeIndex(null) - } - - // Enter key if (event.key === 'Enter') { event.preventDefault() if (isOpen && highlightedIndex >= 0 && suggestions[highlightedIndex]) { handleSuggestionSelect(suggestions[highlightedIndex]) } else if (currentInput.trim()) { - // Submit current input as text search setTextSearch(currentInput.trim()) setCurrentInput('') setIsOpen(false) @@ -182,7 +146,6 @@ export function useSearchState({ return } - // Dropdown navigation if (!isOpen) return switch (event.key) { @@ -216,7 +179,6 @@ export function useSearchState({ }, [ currentInput, - highlightedBadgeIndex, appliedFilters, isOpen, highlightedIndex, @@ -227,12 +189,10 @@ export function useSearchState({ ] ) - // Handle focus const handleFocus = useCallback(() => { updateSuggestions(currentInput) }, [currentInput, updateSuggestions]) - // Handle blur const handleBlur = useCallback(() => { setTimeout(() => { setIsOpen(false) @@ -240,7 +200,6 @@ export function useSearchState({ }, 150) }, []) - // Clear all filters const clearAll = useCallback(() => { setAppliedFilters([]) setCurrentInput('') @@ -253,7 +212,6 @@ export function useSearchState({ } }, [onFiltersChange]) - // Initialize from external value (URL params, etc.) const initializeFromQuery = useCallback((query: string, filters: ParsedFilter[]) => { setAppliedFilters(filters) setTextSearch(query) @@ -261,7 +219,6 @@ export function useSearchState({ }, []) return { - // State appliedFilters, currentInput, textSearch, @@ -269,13 +226,10 @@ export function useSearchState({ suggestions, sections, highlightedIndex, - highlightedBadgeIndex, - // Refs inputRef, dropdownRef, - // Handlers handleInputChange, handleSuggestionSelect, handleKeyDown, @@ -285,7 +239,6 @@ export function useSearchState({ clearAll, initializeFromQuery, - // Setters for external control setHighlightedIndex, } } diff --git a/apps/sim/lib/logs/query-parser.ts b/apps/sim/lib/logs/query-parser.ts index 716ad3426..64f996b42 100644 --- a/apps/sim/lib/logs/query-parser.ts +++ b/apps/sim/lib/logs/query-parser.ts @@ -23,6 +23,8 @@ const FILTER_FIELDS = { workflow: 'string', trigger: 'string', execution: 'string', + executionId: 'string', + workflowId: 'string', id: 'string', cost: 'number', duration: 'number', @@ -215,11 +217,13 @@ export function queryToApiParams(parsedQuery: ParsedQuery): Record 0) { suggestions.push({ id: 'filter-key-workflow', @@ -249,12 +232,10 @@ export class SearchSuggestions { : null } - // Trigger filter values (core + integrations) if (key === 'trigger') { const allTriggers = this.getAllTriggers() const suggestions = allTriggers .filter((t) => !partial || t.label.toLowerCase().includes(partial.toLowerCase())) - .slice(0, 15) // Show more since we have core + integrations .map((t) => ({ id: `filter-value-trigger-${t.value}`, value: `trigger:${t.value}`, @@ -273,11 +254,9 @@ export class SearchSuggestions { : null } - // Workflow filter values if (key === 'workflow') { const suggestions = this.workflowsData .filter((w) => !partial || w.name.toLowerCase().includes(partial.toLowerCase())) - .slice(0, 8) .map((w) => ({ id: `filter-value-workflow-${w.id}`, value: `workflow:"${w.name}"`, @@ -295,11 +274,9 @@ export class SearchSuggestions { : null } - // Folder filter values if (key === 'folder') { const suggestions = this.foldersData .filter((f) => !partial || f.name.toLowerCase().includes(partial.toLowerCase())) - .slice(0, 8) .map((f) => ({ id: `filter-value-folder-${f.id}`, value: `folder:"${f.name}"`, @@ -326,7 +303,6 @@ export class SearchSuggestions { const sections: Array<{ title: string; suggestions: Suggestion[] }> = [] const allSuggestions: Suggestion[] = [] - // Show all results option const showAllSuggestion: Suggestion = { id: 'show-all', value: query, @@ -335,7 +311,6 @@ export class SearchSuggestions { } allSuggestions.push(showAllSuggestion) - // Match filter values (e.g., "info" → "Status: Info") const matchingFilterValues = this.getMatchingFilterValues(query) if (matchingFilterValues.length > 0) { sections.push({ @@ -345,7 +320,6 @@ export class SearchSuggestions { allSuggestions.push(...matchingFilterValues) } - // Match triggers const matchingTriggers = this.getMatchingTriggers(query) if (matchingTriggers.length > 0) { sections.push({ @@ -355,7 +329,6 @@ export class SearchSuggestions { allSuggestions.push(...matchingTriggers) } - // Match workflows const matchingWorkflows = this.getMatchingWorkflows(query) if (matchingWorkflows.length > 0) { sections.push({ @@ -365,7 +338,6 @@ export class SearchSuggestions { allSuggestions.push(...matchingWorkflows) } - // Match folders const matchingFolders = this.getMatchingFolders(query) if (matchingFolders.length > 0) { sections.push({ @@ -375,7 +347,6 @@ export class SearchSuggestions { allSuggestions.push(...matchingFolders) } - // Add filter keys if no specific matches if ( matchingFilterValues.length === 0 && matchingTriggers.length === 0 &&