diff --git a/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.story.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.story.tsx new file mode 100644 index 00000000000..2e5088187aa --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.story.tsx @@ -0,0 +1,60 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ComponentProps } from 'react'; + +import { Stack, Text } from '@grafana/ui'; + +import { AlertLabel } from './AlertLabel'; + +const meta: Meta = { + component: AlertLabel, + title: 'Rules/AlertLabel', +}; + +export default meta; + +export const Basic: StoryObj = { + render: (args: ComponentProps) => ( + + + + + + + ), +}; + +export const Sizes: StoryObj = { + render: (args: ComponentProps) => ( + + md + + sm + + xs + + + ), +}; + +export const Clickable: StoryObj = { + render: (args: ComponentProps) => ( + console.log('clicked', key, value)} + /> + ), +}; + +export const WithIconAndColor: StoryObj = { + render: (args: ComponentProps) => ( + + + + + + + + ), +}; diff --git a/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.test.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.test.tsx new file mode 100644 index 00000000000..5c3703af05b --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.test.tsx @@ -0,0 +1,33 @@ +import { render, screen } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; + +import { AlertLabel } from './AlertLabel'; + +describe('Label', () => { + it('renders label and value with correct aria-label', () => { + render(); + + const item = screen.getByTestId('label-value'); + expect(item).toBeInTheDocument(); + expect(item).toHaveAttribute('aria-label', 'foo: bar'); + expect(screen.getByText('foo')).toBeInTheDocument(); + expect(screen.getByText('bar')).toBeInTheDocument(); + }); + + it('calls onClick when clicked', async () => { + const onClick = jest.fn(); + render(); + + await userEvent.click(screen.getByRole('button')); + expect(onClick).toHaveBeenCalledWith(['prod', 'env']); + }); + + it('calls onClick when Enter is pressed', async () => { + const onClick = jest.fn(); + render(); + + const button = screen.getByRole('button'); + await userEvent.type(button, '{enter}'); + expect(onClick).toHaveBeenCalledWith(['eu-west-1', 'region']); + }); +}); diff --git a/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.tsx new file mode 100644 index 00000000000..42df1b5406b --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabel.tsx @@ -0,0 +1,226 @@ +import { css, cx } from '@emotion/css'; +import { CSSProperties, HTMLAttributes, useMemo } from 'react'; +import tinycolor2 from 'tinycolor2'; +import { MergeExclusive } from 'type-fest'; + +import { GrafanaTheme2, IconName } from '@grafana/data'; +import { Icon, Stack, getTagColorsFromName, useStyles2 } from '@grafana/ui'; + +export type LabelSize = 'md' | 'sm' | 'xs'; + +interface BaseProps extends Omit, 'onClick' | 'className'> { + icon?: IconName; + labelKey?: string; + value?: string; + size?: LabelSize; + onClick?: ([value, key]: [string | undefined, string | undefined]) => void; +} + +type Props = BaseProps & MergeExclusive<{ color?: string }, { colorBy?: 'key' | 'value' | 'both' }>; + +const AlertLabel = (props: Props) => { + const { labelKey, value, icon, color, colorBy, size = 'md', onClick, ...rest } = props; + const theColor = getColorFromProps({ color, colorBy, labelKey, value }); + const styles = useStyles2(getStyles, theColor, size); + + const ariaLabel = `${labelKey}: ${value}`; + const keyless = !Boolean(labelKey); + + const innerLabel = useMemo( + () => ( + + {labelKey && ( +
+ + {icon && } + {labelKey && ( + + {labelKey ?? ''} + + )} + +
+ )} +
+ {value ?? '-'} +
+
+ ), + [labelKey, styles.label, styles.labelText, styles.value, styles.valueWithoutKey, icon, keyless, value] + ); + + return ( +
+ {onClick && (labelKey || value) ? ( + + ) : ( + innerLabel + )} +
+ ); +}; + +function getAccessibleTagColor(name?: string): string | undefined { + if (!name) { + return; + } + const attempts = Array.from({ length: 6 }, (_, i) => name + '-'.repeat(i)); + const readableAttempt = attempts.find((attempt) => { + const candidate = getTagColorsFromName(attempt).color; + return ( + tinycolor2.isReadable(candidate, '#000', { level: 'AA', size: 'small' }) || + tinycolor2.isReadable(candidate, '#fff', { level: 'AA', size: 'small' }) + ); + }); + const chosen = readableAttempt ?? name; + return getTagColorsFromName(chosen).color; +} + +function getColorFromProps({ + color, + colorBy, + labelKey, + value, +}: Pick) { + if (color) { + return getAccessibleTagColor(color); + } + + if (colorBy === 'key') { + return getAccessibleTagColor(labelKey); + } + + if (colorBy === 'value') { + return getAccessibleTagColor(value); + } + + if (colorBy === 'both' && labelKey && value) { + return getAccessibleTagColor(labelKey + value); + } + + return; +} + +function getReadableFontColor(bg: string, fallback: string): string { + // First: explicitly check black + if (tinycolor2.isReadable(bg, '#000', { level: 'AA', size: 'small' })) { + return '#000'; + } + + // Then: explicitly check white + if (tinycolor2.isReadable(bg, '#fff', { level: 'AA', size: 'small' })) { + return '#fff'; + } + + // Then: try fallback if it’s readable + if (tinycolor2.isReadable(bg, fallback, { level: 'AA', size: 'small' })) { + return tinycolor2(fallback).toHexString(); + } + + // Last resort: pick the "most readable", even if not AA-compliant + return tinycolor2 + .mostReadable(bg, ['#000', '#fff', fallback], { + includeFallbackColors: true, + }) + .toHexString(); +} + +const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => { + const backgroundColor = color ?? theme.colors.secondary.main; + + const borderColor = theme.isDark + ? tinycolor2(backgroundColor).lighten(5).toString() + : tinycolor2(backgroundColor).darken(5).toString(); + + const valueBackgroundColor = theme.isDark + ? tinycolor2(backgroundColor).darken(5).toString() + : tinycolor2(backgroundColor).lighten(5).toString(); + + const labelFontColor = color + ? getReadableFontColor(backgroundColor, theme.colors.text.primary) + : theme.colors.text.primary; + + const valueFontColor = color + ? getReadableFontColor(valueBackgroundColor, theme.colors.text.primary) + : theme.colors.text.primary; + + let padding: CSSProperties['padding'] = theme.spacing(0.33, 1); + + switch (size) { + case 'sm': + padding = theme.spacing(0.2, 0.6); + break; + case 'xs': + padding = theme.spacing(0, 0.5); + break; + default: + break; + } + + return { + wrapper: css({ + fontSize: theme.typography.bodySmall.fontSize, + borderRadius: theme.shape.borderRadius(2), + }), + labelText: css({ + whiteSpace: 'nowrap', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '300px', + }), + label: css({ + display: 'flex', + alignItems: 'center', + color: labelFontColor, + + padding: padding, + background: backgroundColor, + + border: `solid 1px ${borderColor}`, + borderTopLeftRadius: theme.shape.borderRadius(2), + borderBottomLeftRadius: theme.shape.borderRadius(2), + }), + clickable: css({ + border: 'none', + background: 'none', + outline: 'none', + boxShadow: 'none', + + padding: 0, + margin: 0, + + '&:hover': { + opacity: 0.8, + cursor: 'pointer', + }, + }), + value: css({ + color: valueFontColor, + padding: padding, + background: valueBackgroundColor, + border: `solid 1px ${borderColor}`, + borderLeft: 'none', + borderTopRightRadius: theme.shape.borderRadius(2), + borderBottomRightRadius: theme.shape.borderRadius(2), + whiteSpace: 'pre', + overflow: 'hidden', + textOverflow: 'ellipsis', + maxWidth: '300px', + }), + valueWithoutKey: css({ + borderTopLeftRadius: theme.shape.borderRadius(2), + borderBottomLeftRadius: theme.shape.borderRadius(2), + borderLeft: `solid 1px ${borderColor}`, + }), + }; +}; + +export { AlertLabel }; +export type AlertLabelProps = Props; diff --git a/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.story.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.story.tsx new file mode 100644 index 00000000000..cc963d19c17 --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.story.tsx @@ -0,0 +1,48 @@ +import type { Meta, StoryObj } from '@storybook/react'; +import { ComponentProps } from 'react'; + +import { Stack } from '@grafana/ui'; + +import { AlertLabels } from './AlertLabels'; + +const meta: Meta = { + component: AlertLabels, + title: 'Rules/AlertLabels', +}; + +export default meta; + +export const Basic: StoryObj = { + render: (args: ComponentProps) => ( + + + + + + ), +}; + +export const WithCommonLabels: StoryObj = { + render: (args: ComponentProps) => ( + + ), +}; diff --git a/public/app/features/alerting/unified/components/AlertLabels.test.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.test.tsx similarity index 82% rename from public/app/features/alerting/unified/components/AlertLabels.test.tsx rename to packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.test.tsx index ff6052ac1af..8fec49c0226 100644 --- a/public/app/features/alerting/unified/components/AlertLabels.test.tsx +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.test.tsx @@ -6,9 +6,9 @@ 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' }; + const another = { foo: 'bar', baz: 'qux', extra: 'z' }; - render(); + render(); expect(screen.getByText('+2 common labels')).toBeInTheDocument(); await userEvent.click(screen.getByRole('button')); diff --git a/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.tsx b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.tsx new file mode 100644 index 00000000000..3646c02a13e --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/components/labels/AlertLabels.tsx @@ -0,0 +1,97 @@ +import { css } from '@emotion/css'; +import { chain } from 'lodash'; +import { useState } from 'react'; + +import { GrafanaTheme2 } from '@grafana/data'; +import { Trans, t } from '@grafana/i18n'; +import { Button, useStyles2 } from '@grafana/ui'; + +import { findCommonLabels, isPrivateLabel } from '../../utils/labels'; + +import { AlertLabel, LabelSize } from './AlertLabel'; + +export interface AlertLabelsProps { + labels: Record; + displayCommonLabels?: boolean; + labelSets?: Array>; + size?: LabelSize; + onClick?: ([value, key]: [string | undefined, string | undefined]) => void; +} + +export const AlertLabels = ({ labels, displayCommonLabels, labelSets, size, onClick }: AlertLabelsProps) => { + const styles = useStyles2(getStyles, size); + const [showCommonLabels, setShowCommonLabels] = useState(false); + + const computedCommonLabels = + displayCommonLabels && Array.isArray(labelSets) && labelSets.length > 1 ? findCommonLabels(labelSets) : {}; + + const labelsToShow = chain(labels) + .toPairs() + .reject(isPrivateLabel) + .reject(([key]) => (showCommonLabels ? false : key in computedCommonLabels)) + .value(); + + const commonLabelsCount = Object.keys(computedCommonLabels).length; + const hasCommonLabels = commonLabelsCount > 0; + const tooltip = t('alert-labels.button.show.tooltip', 'Show common labels'); + + return ( +
+ {labelsToShow.map(([label, value]) => { + return ( + + ); + })} + + {!showCommonLabels && hasCommonLabels && ( +
+ +
+ )} + {showCommonLabels && hasCommonLabels && ( +
+ +
+ )} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2, size?: LabelSize) => { + return { + wrapper: css({ + display: 'flex', + flexWrap: 'wrap', + alignItems: 'center', + + gap: size === 'md' ? theme.spacing() : theme.spacing(0.5), + }), + }; +}; diff --git a/packages/grafana-alerting/src/grafana/rules/utils/labels.ts b/packages/grafana-alerting/src/grafana/rules/utils/labels.ts new file mode 100644 index 00000000000..1ab53c0eacb --- /dev/null +++ b/packages/grafana-alerting/src/grafana/rules/utils/labels.ts @@ -0,0 +1,37 @@ +export const GRAFANA_ORIGIN_LABEL = '__grafana_origin'; + +export function isPrivateLabelKey(labelKey: string) { + return (labelKey.startsWith('__') && labelKey.endsWith('__')) || labelKey === GRAFANA_ORIGIN_LABEL; +} + +export const isPrivateLabel = ([key, _]: [string, string]) => isPrivateLabelKey(key); + +/** + * Returns a map labels that are common to the given label sets. + */ +export function findCommonLabels(labelSets: Array>): Record { + if (!Array.isArray(labelSets) || labelSets.length === 0) { + return {}; + } + return labelSets.reduce( + (acc, labels) => { + if (!labels) { + throw new Error('Need parsed labels to find common labels.'); + } + // Remove incoming labels that are missing or not matching in value + Object.keys(labels).forEach((key) => { + if (acc[key] === undefined || acc[key] !== labels[key]) { + delete acc[key]; + } + }); + // Remove common labels that are missing from incoming label set + Object.keys(acc).forEach((key) => { + if (labels[key] === undefined) { + delete acc[key]; + } + }); + return acc; + }, + { ...labelSets[0] } + ); +} diff --git a/packages/grafana-alerting/src/index.ts b/packages/grafana-alerting/src/index.ts index 70a654fcd29..288d2c776fd 100644 --- a/packages/grafana-alerting/src/index.ts +++ b/packages/grafana-alerting/src/index.ts @@ -7,5 +7,9 @@ * Breaking changes should be avoided to maintain backwards compatibility for consumers of this package. */ +export { AlertLabels } from './grafana/rules/components/labels/AlertLabels'; +export { AlertLabel } from './grafana/rules/components/labels/AlertLabel'; +// keep label utils internal to the app for now + // This is a dummy export so typescript doesn't error importing an "empty module" export const index = {}; diff --git a/packages/grafana-alerting/src/locales/en-US/grafana-alerting.json b/packages/grafana-alerting/src/locales/en-US/grafana-alerting.json index e15baa2cce7..56df665e01e 100644 --- a/packages/grafana-alerting/src/locales/en-US/grafana-alerting.json +++ b/packages/grafana-alerting/src/locales/en-US/grafana-alerting.json @@ -1,5 +1,18 @@ { + "alert-labels": { + "button": { + "hide": "Hide common labels", + "show": { + "tooltip": "Show common labels" + } + } + }, "alerting": { + "alert-labels": { + "aria-label-labels": "Labels", + "common-labels-count_one": "+{{count}} common labels", + "common-labels-count_other": "+{{count}} common labels" + }, "paused-badge": { "paused": "Paused" } diff --git a/packages/grafana-alerting/src/unstable.ts b/packages/grafana-alerting/src/unstable.ts index 7fadd98eb45..5f9bc060350 100644 --- a/packages/grafana-alerting/src/unstable.ts +++ b/packages/grafana-alerting/src/unstable.ts @@ -28,6 +28,8 @@ export * from './grafana/notificationPolicies/types'; // Rules export { StateText } from './grafana/rules/components/state/StateText'; export { StateIcon } from './grafana/rules/components/state/StateIcon'; +export { AlertLabel, type AlertLabelProps } from './grafana/rules/components/labels/AlertLabel'; +export { AlertLabels, type AlertLabelsProps } from './grafana/rules/components/labels/AlertLabels'; // Matchers export { type LabelMatcher, type Label } from './grafana/matchers/types'; diff --git a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx index b80af2ce4d1..33c8f7918a4 100644 --- a/public/app/features/alerting/unified/RedirectToRuleViewer.tsx +++ b/public/app/features/alerting/unified/RedirectToRuleViewer.tsx @@ -3,12 +3,12 @@ import { useMemo } from 'react'; import { Navigate } from 'react-router-dom-v5-compat'; import { useLocation } from 'react-use'; +import { AlertLabels } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { config, isFetchError } from '@grafana/runtime'; import { Alert, Card, Icon, LoadingPlaceholder, useStyles2 } from '@grafana/ui'; -import { AlertLabels } from './components/AlertLabels'; import { RuleViewerLayout } from './components/rule-viewer/RuleViewerLayout'; import { useCloudCombinedRulesMatching } from './hooks/useCombinedRule'; import { getRulesSourceByName } from './utils/datasource'; diff --git a/public/app/features/alerting/unified/components/AlertLabels.tsx b/public/app/features/alerting/unified/components/AlertLabels.tsx deleted file mode 100644 index c85f6f978fe..00000000000 --- a/public/app/features/alerting/unified/components/AlertLabels.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { css } from '@emotion/css'; -import { chain } from 'lodash'; -import { useState } from 'react'; - -import { GrafanaTheme2 } from '@grafana/data'; -import { Trans, t } from '@grafana/i18n'; -import { Button, getTagColorsFromName, useStyles2 } from '@grafana/ui'; - -import { isPrivateLabel } from '../utils/labels'; - -import { Label, LabelSize } from './Label'; - -interface Props { - labels: Record; - commonLabels?: Record; - size?: LabelSize; - onClick?: (label: string, value: string) => void; -} - -export const AlertLabels = ({ labels, commonLabels = {}, size, onClick }: Props) => { - const styles = useStyles2(getStyles, size); - const [showCommonLabels, setShowCommonLabels] = useState(false); - - const labelsToShow = chain(labels) - .toPairs() - .reject(isPrivateLabel) - .reject(([key]) => (showCommonLabels ? false : key in commonLabels)) - .value(); - - const commonLabelsCount = Object.keys(commonLabels).length; - const hasCommonLabels = commonLabelsCount > 0; - const tooltip = t('alert-labels.button.show.tooltip', 'Show common labels'); - - return ( -
- {labelsToShow.map(([label, value]) => { - return ( -
- ); -}; - -function getLabelColor(input: string): string { - return getTagColorsFromName(input).color; -} - -const getStyles = (theme: GrafanaTheme2, size?: LabelSize) => { - return { - wrapper: css({ - display: 'flex', - flexWrap: 'wrap', - alignItems: 'center', - - gap: size === 'md' ? theme.spacing() : theme.spacing(0.5), - }), - }; -}; diff --git a/public/app/features/alerting/unified/components/Label.tsx b/public/app/features/alerting/unified/components/Label.tsx deleted file mode 100644 index 30379578968..00000000000 --- a/public/app/features/alerting/unified/components/Label.tsx +++ /dev/null @@ -1,148 +0,0 @@ -import { css } from '@emotion/css'; -import { CSSProperties, ReactNode, useMemo } from 'react'; -import tinycolor2 from 'tinycolor2'; - -import { GrafanaTheme2, IconName } from '@grafana/data'; -import { Icon, Stack, useStyles2 } from '@grafana/ui'; - -export type LabelSize = 'md' | 'sm' | 'xs'; - -interface Props { - icon?: IconName; - label?: ReactNode; - value: ReactNode; - color?: string; - size?: LabelSize; - onClick?: (label: string, value: string) => void; -} - -// TODO allow customization with color prop -const Label = ({ label, value, icon, color, size = 'md', onClick }: Props) => { - const styles = useStyles2(getStyles, color, size); - const ariaLabel = `${label}: ${value}`; - const labelStr = label?.toString() ?? ''; - const valueStr = value?.toString() ?? ''; - - const innerLabel = useMemo( - () => ( - -
- - {icon && } - {label && ( - - {label ?? ''} - - )} - -
-
- {value ?? '-'} -
-
- ), - [icon, label, value, styles] - ); - - return ( -
- {onClick ? ( -
onClick(labelStr, valueStr)} - onKeyDown={(e) => { - // needed for accessiblity: handle keyboard navigation - if (e.key === 'Enter') { - onClick(labelStr, valueStr); - e.preventDefault(); - } - }} - > - {innerLabel} -
- ) : ( - innerLabel - )} -
- ); -}; - -const getStyles = (theme: GrafanaTheme2, color?: string, size?: string) => { - const backgroundColor = color ?? theme.colors.secondary.main; - - const borderColor = theme.isDark - ? tinycolor2(backgroundColor).lighten(5).toString() - : tinycolor2(backgroundColor).darken(5).toString(); - - const valueBackgroundColor = theme.isDark - ? tinycolor2(backgroundColor).darken(5).toString() - : tinycolor2(backgroundColor).lighten(5).toString(); - - const fontColor = color - ? tinycolor2.mostReadable(backgroundColor, ['#000', '#fff']).toString() - : theme.colors.text.primary; - - let padding: CSSProperties['padding'] = theme.spacing(0.33, 1); - - switch (size) { - case 'sm': - padding = theme.spacing(0.2, 0.6); - break; - case 'xs': - padding = theme.spacing(0, 0.5); - break; - default: - break; - } - - return { - wrapper: css({ - color: fontColor, - fontSize: theme.typography.bodySmall.fontSize, - - borderRadius: theme.shape.borderRadius(2), - }), - labelText: css({ - whiteSpace: 'nowrap', - overflow: 'hidden', - textOverflow: 'ellipsis', - maxWidth: '300px', - }), - label: css({ - display: 'flex', - alignItems: 'center', - color: 'inherit', - - padding: padding, - background: backgroundColor, - - border: `solid 1px ${borderColor}`, - borderTopLeftRadius: theme.shape.borderRadius(2), - borderBottomLeftRadius: theme.shape.borderRadius(2), - }), - clickable: css({ - '&:hover': { - opacity: 0.8, - cursor: 'pointer', - }, - }), - value: css({ - color: 'inherit', - padding: padding, - background: valueBackgroundColor, - border: `solid 1px ${borderColor}`, - borderLeft: 'none', - borderTopRightRadius: theme.shape.borderRadius(2), - borderBottomRightRadius: theme.shape.borderRadius(2), - whiteSpace: 'pre', - overflow: 'hidden', - textOverflow: 'ellipsis', - maxWidth: '300px', - }), - }; -}; - -export { Label }; diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx index fe9aa993e1a..ec6daa076a9 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroup.tsx @@ -1,13 +1,13 @@ import { css } from '@emotion/css'; import { useState } from 'react'; +import { AlertLabels } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans } from '@grafana/i18n'; import { Stack, TextLink, useStyles2 } from '@grafana/ui'; import { AlertmanagerGroup } from 'app/plugins/datasource/alertmanager/types'; import { createContactPointSearchLink } from '../../utils/misc'; -import { AlertLabels } from '../AlertLabels'; import { CollapseToggle } from '../CollapseToggle'; import { MetaText } from '../MetaText'; diff --git a/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx b/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx index ca9f0418a32..57d3165969c 100644 --- a/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx +++ b/public/app/features/alerting/unified/components/alert-groups/AlertGroupAlertsTable.tsx @@ -1,12 +1,12 @@ import { css } from '@emotion/css'; import { useMemo } from 'react'; +import { AlertLabels } from '@grafana/alerting/unstable'; import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { useStyles2 } from '@grafana/ui'; import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types'; -import { AlertLabels } from '../AlertLabels'; import { DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable'; import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines'; import { AmAlertStateTag } from '../silences/AmAlertStateTag'; diff --git a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx index a2a25fcc8d0..2eded5fb40f 100644 --- a/public/app/features/alerting/unified/components/notification-policies/Policy.tsx +++ b/public/app/features/alerting/unified/components/notification-policies/Policy.tsx @@ -5,6 +5,7 @@ import * as React from 'react'; import { FC, Fragment, ReactNode, useState } from 'react'; import { useToggle } from 'react-use'; +import { AlertLabel } from '@grafana/alerting/unstable'; import { GrafanaTheme2 } from '@grafana/data'; import { Trans, t } from '@grafana/i18n'; import { @@ -42,7 +43,6 @@ import { InheritableProperties, getInheritedProperties } from '../../utils/notif import { InsertPosition } from '../../utils/routeTree'; import { Authorize } from '../Authorize'; import { PopupCard } from '../HoverCard'; -import { Label } from '../Label'; import { MetaText } from '../MetaText'; import { ProvisioningBadge } from '../Provisioning'; import { Spacer } from '../Spacer'; @@ -720,7 +720,13 @@ const InheritedProperties: FC<{ properties: InheritableProperties }> = ({ proper return null; } - return