diff --git a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx index 53e0ff6f0e2..927768fa5bb 100644 --- a/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx +++ b/apps/studio/components/interfaces/Settings/Logs/PreviewFilterPanel.tsx @@ -1,24 +1,71 @@ -import { Eye, EyeOff, RefreshCw, Search, Terminal, X } from 'lucide-react' +import { Eye, EyeOff, RefreshCw, Search, Terminal } from 'lucide-react' import Link from 'next/link' import { useRouter } from 'next/router' -import { useEffect, useState } from 'react' +import { useEffect, useMemo, useState, useCallback, useRef } from 'react' +import dayjs from 'dayjs' import { useParams } from 'common' -import { ButtonTooltip } from 'components/ui/ButtonTooltip' import CSVButton from 'components/ui/CSVButton' import DatabaseSelector from 'components/ui/DatabaseSelector' import { useLoadBalancersQuery } from 'data/read-replicas/load-balancers-query' import { IS_PLATFORM } from 'lib/constants' -import { Button, Input, Tooltip, TooltipContent, TooltipTrigger, cn } from 'ui' +import { Button, Tooltip, TooltipContent, TooltipTrigger, cn } from 'ui' +import { Calendar } from 'ui' import DatePickers from './Logs.DatePickers' import { FILTER_OPTIONS, LOG_ROUTES_WITH_REPLICA_SUPPORT, LogsTableName, PREVIEWER_DATEPICKER_HELPERS, + SQL_FILTER_TEMPLATES, } from './Logs.constants' import type { Filters, LogSearchCallback, LogTemplate } from './Logs.types' -import LogsFilterPopover from './LogsFilterPopover' +import { FilterBar } from 'ui-patterns/FilterBar' +import type { + FilterGroup, + FilterCondition, + FilterProperty, + CustomOptionProps, +} from 'ui-patterns/FilterBar' + +interface CustomDateRangePickerProps { + value?: { from: Date; to?: Date } + onChange: (range: { from: Date; to?: Date } | undefined) => void +} + +function CustomDateRangePicker({ onChange, onCancel }: CustomOptionProps) { + const [dateRange, setDateRange] = useState() + + return ( +
+ +
+ + +
+
+ ) +} interface PreviewFilterPanelProps { defaultSearchValue?: string @@ -43,9 +90,31 @@ interface PreviewFilterPanelProps { className?: string } -/** - * Logs control panel header + wrapper - */ +function useDebounce void>(callback: T, delay: number) { + const timeoutRef = useRef() + + useEffect(() => { + return () => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + } + }, []) + + return useCallback( + (...args: Parameters) => { + if (timeoutRef.current) { + clearTimeout(timeoutRef.current) + } + + timeoutRef.current = setTimeout(() => { + callback(...args) + }, delay) + }, + [callback, delay] + ) as T +} + const PreviewFilterPanel = ({ isLoading, newCount, @@ -68,163 +137,223 @@ const PreviewFilterPanel = ({ }: PreviewFilterPanelProps) => { const router = useRouter() const { ref } = useParams() - const [search, setSearch] = useState('') const { data: loadBalancers } = useLoadBalancersQuery({ projectRef: ref }) - // [Joshen] These are the routes tested that can show replica logs const showDatabaseSelector = IS_PLATFORM && LOG_ROUTES_WITH_REPLICA_SUPPORT.includes(router.pathname) - const hasEdits = search !== defaultSearchValue + const filterProperties = useMemo(() => { + const tableFilters = FILTER_OPTIONS[table] + if (!tableFilters) return [] - // Sync local state with provided default value - useEffect(() => { - if (search !== defaultSearchValue) { - setSearch(defaultSearchValue) + const properties: FilterProperty[] = [] + + // Add search query as a special filter + properties.push({ + label: 'Search', + name: 'search_query', + type: 'string' as const, + operators: ['='], + }) + + // Add date range filter + properties.push({ + label: 'Time Range', + name: 'timerange', + type: 'string' as const, + operators: ['='], + options: [ + { + label: 'Last 15 minutes', + value: `${dayjs().subtract(15, 'minute').toISOString()}|${dayjs().toISOString()}`, + }, + { + label: 'Last 30 minutes', + value: `${dayjs().subtract(30, 'minute').toISOString()}|${dayjs().toISOString()}`, + }, + { + label: 'Last hour', + value: `${dayjs().subtract(1, 'hour').toISOString()}|${dayjs().toISOString()}`, + }, + { + label: 'Last 3 hours', + value: `${dayjs().subtract(3, 'hour').toISOString()}|${dayjs().toISOString()}`, + }, + { + label: 'Last 24 hours', + value: `${dayjs().subtract(24, 'hour').toISOString()}|${dayjs().toISOString()}`, + }, + { + label: 'Custom Range...', + component: (props: CustomOptionProps) => , + }, + ], + }) + + // Add table-specific filters + Object.entries(tableFilters).forEach(([key, filterSet]) => { + properties.push({ + label: filterSet.label, + name: filterSet.key, + type: 'string' as const, + operators: ['='], + options: filterSet.options.map((option) => ({ + label: option.label, + value: option.key, + description: option.description, + })), + }) + }) + + return properties + }, [table]) + + // Convert Logs.Filters to FilterBar.FilterGroup + const filterBarFilters = useMemo(() => { + const conditions: FilterCondition[] = [] + + // Handle search query as a special case + if (filters.search_query) { + conditions.push({ + propertyName: 'search_query', + operator: '=', + value: String(filters.search_query), + }) } - }, [defaultSearchValue]) - const RefreshButton = () => ( - - - - - - - {showDatabaseSelector ? ( -
- - - - - - Open query in Logs Explorer - - - 0 - ? [{ id: `${ref}-all`, name: 'API Load Balancer' }] - : [] - : [] - } - /> + + + {showDatabaseSelector ? ( + <> + + + + + + Open query in Logs Explorer + + + 0 + ? [{ id: `${ref}-all`, name: 'API Load Balancer' }] + : [] + : [] + } + /> + + ) : ( + + )}
- ) : ( - - )} + ) }