Skip to content
Draft
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
5 changes: 5 additions & 0 deletions .changeset/source-vars-3.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@hyperdx/app': minor
---

feat: add data source variables to dashboard filters and tiles
174 changes: 121 additions & 53 deletions packages/app/src/DBDashboardPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,9 @@ import {
} from '@hyperdx/common-utils/dist/core/metadata';
import {
convertToDashboardTemplate,
displayTypeSupportsBuilderAlerts,
displayTypeSupportsPromQLAlerts,
displayTypeSupportsRawSqlAlerts,
Granularity,
isTimeSeriesDisplayType,
savedChartConfigSupportsAlerts,
} from '@hyperdx/common-utils/dist/core/utils';
import {
getAdhocFilterTargetsForTile,
Expand All @@ -56,6 +54,11 @@ import {
isImportableDashboard,
} from '@hyperdx/common-utils/dist/iac';
import { isMissingFiltersMacro } from '@hyperdx/common-utils/dist/macros';
import {
getConfigReferenceVariables,
resolveConfigReferences,
restoreConfigReferences,
} from '@hyperdx/common-utils/dist/referenceVariables';
import {
AlertState,
BroadcastFilter,
Expand Down Expand Up @@ -455,6 +458,7 @@ const Tile = ({
filters,
variables,
unsatisfiedRequiredFilters,
referenceVariableNames,
showAlertAnnotations,
showReleaseAnnotations,
isLive,
Expand Down Expand Up @@ -489,6 +493,12 @@ const Tile = ({
variables?: ChartVariable[];
/** The dashboard's required filters that have nothing selected. */
unsatisfiedRequiredFilters?: DashboardFilter[];
/**
* The data source and connection variables the tile's stored config
* references. `chart` carries their selections, or the references while
* unselected.
*/
referenceVariableNames?: string[];
// When true, draw alert firing/recovery annotations on this tile's chart.
showAlertAnnotations?: boolean;
// When true, draw release markers on this tile's chart.
Expand Down Expand Up @@ -604,8 +614,17 @@ const Tile = ({
id: chart.config.source,
});

const usesReferenceVariable = !!referenceVariableNames?.length;

const isSourceMissing =
!!chart.config.source && isSourceFetched && source == null;
// A data source or connection variable can select a source on another
// connection than the one a raw SQL tile queries.
const isSourceOnOtherConnection =
usesReferenceVariable &&
source != null &&
isRawSqlSavedChartConfig(chart.config) &&
source.connection !== chart.config.connection;
const isSourceUnset =
!!chart.config &&
isBuilderSavedChartConfig(chart.config) &&
Expand Down Expand Up @@ -655,9 +674,15 @@ const Tile = ({
sourceId: chart.config.source,
unsatisfiedRequiredFilters,
referencedVariables: tileVariables,
referenceVariableNames,
}),
),
[unsatisfiedRequiredFilters, chart.config, tileVariables],
[
unsatisfiedRequiredFilters,
chart.config,
tileVariables,
referenceVariableNames,
],
);
const missingRequiredFilterNames = useMemo<string[]>(
() => JSON.parse(serializedMissingRequiredFilterNames),
Expand Down Expand Up @@ -929,16 +954,13 @@ const Tile = ({
return url && addTimeZone(url);
}, [dateRange, queriedConfig, source, addTimeZone]);

// An alert must query one fixed source and connection
const supportsAlerts =
!usesReferenceVariable && savedChartConfigSupportsAlerts(chart.config);

const hoverToolbar = useMemo(() => {
if (readOnly) return null;

const isRawSql = isRawSqlSavedChartConfig(chart.config);
const isPromQL = isPromqlSavedChartConfig(chart.config);
const displayTypeSupportsAlerts = isRawSql
? displayTypeSupportsRawSqlAlerts(chart.config.displayType)
: isPromQL
? displayTypeSupportsPromQLAlerts(chart.config.displayType)
: displayTypeSupportsBuilderAlerts(chart.config.displayType);
const canMoveToGroup =
onMoveToGroup && moveTargets && moveTargets.length > 0;
return (
Expand Down Expand Up @@ -968,7 +990,7 @@ const Tile = ({
</Tooltip>
)}

{displayTypeSupportsAlerts &&
{supportsAlerts &&
(alert ? (
// Existing alert: bell with a colored status dot indicator.
<Indicator
Expand Down Expand Up @@ -1073,7 +1095,7 @@ const Tile = ({
alertTooltip,
moveTargets,
replaySearchUrl,
chart.config,
supportsAlerts,
chart.id,
chart.containerId,
chart.tabId,
Expand All @@ -1091,13 +1113,6 @@ const Tile = ({
const collapsedMenuItems = useMemo(() => {
if (readOnly) return null;

const isRawSql = isRawSqlSavedChartConfig(chart.config);
const isPromQL = isPromqlSavedChartConfig(chart.config);
const showAlerts = isRawSql
? displayTypeSupportsRawSqlAlerts(chart.config.displayType)
: isPromQL
? displayTypeSupportsPromQLAlerts(chart.config.displayType)
: displayTypeSupportsBuilderAlerts(chart.config.displayType);
const canMoveToGroup =
onMoveToGroup && moveTargets && moveTargets.length > 0;
return (
Expand All @@ -1113,7 +1128,7 @@ const Tile = ({
Replay search
</Menu.Item>
)}
{showAlerts && (
{supportsAlerts && (
<>
<Menu.Item
leftSection={
Expand Down Expand Up @@ -1164,7 +1179,7 @@ const Tile = ({
alertTooltip,
moveTargets,
replaySearchUrl,
chart.config,
supportsAlerts,
chart.containerId,
chart.tabId,
onDeleteClick,
Expand Down Expand Up @@ -1255,7 +1270,7 @@ const Tile = ({
toolbarItems={toolbar}
data-testid="tile-missing-required-filters"
>
{`Missing required filters: ${missingRequiredFilterNames.join(
{`Missing required filters or variables: ${missingRequiredFilterNames.join(
', ',
)}. Select a value for each to load this tile.`}
</TilePlaceholder>
Expand All @@ -1269,6 +1284,11 @@ const Tile = ({
The data source for this tile is not set. Edit the tile to select
a data source.
</TilePlaceholder>
) : isSourceOnOtherConnection ? (
<TilePlaceholder title={title} toolbarItems={toolbar}>
The selected data source is on a different connection than this
tile&apos;s query. Select a source on the same connection.
</TilePlaceholder>
) : (
<>
{effectiveQueriedConfig &&
Expand Down Expand Up @@ -1501,6 +1521,7 @@ const Tile = ({
filterWarning,
isSourceMissing,
isSourceUnset,
isSourceOnOtherConnection,
isBlockedByRequiredFilters,
missingRequiredFilterNames,
hasBeenVisible,
Expand Down Expand Up @@ -1900,28 +1921,6 @@ function DBDashboardPage({
const { data: allDashboards } = useDashboards();

const [highlightedTileId] = useQueryState('highlightedTileId');
const tableConnections = useMemo(() => {
if (!dashboard) return [];
const tc: TableConnection[] = [];

for (const { config } of dashboard.tiles) {
if (!isBuilderSavedChartConfig(config)) continue;
const source = sources?.find(v => v.id === config.source);
if (!source || source.disabled) continue;
// TODO: will need to update this when we allow for multiple metrics per chart
const firstSelect = config.select[0];
const metricType =
typeof firstSelect !== 'string' ? firstSelect?.metricType : undefined;
const tableName = getMetricTableName(source, metricType);
if (!tableName) continue;
tc.push({
...tcFromSource(source),
tableName,
});
}

return tc;
}, [dashboard, sources]);

const [granularity, setGranularity] = useQueryState(
'granularity',
Expand Down Expand Up @@ -1966,8 +1965,60 @@ function DBDashboardPage({
ignoredVariableNames,
getFilterQueriesForSource,
variables,
referenceVariableValues,
resolvedFilters,
unresolvedReferenceVariablesByFilterId,
unsatisfiedRequiredFilters,
} = useDashboardFilters(filters);
} = useDashboardFilters(filters, { sources });
// Tiles as they query, by ID: source and connection variable references
// replaced by the selections. Edits and saves read `dashboard.tiles`, so the
// stored references survive.
const resolvedTiles = useMemo(
() =>
new Map(
(dashboard?.tiles ?? []).map(tile => {
const config = resolveConfigReferences(
tile.config,
referenceVariableValues,
);
return [tile.id, config === tile.config ? tile : { ...tile, config }];
}),
),
[dashboard?.tiles, referenceVariableValues],
);
// Kept apart from `resolvedTiles` so a tile's list only changes with its config
const referenceVariableNamesByTileId = useMemo(
() =>
new Map(
(dashboard?.tiles ?? []).map(tile => [
tile.id,
getConfigReferenceVariables(tile.config),
]),
),
[dashboard?.tiles],
);

const tableConnections = useMemo(() => {
const tc: TableConnection[] = [];

for (const { config } of resolvedTiles.values()) {
if (!isBuilderSavedChartConfig(config)) continue;
const source = sources?.find(v => v.id === config.source);
if (!source || source.disabled) continue;
// TODO: will need to update this when we allow for multiple metrics per chart
const firstSelect = config.select[0];
const metricType =
typeof firstSelect !== 'string' ? firstSelect?.metricType : undefined;
const tableName = getMetricTableName(source, metricType);
if (!tableName) continue;
tc.push({
...tcFromSource(source),
tableName,
});
}

return tc;
}, [resolvedTiles, sources]);

const dashboardReady =
!!dashboard?.id &&
Expand Down Expand Up @@ -2406,10 +2457,10 @@ function DBDashboardPage({
// The filter bar is hidden in kiosk mode, so a click would add conditions
// the viewer can't see or remove.
if (isKioskMode) return handlers;
for (const tile of dashboard?.tiles ?? []) {
for (const tile of resolvedTiles.values()) {
const sourceId = 'source' in tile.config ? tile.config.source : undefined;
const targetIds = getAdhocFilterTargetsForTile(
filters,
resolvedFilters,
tile.config,
sourceId,
).map(filter => filter.id);
Expand All @@ -2420,19 +2471,23 @@ function DBDashboardPage({
}
}
return handlers;
}, [isKioskMode, dashboard?.tiles, filters, applyAdhocAction]);
}, [isKioskMode, resolvedTiles, resolvedFilters, applyAdhocAction]);

const renderTileComponent = useCallback(
(chart: Tile) => {
const resolvedChart = resolvedTiles.get(chart.id) ?? chart;
// Resolve the tile's source ID so per-source-scoped filters can be
// narrowed to only the tiles they target. Builder and RawSQL configs
// both carry a `source` field; markdown / other configs don't.
const tileSourceId =
'source' in chart.config ? chart.config.source : undefined;
'source' in resolvedChart.config
? resolvedChart.config.source
: undefined;
return (
<Tile
key={chart.id}
chart={chart}
chart={resolvedChart}
referenceVariableNames={referenceVariableNamesByTileId.get(chart.id)}
dateRange={searchedTimeRange}
onEditClick={() => setEditedTile(chart)}
readOnly={isKioskMode}
Expand All @@ -2459,7 +2514,14 @@ function DBDashboardPage({
c => c.id === chart.id,
);
if (chartIndex === -1) return;
draft.tiles[chartIndex] = newChart;
// Keep the source as stored, not as resolved for display
draft.tiles[chartIndex] = {
...newChart,
config: restoreConfigReferences(
newChart.config,
chart.config,
),
};
}),
);
}
Expand Down Expand Up @@ -2532,6 +2594,8 @@ function DBDashboardPage({
);
},
[
resolvedTiles,
referenceVariableNamesByTileId,
dashboard,
searchedTimeRange,
isRefreshEnabled,
Expand Down Expand Up @@ -3324,14 +3388,18 @@ function DBDashboardPage({
)}
{!isKioskMode && (
<DashboardFilters
filters={filters}
filters={resolvedFilters}
selectionByFilterId={selectionByFilterId}
onSetFilterValue={setFilterValue}
adhocConditionsByFilterId={adhocConditionsByFilterId}
onSetAdhocConditions={setAdhocConditions}
dateRange={searchedTimeRange}
variables={variables}
unsatisfiedRequiredFilters={unsatisfiedRequiredFilters}
referenceVariableValues={referenceVariableValues}
unresolvedReferenceVariablesByFilterId={
unresolvedReferenceVariablesByFilterId
}
/>
)}
{/* Selection indicator */}
Expand Down
Loading
Loading