fix(studio): preserve deep-linked date range in Unified Logs FE-4020 (#48685)

## Problem

Clicking a bar in a usage chart (e.g. the Postgres activity chart on the
project dashboard) navigates to Unified Logs with the log_type filter
applied correctly, but the clicked bar's time range is silently dropped:
the page falls back to the default last-hour window. If the actual
matching logs are outside that window, the main list shows "No results
found" even though the sidebar facet count (computed from the correct
deep-linked range) shows a nonzero count.

Root cause: the table's initial `columnFilters` state was seeded only
from the `filter` URL param, never from `date`. A debounced effect syncs
`columnFilters` back into the URL shortly after mount, and for the
`date` field it treats a missing `columnFilters` entry as a cleared
brush, overwriting the deep-linked `date` param with null.

## Fix

Added `buildDefaultColumnFilters` in `UnifiedLogs.filters.ts`, which
seeds a `date` entry into the initial `columnFilters` from `search.date`
when present, alongside the existing filter-param seeding.
`UnifiedLogs.tsx` now uses this helper instead of building
`defaultColumnFilters` inline, so a deep-linked range survives the
debounced round-trip instead of getting nulled out.

## How to test

- On the project dashboard, click a bar in a usage chart (e.g. Postgres
activity) for a time period further back than the last hour.
- Expected result: Unified Logs opens with both the log_type filter and
the clicked bar's date range applied, and the row list matches the
sidebar facet count instead of showing "No results found".
- `UnifiedLogs.filters.test.ts` has unit tests covering the new seeding
behavior.

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
  * Improved log filtering from URL parameters.
  * Preserved valid date ranges when opening deep-linked log views.
* Prevented malformed or duplicate date filters from appearing in the
logs table.

* **Tests**
* Added coverage for valid, missing, malformed, and duplicate date
filter scenarios.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
Jordi Enric authored and GitHub committed 2026-08-04 12:37:59 +02:00
1 parent afda8461a3
commit 2ba2c37163
3 files changed
+59 -2

No files matched your search

@@ -1,6 +1,7 @@
import { describe, expect, it } from 'vitest'
import {
buildDefaultColumnFilters,
buildFilterSearchUpdate,
columnFiltersToLogsFilters,
logsFiltersToColumnFilters,
@@ -122,3 +123,46 @@ describe('buildFilterSearchUpdate', () => {
expect(update.date).toBeNull()
})
})
describe('buildDefaultColumnFilters', () => {
const fields = [
{ value: 'date', type: 'timerange' },
{ value: 'log_type', type: 'checkbox' },
]
it('seeds a deep-linked `date` range so it survives the debounced sync back to the URL', () => {
const range = [new Date('2026-05-08T00:00:00Z'), new Date('2026-05-08T01:00:00Z')]
const columnFilters = buildDefaultColumnFilters({
filter: ['log_type:eq:postgres'],
date: range,
})
expect(columnFilters).toEqual([
{ id: 'log_type', value: ['postgres'] },
{ id: 'date', value: range },
])
// Regression guard: without the `date` entry above, this would null out the range.
const update = buildFilterSearchUpdate(columnFilters, fields)
expect(update.date).toBe(range)
})
it('omits `date` when no range is present, matching the pre-existing no-filter case', () => {
expect(buildDefaultColumnFilters({ filter: ['log_type:eq:postgres'], date: null })).toEqual([
{ id: 'log_type', value: ['postgres'] },
])
})
it('omits `date` for a malformed single-element range', () => {
const columnFilters = buildDefaultColumnFilters({
filter: ['log_type:eq:postgres'],
date: [new Date('2026-05-08T00:00:00Z')],
})
expect(columnFilters).toEqual([{ id: 'log_type', value: ['postgres'] }])
})
it('does not duplicate the `date` id when a hand-crafted `filter` param also targets it', () => {
const range = [new Date('2026-05-08T00:00:00Z'), new Date('2026-05-08T01:00:00Z')]
const columnFilters = buildDefaultColumnFilters({ filter: ['date:eq:123'], date: range })
expect(columnFilters).toEqual([{ id: 'date', value: range }])
})
})
@@ -89,6 +89,19 @@ export const logsFiltersToColumnFilters = (
)
}
// Seeds `date` too, so `logsFiltersToColumnFilters` (which only covers the `filter`
// param) doesn't leave it out and get nulled by the debounced sync back to `search`.
export const buildDefaultColumnFilters = (search: {
filter?: string[] | null
date?: Date[] | null
}): { id: string; value: unknown }[] => {
const filters: { id: string; value: unknown }[] = logsFiltersToColumnFilters(
parseLogsFilterUrlParams(search.filter)
).filter((f) => f.id !== 'date')
if (search.date?.length === 2) filters.push({ id: 'date', value: search.date })
return filters
}
export const columnFiltersToLogsFilters = (
columnFilters: { id: string; value: unknown }[],
filterableNames?: Set<string>
@@ -38,8 +38,8 @@ import { ServiceFlowPanel } from './ServiceFlowPanel'
import { SEARCH_PARAMS_PARSER } from './UnifiedLogs.constants'
import { filterFields as defaultFilterFields } from './UnifiedLogs.fields'
import {
buildDefaultColumnFilters,
buildFilterSearchUpdate,
logsFiltersToColumnFilters,
parseLogsFilterUrlParams,
} from './UnifiedLogs.filters'
import { useLiveMode, useResetFocus } from './UnifiedLogs.hooks'
@@ -96,7 +96,7 @@ export const UnifiedLogs = () => {
const defaultColumnSorting = search.sort ? [search.sort] : []
const defaultColumnVisibility = { uuid: false }
const defaultColumnFilters = logsFiltersToColumnFilters(parseLogsFilterUrlParams(search.filter))
const defaultColumnFilters = buildDefaultColumnFilters(search)
const [topBarHeight, setTopBarHeight] = useState(0)
const topBarRef = useRef<HTMLDivElement>(null)