Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Support multiple measures and dimenisons in alerts #5716

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
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
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ export async function getDashboardFromAggregationRequest({
if (req.having?.cond?.exprs?.length && req.dimensions?.[0]?.name) {
const dimension = req.dimensions[0].name;
if (
// we only support 1-1 map from measure filter to dimension
req.having.cond.exprs.length > 1 ||
// we do not support comparison based measure filter
exprHasComparison(req.having) ||
// there is already measure filter from dashboard during alert creation
dashboard.dimensionThresholdFilters.length > 0
) {
const expr = await convertExprToToplist(
Expand Down
69 changes: 34 additions & 35 deletions web-admin/src/features/dashboards/query-mappers/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@
ComparisonDeltaRelativeSuffix,
ComparisonPercentOfTotal,
} from "@rilldata/web-common/features/dashboards/filters/measure-filters/measure-filter-entry";
import { MeasureFilterType } from "@rilldata/web-common/features/dashboards/filters/measure-filters/measure-filter-options";

Check failure on line 6 in web-admin/src/features/dashboards/query-mappers/utils.ts

View workflow job for this annotation

GitHub Actions / build

'MeasureFilterType' is defined but never used
import {
createInExpression,
forEachIdentifier,
getAllIdentifiers,
} from "@rilldata/web-common/features/dashboards/stores/filter-utils";
import type { MetricsExplorerEntity } from "@rilldata/web-common/features/dashboards/stores/metrics-explorer-entity";
import { PreviousCompleteRangeMap } from "@rilldata/web-common/features/dashboards/time-controls/time-range-mappers";
Expand All @@ -20,6 +22,7 @@
queryServiceMetricsViewAggregation,
type QueryServiceMetricsViewAggregationBody,
type V1Expression,
V1MetricsViewAggregationMeasure,
type V1TimeRange,
type V1TimeRangeSummary,
} from "@rilldata/web-common/runtime-client";
Expand Down Expand Up @@ -116,48 +119,46 @@
queryClient: QueryClient,
instanceId: string,
metricsView: string,
dimensionName: string,
measureName: string,
dimensionNames: string[],
measureNames: string[],
timeRange: V1TimeRange | undefined,
comparisonTimeRange: V1TimeRange | undefined,
executionTime: string,
where: V1Expression | undefined,
having: V1Expression,
) {
let hasPercentOfTotals = false;
const havingIdentifiers = getAllIdentifiers(having);
forEachIdentifier(having, (_, ident) => {
if (ident?.endsWith(ComparisonPercentOfTotal)) {
hasPercentOfTotals = true;
}
});
const measures: V1MetricsViewAggregationMeasure[] = [];
measureNames.forEach((measure) => {
measures.push({ name: measure });
if (comparisonTimeRange) {
measures.push(
{
name: measure + ComparisonDeltaAbsoluteSuffix,
comparisonDelta: { measure },
},
{
name: measure + ComparisonDeltaRelativeSuffix,
comparisonRatio: { measure },
},
);
}
if (havingIdentifiers.includes(measure + ComparisonPercentOfTotal)) {
measures.push({
name: measure + ComparisonPercentOfTotal,
percentOfTotal: { measure },
});
}
});

const toplistBody: QueryServiceMetricsViewAggregationBody = {
measures: [
{
name: measureName,
},
...(comparisonTimeRange
? [
{
name: measureName + ComparisonDeltaAbsoluteSuffix,
comparisonDelta: { measure: measureName },
},
{
name: measureName + ComparisonDeltaRelativeSuffix,
comparisonRatio: { measure: measureName },
},
]
: []),
...(hasPercentOfTotals
? [
{
name: measureName + ComparisonPercentOfTotal,
percentOfTotal: { measure: measureName },
},
]
: []),
],
dimensions: [{ name: dimensionName }],
measures,
dimensions: dimensionNames.map((d) => ({ name: d })),
...(timeRange
? {
timeRange: {
Expand All @@ -176,12 +177,10 @@
: {}),
where,
having,
sort: [
{
name: measureName,
desc: false,
},
],
sort: measures.map((m) => ({
name: m,
desc: false,
})),
limit: "250",
};
const toplist = await queryClient.fetchQuery({
Expand Down
16 changes: 12 additions & 4 deletions web-common/src/components/forms/Select.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

const dispatch = createEventDispatcher();

export let value: string;
export let value: string | string[];
export let id: string;
export let label: string;
export let options: {
Expand All @@ -19,8 +19,11 @@
export let optional: boolean = false;
export let tooltip: string = "";
export let width: number | null = null;
export let multiple = false;

$: selected = options.find((option) => option.value === value);
$: selected = multiple
? options.filter((option) => value.includes(option.value))
: options.find((option) => option.value === value);
</script>

<div class="flex flex-col gap-y-2">
Expand Down Expand Up @@ -49,10 +52,15 @@
{selected}
onSelectedChange={(newSelection) => {
if (!newSelection) return;
value = newSelection.value;
dispatch("change", newSelection.value);
if (Array.isArray(newSelection)) {
value = newSelection.map((v) => v.value);
} else {
value = newSelection.value;
}
dispatch("change", value);
}}
items={options}
{multiple}
>
<Select.Trigger class="px-3 gap-x-2 {width && `w-[${width}px]`}">
<Select.Value
Expand Down
11 changes: 7 additions & 4 deletions web-common/src/features/alerts/BaseAlertForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,14 @@
$form.name = name;
}

$: measure = $form.measure;
function measureUpdated(mes: string) {
$form.criteria.forEach((c) => (c.measure = mes));
$: measures = $form.measures;
function measureUpdated(measures: string[]) {
$form.criteria.forEach((c) => {
if (measures.includes(c.measure)) return;
c.measure = measures[0];
});
}
$: measureUpdated(measure);
$: measureUpdated(measures);
</script>

<!-- 802px = 1px border on each side of the form + 3 tabs with a 200px fixed-width -->
Expand Down
16 changes: 10 additions & 6 deletions web-common/src/features/alerts/CreateAlertForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@
dimension = $dashboardStore.selectedDimensionName ?? "";
}

let measure = "";
if ($dashboardStore.tdd.expandedMeasureName) {
measure = $dashboardStore.tdd.expandedMeasureName;
} else if ($dashboardStore.leaderboardMeasureName) {
measure = $dashboardStore.leaderboardMeasureName;
}

// TODO: get metrics view spec
const timeRange = mapTimeRange(timeControls, {});
const comparisonTimeRange = mapComparisonTimeRange(
Expand All @@ -66,16 +73,13 @@
const formState = createForm<AlertFormValues>({
initialValues: {
name: "",
measure:
$dashboardStore.tdd.expandedMeasureName ??
$dashboardStore.leaderboardMeasureName ??
"",
splitByDimension: dimension,
measures: measure ? [measure] : [],
dimensions: dimension ? [dimension] : [],
evaluationInterval: "",
criteria: [
{
...getEmptyMeasureFilterEntry(),
measure: $dashboardStore.leaderboardMeasureName ?? "",
measure,
},
],
criteriaOperation: V1Operation.OPERATION_AND,
Expand Down
2 changes: 1 addition & 1 deletion web-common/src/features/alerts/alert-preview-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ function getAlertPreviewQueryOptions(
> {
return {
enabled:
!!formValues.measure &&
!!formValues.measures.length &&
!!metricsViewSpec &&
(!formValues.timeRange || !!formValues.timeRange.end) &&
(!formValues.comparisonTimeRange || !!formValues.comparisonTimeRange.end),
Expand Down
18 changes: 7 additions & 11 deletions web-common/src/features/alerts/criteria-tab/CriteriaForm.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,13 @@
$form["metricsViewName"],
);

$: measure = $metricsView.data?.measures?.find(
(m) => m.name === $form["measure"],
);
$: measureOptions = [
{
value: $form["measure"],
label: measure?.label?.length
? measure.label
: measure?.expression ?? $form["measure"],
},
];
$: measureOptions =
$metricsView.data?.measures
?.filter((m) => $form["measures"].includes(m.name ?? ""))
.map((m) => ({
value: m.name!,
label: m.label?.length ? m.label : m.expression ?? m.name ?? "",
})) ?? [];
$: selectedMeasure = $metricsView.data?.measures?.find(
(m) => m.name === $form["criteria"][index].measure,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
function handleAddCriteria() {
$form["criteria"] = $form["criteria"].concat({
...getEmptyMeasureFilterEntry(),
measure: $form.measure,
measure: $form.measures[0],
});
}
</script>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,10 @@ export function getTypeOptions(
);
}

if (selectedMeasure?.validPercentOfTotal && formValues.splitByDimension) {
if (
selectedMeasure?.validPercentOfTotal &&
formValues.dimensions.length > 0
) {
options.push(MeasureFilterPercentOfTotalOption);
} else {
options.push({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,19 +57,21 @@
title="Alert data"
>
<Select
bind:value={$form["measure"]}
id="measure"
label="Measure"
bind:value={$form["measures"]}
id="measures"
label="Measure(s)"
options={measureOptions}
placeholder="Select a measure"
multiple
/>
<Select
bind:value={$form["splitByDimension"]}
id="splitByDimension"
label="Split by dimension"
bind:value={$form["dimensions"]}
id="dimensions"
label="Split by dimension(s)"
optional
options={dimensionOptions}
placeholder="Select a dimension"
multiple
/>
</FormSection>
<FormSection
Expand Down
8 changes: 4 additions & 4 deletions web-common/src/features/alerts/extract-alert-form-values.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type AlertFormValuesSubset = Pick<
| "dimensionThresholdFilters"
| "timeRange"
| "comparisonTimeRange"
| "measure"
| "splitByDimension"
| "measures"
| "dimensions"
| "criteria"
| "criteriaOperation"
>;
Expand Down Expand Up @@ -66,8 +66,8 @@ export function extractAlertFormValues(
);

return {
measure: measures[0]?.name ?? "",
splitByDimension: dimensions[0]?.name ?? "",
measures: measures.map((m) => m.name ?? ""),
dimensions: dimensions.map((d) => d.name ?? ""),

criteria: (queryArgs.having?.cond?.exprs?.map(
mapExprToMeasureFilter,
Expand Down
Loading
Loading