From 0be9658064ec1dff74f850a8745f45bd78bb6c46 Mon Sep 17 00:00:00 2001 From: Gilles De Mey Date: Thu, 13 Jul 2023 16:00:49 +0200 Subject: [PATCH] Alerting: Adds support for toggling common labels (#71497) --- .../unified/components/AlertLabels.test.tsx | 25 +++++++++++ .../unified/components/AlertLabels.tsx | 45 ++++++++++++++++--- .../components/rules/AlertInstancesTable.tsx | 24 +++++++--- 3 files changed, 83 insertions(+), 11 deletions(-) create mode 100644 public/app/features/alerting/unified/components/AlertLabels.test.tsx diff --git a/public/app/features/alerting/unified/components/AlertLabels.test.tsx b/public/app/features/alerting/unified/components/AlertLabels.test.tsx new file mode 100644 index 00000000000..381240ae059 --- /dev/null +++ b/public/app/features/alerting/unified/components/AlertLabels.test.tsx @@ -0,0 +1,25 @@ +import { screen, render, waitFor } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import React from 'react'; + +import { AlertLabels } from './AlertLabels'; + +describe('AlertLabels', () => { + it('should toggle show / hide common labels', async () => { + const labels = { foo: 'bar', bar: 'baz', baz: 'qux' }; + const commonLabels = { foo: 'bar', baz: 'qux' }; + + render(); + expect(screen.getByText('+2 common labels')).toBeInTheDocument(); + + userEvent.click(screen.getByRole('button')); + await waitFor(() => { + expect(screen.getByText('Hide common labels')).toBeInTheDocument(); + }); + + userEvent.click(screen.getByRole('button')); + await waitFor(() => { + expect(screen.getByText('+2 common labels')).toBeInTheDocument(); + }); + }); +}); diff --git a/public/app/features/alerting/unified/components/AlertLabels.tsx b/public/app/features/alerting/unified/components/AlertLabels.tsx index cadcaaeec59..cd7b6cc1412 100644 --- a/public/app/features/alerting/unified/components/AlertLabels.tsx +++ b/public/app/features/alerting/unified/components/AlertLabels.tsx @@ -1,26 +1,60 @@ import { css } from '@emotion/css'; import { chain } from 'lodash'; -import React from 'react'; +import pluralize from 'pluralize'; +import React, { useState } from 'react'; import { GrafanaTheme2 } from '@grafana/data'; -import { getTagColorsFromName, useStyles2 } from '@grafana/ui'; +import { Button, getTagColorsFromName, useStyles2 } from '@grafana/ui'; import { Label, LabelSize } from './Label'; interface Props { labels: Record; + commonLabels?: Record; size?: LabelSize; } -export const AlertLabels = ({ labels, size }: Props) => { +export const AlertLabels = ({ labels, commonLabels = {}, size }: Props) => { const styles = useStyles2((theme) => getStyles(theme, size)); - const pairs = chain(labels).toPairs().reject(isPrivateKey).value(); + const [showCommonLabels, setShowCommonLabels] = useState(false); + + const labelsToShow = chain(labels) + .toPairs() + .reject(isPrivateKey) + .reject(([key]) => (showCommonLabels ? false : key in commonLabels)) + .value(); + + const commonLabelsCount = Object.keys(commonLabels).length; + const hasCommonLabels = commonLabelsCount > 0; return (
- {pairs.map(([label, value]) => ( + {labelsToShow.map(([label, value]) => (
); }; @@ -35,6 +69,7 @@ const getStyles = (theme: GrafanaTheme2, size?: LabelSize) => ({ wrapper: css` display: flex; flex-wrap: wrap; + align-items: center; gap: ${size === 'md' ? theme.spacing() : theme.spacing(0.5)}; `, diff --git a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx index dd2bd4302dc..4096e0e1792 100644 --- a/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertInstancesTable.tsx @@ -1,6 +1,6 @@ import React, { useMemo } from 'react'; -import { dateTime } from '@grafana/data'; +import { dateTime, findCommonLabels } from '@grafana/data'; import { Alert, PaginationProps } from 'app/types/unified-alerting'; import { alertInstanceKey } from '../../utils/rules'; @@ -16,17 +16,27 @@ interface Props { footerRow?: JSX.Element; } -type AlertTableColumnProps = DynamicTableColumnProps; -type AlertTableItemProps = DynamicTableItemProps; +interface AlertWithCommonLabels extends Alert { + commonLabels?: Record; +} + +type AlertTableColumnProps = DynamicTableColumnProps; +type AlertTableItemProps = DynamicTableItemProps; export const AlertInstancesTable = ({ instances, pagination, footerRow }: Props) => { + const commonLabels = useMemo(() => { + // only compute the common labels if we have more than 1 instance, if we don't then that single instance + // will have the complete set of common labels and no unique ones + return instances.length > 1 ? findCommonLabels(instances.map((instance) => instance.labels)) : {}; + }, [instances]); + const items = useMemo( (): AlertTableItemProps[] => instances.map((instance) => ({ - data: instance, + data: { ...instance, commonLabels }, id: alertInstanceKey(instance), })), - [instances] + [commonLabels, instances] ); return ( @@ -53,7 +63,9 @@ const columns: AlertTableColumnProps[] = [ id: 'labels', label: 'Labels', // eslint-disable-next-line react/display-name - renderCell: ({ data: { labels } }) => , + renderCell: ({ data: { labels, commonLabels } }) => ( + + ), }, { id: 'created',