Skip to content

Commit 2eedfb2

Browse files
authored
feat: Support dashboard variables in chart builder tiles (#2901)
1 parent c592207 commit 2eedfb2

31 files changed

Lines changed: 2518 additions & 347 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@hyperdx/app': patch
3+
'@hyperdx/common-utils': patch
4+
---
5+
6+
feat: Substitute dashboard variables in chart builder tiles

‎packages/app/src/DBDashboardPage.tsx‎

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ import FullscreenPanelModal from '@/components/FullscreenPanelModal';
144144
import ResourceTerraformPopover from '@/components/Iac/ResourceTerraformPopover';
145145
import { PageHeader } from '@/components/PageHeader';
146146
import { PageLayout } from '@/components/PageLayout';
147+
import { SqlVariablesProvider } from '@/components/SQLEditor/variableCompletions';
147148
import { TimePicker } from '@/components/TimePicker';
148149
import { parseTimeRangeInput } from '@/components/TimePicker/utils';
149150
import {
@@ -551,7 +552,7 @@ const Tile = forwardRef(
551552
// changes to `tileVariables`.
552553
const serializedTileVariables = useMemo(
553554
() =>
554-
!!variables && isRawSqlSavedChartConfig(chart.config)
555+
!!variables && !isPromqlSavedChartConfig(chart.config)
555556
? JSON.stringify(filterReferencedVariables(chart.config, variables))
556557
: undefined,
557558
[chart.config, variables],
@@ -646,6 +647,7 @@ const Tile = forwardRef(
646647
: undefined,
647648
sampleWeightExpression: getSampleWeightExpression(source),
648649
filters,
650+
variables: tileVariables,
649651
metricTables: isMetricSource ? source.metricTables : undefined,
650652
});
651653
}
@@ -1605,23 +1607,28 @@ const EditTileModal = ({
16051607
{/* Isolate chart cross-syncing to this edit modal: the preview chart
16061608
must not drive shadow tooltips on the dashboard tiles behind it. */}
16071609
<IsolatedChartSyncProvider>
1608-
<EditTimeChartForm
1609-
dashboardId={dashboardId}
1610-
chartConfig={chart.config}
1611-
variables={variables}
1612-
dateRange={dateRange}
1613-
isSaving={isSaving}
1614-
onSave={config => {
1615-
onSave({
1616-
...chart,
1617-
config: config,
1618-
});
1619-
}}
1620-
onClose={handleClose}
1621-
onDirtyChange={setHasUnsavedChanges}
1622-
isDashboardForm
1623-
autoRun
1624-
/>
1610+
{/* Offers the dashboard's variables as completions in every
1611+
expression input the editor renders. */}
1612+
<SqlVariablesProvider variables={variables}>
1613+
<EditTimeChartForm
1614+
data-testid="tile-editor-form"
1615+
dashboardId={dashboardId}
1616+
chartConfig={chart.config}
1617+
variables={variables}
1618+
dateRange={dateRange}
1619+
isSaving={isSaving}
1620+
onSave={config => {
1621+
onSave({
1622+
...chart,
1623+
config: config,
1624+
});
1625+
}}
1626+
onClose={handleClose}
1627+
onDirtyChange={setHasUnsavedChanges}
1628+
isDashboardForm
1629+
autoRun
1630+
/>
1631+
</SqlVariablesProvider>
16251632
</IsolatedChartSyncProvider>
16261633
</ZIndexContext.Provider>
16271634
)}

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

Lines changed: 7 additions & 141 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,7 @@ import {
66
isPromqlSavedChartConfig,
77
isRawSqlSavedChartConfig,
88
} from '@hyperdx/common-utils/dist/guards';
9-
import {
10-
MACRO_SUGGESTIONS,
11-
MacroSuggestion,
12-
VARIABLE_MACRO_SUGGESTIONS,
13-
} from '@hyperdx/common-utils/dist/macros';
9+
import { MACRO_SUGGESTIONS } from '@hyperdx/common-utils/dist/macros';
1410
import { QUERY_PARAMS_BY_DISPLAY_TYPE } from '@hyperdx/common-utils/dist/rawSqlParams';
1511
import {
1612
BuilderSavedChartConfig,
@@ -30,76 +26,16 @@ import {
3026
SourceKind,
3127
TSource,
3228
} from '@hyperdx/common-utils/dist/types';
33-
import {
34-
substituteVariables,
35-
VARIABLE_FORMATS,
36-
VariableFormat,
37-
} from '@hyperdx/common-utils/dist/variables';
3829

3930
import { getStoredLanguage } from '@/components/SearchInput';
4031
import { type SQLCompletion } from '@/components/SQLEditor/utils';
32+
import {
33+
buildVariableCompletions,
34+
toMacroCompletion,
35+
} from '@/components/SQLEditor/variableCompletions';
4136

4237
import { ChartEditorFormState } from './types';
4338

44-
/** What each `${name:format}` renders, for the completion's help text. */
45-
const VARIABLE_FORMAT_DESCRIPTIONS: Record<VariableFormat, string> = {
46-
sqlstring: "Quoted and comma-separated, escaped for SQL. e.g. 'a', 'b', 'c'",
47-
csv: 'Comma-separated and unquoted. Not SQL-escaped. e.g. a,b,c',
48-
regex: 'A regex alternation. Regex escaped. e.g. (a|b|c)',
49-
lucene: 'An OR of quoted terms, for Lucene inputs. e.g. ("a" OR "b" OR "c")',
50-
};
51-
52-
/** What `snippet` expands to against the variable's current selection. */
53-
function describeVariableExpansion(
54-
snippet: string,
55-
variable: ChartVariable,
56-
): string | undefined {
57-
let expansion: string;
58-
try {
59-
expansion = substituteVariables(snippet, [variable]);
60-
} catch {
61-
return undefined;
62-
}
63-
return `Expands to: ${expansion === '' ? '(empty string)' : expansion}`;
64-
}
65-
66-
/**
67-
* Completion help built as markup rather than a string, so `footnote` sits on
68-
* its own line.
69-
*
70-
* CodeMirror turns a string `info` into a single text node, where a `\n` is
71-
* subject to the inherited `white-space` and generally collapses to a space.
72-
* A `Node` is rendered as given, so the break is structural.
73-
*/
74-
function completionInfo(description: string, footnote: string) {
75-
return () => {
76-
const dom = document.createElement('div');
77-
78-
const main = document.createElement('div');
79-
main.textContent = description;
80-
dom.appendChild(main);
81-
82-
const sub = document.createElement('div');
83-
sub.className = 'cm-completionInfo-footnote';
84-
sub.textContent = footnote;
85-
dom.appendChild(sub);
86-
87-
return dom;
88-
};
89-
}
90-
91-
const toMacroCompletion = ({
92-
name,
93-
minArgs,
94-
description,
95-
}: MacroSuggestion): SQLCompletion => ({
96-
label: `$__${name}`,
97-
apply: minArgs > 0 ? `$__${name}(` : `$__${name}`,
98-
detail: 'macro',
99-
info: description,
100-
type: 'function',
101-
});
102-
10339
/**
10440
* Autocomplete entries offered on top of columns/keywords in the raw SQL
10541
* editor: query params, macros, and variables (when available).
@@ -124,80 +60,10 @@ export function buildRawSqlCompletions({
12460
}),
12561
);
12662

127-
const macroCompletions = MACRO_SUGGESTIONS.map(toMacroCompletion);
128-
129-
if (!variables?.length) {
130-
return [...paramCompletions, ...macroCompletions];
131-
}
132-
133-
const variableMacroCompletions =
134-
VARIABLE_MACRO_SUGGESTIONS.map(toMacroCompletion);
135-
136-
const variableCompletions = variables.flatMap((variable): SQLCompletion[] => {
137-
const { name } = variable;
138-
139-
/** A static description and an expansion preview given the current variable selections */
140-
const help = (snippet: string | undefined, description: string) => {
141-
const expansion =
142-
snippet == null
143-
? undefined
144-
: describeVariableExpansion(snippet, variable);
145-
return expansion ? completionInfo(description, expansion) : description;
146-
};
147-
148-
return [
149-
...(variable.expression
150-
? [
151-
{
152-
label: `$__filter(${name})`,
153-
apply: `$__filter(${name})`,
154-
detail: 'variable filter',
155-
info: help(
156-
`$__filter(${name})`,
157-
`Filters by the ${name} variable using its defined expression. Matches every row when no values are selected for the variable.`,
158-
),
159-
type: 'function',
160-
},
161-
]
162-
: []),
163-
{
164-
label: `$${name}`,
165-
apply: `$${name}`,
166-
detail: 'variable',
167-
info: help(
168-
`$${name}`,
169-
`The selected values of ${name}, in the default sqlstring format. Has no valid empty state — prefer $__filter(<expression>, ${name}).`,
170-
),
171-
type: 'variable',
172-
},
173-
{
174-
label: `\${${name}}`,
175-
apply: `\${${name}}`,
176-
detail: 'variable',
177-
info: help(
178-
`\${${name}}`,
179-
`The same as $${name}, but delimited — use it when the reference runs into following word characters, as in \${${name}}_total.`,
180-
),
181-
type: 'variable',
182-
},
183-
...VARIABLE_FORMATS.map((format): SQLCompletion => {
184-
const reference = `\${${name}:${format}}`;
185-
return {
186-
label: reference,
187-
apply: reference,
188-
detail: 'variable',
189-
info: help(reference, VARIABLE_FORMAT_DESCRIPTIONS[format]),
190-
type: 'variable',
191-
};
192-
}),
193-
];
194-
});
195-
19663
return [
19764
...paramCompletions,
198-
...macroCompletions,
199-
...variableMacroCompletions,
200-
...variableCompletions,
65+
...MACRO_SUGGESTIONS.map(toMacroCompletion),
66+
...buildVariableCompletions(variables),
20167
];
20268
}
20369

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ export function ChartActionBar({
9393
disableKeywordAutocomplete
9494
onSubmit={onSubmit}
9595
label="ORDER BY"
96+
enableVariables
9697
/>
9798
</div>
9899
)}

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

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,7 @@ export function ChartEditorControls({
188188
}
189189
onSubmit={onSubmit}
190190
label="Pattern Expression"
191+
enableVariables
191192
/>
192193
{typeof select === 'string' &&
193194
select.length > 0 &&
@@ -209,6 +210,7 @@ export function ChartEditorControls({
209210
setValue('whereLanguage', lang)
210211
}
211212
showLabel={false}
213+
enableVariables
212214
/>
213215
</Flex>
214216
) : displayType !== DisplayType.Search && Array.isArray(select) ? (
@@ -276,6 +278,7 @@ export function ChartEditorControls({
276278
placeholder="SQL Columns"
277279
onSubmit={onSubmit}
278280
disableKeywordAutocomplete
281+
enableVariables
279282
/>
280283
</div>
281284
{displayType === DisplayType.Table && (
@@ -298,6 +301,7 @@ export function ChartEditorControls({
298301
name="having"
299302
placeholder="SQL HAVING clause (ex. count() > 100)"
300303
onSubmit={onSubmit}
304+
enableVariables
301305
/>
302306
</div>
303307
</>
@@ -430,6 +434,7 @@ export function ChartEditorControls({
430434
}
431435
onSubmit={onSubmit}
432436
label="SELECT"
437+
enableVariables
433438
/>
434439
<SearchWhereInput
435440
tableConnection={tableConnection}
@@ -442,6 +447,7 @@ export function ChartEditorControls({
442447
setValue('whereLanguage', lang)
443448
}
444449
showLabel={false}
450+
enableVariables
445451
/>
446452
</Flex>
447453
)}

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

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -409,6 +409,7 @@ export function ChartSeriesEditor({
409409
name={`${namePrefix}valueExpression`}
410410
placeholder="SQL Column"
411411
onSubmit={onSubmit}
412+
enableVariables
412413
/>
413414
</div>
414415
)}
@@ -439,6 +440,7 @@ export function ChartSeriesEditor({
439440
showLabel={false}
440441
additionalSuggestions={attributeSuggestions}
441442
data-testid="series-where-input"
443+
enableVariables
442444
/>
443445
</div>
444446
</>
@@ -464,6 +466,7 @@ export function ChartSeriesEditor({
464466
placeholder="SQL Columns"
465467
disableKeywordAutocomplete
466468
onSubmit={onSubmit}
469+
enableVariables
467470
/>
468471
</div>
469472
{showHaving && (
@@ -479,6 +482,7 @@ export function ChartSeriesEditor({
479482
placeholder="SQL HAVING clause (ex. count() > 100)"
480483
disableKeywordAutocomplete
481484
onSubmit={onSubmit}
485+
enableVariables
482486
/>
483487
</div>
484488
</>

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import {
1313
displayTypeSupportsRawSqlAlerts,
1414
} from '@hyperdx/common-utils/dist/core/utils';
1515
import {
16-
isRawSqlChartConfig,
16+
isPromqlChartConfig,
1717
isRawSqlSavedChartConfig,
1818
} from '@hyperdx/common-utils/dist/guards';
1919
import {
@@ -374,7 +374,7 @@ export default function EditTimeChartForm({
374374

375375
// Attach variables so that variable references can be validated and expanded in the preview
376376
const previewConfig = useMemo(() => {
377-
if (queriedConfig == null || !isRawSqlChartConfig(queriedConfig)) {
377+
if (queriedConfig == null || isPromqlChartConfig(queriedConfig)) {
378378
return queriedConfig;
379379
}
380380
return {

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ export function HeatmapSeriesEditor({
3939
setValue('whereLanguage', lang)
4040
}
4141
showLabel={false}
42+
enableVariables
4243
/>
4344
<Divider />
4445
<Flex justify="flex-end">

0 commit comments

Comments
 (0)