Skip to content

Commit 0c221d0

Browse files
committed
feat: Broadcast adhoc filters to promql tiles
1 parent bee0797 commit 0c221d0

19 files changed

Lines changed: 626 additions & 82 deletions

‎.changeset/adhoc-filter-broadcast.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
'@hyperdx/app': patch
44
---
55

6-
feat: Apply adhoc dashboard filter conditions to builder and raw SQL tiles
6+
feat: Broadcast adhoc dashboard filter conditions

‎packages/app/src/DBDashboardPage.tsx‎

Lines changed: 34 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ import {
3535
isTimeSeriesDisplayType,
3636
} from '@hyperdx/common-utils/dist/core/utils';
3737
import { getBlockingRequiredFilterNames } from '@hyperdx/common-utils/dist/dashboardFilterValues';
38+
import {
39+
getPromqlFilters,
40+
getSqlFilters,
41+
} from '@hyperdx/common-utils/dist/filters';
3842
import {
3943
displayTypeRequiresSource,
4044
isBuilderChartConfig,
@@ -51,12 +55,12 @@ import {
5155
import { isMissingFiltersMacro } from '@hyperdx/common-utils/dist/macros';
5256
import {
5357
AlertState,
58+
BroadcastFilter,
5459
ChartConfigWithDateRange,
5560
ChartVariable,
5661
DashboardContainer as DashboardContainerSchema,
5762
DashboardFilter,
5863
DisplayType,
59-
Filter,
6064
getSampleWeightExpression,
6165
isLogSource,
6266
isTraceSource,
@@ -471,7 +475,7 @@ const Tile = ({
471475
onSettled?: () => void;
472476
granularity: SQLInterval | undefined;
473477
onTimeRangeSelect: (start: Date, end: Date) => void;
474-
filters?: Filter[];
478+
filters?: BroadcastFilter[];
475479
variables?: ChartVariable[];
476480
/** The dashboard's required filters that have nothing selected. */
477481
unsatisfiedRequiredFilters?: DashboardFilter[];
@@ -654,6 +658,15 @@ const Tile = ({
654658
missingRequiredFilterNames.length > 0 &&
655659
displayTypeRequiresSource(chart.config.displayType);
656660

661+
const sqlFilters = useMemo(
662+
() => (filters ? getSqlFilters(filters) : undefined),
663+
[filters],
664+
);
665+
const promqlFilters = useMemo(
666+
() => (filters ? getPromqlFilters(filters) : undefined),
667+
[filters],
668+
);
669+
657670
useEffect(() => {
658671
if (isPromqlSavedChartConfig(chart.config)) {
659672
if (source != null) {
@@ -664,6 +677,7 @@ const Tile = ({
664677
dateRange,
665678
granularity,
666679
variables: tileVariables,
680+
filters: promqlFilters,
667681
});
668682
}
669683
return;
@@ -676,7 +690,7 @@ const Tile = ({
676690
...chart.config,
677691
dateRange,
678692
granularity,
679-
filters,
693+
filters: sqlFilters,
680694
variables: tileVariables,
681695
});
682696
} else if (source != null) {
@@ -695,7 +709,7 @@ const Tile = ({
695709
sampleWeightExpression: getSampleWeightExpression(source),
696710
dateRange,
697711
granularity,
698-
filters,
712+
filters: sqlFilters,
699713
variables: tileVariables,
700714
});
701715
}
@@ -736,13 +750,21 @@ const Tile = ({
736750
? source.bodyExpression
737751
: undefined,
738752
sampleWeightExpression: getSampleWeightExpression(source),
739-
filters,
753+
filters: sqlFilters,
740754
variables: tileVariables,
741755
metricTables: isMetricSource ? source.metricTables : undefined,
742756
});
743757
}
744758
}
745-
}, [source, chart, dateRange, granularity, filters, tileVariables]);
759+
}, [
760+
source,
761+
chart,
762+
dateRange,
763+
granularity,
764+
sqlFilters,
765+
promqlFilters,
766+
tileVariables,
767+
]);
746768

747769
const [hovered, setHovered] = useState(false);
748770

@@ -813,7 +835,7 @@ const Tile = ({
813835
where: builderConfig?.where,
814836
whereLanguage: builderConfig?.whereLanguage,
815837
select: builderConfig?.select,
816-
filters,
838+
filters: sqlFilters,
817839
},
818840
);
819841

@@ -823,10 +845,10 @@ const Tile = ({
823845
);
824846

825847
const filterWarning = useMemo(() => {
826-
const doFiltersExist = !!filters?.filter(
848+
const doFiltersExist = !!sqlFilters?.filter(
827849
f => (f.type === 'lucene' || f.type === 'sql') && f.condition.trim(),
828850
)?.length;
829-
const doLuceneFiltersExist = !!filters?.filter(
851+
const doLuceneFiltersExist = !!sqlFilters?.filter(
830852
f => f.type === 'lucene' && f.condition.trim(),
831853
)?.length;
832854

@@ -862,7 +884,7 @@ const Tile = ({
862884
<IconZoomExclamation size={16} color="var(--color-text-danger)" />
863885
</Tooltip>
864886
);
865-
}, [filters, queriedConfig, source]);
887+
}, [sqlFilters, queriedConfig, source]);
866888

867889
const replaySearchUrl = useMemo(() => {
868890
return buildDashboardReplaySearchUrl({
@@ -1656,7 +1678,7 @@ const EditTileModal = ({
16561678
isSaving?: boolean;
16571679
onSave: (chart: Tile) => void;
16581680
variables?: ChartVariable[];
1659-
getDashboardFilters: (sourceId: string | undefined) => Filter[];
1681+
getDashboardFilters: (sourceId: string | undefined) => BroadcastFilter[];
16601682
unsatisfiedRequiredFilters?: DashboardFilter[];
16611683
}) => {
16621684
const contextZIndex = useZIndex();
@@ -2435,7 +2457,7 @@ function DBDashboardPage({
24352457
// `sourceId`. Shared with the tile editor so its preview queries what the
24362458
// tile does.
24372459
const getTileFilters = useCallback(
2438-
(sourceId: string | undefined): Filter[] => [
2460+
(sourceId: string | undefined): BroadcastFilter[] => [
24392461
{
24402462
type: whereLanguage === 'sql' ? 'sql' : 'lucene',
24412463
condition: where,

‎packages/app/src/components/DBEditTimeChartForm/EditTimeChartForm.tsx‎

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,11 @@ import {
2222
isRawSqlSavedChartConfig,
2323
} from '@hyperdx/common-utils/dist/guards';
2424
import {
25+
BroadcastFilter,
2526
ChartConfigWithDateRange,
2627
ChartVariable,
2728
DashboardFilter,
2829
DisplayType,
29-
Filter,
3030
HeatmapMode,
3131
SavedChartConfig,
3232
SourceKind,
@@ -106,6 +106,7 @@ import {
106106
displayTypeToActiveTab,
107107
resolveTilePreviewFilters,
108108
TABS_WITH_GENERATED_SQL,
109+
withBroadcastFilters,
109110
zSavedChartConfig,
110111
} from './utils';
111112

@@ -115,7 +116,7 @@ type EditTimeChartFormProps = {
115116
/** Variables and their selected values, from the parent dashboard (if one exists). */
116117
variables?: ChartVariable[];
117118
/** Function returning the dashboard's broadcast filters for the given source, if any. */
118-
getDashboardFilters?: (sourceId: string | undefined) => Filter[];
119+
getDashboardFilters?: (sourceId: string | undefined) => BroadcastFilter[];
119120
/** The dashboard's required filters that have nothing selected, if any */
120121
unsatisfiedRequiredFilters?: DashboardFilter[];
121122
displayedTimeInputValue?: string;
@@ -524,8 +525,7 @@ export default function EditTimeChartForm({
524525
return queriedConfig;
525526
}
526527
return {
527-
...queriedConfig,
528-
filters: previewDashboardFilters?.filters,
528+
...withBroadcastFilters(queriedConfig, previewDashboardFilters?.filters),
529529
variables: previewDashboardFilters?.variables,
530530
};
531531
}, [queriedConfig, previewDashboardFilters]);

‎packages/app/src/components/DBEditTimeChartForm/__tests__/utils.test.ts‎

Lines changed: 51 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ import {
33
ChartVariable,
44
DashboardFilter,
55
DisplayType,
6+
Filter,
67
PromqlExpressionList,
8+
PromqlFilter,
79
SavedChartConfig,
810
SourceKind,
911
TSource,
@@ -23,6 +25,7 @@ import {
2325
seriesToFilters,
2426
tabQueriesData,
2527
TABS_WITH_GENERATED_SQL,
28+
withBroadcastFilters,
2629
} from '@/components/DBEditTimeChartForm/utils';
2730

2831
// ---------------------------------------------------------------------------
@@ -639,6 +642,7 @@ describe('resolveTilePreviewFilters', () => {
639642
{ type: 'sql' as const, condition: "ServiceName IN ('api')" },
640643
];
641644
const variables = [{ name: 'service', values: ['api'] }];
645+
const promqlFilter: PromqlFilter = { type: 'promql', condition: 'job="api"' };
642646

643647
const promqlConfig: ChartConfigWithDateRange = {
644648
configType: 'promql',
@@ -696,8 +700,30 @@ describe('resolveTilePreviewFilters', () => {
696700
});
697701
});
698702

699-
it('never hands filters to a PromQL tile', () => {
700-
expect(resolve({ config: promqlConfig }).filters).toBeUndefined();
703+
it('never hands SQL filters to a PromQL tile', () => {
704+
expect(resolve({ config: promqlConfig }).filters).toEqual([]);
705+
});
706+
707+
it('hands each tile the filters in its own query language', () => {
708+
const filters = [...dashboardFilters, promqlFilter];
709+
710+
expect(resolve({ config: promqlConfig, filters }).filters).toEqual([
711+
promqlFilter,
712+
]);
713+
expect(resolve({ filters }).filters).toEqual(dashboardFilters);
714+
expect(
715+
resolve({ config: rawSqlWithFiltersMacro, filters }).filters,
716+
).toEqual(dashboardFilters);
717+
});
718+
719+
it('drops PromQL filters when turned off', () => {
720+
expect(
721+
resolve({
722+
config: promqlConfig,
723+
filters: [promqlFilter],
724+
applySelections: false,
725+
}).filters,
726+
).toBeUndefined();
701727
});
702728

703729
it('drops the filters a raw-SQL template would not apply', () => {
@@ -761,6 +787,29 @@ describe('resolveTilePreviewFilters', () => {
761787
});
762788
});
763789

790+
describe('withBroadcastFilters', () => {
791+
const sqlFilter: Filter = { type: 'sql', condition: "ServiceName = 'api'" };
792+
const promqlFilter: PromqlFilter = { type: 'promql', condition: 'job="api"' };
793+
const promqlConfig: ChartConfigWithDateRange = {
794+
configType: 'promql',
795+
promqlExpression: 'up',
796+
connection: 'local',
797+
dateRange,
798+
};
799+
800+
it('attaches only PromQL filters to a PromQL config', () => {
801+
expect(
802+
withBroadcastFilters(promqlConfig, [sqlFilter, promqlFilter]),
803+
).toMatchObject({ filters: [promqlFilter] });
804+
});
805+
806+
it('attaches only SQL and Lucene filters to other configs', () => {
807+
expect(
808+
withBroadcastFilters(builderConfig, [sqlFilter, promqlFilter]),
809+
).toMatchObject({ filters: [sqlFilter] });
810+
});
811+
});
812+
764813
// ---------------------------------------------------------------------------
765814
// buildSampleEventsConfig
766815
// ---------------------------------------------------------------------------

‎packages/app/src/components/DBEditTimeChartForm/utils.ts‎

Lines changed: 27 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ import {
1212
configConsumesBroadcastFilters,
1313
getBlockingRequiredFilterNames,
1414
} from '@hyperdx/common-utils/dist/dashboardFilterValues';
15+
import {
16+
getPromqlFilters,
17+
getSqlFilters,
18+
} from '@hyperdx/common-utils/dist/filters';
1519
import {
1620
isBuilderChartConfig,
1721
isPromqlChartConfig,
@@ -20,6 +24,7 @@ import {
2024
} from '@hyperdx/common-utils/dist/guards';
2125
import { substitutePromqlChartConfigTemplates } from '@hyperdx/common-utils/dist/macros';
2226
import {
27+
BroadcastFilter,
2328
BuilderChartConfigWithDateRange,
2429
ChartAlertBaseSchema,
2530
ChartConfigWithDateRange,
@@ -201,8 +206,8 @@ export function resolvePreviewVariables({
201206

202207
/** What the dashboard's filter state contributes to a tile preview. */
203208
export type TilePreviewFilters = {
204-
/** Broadcast filter conditions to query the preview with. */
205-
filters: Filter[] | undefined;
209+
/** Broadcast filter conditions to query the preview with, in its query language. */
210+
filters: BroadcastFilter[] | undefined;
206211
/** The referenced variables, with or without their selections. */
207212
variables: ChartVariable[] | undefined;
208213
/** Names of the required filters that have nothing selected. */
@@ -225,7 +230,7 @@ export function resolveTilePreviewFilters({
225230
}: {
226231
config: ChartConfigWithDateRange;
227232
sourceId: string | undefined;
228-
filters: Filter[] | undefined;
233+
filters: BroadcastFilter[] | undefined;
229234
variables: ChartVariable[] | undefined;
230235
unsatisfiedRequiredFilters: DashboardFilter[] | undefined;
231236
applySelections: boolean;
@@ -244,13 +249,16 @@ export function resolveTilePreviewFilters({
244249
};
245250
}
246251

247-
const consumesBroadcastFilters = configConsumesBroadcastFilters(
248-
config,
249-
sourceId,
250-
);
252+
// PromQL tiles take only PromQL conditions; `configConsumesBroadcastFilters`
253+
// stays false for them so that required SQL filters don't block them.
254+
const previewFilters = isPromqlChartConfig(config)
255+
? filters && getPromqlFilters(filters)
256+
: configConsumesBroadcastFilters(config, sourceId) && filters
257+
? getSqlFilters(filters)
258+
: undefined;
251259

252260
return {
253-
filters: consumesBroadcastFilters ? filters : undefined,
261+
filters: previewFilters,
254262
variables: previewVariables,
255263
missingRequiredFilterNames: getBlockingRequiredFilterNames({
256264
config,
@@ -261,6 +269,17 @@ export function resolveTilePreviewFilters({
261269
};
262270
}
263271

272+
/** The config with the broadcast filters in its own query language attached. */
273+
export function withBroadcastFilters(
274+
config: ChartConfigWithDateRange,
275+
filters: BroadcastFilter[] | undefined,
276+
): ChartConfigWithDateRange {
277+
if (isPromqlChartConfig(config)) {
278+
return { ...config, filters: filters && getPromqlFilters(filters) };
279+
}
280+
return { ...config, filters: filters && getSqlFilters(filters) };
281+
}
282+
264283
/** One expression as a PromQL tile queries it. */
265284
type RenderedPromqlEntry = {
266285
id: string;

0 commit comments

Comments
 (0)