Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/ui/src/dashboard/flow/flowFilterPrefs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, expect, it, beforeEach } from 'vitest'
import {
FLOW_FILTER_PREFS_LS_KEY,
initialFlowFilters,
loadStoredFlowPrefs,
saveStoredFlowPrefs,
} from './flowFilterPrefs'
import { DEFAULT_FILTERS } from './flowFilterPrefs'

describe('flowFilterPrefs', () => {
beforeEach(() => {
localStorage.clear()
})

it('round-trips host grouping and toggles', () => {
saveStoredFlowPrefs({
...DEFAULT_FILTERS,
hostGrouping: 'group-by-alias',
isSimplify: true,
isAbsoluteTime: true,
isHighContrast: true,
})
expect(loadStoredFlowPrefs()).toEqual({
hostGrouping: 'group-by-alias',
isSimplify: true,
isAbsoluteTime: true,
isHighContrast: true,
})
})

it('ignores invalid stored host grouping', () => {
localStorage.setItem(
FLOW_FILTER_PREFS_LS_KEY,
JSON.stringify({ hostGrouping: 'invalid', isSimplify: true }),
)
expect(loadStoredFlowPrefs()).toEqual({ isSimplify: true })
})

it('initialFlowFilters merges stored prefs with empty exclusion sets', () => {
localStorage.setItem(
FLOW_FILTER_PREFS_LS_KEY,
JSON.stringify({ hostGrouping: 'group-by-ip' }),
)
const f = initialFlowFilters()
expect(f.hostGrouping).toBe('group-by-ip')
expect(f.ipExcluded.size).toBe(0)
expect(f.methodExcluded.size).toBe(0)
})
})
84 changes: 84 additions & 0 deletions src/ui/src/dashboard/flow/flowFilterPrefs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import type { HostGrouping } from './flow-data'

export const FLOW_FILTER_PREFS_LS_KEY = 'homer_callflow_prefs'

const HOST_GROUPINGS: HostGrouping[] = ['ungrouped', 'group-by-ip', 'group-by-alias']

export interface FlowFilters {
isSimplify: boolean
isAbsoluteTime: boolean
isHighContrast: boolean
hostGrouping: HostGrouping
ipExcluded: Set<string>
methodExcluded: Set<string>
payloadTypeExcluded: Set<string>
callIdExcluded: Set<string>
}

export const DEFAULT_FILTERS: FlowFilters = {
isSimplify: false,
isAbsoluteTime: false,
isHighContrast: false,
hostGrouping: 'ungrouped',
ipExcluded: new Set(),
methodExcluded: new Set(),
payloadTypeExcluded: new Set(),
callIdExcluded: new Set(),
}

export interface StoredFlowPrefs {
hostGrouping?: HostGrouping
isSimplify?: boolean
isAbsoluteTime?: boolean
isHighContrast?: boolean
}

export function isHostGrouping(value: unknown): value is HostGrouping {
return typeof value === 'string' && (HOST_GROUPINGS as string[]).includes(value)
}

export function loadStoredFlowPrefs(): StoredFlowPrefs {
if (typeof localStorage === 'undefined') return {}
try {
const raw = localStorage.getItem(FLOW_FILTER_PREFS_LS_KEY)
if (!raw) return {}
const parsed = JSON.parse(raw) as StoredFlowPrefs
if (!parsed || typeof parsed !== 'object') return {}
const out: StoredFlowPrefs = {}
if (isHostGrouping(parsed.hostGrouping)) out.hostGrouping = parsed.hostGrouping
if (typeof parsed.isSimplify === 'boolean') out.isSimplify = parsed.isSimplify
if (typeof parsed.isAbsoluteTime === 'boolean') out.isAbsoluteTime = parsed.isAbsoluteTime
if (typeof parsed.isHighContrast === 'boolean') out.isHighContrast = parsed.isHighContrast
return out
} catch {
return {}
}
}

export function saveStoredFlowPrefs(filters: FlowFilters): void {
if (typeof localStorage === 'undefined') return
const payload: StoredFlowPrefs = {
hostGrouping: filters.hostGrouping,
isSimplify: filters.isSimplify,
isAbsoluteTime: filters.isAbsoluteTime,
isHighContrast: filters.isHighContrast,
}
try {
localStorage.setItem(FLOW_FILTER_PREFS_LS_KEY, JSON.stringify(payload))
} catch {
/* ignore quota / private mode */
}
}

/** UI prefs from localStorage + fresh per-call exclusion sets. */
export function initialFlowFilters(): FlowFilters {
const stored = loadStoredFlowPrefs()
return {
...DEFAULT_FILTERS,
...stored,
ipExcluded: new Set(),
methodExcluded: new Set(),
payloadTypeExcluded: new Set(),
callIdExcluded: new Set(),
}
}
41 changes: 16 additions & 25 deletions src/ui/src/dashboard/flow/useFlowFilters.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,21 @@
import { useMemo, useState } from 'react'
import type { HostGrouping, RawMessage } from './flow-data'
import { useEffect, useMemo, useState } from 'react'
import type { RawMessage } from './flow-data'
import { payloadTypeOf } from './flow-data'
import {
DEFAULT_FILTERS,
initialFlowFilters,
saveStoredFlowPrefs,
type FlowFilters,
} from './flowFilterPrefs'
Comment thread
github-code-quality[bot] marked this conversation as resolved.
Fixed

export type { FlowFilters } from './flowFilterPrefs'
export { DEFAULT_FILTERS } from './flowFilterPrefs'

export interface FilterToken {
value: string
selected: boolean
}

export interface FlowFilters {
isSimplify: boolean
isAbsoluteTime: boolean
isHighContrast: boolean
hostGrouping: HostGrouping
ipExcluded: Set<string>
methodExcluded: Set<string>
payloadTypeExcluded: Set<string>
callIdExcluded: Set<string>
}

export const DEFAULT_FILTERS: FlowFilters = {
isSimplify: false,
isAbsoluteTime: false,
isHighContrast: false,
hostGrouping: 'ungrouped',
ipExcluded: new Set(),
methodExcluded: new Set(),
payloadTypeExcluded: new Set(),
callIdExcluded: new Set(),
}

function collectUnique(items: RawMessage[], picker: (m: RawMessage) => string[]): string[] {
const set = new Set<string>()
items.forEach((m) => {
Expand Down Expand Up @@ -65,7 +52,11 @@ export interface UseFlowFiltersResult {
}

export function useFlowFilters(items: RawMessage[] | null | undefined): UseFlowFiltersResult {
const [filters, setFilters] = useState<FlowFilters>(DEFAULT_FILTERS)
const [filters, setFilters] = useState<FlowFilters>(initialFlowFilters)

useEffect(() => {
saveStoredFlowPrefs(filters)
}, [filters.hostGrouping, filters.isSimplify, filters.isAbsoluteTime, filters.isHighContrast])

const { filterIP, filterMethod, filterPayloadType, filterCallId, filteredItems } = useMemo(() => {
const safe = items ?? []
Expand Down
Loading