From 87794bec12ec660e9805edccbf59944230da16f8 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Wed, 29 Oct 2025 15:21:05 +0100 Subject: [PATCH] Alerting: Add instances with no label value to an ungrouped group (#113170) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit add instances with no label value to an ungrouped group in alerting triage – these will be collapsed by default --- .../unified/triage/rows/FolderGroupRow.tsx | 3 +- .../alerting/unified/triage/rows/GroupRow.tsx | 15 +++++++-- .../alerting/unified/triage/rows/utils.ts | 6 ++-- .../unified/triage/scene/Workbench.tsx | 32 +++++++++++++------ .../alerting/unified/triage/scene/utils.ts | 9 ++---- .../features/alerting/unified/triage/types.ts | 5 ++- 6 files changed, 46 insertions(+), 24 deletions(-) diff --git a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx index 4809a44573a..1e5e1156452 100644 --- a/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/FolderGroupRow.tsx @@ -1,4 +1,5 @@ import { css } from '@emotion/css'; +import { isString } from 'lodash'; import React from 'react'; import { GrafanaTheme2 } from '@grafana/data'; @@ -27,7 +28,7 @@ export const FolderGroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, childr title={ - {row.metadata.value} + {isString(row.metadata.value) && {row.metadata.value}} } isOpenByDefault={true} diff --git a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx index 4499672dd6e..1bb73674165 100644 --- a/public/app/features/alerting/unified/triage/rows/GroupRow.tsx +++ b/public/app/features/alerting/unified/triage/rows/GroupRow.tsx @@ -5,9 +5,10 @@ import { AlertLabel } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { useStyles2 } from '@grafana/ui'; -import { GenericGroupedRow } from '../types'; +import { EmptyLabelValue, GenericGroupedRow } from '../types'; import { GenericRow } from './GenericRow'; +import { formatLabelValue } from './utils'; interface GroupRowProps { row: GenericGroupedRow; @@ -19,13 +20,21 @@ interface GroupRowProps { export const GroupRow = ({ row, leftColumnWidth, rowKey, depth = 0, children }: GroupRowProps) => { const styles = useStyles2(getStyles); + const isEmptyValue = row.metadata.value === EmptyLabelValue; return ( } - isOpenByDefault={true} + title={ + + } + isOpenByDefault={!isEmptyValue} leftColumnClassName={styles.groupRow} rightColumnClassName={styles.groupRow} depth={depth} diff --git a/public/app/features/alerting/unified/triage/rows/utils.ts b/public/app/features/alerting/unified/triage/rows/utils.ts index 6a4d0731643..d5b193ee755 100644 --- a/public/app/features/alerting/unified/triage/rows/utils.ts +++ b/public/app/features/alerting/unified/triage/rows/utils.ts @@ -1,4 +1,4 @@ -import { WorkbenchRow } from '../types'; +import { EmptyLabelValue, LabelValue, WorkbenchRow } from '../types'; // Generate unique keys for WorkbenchRow items export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string { @@ -8,6 +8,8 @@ export function generateRowKey(row: WorkbenchRow, fallbackIndex: number): string } else { // For GenericGroupedRow, create key from label and value const groupedRow = row; - return `group-${groupedRow.metadata.label}-${groupedRow.metadata.value}`; + return `group-${groupedRow.metadata.label}-${formatLabelValue(groupedRow.metadata.value)}`; } } + +export const formatLabelValue = (value: LabelValue): string => (value === EmptyLabelValue ? '' : value); diff --git a/public/app/features/alerting/unified/triage/scene/Workbench.tsx b/public/app/features/alerting/unified/triage/scene/Workbench.tsx index 356a2924461..f202a5cd0a0 100644 --- a/public/app/features/alerting/unified/triage/scene/Workbench.tsx +++ b/public/app/features/alerting/unified/triage/scene/Workbench.tsx @@ -1,3 +1,4 @@ +import { isEmpty } from 'lodash'; import { ArrayValues } from 'type-fest'; import { DataFrame, PanelData } from '@grafana/data'; @@ -6,7 +7,7 @@ import { useQueryRunner, useTimeRange, useVariableValues } from '@grafana/scenes import { Workbench } from '../Workbench'; import { DEFAULT_FIELDS, METRIC_NAME, VARIABLES } from '../constants'; -import { AlertRuleRow, GenericGroupedRow, WorkbenchRow } from '../types'; +import { AlertRuleRow, EmptyLabelValue, GenericGroupedRow, WorkbenchRow } from '../types'; import { convertTimeRangeToDomain, getDataQuery, useQueryFilter } from './utils'; @@ -79,29 +80,40 @@ function groupData(dataPoints: DataPoint[], groupBy: string[], depth: number): W } const groupByKey = groupBy[depth]; - const grouped = new Map(); + const grouped = new Map(); for (const dp of dataPoints) { - const key = String(dp[groupByKey] ?? 'undefined'); - if (!grouped.has(key)) { - grouped.set(key, []); + const mapKey = dp[groupByKey] ?? EmptyLabelValue; + if (!grouped.has(mapKey)) { + grouped.set(mapKey, []); } - grouped.get(key)?.push(dp); + grouped.get(mapKey)?.push(dp); } const result: GenericGroupedRow[] = []; + const emptyGroups: GenericGroupedRow[] = []; + for (const [value, rows] of grouped.entries()) { - result.push({ + const labelValue = isEmpty(value) ? EmptyLabelValue : value; + + const group: GenericGroupedRow = { type: 'group', metadata: { label: groupByKey, - value: value, + value: labelValue, }, rows: groupData(rows, groupBy, depth + 1), - }); + }; + + // Separate empty label groups to append at the end + if (group.metadata.value === EmptyLabelValue) { + emptyGroups.push(group); + } else { + result.push(group); + } } - return result; + return [...result, ...emptyGroups]; } // @TODO narrower types for PanelData! (if possible) diff --git a/public/app/features/alerting/unified/triage/scene/utils.ts b/public/app/features/alerting/unified/triage/scene/utils.ts index 19886a760e0..456e3f3c7bf 100644 --- a/public/app/features/alerting/unified/triage/scene/utils.ts +++ b/public/app/features/alerting/unified/triage/scene/utils.ts @@ -1,6 +1,6 @@ import { TimeRange } from '@grafana/data'; import { SceneDataQuery } from '@grafana/scenes'; -import { useVariableValue, useVariableValues } from '@grafana/scenes-react'; +import { useVariableValue } from '@grafana/scenes-react'; import { DataSourceRef } from '@grafana/schema'; import { DATASOURCE_UID, VARIABLES } from '../constants'; @@ -44,11 +44,6 @@ export function convertTimeRangeToDomain(timeRange: TimeRange): Domain { * This hook will create a Prometheus label matcher string from the "groupBy" and "filters" variables */ export function useQueryFilter(): string { - const [groupBy = []] = useVariableValues(VARIABLES.groupBy); const [filters = ''] = useVariableValue(VARIABLES.filters); - - const groupByFilter = stringifyGroupFilter(groupBy); - const queryFilter = [groupByFilter, filters].filter((s) => Boolean(s)).join(','); - - return queryFilter; + return filters; } diff --git a/public/app/features/alerting/unified/triage/types.ts b/public/app/features/alerting/unified/triage/types.ts index b599f46cb33..c878f8f303c 100644 --- a/public/app/features/alerting/unified/triage/types.ts +++ b/public/app/features/alerting/unified/triage/types.ts @@ -18,7 +18,10 @@ export interface GenericGroupedRow { type: 'group'; metadata: { label: string; - value: string; + value: LabelValue; }; rows: WorkbenchRow[]; } + +export type LabelValue = string | typeof EmptyLabelValue; +export const EmptyLabelValue = Symbol('empty label value');