Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
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
6 changes: 6 additions & 0 deletions .changeset/dashboard-variable-builder-charts.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@hyperdx/app': patch
'@hyperdx/common-utils': patch
---

feat: Substitute dashboard variables in chart builder tiles
43 changes: 25 additions & 18 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,7 @@ import FullscreenPanelModal from '@/components/FullscreenPanelModal';
import ResourceTerraformPopover from '@/components/Iac/ResourceTerraformPopover';
import { PageHeader } from '@/components/PageHeader';
import { PageLayout } from '@/components/PageLayout';
import { SqlVariablesProvider } from '@/components/SQLEditor/variableCompletions';
import { TimePicker } from '@/components/TimePicker';
import { parseTimeRangeInput } from '@/components/TimePicker/utils';
import {
Expand Down Expand Up @@ -551,7 +552,7 @@ const Tile = forwardRef(
// changes to `tileVariables`.
const serializedTileVariables = useMemo(
() =>
!!variables && isRawSqlSavedChartConfig(chart.config)
!!variables && !isPromqlSavedChartConfig(chart.config)
? JSON.stringify(filterReferencedVariables(chart.config, variables))
: undefined,
[chart.config, variables],
Expand Down Expand Up @@ -646,6 +647,7 @@ const Tile = forwardRef(
: undefined,
sampleWeightExpression: getSampleWeightExpression(source),
filters,
variables: tileVariables,
metricTables: isMetricSource ? source.metricTables : undefined,
});
}
Expand Down Expand Up @@ -1605,23 +1607,28 @@ const EditTileModal = ({
{/* Isolate chart cross-syncing to this edit modal: the preview chart
must not drive shadow tooltips on the dashboard tiles behind it. */}
<IsolatedChartSyncProvider>
<EditTimeChartForm
dashboardId={dashboardId}
chartConfig={chart.config}
variables={variables}
dateRange={dateRange}
isSaving={isSaving}
onSave={config => {
onSave({
...chart,
config: config,
});
}}
onClose={handleClose}
onDirtyChange={setHasUnsavedChanges}
isDashboardForm
autoRun
/>
{/* Offers the dashboard's variables as completions in every
expression input the editor renders. */}
<SqlVariablesProvider variables={variables}>
<EditTimeChartForm
data-testid="tile-editor-form"
dashboardId={dashboardId}
chartConfig={chart.config}
variables={variables}
dateRange={dateRange}
isSaving={isSaving}
onSave={config => {
onSave({
...chart,
config: config,
});
}}
onClose={handleClose}
onDirtyChange={setHasUnsavedChanges}
isDashboardForm
autoRun
/>
</SqlVariablesProvider>
</IsolatedChartSyncProvider>
</ZIndexContext.Provider>
)}
Expand Down
148 changes: 7 additions & 141 deletions packages/app/src/components/ChartEditor/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import {
isPromqlSavedChartConfig,
isRawSqlSavedChartConfig,
} from '@hyperdx/common-utils/dist/guards';
import {
MACRO_SUGGESTIONS,
MacroSuggestion,
VARIABLE_MACRO_SUGGESTIONS,
} from '@hyperdx/common-utils/dist/macros';
import { MACRO_SUGGESTIONS } from '@hyperdx/common-utils/dist/macros';
import { QUERY_PARAMS_BY_DISPLAY_TYPE } from '@hyperdx/common-utils/dist/rawSqlParams';
import {
BuilderSavedChartConfig,
Expand All @@ -30,76 +26,16 @@ import {
SourceKind,
TSource,
} from '@hyperdx/common-utils/dist/types';
import {
substituteVariables,
VARIABLE_FORMATS,
VariableFormat,
} from '@hyperdx/common-utils/dist/variables';

import { getStoredLanguage } from '@/components/SearchInput';
import { type SQLCompletion } from '@/components/SQLEditor/utils';
import {
buildVariableCompletions,
toMacroCompletion,
} from '@/components/SQLEditor/variableCompletions';

import { ChartEditorFormState } from './types';

/** What each `${name:format}` renders, for the completion's help text. */
const VARIABLE_FORMAT_DESCRIPTIONS: Record<VariableFormat, string> = {
sqlstring: "Quoted and comma-separated, escaped for SQL. e.g. 'a', 'b', 'c'",
csv: 'Comma-separated and unquoted. Not SQL-escaped. e.g. a,b,c',
regex: 'A regex alternation. Regex escaped. e.g. (a|b|c)',
lucene: 'An OR of quoted terms, for Lucene inputs. e.g. ("a" OR "b" OR "c")',
};

/** What `snippet` expands to against the variable's current selection. */
function describeVariableExpansion(

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All this stuff has been moved to packages/app/src/components/SQLEditor/variableCompletions.tsx

snippet: string,
variable: ChartVariable,
): string | undefined {
let expansion: string;
try {
expansion = substituteVariables(snippet, [variable]);
} catch {
return undefined;
}
return `Expands to: ${expansion === '' ? '(empty string)' : expansion}`;
}

/**
* Completion help built as markup rather than a string, so `footnote` sits on
* its own line.
*
* CodeMirror turns a string `info` into a single text node, where a `\n` is
* subject to the inherited `white-space` and generally collapses to a space.
* A `Node` is rendered as given, so the break is structural.
*/
function completionInfo(description: string, footnote: string) {
return () => {
const dom = document.createElement('div');

const main = document.createElement('div');
main.textContent = description;
dom.appendChild(main);

const sub = document.createElement('div');
sub.className = 'cm-completionInfo-footnote';
sub.textContent = footnote;
dom.appendChild(sub);

return dom;
};
}

const toMacroCompletion = ({
name,
minArgs,
description,
}: MacroSuggestion): SQLCompletion => ({
label: `$__${name}`,
apply: minArgs > 0 ? `$__${name}(` : `$__${name}`,
detail: 'macro',
info: description,
type: 'function',
});

/**
* Autocomplete entries offered on top of columns/keywords in the raw SQL
* editor: query params, macros, and variables (when available).
Expand All @@ -124,80 +60,10 @@ export function buildRawSqlCompletions({
}),
);

const macroCompletions = MACRO_SUGGESTIONS.map(toMacroCompletion);

if (!variables?.length) {
return [...paramCompletions, ...macroCompletions];
}

const variableMacroCompletions =
VARIABLE_MACRO_SUGGESTIONS.map(toMacroCompletion);

const variableCompletions = variables.flatMap((variable): SQLCompletion[] => {
const { name } = variable;

/** A static description and an expansion preview given the current variable selections */
const help = (snippet: string | undefined, description: string) => {
const expansion =
snippet == null
? undefined
: describeVariableExpansion(snippet, variable);
return expansion ? completionInfo(description, expansion) : description;
};

return [
...(variable.expression
? [
{
label: `$__filter(${name})`,
apply: `$__filter(${name})`,
detail: 'variable filter',
info: help(
`$__filter(${name})`,
`Filters by the ${name} variable using its defined expression. Matches every row when no values are selected for the variable.`,
),
type: 'function',
},
]
: []),
{
label: `$${name}`,
apply: `$${name}`,
detail: 'variable',
info: help(
`$${name}`,
`The selected values of ${name}, in the default sqlstring format. Has no valid empty state — prefer $__filter(<expression>, ${name}).`,
),
type: 'variable',
},
{
label: `\${${name}}`,
apply: `\${${name}}`,
detail: 'variable',
info: help(
`\${${name}}`,
`The same as $${name}, but delimited — use it when the reference runs into following word characters, as in \${${name}}_total.`,
),
type: 'variable',
},
...VARIABLE_FORMATS.map((format): SQLCompletion => {
const reference = `\${${name}:${format}}`;
return {
label: reference,
apply: reference,
detail: 'variable',
info: help(reference, VARIABLE_FORMAT_DESCRIPTIONS[format]),
type: 'variable',
};
}),
];
});

return [
...paramCompletions,
...macroCompletions,
...variableMacroCompletions,
...variableCompletions,
...MACRO_SUGGESTIONS.map(toMacroCompletion),
...buildVariableCompletions(variables),
];
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ export function ChartActionBar({
disableKeywordAutocomplete
onSubmit={onSubmit}
label="ORDER BY"
enableVariables
/>
</div>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ export function ChartEditorControls({
}
onSubmit={onSubmit}
label="Pattern Expression"
enableVariables
/>
{typeof select === 'string' &&
select.length > 0 &&
Expand All @@ -209,6 +210,7 @@ export function ChartEditorControls({
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
</Flex>
) : displayType !== DisplayType.Search && Array.isArray(select) ? (
Expand Down Expand Up @@ -276,6 +278,7 @@ export function ChartEditorControls({
placeholder="SQL Columns"
onSubmit={onSubmit}
disableKeywordAutocomplete
enableVariables
/>
</div>
{displayType === DisplayType.Table && (
Expand All @@ -298,6 +301,7 @@ export function ChartEditorControls({
name="having"
placeholder="SQL HAVING clause (ex. count() > 100)"
onSubmit={onSubmit}
enableVariables
/>
</div>
</>
Expand Down Expand Up @@ -430,6 +434,7 @@ export function ChartEditorControls({
}
onSubmit={onSubmit}
label="SELECT"
enableVariables
/>
<SearchWhereInput
tableConnection={tableConnection}
Expand All @@ -442,6 +447,7 @@ export function ChartEditorControls({
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
</Flex>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -409,6 +409,7 @@ export function ChartSeriesEditor({
name={`${namePrefix}valueExpression`}
placeholder="SQL Column"
onSubmit={onSubmit}
enableVariables
/>
</div>
)}
Expand Down Expand Up @@ -439,6 +440,7 @@ export function ChartSeriesEditor({
showLabel={false}
additionalSuggestions={attributeSuggestions}
data-testid="series-where-input"
enableVariables
/>
</div>
</>
Expand All @@ -464,6 +466,7 @@ export function ChartSeriesEditor({
placeholder="SQL Columns"
disableKeywordAutocomplete
onSubmit={onSubmit}
enableVariables
/>
</div>
{showHaving && (
Expand All @@ -479,6 +482,7 @@ export function ChartSeriesEditor({
placeholder="SQL HAVING clause (ex. count() > 100)"
disableKeywordAutocomplete
onSubmit={onSubmit}
enableVariables
/>
</div>
</>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
displayTypeSupportsRawSqlAlerts,
} from '@hyperdx/common-utils/dist/core/utils';
import {
isRawSqlChartConfig,
isPromqlChartConfig,
isRawSqlSavedChartConfig,
} from '@hyperdx/common-utils/dist/guards';
import {
Expand Down Expand Up @@ -374,7 +374,7 @@ export default function EditTimeChartForm({

// Attach variables so that variable references can be validated and expanded in the preview
const previewConfig = useMemo(() => {
if (queriedConfig == null || !isRawSqlChartConfig(queriedConfig)) {
if (queriedConfig == null || isPromqlChartConfig(queriedConfig)) {
return queriedConfig;
}
return {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ export function HeatmapSeriesEditor({
setValue('whereLanguage', lang)
}
showLabel={false}
enableVariables
/>
<Divider />
<Flex justify="flex-end">
Expand Down
Loading
Loading