diff --git a/apps/studio/components/interfaces/logs-new/LogTypeIcon.tsx b/apps/studio/components/interfaces/UnifiedLogs/LogTypeIcon.tsx similarity index 97% rename from apps/studio/components/interfaces/logs-new/LogTypeIcon.tsx rename to apps/studio/components/interfaces/UnifiedLogs/LogTypeIcon.tsx index 728052651a6..91dadb4a4cc 100644 --- a/apps/studio/components/interfaces/logs-new/LogTypeIcon.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/LogTypeIcon.tsx @@ -3,7 +3,7 @@ import { Auth, EdgeFunctions, Storage } from 'icons' import { BookHeart, Box, Cpu, Database, Globe } from 'lucide-react' import { cn, Tooltip, TooltipContent, TooltipTrigger } from 'ui' -import { type LOG_TYPES } from './schema' +import { type LOG_TYPES } from './UnifiedLogs.schema' interface LogTypeIconProps { type: (typeof LOG_TYPES)[number] diff --git a/apps/studio/components/interfaces/UnifiedLogs/TooltipLabel.tsx b/apps/studio/components/interfaces/UnifiedLogs/TooltipLabel.tsx new file mode 100644 index 00000000000..b29ae4c7946 --- /dev/null +++ b/apps/studio/components/interfaces/UnifiedLogs/TooltipLabel.tsx @@ -0,0 +1,10 @@ +import { getLevelLabel } from './UnifiedLogs.utils' + +export const TooltipLabel = ({ level }: { level: 'success' | 'warning' | 'error' }) => { + return ( +
+
{level}
+
{getLevelLabel(level)}
+
+ ) +} diff --git a/apps/studio/components/interfaces/logs-new/search-params.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.constants.tsx similarity index 58% rename from apps/studio/components/interfaces/logs-new/search-params.ts rename to apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.constants.tsx index 78711ac0513..00636b8a0f7 100644 --- a/apps/studio/components/interfaces/logs-new/search-params.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.constants.tsx @@ -1,30 +1,42 @@ import { createParser, - // createSearchParamsCache, // Removed for Pages Router compatibility - createSerializer, // Uncommented for Pages Router compatibility parseAsArrayOf, parseAsBoolean, parseAsInteger, parseAsString, parseAsStringLiteral, parseAsTimestamp, - type inferParserType, - useQueryStates, // Added for potential hook usage } from 'nuqs' -// Note: import from 'nuqs/server' comment removed -import { - ARRAY_DELIMITER, - RANGE_DELIMITER, - SLIDER_DELIMITER, - SORT_DELIMITER, -} from 'components/interfaces/DataTableDemo/lib/delimiters' -import { REGIONS } from 'components/interfaces/DataTableDemo/constants/region' -import { METHODS } from 'components/interfaces/DataTableDemo/constants/method' -import { LEVELS } from 'components/interfaces/DataTableDemo/constants/levels' -// https://logs.run/i?sort=latency.desc +import { ChartConfig } from 'ui' +import { TooltipLabel } from './TooltipLabel' -export const parseAsSort = createParser({ +export const CHART_CONFIG = { + success: { + label: , + color: 'hsl(var(--foreground-muted))', + }, + warning: { + label: , + color: 'hsl(var(--warning-default))', + }, + error: { + label: , + color: 'hsl(var(--destructive-default))', + }, +} satisfies ChartConfig + +export const ARRAY_DELIMITER = ',' +const SLIDER_DELIMITER = '-' +const SPACE_DELIMITER = '_' +export const RANGE_DELIMITER = '-' +export const SORT_DELIMITER = '.' + +const LEVELS = ['success', 'warning', 'error'] as const +const REGIONS = ['ams', 'fra', 'gru', 'hkg', 'iad', 'syd'] as const +const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] as const + +const parseAsSort = createParser({ parse(queryValue) { const [id, desc] = queryValue.split(SORT_DELIMITER) if (!id && !desc) return null @@ -35,7 +47,7 @@ export const parseAsSort = createParser({ }, }) -export const searchParamsParser = { +export const SEARCH_PARAMS_PARSER = { // CUSTOM FILTERS level: parseAsArrayOf(parseAsStringLiteral(LEVELS), ARRAY_DELIMITER), log_type: parseAsArrayOf(parseAsString, ARRAY_DELIMITER), @@ -62,14 +74,3 @@ export const searchParamsParser = { // REQUIRED FOR SELECTION uuid: parseAsString, } - -// Removed for Pages Router compatibility -// export const searchParamsCache = createSearchParamsCache(searchParamsParser) -export const searchParamsSerializer = createSerializer(searchParamsParser) // Uncommented - -export type SearchParamsType = inferParserType - -// Optional: Add a hook similar to the default demo if needed elsewhere -// export function useInfiniteSearchParams() { -// return useQueryStates(searchParamsParser) -// } diff --git a/apps/studio/components/interfaces/logs-new/schema.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.schema.ts similarity index 57% rename from apps/studio/components/interfaces/logs-new/schema.ts rename to apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.schema.ts index baae35c770d..b29e630f4ba 100644 --- a/apps/studio/components/interfaces/logs-new/schema.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.schema.ts @@ -1,34 +1,9 @@ -import { - ARRAY_DELIMITER, - RANGE_DELIMITER, - SLIDER_DELIMITER, -} from 'components/interfaces/DataTableDemo/lib/delimiters' +import { z } from 'zod' + +import { LEVELS } from './constants/levels' import { METHODS } from './constants/method' import { REGIONS } from './constants/region' -import { z } from 'zod' -import { LEVELS } from './constants/levels' - -// https://github.com/colinhacks/zod/issues/2985#issue-2008642190 -const stringToBoolean = z - .string() - .toLowerCase() - .transform((val) => { - try { - return JSON.parse(val) - } catch (e) { - console.log(e) - return undefined - } - }) - .pipe(z.boolean().optional()) - -// export const timingSchema = z.object({ -// 'timing.dns': z.number(), -// 'timing.connection': z.number(), -// 'timing.tls': z.number(), -// 'timing.ttfb': z.number(), -// 'timing.transfer': z.number(), -// }) +import { ARRAY_DELIMITER, RANGE_DELIMITER } from './UnifiedLogs.constants' export const LOG_TYPES = [ 'edge', @@ -50,26 +25,18 @@ export const columnSchema = z.object({ host: z.string(), pathname: z.string(), level: z.enum(LEVELS), - // latency: z.number(), status: z.number(), - // regions: z.enum(REGIONS).array(), date: z.date(), - // headers: z.record(z.string()), - // message: z.string().optional(), timestamp: z.number(), event_message: z.string().optional(), log_count: z.number().optional(), // used to count function logs for a given execution_id - // percentile: z.number().optional(), has_trace: z.boolean().optional(), logs: z.array(z.any()).optional(), // array of function logs auth_user: z.string().optional(), }) -// .merge(timingSchema) export type ColumnSchema = z.infer -// export type TimingSchema = z.infer -// TODO: can we get rid of this in favor of nuqs search-params? export const columnFilterSchema = z.object({ level: z .string() @@ -83,36 +50,6 @@ export const columnFilterSchema = z.object({ .optional(), host: z.string().optional(), pathname: z.string().optional(), - // latency: z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), - // 'timing.dns': z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), - // 'timing.connection': z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), - // 'timing.tls': z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), - // 'timing.ttfb': z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), - // 'timing.transfer': z - // .string() - // .transform((val) => val.split(SLIDER_DELIMITER)) - // .pipe(z.coerce.number().array().max(2)) - // .optional(), status: z .string() .transform((val) => val.split(ARRAY_DELIMITER)) diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts new file mode 100644 index 00000000000..d1f345f3731 --- /dev/null +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.types.ts @@ -0,0 +1,10 @@ +import { type inferParserType } from 'nuqs' + +import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants' + +export type UnifiedLogsMeta = { + logTypeCounts: Record + currentPercentiles: Record +} + +export type SearchParamsType = inferParserType diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts new file mode 100644 index 00000000000..67324e1ca29 --- /dev/null +++ b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogs.utils.ts @@ -0,0 +1,104 @@ +// @ts-ignore +import { _LEVELS } from '@/constants/levels' +import { type Table as TTable } from '@tanstack/react-table' +import { useQueryState } from 'nuqs' + +import { useHotKey } from 'hooks/ui/useHotKey' +import { useEffect, useMemo, useRef } from 'react' +import { cn } from 'ui' +import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants' +import { FacetMetadataSchema } from './UnifiedLogs.schema' + +export const useResetFocus = () => { + useHotKey(() => { + // FIXME: some dedicated div[tabindex="0"] do not auto-unblur (e.g. the DataTableFilterResetButton) + // REMINDER: we cannot just document.activeElement?.blur(); as the next tab will focus the next element in line, + // which is not what we want. We want to reset entirely. + document.body.setAttribute('tabindex', '0') + document.body.focus() + document.body.removeAttribute('tabindex') + }, '.') +} + +// TODO: make a BaseObject (incl. date and uuid e.g. for every upcoming branch of infinite table) +export const useLiveMode = (data: TData[]) => { + const [live] = useQueryState('live', SEARCH_PARAMS_PARSER.live) + // REMINDER: used to capture the live mode on timestamp + const liveTimestamp = useRef(live ? new Date().getTime() : undefined) + + useEffect(() => { + if (live) liveTimestamp.current = new Date().getTime() + else liveTimestamp.current = undefined + }, [live]) + + const anchorRow = useMemo(() => { + if (!live) return undefined + + const item = data.find((item) => { + // return first item that is there if not liveTimestamp + if (!liveTimestamp.current) return true + // return first item that is after the liveTimestamp + if (item.date.getTime() > liveTimestamp.current) return false + return true + // return first item if no liveTimestamp + }) + + return item + }, [live, data]) + + return { row: anchorRow, timestamp: liveTimestamp.current } +} + +export const getFacetedUniqueValues = (facets?: Record) => { + return (table: TTable, columnId: string) => { + return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || []) + } +} + +export const getFacetedMinMaxValues = (facets?: Record) => { + return (table: TTable, columnId: string) => { + const min = facets?.[columnId]?.min + const max = facets?.[columnId]?.max + if (typeof min === 'number' && typeof max === 'number') return [min, max] + if (typeof min === 'number') return [min, min] + if (typeof max === 'number') return [max, max] + return undefined + } +} + +export const getLevelLabel = (value: (typeof _LEVELS)[number]): string => { + switch (value) { + case 'success': + return '2xx' + case 'warning': + return '4xx' + case 'error': + return '5xx' + default: + return 'Unknown' + } +} + +export function getLevelRowClassName(value: (typeof _LEVELS)[number]): string { + switch (value) { + case 'success': + return '' + case 'warning': + return cn( + 'bg-warning/5 hover:bg-warning/10 data-[state=selected]:bg-warning/20 focus-visible:bg-warning/10', + 'dark:bg-warning/10 dark:hover:bg-warning/20 dark:data-[state=selected]:bg-warning/30 dark:focus-visible:bg-warning/20' + ) + case 'error': + return cn( + 'bg-destructive/5 hover:bg-destructive/10 data-[state=selected]:bg-destructive/20 focus-visible:bg-destructive/10', + 'dark:bg-error/10 dark:hover:bg-destructive/20 dark:data-[state=selected]:bg-destructive/30 dark:focus-visible:bg-destructive/20' + ) + case 'info': + return cn( + 'bg-info/5 hover:bg-info/10 data-[state=selected]:bg-info/20 focus-visible:bg-info/10', + 'dark:bg-info/10 dark:hover:bg-info/20 dark:data-[state=selected]:bg-info/30 dark:focus-visible:bg-info/20' + ) + default: + return '' + } +} diff --git a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogsPreviewer.tsx b/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogsPreviewer.tsx deleted file mode 100644 index 4d98010eebe..00000000000 --- a/apps/studio/components/interfaces/UnifiedLogs/UnifiedLogsPreviewer.tsx +++ /dev/null @@ -1,251 +0,0 @@ -import dayjs from 'dayjs' -import { Rewind } from 'lucide-react' -import { useRouter } from 'next/router' -import { PropsWithChildren, useEffect, useState } from 'react' - -import { useParams } from 'common' -import PreviewFilterPanel from 'components/interfaces/Settings/Logs/PreviewFilterPanel' -import LoadingOpacity from 'components/ui/LoadingOpacity' -import ShimmerLine from 'components/ui/ShimmerLine' -import useUnifiedLogs from 'hooks/analytics/useUnifiedLogs' -import { useLogsUrlState } from 'hooks/analytics/useLogsUrlState' -import { useSelectedLog } from 'hooks/analytics/useSelectedLog' -import useSingleLog from 'hooks/analytics/useSingleLog' -import { useFlag } from 'hooks/ui/useFlag' -import { Button } from 'ui' -import { LogsBarChart } from 'ui-patterns/LogsBarChart' -import LogTable from '../Settings/Logs/LogTable' -import { DatePickerValue } from '../Settings/Logs/Logs.DatePickers' -import { LogsTableName, PREVIEWER_DATEPICKER_HELPERS } from '../Settings/Logs/Logs.constants' -import type { - Filters, - LogSearchCallback, - LogTemplate, - QueryType, -} from '../Settings/Logs/Logs.types' -import { PreviewFilterPanelWithUniversal } from '../Settings/Logs/PreviewFilterPanelWithUniversal' - -interface UnifiedLogsPreviewerProps { - projectRef: string - filterOverride?: Filters - condensedLayout?: boolean - EmptyState?: React.ReactNode - filterPanelClassName?: string -} - -export const UnifiedLogsPreviewer = ({ - projectRef, - filterOverride, - condensedLayout = false, - children, - EmptyState, - filterPanelClassName, -}: PropsWithChildren) => { - const useUniversalFilterBar = useFlag('universalFilterBar') - const router = useRouter() - const { db } = useParams() - - const [showChart, setShowChart] = useState(true) - const [selectedDatePickerValue, setSelectedDatePickerValue] = useState( - getDefaultDatePickerValue() - ) - - const { search, setSearch, timestampStart, timestampEnd, setTimeRange, filters, setFilters } = - useLogsUrlState() - const [selectedLogId, setSelectedLogId] = useSelectedLog() - - // Use the custom useUnifiedLogs hook - const { - error, - logData, - params, - newCount, - isLoading, - eventChartData, - isLoadingOlder, - loadOlder, - refresh, - } = useUnifiedLogs({ projectRef, filterOverride }) - - const { - data: selectedLog, - isLoading: isSelectedLogLoading, - error: selectedLogError, - } = useSingleLog({ - projectRef, - id: selectedLogId ?? undefined, - queryType: 'unified', - paramsToMerge: params, - }) - - // TODO: Move this to useLogsUrlState to simplify LogsPreviewer - function getDefaultDatePickerValue() { - const iso_timestamp_start = router.query.iso_timestamp_start as string - const iso_timestamp_end = router.query.iso_timestamp_end as string - - if (iso_timestamp_start && iso_timestamp_end) { - return { - to: iso_timestamp_end, - from: iso_timestamp_start, - text: `${dayjs(iso_timestamp_start).format('DD MMM, HH:mm')} - ${dayjs(iso_timestamp_end).format('DD MMM, HH:mm')}`, - isHelper: false, - } - } - - const defaultDatePickerValue = PREVIEWER_DATEPICKER_HELPERS.find((x) => x.default) - return { - to: defaultDatePickerValue!.calcTo(), - from: defaultDatePickerValue!.calcFrom(), - text: defaultDatePickerValue!.text, - isHelper: true, - } - } - - const onSelectTemplate = (template: LogTemplate) => { - setFilters({ ...filters, search_query: template.searchString }) - } - - // For helper date picker values, reset the timestamp start to prevent data caching - const handleRefresh = () => { - if (selectedDatePickerValue.isHelper) { - const helper = PREVIEWER_DATEPICKER_HELPERS.find( - (x) => x.text === selectedDatePickerValue.text - ) - if (helper) { - const newTimestampStart = helper.calcFrom() - setTimeRange(newTimestampStart, timestampEnd) - } - } - refresh() - } - - const handleSearch: LogSearchCallback = async (event, { query, to, from }) => { - if (event === 'search-input-change') { - setSearch(query || '') - setSelectedLogId(null) - } else if (event === 'event-chart-bar-click') { - setTimeRange(from || '', to || '') - } else if (event === 'datepicker-change') { - setTimeRange(from || '', to || '') - } - } - - // Common props shared between both filter panel components to avoid duplication - const filterPanelProps = { - className: filterPanelClassName, - csvData: logData, - isLoading, - newCount, - onRefresh: handleRefresh, - onSearch: handleSearch, - defaultSearchValue: search, - defaultToValue: timestampEnd, - defaultFromValue: timestampStart, - queryUrl: `/project/${projectRef}/logs/explorer?q=${encodeURIComponent( - params.sql || '' - )}&its=${encodeURIComponent(timestampStart)}&ite=${encodeURIComponent(timestampEnd)}`, - onSelectTemplate, - filters, - onFiltersChange: setFilters, - table: LogsTableName.UNIFIED, - condensedLayout, - isShowingEventChart: showChart, - onToggleEventChart: () => setShowChart(!showChart), - onSelectedDatabaseChange: (id: string) => { - setFilters({ ...filters, database: id !== projectRef ? id : undefined }) - const { db, ...params } = router.query - router.push({ - pathname: router.pathname, - query: id !== projectRef ? { ...router.query, db: id } : params, - }) - }, - selectedDatePickerValue, - setSelectedDatePickerValue, - } - - return ( -
- {useUniversalFilterBar ? ( - // Experimental Universal Filter Bar - - ) : ( - // Legacy Filter Panel - - )} - {children} -
0 ? 'mb-2 mt-1 opacity-100' : 'h-0 opacity-0') - } - > -
- {showChart && ( - { - if (!datum?.timestamp) return - - const datumTimestamp = dayjs(datum.timestamp).toISOString() - - const start = dayjs(datumTimestamp).subtract(1, 'minute').toISOString() - const end = dayjs(datumTimestamp).add(1, 'minute').toISOString() - - handleSearch('event-chart-bar-click', { - query: filters.search_query?.toString(), - to: end, - from: start, - }) - }} - EmptyState={ -
-

No data

-

- It may take up to 24 hours for data to refresh -

-
- } - /> - )} -
-
-
- - - setShowChart(!showChart)} - error={error} - EmptyState={EmptyState} - onSelectedLogChange={(log) => setSelectedLogId(log?.id ?? null)} - selectedLog={selectedLog} - isSelectedLogLoading={isSelectedLogLoading} - selectedLogError={selectedLogError ?? undefined} - /> - -
- {!error && logData.length > 0 && ( -
- -
- Showing {logData.length} results -
-
- )} -
- ) -} - -export default UnifiedLogsPreviewer diff --git a/apps/studio/components/interfaces/UnifiedLogs/client.tsx b/apps/studio/components/interfaces/UnifiedLogs/client.tsx new file mode 100644 index 00000000000..0d91d16a946 --- /dev/null +++ b/apps/studio/components/interfaces/UnifiedLogs/client.tsx @@ -0,0 +1,445 @@ +import { useInfiniteQuery } from '@tanstack/react-query' +import { + ColumnFiltersState, + getCoreRowModel, + getFacetedRowModel, + getFilteredRowModel, + getSortedRowModel, + getFacetedMinMaxValues as getTTableFacetedMinMaxValues, + getFacetedUniqueValues as getTTableFacetedUniqueValues, + Row, + RowSelectionState, + SortingState, + useReactTable, + VisibilityState, +} from '@tanstack/react-table' +import { useQueryStates } from 'nuqs' +import { useEffect, useMemo, useState } from 'react' + +// import { useLocalStorage } from 'components/interfaces/DataTableDemo/hooks/use-local-storage' +import { DataTableInfinite } from 'components/interfaces/data-table/data-table-infinite' +import { TimelineChart } from 'components/interfaces/data-table/timeline-chart' +import { DataTableFilterCommand } from 'components/interfaces/DataTableDemo/components/data-table/data-table-filter-command' +import { DataTableProvider } from 'components/interfaces/DataTableDemo/components/data-table/data-table-provider' +import { DataTableSheetDetails } from 'components/interfaces/DataTableDemo/components/data-table/data-table-sheet/data-table-sheet-details' +import { DataTableToolbar } from 'components/interfaces/DataTableDemo/components/data-table/data-table-toolbar' +import { LiveButton } from 'components/interfaces/DataTableDemo/infinite/_components/live-button' +import { LiveRow } from 'components/interfaces/DataTableDemo/infinite/_components/live-row' +import { RefreshButton } from 'components/interfaces/DataTableDemo/infinite/_components/refresh-button' +import { getLevelRowClassName } from 'components/interfaces/DataTableDemo/lib/request/level' +import { arrSome, inDateRange } from 'components/interfaces/DataTableDemo/lib/table/filterfns' + +// components pulled out and modified +import { DataTableHeaderLayout } from '../data-table/data-table-header-layout' +import { DataTableSideBarLayout } from '../data-table/data-table-side-bar-layout' +import { FilterSideBar } from '../data-table/filter-side-bar' + +// specific imports +import { useParams } from 'common' +import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' +import { + ChartConfig, + cn, + ResizableHandle, + ResizablePanel, + ResizablePanelGroup, + Separator, + Tabs_Shadcn_ as Tabs, + TabsContent_Shadcn_ as TabsContent, + TabsList_Shadcn_ as TabsList, + TabsTrigger_Shadcn_ as TabsTrigger, +} from 'ui' + +import { columns, logEventBus } from './columns' +import { MemoizedDataTableSheetContent } from './components/data-table-sheet-content' +import { FunctionLogsTab } from './components/function-logs-tab' +import { TraceDetailTab } from './components/trace-detail-tab' +import { filterFields as defaultFilterFields, sheetFields } from './constants' +import { dataOptions, useChartData } from './query-options-new' + +import { CHART_CONFIG, SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants' +import { getFacetedUniqueValues, useLiveMode, useResetFocus } from './UnifiedLogs.utils' + +// Debug mode flag - set to true to enable detailed logs +const DEBUG_FILTER_PROCESSING = false + +/** + * [Joshen] Slowly... This branch is a huge mess... Needs a shit ton of refactoring + * - Deprecate infinite-logs-example + * - Deprecate data-table + * - Deprecate DataTableDemo + * - Deprecate Trace (?) + * - Rename logs-new to UnifiedLogs + * - Components all need to be renamed for follow convention + */ + +export const UnifiedLogsTable = () => { + useResetFocus() + + const { ref: projectRef } = useParams() + const [search, setSearch] = useQueryStates(SEARCH_PARAMS_PARSER) + + const { sort, start, size, uuid, cursor, direction, live, ...filter } = search + const defaultColumnSorting = sort ? [sort] : [] + const defaultColumnVisibility = { uuid: false } + const defaultRowSelection = search.uuid ? { [search.uuid]: true } : {} + const defaultColumnFilters = Object.entries(filter) + .map(([key, value]) => ({ id: key, value })) + .filter(({ value }) => value ?? undefined) + + const [topBarHeight, setTopBarHeight] = useState(0) + const [activeTab, setActiveTab] = useState('details') + const [sorting, setSorting] = useState(defaultColumnSorting) + const [columnFilters, setColumnFilters] = useState(defaultColumnFilters) + const [rowSelection, setRowSelection] = useState(defaultRowSelection) + + const [columnVisibility, setColumnVisibility] = useLocalStorageQuery( + 'data-table-visibility', + defaultColumnVisibility + ) + const [columnOrder, setColumnOrder] = useLocalStorageQuery( + 'data-table-column-order', + [] + ) + + const { data, isFetching, isLoading, fetchNextPage, hasNextPage, fetchPreviousPage, refetch } = + // @ts-ignore + useInfiniteQuery(dataOptions(search, projectRef ?? '')) + + const flatData = useMemo(() => { + return data?.pages?.flatMap((page) => page.data ?? []) ?? [] + }, [data?.pages]) + const liveMode = useLiveMode(flatData) + + // Add the chart data query for the entire time period + const { data: chartDataResult } = useChartData(search, projectRef ?? '') + + // REMINDER: meta data is always the same for all pages as filters do not change(!) + const lastPage = data?.pages?.[data?.pages.length - 1] + // Use the totalCount from chartDataResult which gives us the actual count of logs in the time period + // instead of the hardcoded 10000 value + const totalDBRowCount = chartDataResult?.totalCount || lastPage?.meta?.totalRowCount + const filterDBRowCount = lastPage?.meta?.filterRowCount + const metadata = lastPage?.meta?.metadata + // Use chart data from the separate query if available, fallback to the default + const chartData = chartDataResult?.chartData || lastPage?.meta?.chartData + const facets = lastPage?.meta?.facets + const totalFetched = flatData?.length + + // Create a filtered version of the chart config based on selected levels + const filteredChartConfig = useMemo(() => { + const levelFilter = search.level || ['success', 'warning', 'error'] + return Object.fromEntries( + Object.entries(CHART_CONFIG).filter(([key]) => levelFilter.includes(key as any)) + ) as ChartConfig + }, [search.level]) + + const getRowClassName = ( + row: Row + ) => { + const rowTimestamp = row.original.timestamp + const isPast = rowTimestamp <= (liveMode.timestamp || -1) + const levelClassName = getLevelRowClassName(row.original.level) + return cn(levelClassName, isPast ? 'opacity-50' : 'opacity-100') + } + + const table = useReactTable({ + data: flatData, + columns, + state: { + columnFilters, + sorting, + columnVisibility, + rowSelection, + columnOrder, + }, + enableMultiRowSelection: false, + columnResizeMode: 'onChange', + filterFns: { inDateRange, arrSome }, + meta: { getRowClassName }, + getRowId: (row) => row.uuid, + onColumnVisibilityChange: setColumnVisibility, + onColumnFiltersChange: setColumnFilters, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnOrderChange: setColumnOrder, + getSortedRowModel: getSortedRowModel(), + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getTTableFacetedUniqueValues(), + getFacetedMinMaxValues: getTTableFacetedMinMaxValues(), + // Here, manually override the filter function for the level column + // to prevent client-side filtering since it's already filtered on the server + // Always return true to pass all level values + // columnFilterFns: { level: () => true }, + // debugAll: process.env.NEXT_PUBLIC_TABLE_DEBUG === 'true', + }) + + const selectedRow = useMemo(() => { + if ((isLoading || isFetching) && !flatData.length) return + const selectedRowKey = Object.keys(rowSelection)?.[0] + return table.getCoreRowModel().flatRows.find((row) => row.id === selectedRowKey) + }, [rowSelection, table, isLoading, isFetching, flatData]) + + const selectedRowKey = Object.keys(rowSelection)?.[0] + + // REMINDER: this is currently needed for the cmdk search + // TODO: auto search via API when the user changes the filter instead of hardcoded + const filterFields = useMemo(() => { + return defaultFilterFields.map((field) => { + const facetsField = facets?.[field.value] + if (!facetsField) return field + if (field.options && field.options.length > 0) return field + + // REMINDER: if no options are set, we need to set them via the API + const options = facetsField.rows.map(({ value }) => ({ label: `${value}`, value })) + + if (field.type === ('slider' as any)) { + return { + ...(field as any), + min: facetsField.min ?? (field as any).min, + max: facetsField.max ?? (field as any).max, + options, + } + } + + return { ...field, options } + }) + }, [facets]) + + useEffect(() => { + if (DEBUG_FILTER_PROCESSING) console.log('========== FILTER CHANGE DETECTED ==========') + if (DEBUG_FILTER_PROCESSING) console.log('Raw columnFilters:', JSON.stringify(columnFilters)) + + // Check for level filters specifically + const levelColumnFilter = columnFilters.find((filter) => filter.id === 'level') + if (DEBUG_FILTER_PROCESSING) console.log('Level column filter:', levelColumnFilter) + + const columnFiltersWithNullable = filterFields.map((field) => { + const filterValue = columnFilters.find((filter) => filter.id === field.value) + if (DEBUG_FILTER_PROCESSING) console.log(`Processing field ${field.value}:`, filterValue) + if (!filterValue) return { id: field.value, value: null } + return { id: field.value, value: filterValue.value } + }) + + // Debug level filter specifically + const levelFilter = columnFiltersWithNullable.find((f) => f.id === 'level') + if (DEBUG_FILTER_PROCESSING) console.log('Level filter after mapping:', levelFilter) + + if (DEBUG_FILTER_PROCESSING) + console.log('All column filters after mapping:', columnFiltersWithNullable) + + const search = columnFiltersWithNullable.reduce( + (prev, curr) => { + if (DEBUG_FILTER_PROCESSING) + console.log(`Processing filter for URL: ${curr.id}`, { + value: curr.value, + type: Array.isArray(curr.value) ? 'array' : typeof curr.value, + isEmpty: Array.isArray(curr.value) && curr.value.length === 0, + isNull: curr.value === null, + }) + + // Add to search parameters + prev[curr.id as string] = curr.value + return prev + }, + {} as Record + ) + + if (DEBUG_FILTER_PROCESSING) console.log('Final search object to be set in URL:', search) + if (DEBUG_FILTER_PROCESSING) console.log('Level value in final search:', search.level) + if (DEBUG_FILTER_PROCESSING) console.log('Is level in search object:', 'level' in search) + + // Set the search state without any console logs + if (DEBUG_FILTER_PROCESSING) console.log('CALLING setSearch with:', JSON.stringify(search)) + setSearch(search) + if (DEBUG_FILTER_PROCESSING) console.log('========== END FILTER PROCESSING ==========') + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [columnFilters]) + + useEffect(() => { + setSearch({ sort: sorting?.[0] || null }) + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [sorting]) + + // TODO: can only share uuid within the first batch + useEffect(() => { + if (isLoading || isFetching) return + if (Object.keys(rowSelection)?.length && !selectedRow) { + setSearch({ uuid: null }) + setRowSelection({}) + } else { + setSearch({ uuid: Object.keys(rowSelection)?.[0] || null }) + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [rowSelection, selectedRow, isLoading, isFetching]) + + // Set up event listener for trace tab selection + useEffect(() => { + const unsubscribe = logEventBus.on('selectTraceTab', (rowId) => { + setRowSelection({ [rowId]: true }) + setActiveTab('trace') + }) + return () => { + unsubscribe() + } + }, [setRowSelection]) + + return ( + + + +
+ + + [ + , + fetchPreviousPage ? ( + + ) : null, + ]} + /> + + + + + + + + + +
+ { + if (!liveMode.timestamp) return null + if (props?.row.original.uuid !== liveMode?.row?.uuid) return null + return + }} + setColumnOrder={setColumnOrder} + setColumnVisibility={setColumnVisibility} + /> +
+
+ {selectedRow?.original?.logs && selectedRow?.original?.logs?.length > 0 && ( + <> + + +
+
+

+ Function Logs ( + {selectedRow?.original?.logs && + Array.isArray(selectedRow?.original?.logs) + ? selectedRow?.original?.logs?.length + : 0} + ) +

+
+
+ +
+
+
+ + )} +
+
+ + {selectedRowKey && ( + <> + + + + +
+ + + + Log Details + Trace + + + + + + + + {selectedRow?.original?.has_trace ? ( + + ) : ( +
+

+ No trace found for this log +

+
+ )} +
+
+
+
+
+
+
+ + )} +
+
+
+
+ ) +} diff --git a/apps/studio/components/interfaces/logs-new/columns.tsx b/apps/studio/components/interfaces/UnifiedLogs/columns.tsx similarity index 66% rename from apps/studio/components/interfaces/logs-new/columns.tsx rename to apps/studio/components/interfaces/UnifiedLogs/columns.tsx index 5134086a848..e54ceb2cc1d 100644 --- a/apps/studio/components/interfaces/logs-new/columns.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/columns.tsx @@ -1,35 +1,15 @@ -'use client' +import type { ColumnDef } from '@tanstack/react-table' +import { Bolt } from 'lucide-react' import { TextWithTooltip } from 'components/interfaces/DataTableDemo/components/custom/text-with-tooltip' import { DataTableColumnHeader } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column-header' -import { DataTableColumnLatency } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-latency' import { DataTableColumnLevelIndicator } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-level-indicator' -import { DataTableColumnRegion } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-region' import { DataTableColumnStatusCode } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-status-code' -import { - HoverCard, - HoverCardContent, - HoverCardTrigger, - TooltipTrigger, - Tooltip, - TooltipContent, -} from 'ui' -import { - getTimingColor, - getTimingLabel, - getTimingPercentage, - timingPhases, -} from 'components/interfaces/DataTableDemo/lib/request/timing' -import { cn } from 'ui' -import { HoverCardPortal } from '@radix-ui/react-hover-card' -import type { ColumnDef, FilterFn } from '@tanstack/react-table' -import { Bolt, Minus, Pyramid, User, Zap } from 'lucide-react' import { HoverCardTimestamp } from 'components/interfaces/DataTableDemo/infinite/_components/hover-card-timestamp' - -// custom imports -import type { ColumnSchema } from './schema' -import { LogTypeIcon } from './LogTypeIcon' +import { Tooltip, TooltipContent, TooltipTrigger } from 'ui' import { AuthUserHoverCard } from './components/auth-user-hover-card' +import { LogTypeIcon } from './LogTypeIcon' +import type { ColumnSchema } from './UnifiedLogs.schema' // Event broadcaster for selecting the trace tab // This will be used to communicate between the columns and the client component @@ -88,39 +68,6 @@ export const columns: ColumnDef[] = [ 'font-mono w-[--col-date-size] max-w-[--col-date-size] min-w-[--col-date-size]', }, }, - // { - // accessorKey: 'id', - // header: ({ column }) => , - // cell: ({ row }) => { - // const value = row.getValue('id') - // return - // }, - // size: 130, - // minSize: 130, - // meta: { - // headerClassName: 'w-[--header-id-size] max-w-[--header-id-size] min-w-[--header-id-size]', - // cellClassName: 'w-[--col-id-size] max-w-[--col-id-size] min-w-[--col-id-size]', - // }, - // }, - // { - // id: 'uuid', - // accessorKey: 'uuid', - // header: 'Request Id', - // cell: ({ row }) => { - // const value = row.getValue('uuid') - // return - // }, - // enableResizing: false, - // size: 130, - // minSize: 130, - // meta: { - // label: 'Request Id', - // cellClassName: - // 'font-mono w-[--col-uuid-size] max-w-[--col-uuid-size] min-w-[--col-uuid-size]', - // headerClassName: - // 'min-w-[--header-uuid-size] w-[--header-uuid-size] max-w-[--header-uuid-size]', - // }, - // }, { accessorKey: 'log_type', header: '', @@ -238,57 +185,6 @@ export const columns: ColumnDef[] = [ headerClassName: 'min-w-[--header-pathname-size] w-[--header-pathname-size]', }, }, - // { - // accessorKey: 'latency', - // // TODO: check if we can right align the table header/cell (makes is easier to read) - // header: ({ column }) => , - // cell: ({ row }) => { - // const value = row.getValue('latency') - // return - // }, - // filterFn: 'inNumberRange', - // enableResizing: false, - // size: 110, - // minSize: 110, - // meta: { - // headerClassName: - // 'w-[--header-latency-size] max-w-[--header-latency-size] min-w-[--header-latency-size]', - // cellClassName: - // 'font-mono w-[--col-latency-size] max-w-[--col-latency-size] min-w-[--col-latency-size]', - // }, - // }, - // { - // accessorKey: 'regions', - // header: 'Region', - // cell: ({ row }) => { - // const value = row.getValue('regions') - // if (Array.isArray(value)) { - // if (value.length > 1) { - // return
{value.join(', ')}
- // } else { - // return ( - //
- // - //
- // ) - // } - // } - // if (typeof value === 'string') { - // return - // } - // return - // }, - // filterFn: 'arrIncludesSome', - // enableResizing: false, - // size: 163, - // minSize: 163, - // meta: { - // headerClassName: - // 'w-[--header-regions-size] max-w-[--header-regions-size] min-w-[--header-regions-size]', - // cellClassName: - // 'font-mono w-[--col-regions-size] max-w-[--col-regions-size] min-w-[--col-regions-size]', - // }, - // }, { accessorKey: 'event_message', header: ({ column }) => , @@ -318,10 +214,9 @@ export const columns: ColumnDef[] = [ ) }, - enableResizing: true, // allow manual resizing + enableResizing: true, size: 400, minSize: 400, - // ❌ don't set maxSize — let it grow meta: { headerClassName: 'w-[--header-event_message-size] max-w-[--header-event_message-size] min-w-[--header-event_message-size]', diff --git a/apps/studio/components/interfaces/logs-new/components/auth-user-hover-card.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/auth-user-hover-card.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/auth-user-hover-card.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/auth-user-hover-card.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/data-table-sheet-content.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/data-table-sheet-content.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/data-table-sheet-content.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/data-table-sheet-content.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/data-table-sheet-details-layout.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/data-table-sheet-details-layout.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/data-table-sheet-details-layout.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/data-table-sheet-details-layout.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/function-logs-tab.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/function-logs-tab.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/function-logs-tab.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/function-logs-tab.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/tabbed-detail-panel.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/tabbed-detail-panel.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/tabbed-detail-panel.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/tabbed-detail-panel.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/trace-button.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/trace-button.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/trace-button.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/trace-button.tsx diff --git a/apps/studio/components/interfaces/logs-new/components/trace-detail-tab.tsx b/apps/studio/components/interfaces/UnifiedLogs/components/trace-detail-tab.tsx similarity index 100% rename from apps/studio/components/interfaces/logs-new/components/trace-detail-tab.tsx rename to apps/studio/components/interfaces/UnifiedLogs/components/trace-detail-tab.tsx diff --git a/apps/studio/components/interfaces/logs-new/constants.tsx b/apps/studio/components/interfaces/UnifiedLogs/constants.tsx similarity index 90% rename from apps/studio/components/interfaces/logs-new/constants.tsx rename to apps/studio/components/interfaces/UnifiedLogs/constants.tsx index ebed8cf5599..e97b3a6abe9 100644 --- a/apps/studio/components/interfaces/logs-new/constants.tsx +++ b/apps/studio/components/interfaces/UnifiedLogs/constants.tsx @@ -1,8 +1,6 @@ -'use client' +import { format } from 'date-fns' +import { User } from 'lucide-react' -import { CopyToClipboardContainer } from 'components/interfaces/DataTableDemo/components/custom/copy-to-clipboard-container' -import { KVTabs } from 'components/interfaces/DataTableDemo/components/custom/kv-tabs' -import { DataTableColumnRegion } from 'components/interfaces/DataTableDemo/components/data-table/data-table-column/data-table-column-region' import type { DataTableFilterField, Option, @@ -10,20 +8,14 @@ import type { } from 'components/interfaces/DataTableDemo/components/data-table/types' import { LEVELS } from 'components/interfaces/DataTableDemo/constants/levels' import { METHODS } from 'components/interfaces/DataTableDemo/constants/method' -import { REGIONS } from 'components/interfaces/DataTableDemo/constants/region' -import { formatMilliseconds } from 'components/interfaces/DataTableDemo/lib/format' import { getLevelColor, getLevelLabel } from 'components/interfaces/DataTableDemo/lib/request/level' import { getStatusColor } from 'components/interfaces/DataTableDemo/lib/request/status-code' import { cn } from 'ui' -import { format } from 'date-fns' -import { PopoverPercentile } from 'components/interfaces/DataTableDemo/infinite/_components/popover-percentile' -import { SheetTimingPhases } from 'components/interfaces/DataTableDemo/infinite/_components/sheet-timing-phases' -import { User } from 'lucide-react' // custom imports -import type { LogsMeta } from './query-options' -import { type ColumnSchema, LOG_TYPES } from './schema' import { TraceButton } from './components/trace-button' +import type { LogsMeta } from './query-options' +import { type ColumnSchema, LOG_TYPES } from './UnifiedLogs.schema' // instead of filterFields, maybe just 'fields' with a filterDisabled prop? // that way, we could have 'message' or 'headers' field with label and value as well as type! diff --git a/apps/studio/components/interfaces/logs-new/constants/date-preset.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/date-preset.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/date-preset.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/date-preset.ts diff --git a/apps/studio/components/interfaces/logs-new/constants/levels.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/levels.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/levels.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/levels.ts diff --git a/apps/studio/components/interfaces/logs-new/constants/method.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/method.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/method.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/method.ts diff --git a/apps/studio/components/interfaces/logs-new/constants/query-params.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/query-params.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/query-params.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/query-params.ts diff --git a/apps/studio/components/interfaces/logs-new/constants/region.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/region.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/region.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/region.ts diff --git a/apps/studio/components/interfaces/logs-new/constants/tag.ts b/apps/studio/components/interfaces/UnifiedLogs/constants/tag.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/constants/tag.ts rename to apps/studio/components/interfaces/UnifiedLogs/constants/tag.ts diff --git a/apps/studio/components/interfaces/logs-new/hooks/useUnifiedLogs.ts b/apps/studio/components/interfaces/UnifiedLogs/hooks/useUnifiedLogs.ts similarity index 100% rename from apps/studio/components/interfaces/logs-new/hooks/useUnifiedLogs.ts rename to apps/studio/components/interfaces/UnifiedLogs/hooks/useUnifiedLogs.ts diff --git a/apps/studio/components/interfaces/logs-new/logs.queries.ts b/apps/studio/components/interfaces/UnifiedLogs/logs.queries.ts similarity index 99% rename from apps/studio/components/interfaces/logs-new/logs.queries.ts rename to apps/studio/components/interfaces/UnifiedLogs/logs.queries.ts index 139783438fa..5f397d8cffc 100644 --- a/apps/studio/components/interfaces/logs-new/logs.queries.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/logs.queries.ts @@ -4,7 +4,7 @@ import dayjs from 'dayjs' import { BASE_CONDITIONS_EXCLUDED_PARAMS, EXCLUDED_QUERY_PARAMS } from './constants/query-params' -import { SearchParamsType } from './search-params' +import { SearchParamsType } from './UnifiedLogs.types' // Types for plan IDs - import actual type if available type PlanId = 'free' | 'pro' | 'team' | 'enterprise' diff --git a/apps/studio/components/interfaces/logs-new/query-options-new.ts b/apps/studio/components/interfaces/UnifiedLogs/query-options-new.ts similarity index 96% rename from apps/studio/components/interfaces/logs-new/query-options-new.ts rename to apps/studio/components/interfaces/UnifiedLogs/query-options-new.ts index 1efbe48d70e..9f739500217 100644 --- a/apps/studio/components/interfaces/logs-new/query-options-new.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/query-options-new.ts @@ -1,11 +1,11 @@ import { useQuery } from '@tanstack/react-query' -import { ARRAY_DELIMITER, SORT_DELIMITER } from 'components/interfaces/DataTableDemo/lib/delimiters' -import { post } from 'data/fetchers' import dayjs from 'dayjs' + +import { post } from 'data/fetchers' import { getLogsChartQuery, getLogsCountQuery, getUnifiedLogsQuery } from './logs.queries' -import type { BaseChartSchema, FacetMetadataSchema } from './schema' -import { ColumnSchema } from './schema' -import type { SearchParamsType } from './search-params' +import { ARRAY_DELIMITER, SORT_DELIMITER } from './UnifiedLogs.constants' +import { ColumnSchema, type BaseChartSchema, type FacetMetadataSchema } from './UnifiedLogs.schema' +import type { SearchParamsType, UnifiedLogsMeta } from './UnifiedLogs.types' // Debug mode flag - set to true to enable detailed logs const DEBUG_MODE = false @@ -23,11 +23,6 @@ export type UnifiedLogSchema = { auth_user: string | null } -export type UnifiedLogsMeta = { - logTypeCounts: Record - currentPercentiles: Record -} - // Extended column schema to include raw timestamp export type ExtendedColumnSchema = ColumnSchema & { timestamp: string // Original database timestamp @@ -279,15 +274,15 @@ export const dataOptions = (search: SearchParamsType, projectRef: string) => { ) // Calculate the appropriate initial cursor based on the selected date range - const getInitialCursor = () => { - if (search.date && search.date.length === 2) { - // Use the end of the selected date range - return new Date(search.date[1]).getTime() - } else { - // Default to current time if no date range is selected - return new Date().getTime() - } - } + // const getInitialCursor = () => { + // if (search.date && search.date.length === 2) { + // // Use the end of the selected date range + // return new Date(search.date[1]).getTime() + // } else { + // // Default to current time if no date range is selected + // return new Date().getTime() + // } + // } // Simply return the options object return { diff --git a/apps/studio/components/interfaces/logs-new/query-options.ts b/apps/studio/components/interfaces/UnifiedLogs/query-options.ts similarity index 96% rename from apps/studio/components/interfaces/logs-new/query-options.ts rename to apps/studio/components/interfaces/UnifiedLogs/query-options.ts index 1ad15cfaf0a..f0e8bc20cd6 100644 --- a/apps/studio/components/interfaces/logs-new/query-options.ts +++ b/apps/studio/components/interfaces/UnifiedLogs/query-options.ts @@ -2,15 +2,10 @@ import type { Percentile } from 'components/interfaces/DataTableDemo/lib/request // Removed keepPreviousData import as it causes persistent lint errors // import { keepPreviousData } from '@tanstack/react-query' import SuperJSON from 'superjson' -import type { BaseChartSchema, ColumnSchema, FacetMetadataSchema } from './schema' -import type { SearchParamsType } from './search-params' +import type { BaseChartSchema, ColumnSchema, FacetMetadataSchema } from './UnifiedLogs.schema' // Removed: import { searchParamsSerializer } from './search-params' -import { - ARRAY_DELIMITER, - RANGE_DELIMITER, - SLIDER_DELIMITER, - SORT_DELIMITER, -} from 'components/interfaces/DataTableDemo/lib/delimiters' +import { ARRAY_DELIMITER, SORT_DELIMITER } from 'components/interfaces/DataTableDemo/lib/delimiters' +import { SearchParamsType } from './UnifiedLogs.types' // Restore the helper function to create query params string const createApiQueryString = (params: Record): string => { diff --git a/apps/studio/components/interfaces/logs-new/client.tsx b/apps/studio/components/interfaces/logs-new/client.tsx deleted file mode 100644 index da438eba99f..00000000000 --- a/apps/studio/components/interfaces/logs-new/client.tsx +++ /dev/null @@ -1,552 +0,0 @@ -'use client' - -import { useQueryState, useQueryStates } from 'nuqs' -import * as React from 'react' - -import { useInfiniteQuery } from '@tanstack/react-query' -import { - ColumnFiltersState, - getCoreRowModel, - getFacetedRowModel, - getFilteredRowModel, - getSortedRowModel, - getFacetedMinMaxValues as getTTableFacetedMinMaxValues, - getFacetedUniqueValues as getTTableFacetedUniqueValues, - Row, - RowSelectionState, - SortingState, - useReactTable, - VisibilityState, - type Table as TTable, -} from '@tanstack/react-table' -import { DataTableInfinite } from 'components/interfaces/data-table/data-table-infinite' -import { TimelineChart } from 'components/interfaces/data-table/timeline-chart' -import { useHotKey } from 'components/interfaces/DataTableDemo/hooks/use-hot-key' -import { LiveRow } from 'components/interfaces/DataTableDemo/infinite/_components/live-row' - -import { - getLevelLabel, - getLevelRowClassName, -} from 'components/interfaces/DataTableDemo/lib/request/level' -import { arrSome, inDateRange } from 'components/interfaces/DataTableDemo/lib/table/filterfns' -import { ChartConfig, cn, Separator } from 'ui' -import { DataTableFilterCommand } from 'components/interfaces/DataTableDemo/components/data-table/data-table-filter-command' -import { - DataTableProvider, - useDataTable, -} from 'components/interfaces/DataTableDemo/components/data-table/data-table-provider' -import { DataTableSheetDetails } from 'components/interfaces/DataTableDemo/components/data-table/data-table-sheet/data-table-sheet-details' -import { DataTableToolbar } from 'components/interfaces/DataTableDemo/components/data-table/data-table-toolbar' -import { useLocalStorage } from 'components/interfaces/DataTableDemo/hooks/use-local-storage' -import { LiveButton } from 'components/interfaces/DataTableDemo/infinite/_components/live-button' -import { RefreshButton } from 'components/interfaces/DataTableDemo/infinite/_components/refresh-button' -import { Percentile } from 'components/interfaces/DataTableDemo/lib/request/percentile' -import { - ResizableHandle, - ResizablePanel, - ResizablePanelGroup, -} from 'ui/src/components/shadcn/ui/resizable' -import { Button } from 'ui' -import { X } from 'lucide-react' - -// components pulled out and modified -import { FilterSideBar } from '../data-table/filter-side-bar' -import { DataTableHeaderLayout } from '../data-table/data-table-header-layout' -import { DataTableSideBarLayout } from '../data-table/data-table-side-bar-layout' - -// specific imports -import { MemoizedDataTableSheetContent } from './components/data-table-sheet-content' -import type { FacetMetadataSchema, TimelineChartSchema } from './schema' -import { columns, logEventBus } from './columns' -import { searchParamsParser } from './search-params' -import { dataOptions, useChartData } from './query-options-new' -import { filterFields as defaultFilterFields, sheetFields } from './constants' -import { useParams } from 'common' -import { TraceDetailTab } from './components/trace-detail-tab' -import { Tabs, TabsContent, TabsList, TabsTrigger } from 'ui/src/components/shadcn/ui/tabs' -import { FunctionLogsTab } from './components/function-logs-tab' - -// Debug mode flag - set to true to enable detailed logs -const DEBUG_FILTER_PROCESSING = false - -function TooltipLabel({ level }: { level: keyof Omit }) { - return ( -
-
{level}
-
{getLevelLabel(level)}
-
- ) -} - -const chartConfig = { - success: { - label: , - color: 'hsl(var(--foreground-muted))', - }, - warning: { - label: , - color: 'hsl(var(--warning-default))', - }, - error: { - label: , - color: 'hsl(var(--destructive-default))', - }, -} satisfies ChartConfig - -export function Client() { - const [search, setSearch] = useQueryStates(searchParamsParser) - const { ref: projectRef } = useParams() - const [activeTab, setActiveTab] = React.useState('details') - - const { data, isFetching, isLoading, fetchNextPage, hasNextPage, fetchPreviousPage, refetch } = - useInfiniteQuery(dataOptions(search, projectRef ?? '')) - - // Add the chart data query for the entire time period - const { data: chartDataResult, isLoading: isChartLoading } = useChartData( - search, - projectRef ?? '' - ) - - useResetFocus() - - const [topBarHeight, setTopBarHeight] = React.useState(0) - - const flatData = React.useMemo(() => { - return data?.pages?.flatMap((page) => page.data ?? []) ?? [] - }, [data?.pages]) - - const liveMode = useLiveMode(flatData) - - // REMINDER: meta data is always the same for all pages as filters do not change(!) - const lastPage = data?.pages?.[data?.pages.length - 1] - // Use the totalCount from chartDataResult which gives us the actual count of logs in the time period - // instead of the hardcoded 10000 value - const totalDBRowCount = chartDataResult?.totalCount || lastPage?.meta?.totalRowCount - const filterDBRowCount = lastPage?.meta?.filterRowCount - const metadata = lastPage?.meta?.metadata - // Use chart data from the separate query if available, fallback to the default - const chartData = chartDataResult?.chartData || lastPage?.meta?.chartData - const facets = lastPage?.meta?.facets - const totalFetched = flatData?.length - - const { sort, start, size, uuid, cursor, direction, live, ...filter } = search - - // Create a filtered version of the chart config based on selected levels - const filteredChartConfig = React.useMemo(() => { - const levelFilter = search.level || ['success', 'warning', 'error'] - return Object.fromEntries( - Object.entries(chartConfig).filter(([key]) => levelFilter.includes(key)) - ) as ChartConfig - }, [search.level]) - - const defaultColumnFilters = Object.entries(filter) - .map(([key, value]) => ({ - id: key, - value, - })) - .filter(({ value }) => value ?? undefined) - - const defaultColumnSorting = sort ? [sort] : [] - const defaultColumnVisibility = { - uuid: false, - } - const defaultRowSelection = search.uuid ? { [search.uuid]: true } : {} - - const [columnFilters, setColumnFilters] = React.useState(defaultColumnFilters) - const [sorting, setSorting] = React.useState(defaultColumnSorting) - const [columnVisibility, setColumnVisibility] = useLocalStorage( - 'data-table-visibility', - defaultColumnVisibility - ) - const [rowSelection, setRowSelection] = React.useState(defaultRowSelection) - const [columnOrder, setColumnOrder] = useLocalStorage('data-table-column-order', []) - - // REMINDER: this is currently needed for the cmdk search - // TODO: auto search via API when the user changes the filter instead of hardcoded - const filterFields = React.useMemo(() => { - return defaultFilterFields.map((field) => { - const facetsField = facets?.[field.value] - if (!facetsField) return field - if (field.options && field.options.length > 0) return field - - // REMINDER: if no options are set, we need to set them via the API - const options = facetsField.rows.map(({ value }) => { - return { - label: `${value}`, - value, - } - }) - - if (field.type === ('slider' as any)) { - return { - ...(field as any), - min: facetsField.min ?? (field as any).min, - max: facetsField.max ?? (field as any).max, - options, - } - } - - return { ...field, options } - }) - }, [facets]) - - const getRowClassName = ( - row: Row - ) => { - const rowTimestamp = row.original.timestamp - const isPast = rowTimestamp <= (liveMode.timestamp || -1) - const levelClassName = getLevelRowClassName(row.original.level) - return cn(levelClassName, isPast ? 'opacity-50' : 'opacity-100') - } - - const table = useReactTable({ - data: flatData, - columns, - state: { - columnFilters, - sorting, - columnVisibility, - rowSelection, - columnOrder, - }, - enableMultiRowSelection: false, - columnResizeMode: 'onChange', - getRowId: (row) => row.uuid, - onColumnVisibilityChange: setColumnVisibility, - onColumnFiltersChange: setColumnFilters, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnOrderChange: setColumnOrder, - getSortedRowModel: getSortedRowModel(), - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getTTableFacetedUniqueValues(), - getFacetedMinMaxValues: getTTableFacetedMinMaxValues(), - filterFns: { inDateRange, arrSome }, - // Here, manually override the filter function for the level column - // to prevent client-side filtering since it's already filtered on the server - columnFilterFns: { - level: () => true, // Always return true to pass all level values - }, - // debugAll: process.env.NEXT_PUBLIC_TABLE_DEBUG === 'true', - meta: { getRowClassName }, - }) - - const selectedRow = React.useMemo(() => { - if ((isLoading || isFetching) && !flatData.length) return - const selectedRowKey = Object.keys(rowSelection)?.[0] - return table.getCoreRowModel().flatRows.find((row) => row.id === selectedRowKey) - }, [rowSelection, table, isLoading, isFetching, flatData]) - - const selectedRowKey = Object.keys(rowSelection)?.[0] - - React.useEffect(() => { - if (DEBUG_FILTER_PROCESSING) console.log('========== FILTER CHANGE DETECTED ==========') - if (DEBUG_FILTER_PROCESSING) console.log('Raw columnFilters:', JSON.stringify(columnFilters)) - - // Check for level filters specifically - const levelColumnFilter = columnFilters.find((filter) => filter.id === 'level') - if (DEBUG_FILTER_PROCESSING) console.log('Level column filter:', levelColumnFilter) - - const columnFiltersWithNullable = filterFields.map((field) => { - const filterValue = columnFilters.find((filter) => filter.id === field.value) - if (DEBUG_FILTER_PROCESSING) console.log(`Processing field ${field.value}:`, filterValue) - if (!filterValue) return { id: field.value, value: null } - return { id: field.value, value: filterValue.value } - }) - - // Debug level filter specifically - const levelFilter = columnFiltersWithNullable.find((f) => f.id === 'level') - if (DEBUG_FILTER_PROCESSING) console.log('Level filter after mapping:', levelFilter) - - if (DEBUG_FILTER_PROCESSING) - console.log('All column filters after mapping:', columnFiltersWithNullable) - - const search = columnFiltersWithNullable.reduce( - (prev, curr) => { - if (DEBUG_FILTER_PROCESSING) - console.log(`Processing filter for URL: ${curr.id}`, { - value: curr.value, - type: Array.isArray(curr.value) ? 'array' : typeof curr.value, - isEmpty: Array.isArray(curr.value) && curr.value.length === 0, - isNull: curr.value === null, - }) - - // Add to search parameters - prev[curr.id as string] = curr.value - return prev - }, - {} as Record - ) - - if (DEBUG_FILTER_PROCESSING) console.log('Final search object to be set in URL:', search) - if (DEBUG_FILTER_PROCESSING) console.log('Level value in final search:', search.level) - if (DEBUG_FILTER_PROCESSING) console.log('Is level in search object:', 'level' in search) - - // Set the search state without any console logs - if (DEBUG_FILTER_PROCESSING) console.log('CALLING setSearch with:', JSON.stringify(search)) - setSearch(search) - if (DEBUG_FILTER_PROCESSING) console.log('========== END FILTER PROCESSING ==========') - - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [columnFilters]) - - React.useEffect(() => { - setSearch({ sort: sorting?.[0] || null }) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [sorting]) - - // TODO: can only share uuid within the first batch - React.useEffect(() => { - if (isLoading || isFetching) return - if (Object.keys(rowSelection)?.length && !selectedRow) { - setSearch({ uuid: null }) - setRowSelection({}) - } else { - setSearch({ uuid: Object.keys(rowSelection)?.[0] || null }) - } - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [rowSelection, selectedRow, isLoading, isFetching]) - - // Set up event listener for trace tab selection - React.useEffect(() => { - const unsubscribe = logEventBus.on('selectTraceTab', (rowId) => { - // Select the row - setRowSelection({ [rowId]: true }) - // Set the active tab to trace - setActiveTab('trace') - }) - - return () => { - unsubscribe() - } - }, [setRowSelection]) - - return ( - <> - - - -
- - - [ - , - fetchPreviousPage ? ( - - ) : null, - ]} - /> - - - - {/* Use ResizablePanelGroup for the log list and details */} - {/*
*/} - - - - -
- { - if (!liveMode.timestamp) return null - if (props?.row.original.uuid !== liveMode?.row?.uuid) return null - return - }} - setColumnOrder={setColumnOrder} - setColumnVisibility={setColumnVisibility} - /> -
-
- {/* - - hello world -
-                      {JSON.stringify(selectedRow?.original, null, 2)}
-                    
-
*/} - {selectedRow?.original?.logs && selectedRow?.original?.logs?.length > 0 && ( - <> - - -
-
-

- Function Logs ( - {selectedRow?.original?.logs && - Array.isArray(selectedRow?.original?.logs) - ? selectedRow?.original?.logs?.length - : 0} - ) -

-
-
- -
-
-
- - )} -
-
- - {selectedRowKey && ( - <> - - - - -
- - - - Log Details - Trace - - - - - - - - {selectedRow?.original?.has_trace ? ( - - ) : ( -
-

- No trace found for this log -

-
- )} -
-
-
-
-
-
-
- - )} -
-
- {/*
*/} -
-
- - ) -} - -function useResetFocus() { - useHotKey(() => { - // FIXME: some dedicated div[tabindex="0"] do not auto-unblur (e.g. the DataTableFilterResetButton) - // REMINDER: we cannot just document.activeElement?.blur(); as the next tab will focus the next element in line, - // which is not what we want. We want to reset entirely. - document.body.setAttribute('tabindex', '0') - document.body.focus() - document.body.removeAttribute('tabindex') - }, '.') -} - -// TODO: make a BaseObject (incl. date and uuid e.g. for every upcoming branch of infinite table) -export function useLiveMode(data: TData[]) { - const [live] = useQueryState('live', searchParamsParser.live) - // REMINDER: used to capture the live mode on timestamp - const liveTimestamp = React.useRef(live ? new Date().getTime() : undefined) - - React.useEffect(() => { - if (live) liveTimestamp.current = new Date().getTime() - else liveTimestamp.current = undefined - }, [live]) - - const anchorRow = React.useMemo(() => { - if (!live) return undefined - - const item = data.find((item) => { - // return first item that is there if not liveTimestamp - if (!liveTimestamp.current) return true - // return first item that is after the liveTimestamp - if (item.date.getTime() > liveTimestamp.current) return false - return true - // return first item if no liveTimestamp - }) - - return item - }, [live, data]) - - return { row: anchorRow, timestamp: liveTimestamp.current } -} - -export function getFacetedUniqueValues(facets?: Record) { - return (table: TTable, columnId: string) => { - return new Map(facets?.[columnId]?.rows?.map(({ value, total }) => [value, total]) || []) - } -} - -export function getFacetedMinMaxValues(facets?: Record) { - return (table: TTable, columnId: string) => { - const min = facets?.[columnId]?.min - const max = facets?.[columnId]?.max - if (typeof min === 'number' && typeof max === 'number') return [min, max] - if (typeof min === 'number') return [min, min] - if (typeof max === 'number') return [max, max] - return undefined - } -} diff --git a/apps/studio/components/interfaces/logs-new/logs.utils.ts b/apps/studio/components/interfaces/logs-new/logs.utils.ts deleted file mode 100644 index 9b82290d74d..00000000000 --- a/apps/studio/components/interfaces/logs-new/logs.utils.ts +++ /dev/null @@ -1,62 +0,0 @@ -/** - * Utilities for log handling and display - */ - -/** - * Determines the log level based on status code - * @param statusCode HTTP status code as number or string - * @returns 'success', 'info', 'warn', or 'error' - */ -export function determineLogLevelFromStatus(statusCode: string | number | undefined): string { - // Default to info if no status code - if (!statusCode) return 'info' - - // Convert to number if it's a string - const code = typeof statusCode === 'string' ? parseInt(statusCode, 10) : statusCode - - // Handle NaN case - if (isNaN(code)) return 'info' - - // Determine level based on status code ranges - if (code >= 200 && code < 300) return 'success' - if (code >= 400 && code < 500) return 'warning' - if (code >= 500) return 'error' - - // Default for other codes - return 'info' -} - -/** - * Determines the log level based on log type and status - * Different log sources may have different level determination logic - */ -export function determineLogLevel( - logType: string, - statusCode: string | number | undefined, - defaultLevel?: string -): string { - // If a meaningful level is already provided, use it - if (defaultLevel && defaultLevel !== 'undefined') return defaultLevel - - // Handle specific log types - switch (logType) { - case 'edge': - return determineLogLevelFromStatus(statusCode) - case 'auth': - case 'edge function': - return determineLogLevelFromStatus(statusCode) - - case 'postgres': - // Could add postgres-specific error code handling here - return statusCode ? 'warn' : 'info' - - case 'function events': - // Default to info if not specified - return defaultLevel || 'info' - - // Add more log types as needed - - default: - return 'info' - } -} diff --git a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx index 99a7926186a..74b943fdf05 100644 --- a/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx +++ b/apps/studio/components/layouts/LogsLayout/LogsLayout.tsx @@ -2,13 +2,13 @@ import { PermissionAction } from '@supabase/shared-types/out/constants' import { useRouter } from 'next/router' import { PropsWithChildren, useEffect } from 'react' +import { LOCAL_STORAGE_KEYS } from 'common' import NoPermission from 'components/ui/NoPermission' import { useAsyncCheckProjectPermissions } from 'hooks/misc/useCheckPermissions' import { useLocalStorageQuery } from 'hooks/misc/useLocalStorage' import { withAuth } from 'hooks/misc/withAuth' import ProjectLayout from '../ProjectLayout/ProjectLayout' import { LogsSidebarMenuV2 } from './LogsSidebarMenuV2' -import { LOCAL_STORAGE_KEYS } from 'common' interface LogsLayoutProps { title?: string @@ -20,16 +20,15 @@ const LogsLayout = ({ children, hideSidebar = false, }: PropsWithChildren) => { - const { isLoading, can: canUseLogsExplorer } = useAsyncCheckProjectPermissions( - PermissionAction.ANALYTICS_READ, - 'logflare' - ) - const router = useRouter() const [_, setLastLogsPage] = useLocalStorageQuery( LOCAL_STORAGE_KEYS.LAST_VISITED_LOGS_PAGE, router.pathname.split('/logs/')[1] || '' ) + const { isLoading, can: canUseLogsExplorer } = useAsyncCheckProjectPermissions( + PermissionAction.ANALYTICS_READ, + 'logflare' + ) useEffect(() => { if (router.pathname.includes('/logs/')) { diff --git a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx index 993484f7197..f47831e49e3 100644 --- a/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx +++ b/apps/studio/components/layouts/LogsLayout/LogsSidebarMenuV2.tsx @@ -80,22 +80,27 @@ export function SidebarCollapsible({ } export function LogsSidebarMenuV2() { + const router = useRouter() + const { ref } = useParams() as { ref: string } + const warehouseEnabled = useFlag('warehouse') + const [searchText, setSearchText] = useState('') const [createCollectionOpen, setCreateCollectionOpen] = useState(false) const canCreateCollection = useCheckPermissions(PermissionAction.ANALYTICS_WRITE, 'logflare') - const router = useRouter() - const { ref } = useParams() as { ref: string } + const { data: tenantData } = useWarehouseTenantQuery({ projectRef: ref }) + const { projectAuthAll: authEnabled, projectStorageAll: storageEnabled, realtimeAll: realtimeEnabled, } = useIsFeatureEnabled(['project_storage:all', 'project_auth:all', 'realtime:all']) - const warehouseEnabled = useFlag('warehouse') + const { data: whCollections, isLoading: whCollectionsLoading } = useWarehouseCollectionsQuery( { projectRef: ref }, { enabled: IS_PLATFORM && warehouseEnabled && !!tenantData } ) + const { plan: orgPlan, isLoading: isOrgPlanLoading } = useCurrentOrgPlan() const isFreePlan = !isOrgPlanLoading && orgPlan?.id === 'free' diff --git a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx index 42b55f8b983..83f7788a2ac 100644 --- a/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx +++ b/apps/studio/components/layouts/ProjectLayout/NavigationBar/NavigationBar.utils.tsx @@ -155,12 +155,6 @@ export const generateOtherRoutes = (ref?: string, project?: Project): Route[] => icon: , link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/unified-logs`), }, - { - key: 'unified-log-2', - label: 'Unified Logs Open Status', - icon: , - link: ref && (isProjectBuilding ? buildingUrl : `/project/${ref}/logs-new`), - }, { key: 'api', label: 'API Docs', diff --git a/apps/studio/hooks/ui/useHotKey.ts b/apps/studio/hooks/ui/useHotKey.ts new file mode 100644 index 00000000000..d0c381b9beb --- /dev/null +++ b/apps/studio/hooks/ui/useHotKey.ts @@ -0,0 +1,17 @@ +import { useEffect } from 'react' + +export function useHotKey(callback: () => void, key: string): void { + useEffect(() => { + function handler(e: KeyboardEvent) { + if (e.key === key && (e.metaKey || e.ctrlKey)) { + // e.preventDefault(); + callback() + } + } + + window.addEventListener('keydown', handler) + return () => { + window.removeEventListener('keydown', handler) + } + }, [key]) +} diff --git a/apps/studio/pages/project/[ref]/demo-infinite.tsx b/apps/studio/pages/project/[ref]/demo-infinite.tsx deleted file mode 100644 index d32013d4fc8..00000000000 --- a/apps/studio/pages/project/[ref]/demo-infinite.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { getQueryClient } from 'components/interfaces/DataTableDemo/providers/get-query-client' -// import { SearchParams } from 'nuqs' // No longer needed directly -import * as React from 'react' -import { Client } from 'components/interfaces/DataTableDemo/infinite/client' -import { dataOptions } from 'components/interfaces/DataTableDemo/light/query-options' -import { searchParamsParser } from 'components/interfaces/DataTableDemo/light/search-params' // Import the parser -// import { useSearchParams } from 'next/navigation' // Switch to useQueryStates -import { useQueryStates } from 'nuqs' // Import useQueryStates - -import DefaultLayout from 'components/layouts/DefaultLayout' -import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' - -import type { NextPageWithLayout } from 'types' - -export const LogPage: NextPageWithLayout = () => { - // const searchParams = useSearchParams() // Replaced - // const params = Object.fromEntries(searchParams.entries()) // Replaced - // const search = searchParamsCache.parse(params) // Replaced - const [search] = useQueryStates(searchParamsParser) // Use useQueryStates with the parser - const queryClient = getQueryClient() - - React.useEffect(() => { - // Pass the parsed search object directly to dataOptions - queryClient.prefetchInfiniteQuery(dataOptions(search)) - }, [search, queryClient]) // Add queryClient to dependencies - - return -} - -LogPage.getLayout = (page) => ( - - {page} - -) - -export default LogPage diff --git a/apps/studio/pages/project/[ref]/demo-light.tsx b/apps/studio/pages/project/[ref]/demo-light.tsx deleted file mode 100644 index 186251ae25f..00000000000 --- a/apps/studio/pages/project/[ref]/demo-light.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { getQueryClient } from 'components/interfaces/DataTableDemo/providers/get-query-client' -// import { SearchParams } from 'nuqs' // No longer needed directly -import * as React from 'react' -import { Client } from 'components/interfaces/DataTableDemo/light/client' -import { dataOptions } from 'components/interfaces/DataTableDemo/light/query-options' -import { searchParamsParser } from 'components/interfaces/DataTableDemo/light/search-params' // Import the parser -// import { useSearchParams } from 'next/navigation' // Switch to useQueryStates -import { useQueryStates } from 'nuqs' // Import useQueryStates - -import DefaultLayout from 'components/layouts/DefaultLayout' -import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' - -import type { NextPageWithLayout } from 'types' - -export const LogPage: NextPageWithLayout = () => { - // const searchParams = useSearchParams() // Replaced - // const params = Object.fromEntries(searchParams.entries()) // Replaced - // const search = searchParamsCache.parse(params) // Replaced - const [search] = useQueryStates(searchParamsParser) // Use useQueryStates with the parser - const queryClient = getQueryClient() - - React.useEffect(() => { - // Pass the parsed search object directly to dataOptions - queryClient.prefetchInfiniteQuery(dataOptions(search)) - }, [search, queryClient]) // Add queryClient to dependencies - - return -} - -LogPage.getLayout = (page) => ( - - {page} - -) - -export default LogPage diff --git a/apps/studio/pages/project/[ref]/demo.tsx b/apps/studio/pages/project/[ref]/demo.tsx deleted file mode 100644 index d4b38a66f2d..00000000000 --- a/apps/studio/pages/project/[ref]/demo.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import DefaultLayout from 'components/layouts/DefaultLayout' -import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' -import * as React from 'react' -import type { NextPageWithLayout } from 'types' -import { columns } from 'components/interfaces/DataTableDemo/default/columns' -import { filterFields } from 'components/interfaces/DataTableDemo/default/constants' -import { data } from 'components/interfaces/DataTableDemo/default/data' -import { DataTable } from 'components/interfaces/DataTableDemo/default/data-table' -import { Skeleton } from 'components/interfaces/DataTableDemo/default/skeleton' -import { useSearchParams } from 'components/interfaces/DataTableDemo/default/search-params' - -export const LogPage: NextPageWithLayout = () => { - const [search] = useSearchParams() - - return ( - }> - ({ - id: key, - value, - })) - .filter(({ value }) => value ?? undefined)} - /> - - ) -} - -LogPage.getLayout = (page) => ( - - {page} - -) - -export default LogPage diff --git a/apps/studio/pages/project/[ref]/logs-new.tsx b/apps/studio/pages/project/[ref]/logs-new.tsx deleted file mode 100644 index 520889c8448..00000000000 --- a/apps/studio/pages/project/[ref]/logs-new.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { getQueryClient } from 'components/interfaces/DataTableDemo/providers/get-query-client' -// import { SearchParams } from 'nuqs' // No longer needed directly -import * as React from 'react' -import { Client } from 'components/interfaces/logs-new/client' -import { dataOptions } from 'components/interfaces/logs-new/query-options-new' -import { searchParamsParser } from 'components/interfaces/logs-new/search-params' // Import the parser -// import { useSearchParams } from 'next/navigation' // Switch to useQueryStates -import { useQueryStates } from 'nuqs' // Import useQueryStates - -import DefaultLayout from 'components/layouts/DefaultLayout' -import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' - -import type { NextPageWithLayout } from 'types' -import { useParams } from 'common' - -export const LogPage: NextPageWithLayout = () => { - const { ref: projectRef } = useParams() - // const searchParams = useSearchParams() // Replaced - // const params = Object.fromEntries(searchParams.entries()) // Replaced - // const search = searchParamsCache.parse(params) // Replaced - const [search] = useQueryStates(searchParamsParser) // Use useQueryStates with the parser - const queryClient = getQueryClient() - - // Remove prefetching to avoid duplicate requests - // React.useEffect(() => { - // // Pass the parsed search object directly to dataOptions - // queryClient.prefetchInfiniteQuery(dataOptions(search, projectRef ?? '')) - // }, [search, queryClient, projectRef]) // Add queryClient and projectRef to dependencies - - return -} - -LogPage.getLayout = (page) => ( - - {page} - -) - -export default LogPage diff --git a/apps/studio/pages/project/[ref]/logss.tsx b/apps/studio/pages/project/[ref]/logss.tsx deleted file mode 100644 index 5b85c7fd29e..00000000000 --- a/apps/studio/pages/project/[ref]/logss.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { getQueryClient } from 'components/interfaces/DataTableDemo/providers/get-query-client' -// import { SearchParams } from 'nuqs' // No longer needed directly -import * as React from 'react' -import { Client } from 'components/interfaces/infinite-logs-example/client' -import { dataOptions } from 'components/interfaces/DataTableDemo/light/query-options' -import { searchParamsParser } from 'components/interfaces/DataTableDemo/light/search-params' // Import the parser -// import { useSearchParams } from 'next/navigation' // Switch to useQueryStates -import { useQueryStates } from 'nuqs' // Import useQueryStates - -import DefaultLayout from 'components/layouts/DefaultLayout' -import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' - -import type { NextPageWithLayout } from 'types' - -export const LogPage: NextPageWithLayout = () => { - // const searchParams = useSearchParams() // Replaced - // const params = Object.fromEntries(searchParams.entries()) // Replaced - // const search = searchParamsCache.parse(params) // Replaced - const [search] = useQueryStates(searchParamsParser) // Use useQueryStates with the parser - const queryClient = getQueryClient() - - React.useEffect(() => { - // Pass the parsed search object directly to dataOptions - queryClient.prefetchInfiniteQuery(dataOptions(search)) - }, [search, queryClient]) // Add queryClient to dependencies - - return -} - -LogPage.getLayout = (page) => ( - - {page} - -) - -export default LogPage diff --git a/apps/studio/pages/project/[ref]/unified-logs.tsx b/apps/studio/pages/project/[ref]/unified-logs.tsx index 490f831a836..5e6bfb5f00c 100644 --- a/apps/studio/pages/project/[ref]/unified-logs.tsx +++ b/apps/studio/pages/project/[ref]/unified-logs.tsx @@ -1,15 +1,9 @@ -import { useRouter } from 'next/router' +import { UnifiedLogsTable } from 'components/interfaces/UnifiedLogs/client' +import DefaultLayout from 'components/layouts/DefaultLayout' import LogsLayout from 'components/layouts/LogsLayout/LogsLayout' import type { NextPageWithLayout } from 'types' -import DefaultLayout from 'components/layouts/DefaultLayout' -import UnifiedLogsPreviewer from 'components/interfaces/UnifiedLogs/UnifiedLogsPreviewer' -export const LogPage: NextPageWithLayout = () => { - const router = useRouter() - const { ref } = router.query - - return -} +export const LogPage: NextPageWithLayout = () => LogPage.getLayout = (page) => (