From 519acc277e1f7837a5e301e9e269ee7b37f78b38 Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 09:57:36 +0530 Subject: [PATCH 01/10] feat: Add chart legend series filtering - Click on legend labels to show only that series in the chart - Shift+Click to select multiple series for comparison - All legend items remain visible with clear visual feedback: - Selected: Bold font, thick line, full opacity - Non-selected: Dimmed (30% opacity) but still visible - Click selected item again to clear filter and show all series - Auto-scale Y-axis when filtering chart series to fit visible data Part of #1558 --- packages/app/src/HDXMultiSeriesTimeChart.tsx | 178 ++++++++++++++++--- packages/app/src/components/DBTimeChart.tsx | 30 ++++ 2 files changed, 179 insertions(+), 29 deletions(-) diff --git a/packages/app/src/HDXMultiSeriesTimeChart.tsx b/packages/app/src/HDXMultiSeriesTimeChart.tsx index 057dacb4c8..bdb98153b7 100644 --- a/packages/app/src/HDXMultiSeriesTimeChart.tsx +++ b/packages/app/src/HDXMultiSeriesTimeChart.tsx @@ -221,17 +221,44 @@ function CopyableLegendItem({ entry }: any) { ); } -function ExpandableLegendItem({ entry, expanded }: any) { +function ExpandableLegendItem({ + entry, + expanded, + isSelected, + isDisabled, + onToggle +}: { + entry: any; + expanded?: boolean; + isSelected?: boolean; + isDisabled?: boolean; + onToggle?: (isShiftKey: boolean) => void; +}) { const [_expanded, setExpanded] = useState(false); const isExpanded = _expanded || expanded; return ( setExpanded(v => !v)} - title="Click to expand" + onClick={(e) => { + if (onToggle) { + onToggle(e.shiftKey); + } else { + setExpanded(v => !v); + } + }} + title={ + isSelected + ? "Click to show all (Shift+click to deselect)" + : "Click to show only this (Shift+click for multi-select)" + } >
@@ -241,8 +268,9 @@ function ExpandableLegendItem({ entry, expanded }: any) { x2="12" y2="2" stroke={entry.color} - opacity={entry.opacity} + opacity={isDisabled ? 0.3 : 1} strokeDasharray={entry.payload?.strokeDasharray} + strokeWidth={isSelected ? 2.5 : 1.5} />
@@ -258,13 +286,31 @@ export const LegendRenderer = memo<{ color: string; }[]; lineDataMap: { [key: string]: LineData }; + allLineData?: LineData[]; + selectedSeries?: Set; + onToggleSeries?: (seriesName: string, isShiftKey?: boolean) => void; }>(props => { - const { payload = [], lineDataMap } = props; + const { payload = [], lineDataMap, allLineData = [], selectedSeries = new Set(), onToggleSeries } = props; + + const hasSelection = selectedSeries.size > 0; + + // Use allLineData to ensure all series are always shown in legend + const allSeriesPayload = useMemo(() => { + if (allLineData.length > 0) { + return allLineData.map(ld => ({ + dataKey: ld.dataKey, + value: ld.displayName || ld.dataKey, + color: ld.color, + payload: { strokeDasharray: ld.isDashed ? '4 3' : '0' } + })); + } + return payload; + }, [allLineData, payload]); const sortedLegendItems = useMemo(() => { // Order items such that current and previous period lines are consecutive const currentPeriodKeyIndex = new Map(); - payload.forEach((line, index) => { + allSeriesPayload.forEach((line, index) => { const currentPeriodKey = lineDataMap[line.dataKey]?.currentPeriodKey || ''; if (!currentPeriodKeyIndex.has(currentPeriodKey)) { @@ -272,7 +318,7 @@ export const LegendRenderer = memo<{ } }); - return payload.sort((a, b) => { + return allSeriesPayload.sort((a, b) => { const keyA = lineDataMap[a.dataKey]?.currentPeriodKey ?? ''; const keyB = lineDataMap[b.dataKey]?.currentPeriodKey ?? ''; @@ -288,13 +334,20 @@ export const LegendRenderer = memo<{ return (
- {shownItems.map((entry, index) => ( - - ))} + {shownItems.map((entry, index) => { + const isSelected = selectedSeries.has(entry.value); + const isDisabled = hasSelection && !isSelected; + return ( + onToggleSeries?.(entry.value, isShiftKey)} + /> + ); + })} {restItems.length ? ( @@ -304,13 +357,20 @@ export const LegendRenderer = memo<{
- {restItems.map((entry, index) => ( - - ))} + {restItems.map((entry, index) => { + const isSelected = selectedSeries.has(entry.value); + const isDisabled = hasSelection && !isSelected; + return ( + onToggleSeries?.(entry.value, isShiftKey)} + /> + ); + })}
@@ -336,6 +396,8 @@ export const MemoChart = memo(function MemoChart({ onTimeRangeSelect, showLegend = true, previousPeriodOffsetSeconds, + selectedSeriesNames, + onToggleSeries, }: { graphResults: any[]; setIsClickActive: (v: any) => void; @@ -351,6 +413,8 @@ export const MemoChart = memo(function MemoChart({ onTimeRangeSelect?: (start: Date, end: Date) => void; showLegend?: boolean; previousPeriodOffsetSeconds?: number; + selectedSeriesNames?: Set; + onToggleSeries?: (seriesName: string, isShiftKey?: boolean) => void; }) { const _id = useId(); const id = _id.replace(/:/g, ''); @@ -361,13 +425,23 @@ export const MemoChart = memo(function MemoChart({ displayType === DisplayType.StackedBar ? BarChart : AreaChart; // LineChart; const lines = useMemo(() => { + const hasSelection = selectedSeriesNames && selectedSeriesNames.size > 0; + const limitedGroupKeys = lineData .map(ld => ld.dataKey) - .slice(0, HARD_LINES_LIMIT); + .slice(0, HARD_LINES_LIMIT) + .filter((key, i) => { + const seriesName = lineData[i]?.displayName ?? key; + // If there's a selection, only show selected series + // If no selection, show all series + return !hasSelection || selectedSeriesNames.has(seriesName); + }); return limitedGroupKeys.map((key, i) => { - const color = lineData[i]?.color; - const strokeDasharray = lineData[i]?.isDashed ? '4 3' : '0'; + const lineDataIndex = lineData.findIndex(ld => ld.dataKey === key); + const color = lineData[lineDataIndex]?.color; + const strokeDasharray = lineData[lineDataIndex]?.isDashed ? '4 3' : '0'; + const seriesName = lineData[lineDataIndex]?.displayName ?? key; const StackedBarWithOverlap = (props: BarProps) => { const { x, y, width, height, fill } = props; @@ -388,7 +462,7 @@ export const MemoChart = memo(function MemoChart({ key={key} type="monotone" dataKey={key} - name={lineData[i]?.displayName ?? key} + name={seriesName} fill={color} opacity={1} stackId="1" @@ -408,12 +482,50 @@ export const MemoChart = memo(function MemoChart({ fill: `url(#time-chart-lin-grad-${id}-${color.replace('#', '').toLowerCase()})`, strokeDasharray, })} - name={lineData[i]?.displayName ?? key} + name={seriesName} isAnimationActive={false} /> ); }); - }, [lineData, displayType, id, isHovered]); + }, [lineData, displayType, id, isHovered, selectedSeriesNames]); + + const yAxisDomain = useMemo(() => { + const hasSelection = selectedSeriesNames && selectedSeriesNames.size > 0; + + if (!hasSelection) { + // No selection, let Recharts auto-calculate based on all data + return ['auto', 'auto']; + } + + // When series are selected, calculate domain based only on visible series + let minValue = Infinity; + let maxValue = -Infinity; + + graphResults.forEach(dataPoint => { + lineData.forEach(ld => { + const seriesName = ld.displayName || ld.dataKey; + // Only consider selected series + if (selectedSeriesNames.has(seriesName)) { + const value = dataPoint[ld.dataKey]; + if (typeof value === 'number' && !isNaN(value)) { + minValue = Math.min(minValue, value); + maxValue = Math.max(maxValue, value); + } + } + }); + }); + + // If we found valid values, return them with some padding + if (minValue !== Infinity && maxValue !== -Infinity) { + const padding = (maxValue - minValue) * 0.1; // 10% padding + return [ + Math.max(0, minValue - padding), // Don't go below 0 + maxValue + padding + ]; + } + + return ['auto', 'auto']; + }, [graphResults, lineData, selectedSeriesNames]); const sizeRef = useRef<[number, number]>([0, 0]); @@ -606,6 +718,7 @@ export const MemoChart = memo(function MemoChart({ minTickGap={25} tickFormatter={tickFormatter} tick={{ fontSize: 11, fontFamily: 'IBM Plex Mono, monospace' }} + domain={yAxisDomain} /> {lines} {isClickActive == null && ( @@ -635,7 +748,14 @@ export const MemoChart = memo(function MemoChart({ } + content={ + + } offset={-100} /> )} diff --git a/packages/app/src/components/DBTimeChart.tsx b/packages/app/src/components/DBTimeChart.tsx index 2a884fe071..20360c75a2 100644 --- a/packages/app/src/components/DBTimeChart.tsx +++ b/packages/app/src/components/DBTimeChart.tsx @@ -243,6 +243,34 @@ function DBTimeChartComponent({ showMVOptimizationIndicator = true, }: DBTimeChartComponentProps) { const [isErrorExpanded, errorExpansion] = useDisclosure(false); + const [selectedSeriesSet, setSelectedSeriesSet] = useState>(new Set()); + + const handleToggleSeries = useCallback((seriesName: string, isShiftKey?: boolean) => { + setSelectedSeriesSet(prev => { + const newSet = new Set(prev); + + if (isShiftKey) { + // Shift-click: add to selection + if (newSet.has(seriesName)) { + newSet.delete(seriesName); + } else { + newSet.add(seriesName); + } + } else { + // Regular click: toggle selection + if (newSet.has(seriesName) && newSet.size === 1) { + // If this is the only selected item, clear selection (show all) + newSet.clear(); + } else { + // Otherwise, select only this one + newSet.clear(); + newSet.add(seriesName); + } + } + + return newSet; + }); + }, []); const originalDateRange = config.dateRange; const { @@ -683,6 +711,8 @@ function DBTimeChartComponent({ showLegend={showLegend} timestampKey={timestampColumn?.name} previousPeriodOffsetSeconds={previousPeriodOffsetSeconds} + selectedSeriesNames={selectedSeriesSet} + onToggleSeries={handleToggleSeries} /> )} From f54c250dce1f46ad277c949b742f9f432ca12f2c Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 09:57:44 +0530 Subject: [PATCH 02/10] chore: Add changeset for chart legend series filtering --- .changeset/chart-legend-filtering.md | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 .changeset/chart-legend-filtering.md diff --git a/.changeset/chart-legend-filtering.md b/.changeset/chart-legend-filtering.md new file mode 100644 index 0000000000..0dae24ba69 --- /dev/null +++ b/.changeset/chart-legend-filtering.md @@ -0,0 +1,6 @@ +--- +'@hyperdx/app': minor +--- + +Add chart legend series filtering with click and shift-click selection + From 326d5dd90a55d33bb5167e75190e40286f051c52 Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 10:38:35 +0530 Subject: [PATCH 03/10] feat: Add dashboard tile tags for organization and filtering - Add tags field to dashboard tiles for better organization - Tiles can be tagged in the edit modal - Click on tile tags to filter the dashboard view - Visual filter indicator shows active tag filter - URL state preservation for sharing filtered views Part of #1558 --- packages/app/src/DBDashboardPage.tsx | 319 +++++++++++++++++---------- packages/app/src/dashboard.ts | 1 + packages/common-utils/src/types.ts | 1 + 3 files changed, 202 insertions(+), 119 deletions(-) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 90f1c0818e..25c60120ce 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -43,6 +43,7 @@ import { Text, Title, Tooltip, + Badge, } from '@mantine/core'; import { useHover } from '@mantine/hooks'; import { notifications } from '@mantine/notifications'; @@ -58,6 +59,7 @@ import { IconTags, IconTrash, IconUpload, + IconX, } from '@tabler/icons-react'; import { ContactSupportText } from '@/components/ContactSupportText'; @@ -75,12 +77,19 @@ import { } from '@/dashboard'; import DBSqlRowTableWithSideBar from './components/DBSqlRowTableWithSidebar'; +import MVOptimizationIndicator from './components/MaterializedViews/MVOptimizationIndicator'; import OnboardingModal from './components/OnboardingModal'; import { Tags } from './components/Tags'; import useDashboardFilters from './hooks/useDashboardFilters'; import { useDashboardRefresh } from './hooks/useDashboardRefresh'; import { parseAsStringWithNewLines } from './utils/queryParsers'; -import { buildTableRowSearchUrl, DEFAULT_CHART_CONFIG } from './ChartUtils'; +import { + buildTableRowSearchUrl, + convertToNumberChartConfig, + convertToTableChartConfig, + convertToTimeChartConfig, + DEFAULT_CHART_CONFIG, +} from './ChartUtils'; import { IS_LOCAL_MODE } from './config'; import { useDashboard } from './dashboard'; import DashboardFilters from './DashboardFilters'; @@ -121,6 +130,7 @@ const Tile = forwardRef( granularity, onTimeRangeSelect, filters, + onTagClick, // Properties forwarded by grid layout className, @@ -142,6 +152,7 @@ const Tile = forwardRef( granularity: SQLInterval | undefined; onTimeRangeSelect: (start: Date, end: Date) => void; filters?: Filter[]; + onTagClick?: (tag: string) => void; // Properties forwarded by grid layout className?: string; @@ -166,6 +177,25 @@ const Tile = forwardRef( ChartConfigWithDateRange | undefined >(undefined); + // Transform the queried config to match what will be queried by the + // child components, so that the MV Optimization indicator is accurate. + const configForMVOptimizationIndicator = useMemo(() => { + if (!queriedConfig) return undefined; + + if ( + queriedConfig.displayType === DisplayType.Line || + queriedConfig.displayType === DisplayType.StackedBar + ) { + return convertToTimeChartConfig(queriedConfig); + } else if (queriedConfig.displayType === DisplayType.Number) { + return convertToNumberChartConfig(queriedConfig); + } else if (queriedConfig.displayType === DisplayType.Table) { + return convertToTableChartConfig(queriedConfig); + } + + return queriedConfig; + }, [queriedConfig]); + const { data: source } = useSource({ id: chart.config.source, }); @@ -224,96 +254,10 @@ const Tile = forwardRef( return tooltip; }, [alert]); - const hoverToolbar = useMemo(() => { - return hovered ? ( - e.stopPropagation()} - key="hover-toolbar" - > - {(chart.config.displayType === DisplayType.Line || - chart.config.displayType === DisplayType.StackedBar) && ( - +} - mr={4} - > - - - - - )} - - - - - - ) : ( - - ); - }, [ - alert, - alertIndicatorColor, - alertTooltip, - chart.config.displayType, - chart.id, - hovered, - onDeleteClick, - onDuplicateClick, - onEditClick, - ]); - - const title = useMemo( - () => ( - - {chart.config.name} - - ), - [chart.config.name], - ); - return (
- - - +
+ + + {chart.config.name} + + {chart.tags && chart.tags.length > 0 && ( + e.stopPropagation()}> + {chart.tags.map(tag => ( + { + e.stopPropagation(); + onTagClick?.(tag); + }} + > + {tag} + + ))} + + )} + + + {hovered ? ( + e.stopPropagation()}> + {(chart.config.displayType === DisplayType.Line || + chart.config.displayType === DisplayType.StackedBar) && ( + +} + mr={4} + > + + + + + )} + + + + + + ) : ( + + )} + {source?.materializedViews?.length && queriedConfig && ( + e.stopPropagation()}> + + + )} + +
e.stopPropagation()} > buildTableRowSearchUrl({ @@ -381,18 +414,10 @@ const Tile = forwardRef( )} {queriedConfig?.displayType === DisplayType.Number && ( - + )} {queriedConfig?.displayType === DisplayType.Markdown && ( - + )} {queriedConfig?.displayType === DisplayType.Search && ( { const contextZIndex = useZIndex(); const modalZIndex = contextZIndex + 10; + const [tileTags, setTileTags] = useState(chart?.tags || []); + + // Update tileTags when chart changes + useEffect(() => { + if (chart) { + setTileTags(chart.tags || []); + } + }, [chart]); + return ( {chart != null && ( + + Tile Tags + + + + { if (!dashboard) return []; const tc: TableConnection[] = []; @@ -734,7 +786,13 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { const tiles = useMemo( () => - (dashboard?.tiles ?? []).map(chart => { + (dashboard?.tiles ?? []) + .filter(chart => { + // Filter tiles based on selected tag + if (!selectedTag) return true; + return chart.tags?.includes(selectedTag); + }) + .map(chart => { return ( { + setSelectedTag(tag === selectedTag ? null : tag); + }} onUpdateChart={newChart => { if (!dashboard) { return; @@ -822,6 +883,8 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { whereLanguage, onTimeRangeSelect, filterQueries, + selectedTag, + setSelectedTag, ], ); @@ -938,18 +1001,36 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { )} - { - if (dashboard != null) { - setDashboard({ - ...dashboard, - name: editedName, - }); - } - }} - /> + + { + if (dashboard != null) { + setDashboard({ + ...dashboard, + name: editedName, + }); + } + }} + /> + {selectedTag && ( + setSelectedTag(null)} + /> + } + > + Filtered by: {selectedTag} + + )} + {!isLocalDashboard && dashboard?.id && ( Date: Thu, 8 Jan 2026 10:38:42 +0530 Subject: [PATCH 04/10] chore: Add changeset for dashboard tile tags --- .changeset/dashboard-tile-tags.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/dashboard-tile-tags.md diff --git a/.changeset/dashboard-tile-tags.md b/.changeset/dashboard-tile-tags.md new file mode 100644 index 0000000000..90403bf774 --- /dev/null +++ b/.changeset/dashboard-tile-tags.md @@ -0,0 +1,7 @@ +--- +'@hyperdx/app': minor +'@hyperdx/common-utils': minor +--- + +Add dashboard tile tags for organization and filtering + From 923c0a88b8760d2e6e193b9be9a7efa1041964e5 Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 10:53:51 +0530 Subject: [PATCH 05/10] fix: Stop event propagation when clearing tag filter --- packages/app/src/DBDashboardPage.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 25c60120ce..256fe89dd4 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -1023,7 +1023,10 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { setSelectedTag(null)} + onClick={(e) => { + e.stopPropagation(); + setSelectedTag(null); + }} /> } > From e0b4cc97667125f6d08a0483d5d97949d70ab71b Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 10:55:49 +0530 Subject: [PATCH 06/10] fix: Force ReactGridLayout to remount when tag filter changes to prevent layout issues --- packages/app/src/DBDashboardPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 256fe89dd4..c56eeaa00f 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -1237,6 +1237,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { } > { From 40295e00f8c4c7f97332827a9b98df3f214fe544 Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 10:57:29 +0530 Subject: [PATCH 07/10] fix: Use CSS display:none for tag filtering to preserve tile positions instead of removing from DOM --- packages/app/src/DBDashboardPage.tsx | 44 +++++++++++++--------------- 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index c56eeaa00f..bbad9e4e95 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -787,31 +787,27 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { const tiles = useMemo( () => (dashboard?.tiles ?? []) - .filter(chart => { - // Filter tiles based on selected tag - if (!selectedTag) return true; - return chart.tags?.includes(selectedTag); - }) .map(chart => { + const isHidden = selectedTag && !chart.tags?.includes(selectedTag); return ( - setEditedTile(chart)} - granularity={ - isRefreshEnabled - ? granularityOverride - : (granularity ?? undefined) - } - filters={[ - { - type: whereLanguage === 'sql' ? 'sql' : 'lucene', - condition: where, - }, - ...(filterQueries ?? []), - ]} - onTimeRangeSelect={onTimeRangeSelect} +
+ setEditedTile(chart)} + granularity={ + isRefreshEnabled + ? granularityOverride + : (granularity ?? undefined) + } + filters={[ + { + type: whereLanguage === 'sql' ? 'sql' : 'lucene', + condition: where, + }, + ...(filterQueries ?? []), + ]} + onTimeRangeSelect={onTimeRangeSelect} isHighlighted={highlightedTileId === chart.id} onTagClick={(tag) => { setSelectedTag(tag === selectedTag ? null : tag); @@ -868,6 +864,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { } }} /> +
); }), [ @@ -1237,7 +1234,6 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { } > { From 63bcd0954bc6c0df8cf77c36dba9fb29e119208d Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 10:59:00 +0530 Subject: [PATCH 08/10] fix: Apply display:none style directly to Tile component instead of wrapper div --- packages/app/src/DBDashboardPage.tsx | 38 ++++++++++++++-------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index bbad9e4e95..340c43bbaf 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -790,28 +790,29 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { .map(chart => { const isHidden = selectedTag && !chart.tags?.includes(selectedTag); return ( -
- setEditedTile(chart)} - granularity={ - isRefreshEnabled - ? granularityOverride - : (granularity ?? undefined) - } - filters={[ - { - type: whereLanguage === 'sql' ? 'sql' : 'lucene', - condition: where, - }, - ...(filterQueries ?? []), - ]} - onTimeRangeSelect={onTimeRangeSelect} + setEditedTile(chart)} + granularity={ + isRefreshEnabled + ? granularityOverride + : (granularity ?? undefined) + } + filters={[ + { + type: whereLanguage === 'sql' ? 'sql' : 'lucene', + condition: where, + }, + ...(filterQueries ?? []), + ]} + onTimeRangeSelect={onTimeRangeSelect} isHighlighted={highlightedTileId === chart.id} onTagClick={(tag) => { setSelectedTag(tag === selectedTag ? null : tag); }} + style={{ display: isHidden ? 'none' : undefined }} onUpdateChart={newChart => { if (!dashboard) { return; @@ -864,7 +865,6 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { } }} /> -
); }), [ From 9af86ac9d46e4fcd28641c2dba5b4d74cef6972c Mon Sep 17 00:00:00 2001 From: Alok Kumar Singh Date: Thu, 8 Jan 2026 11:00:45 +0530 Subject: [PATCH 09/10] debug: Add console logging for tag filtering --- packages/app/src/DBDashboardPage.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 340c43bbaf..6144f78fd0 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -789,6 +789,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { (dashboard?.tiles ?? []) .map(chart => { const isHidden = selectedTag && !chart.tags?.includes(selectedTag); + console.log('Tile:', chart.config.name, 'Tags:', chart.tags, 'SelectedTag:', selectedTag, 'isHidden:', isHidden); return ( Date: Thu, 8 Jan 2026 11:16:26 +0530 Subject: [PATCH 10/10] fix: Properly apply display:none by merging style after grid layout styles --- packages/app/src/DBDashboardPage.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/app/src/DBDashboardPage.tsx b/packages/app/src/DBDashboardPage.tsx index 6144f78fd0..c3d48fe9ff 100644 --- a/packages/app/src/DBDashboardPage.tsx +++ b/packages/app/src/DBDashboardPage.tsx @@ -140,6 +140,7 @@ const Tile = forwardRef( onTouchEnd, children, isHighlighted, + isHidden, }: { chart: Tile; dateRange: [Date, Date]; @@ -162,6 +163,7 @@ const Tile = forwardRef( onTouchEnd?: (e: React.TouchEvent) => void; children?: React.ReactNode; // Resizer tooltip isHighlighted?: boolean; + isHidden?: boolean; }, ref: ForwardedRef, ) => { @@ -267,6 +269,7 @@ const Tile = forwardRef( ref={ref} style={{ ...style, + ...(isHidden ? { display: 'none' } : {}), }} onMouseDown={onMouseDown} onMouseUp={onMouseUp} @@ -813,6 +816,7 @@ function DBDashboardPage({ presetConfig }: { presetConfig?: Dashboard }) { onTagClick={(tag) => { setSelectedTag(tag === selectedTag ? null : tag); }} + isHidden={isHidden} style={{ display: isHidden ? 'none' : undefined }} onUpdateChart={newChart => { if (!dashboard) {