mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Use new "Label" components for alert instance labels (#70997)
This commit is contained in:
@@ -78,7 +78,7 @@ describe('AlertGroups', () => {
|
||||
|
||||
expect(groups).toHaveLength(2);
|
||||
expect(groups[0]).toHaveTextContent('No grouping');
|
||||
expect(groups[1]).toHaveTextContent('severity=warningregion=US-Central');
|
||||
expect(groups[1]).toHaveTextContent('severitywarning regionUS-Central');
|
||||
|
||||
await userEvent.click(ui.groupCollapseToggle.get(groups[0]));
|
||||
expect(ui.groupTable.get()).toBeDefined();
|
||||
@@ -111,9 +111,9 @@ describe('AlertGroups', () => {
|
||||
const groupByWrapper = ui.groupByContainer.get();
|
||||
|
||||
expect(groups).toHaveLength(3);
|
||||
expect(groups[0]).toHaveTextContent('region=NASA');
|
||||
expect(groups[1]).toHaveTextContent('region=EMEA');
|
||||
expect(groups[2]).toHaveTextContent('region=APAC');
|
||||
expect(groups[0]).toHaveTextContent('regionNASA');
|
||||
expect(groups[1]).toHaveTextContent('regionEMEA');
|
||||
expect(groups[2]).toHaveTextContent('regionAPAC');
|
||||
|
||||
await userEvent.type(groupByInput, 'appName{enter}');
|
||||
|
||||
@@ -123,9 +123,9 @@ describe('AlertGroups', () => {
|
||||
|
||||
await waitFor(() => expect(ui.clearButton.get()).toBeInTheDocument());
|
||||
expect(groups).toHaveLength(3);
|
||||
expect(groups[0]).toHaveTextContent('appName=billing');
|
||||
expect(groups[1]).toHaveTextContent('appName=auth');
|
||||
expect(groups[2]).toHaveTextContent('appName=frontend');
|
||||
expect(groups[0]).toHaveTextContent('appNamebilling');
|
||||
expect(groups[1]).toHaveTextContent('appNameauth');
|
||||
expect(groups[2]).toHaveTextContent('appNamefrontend');
|
||||
|
||||
await userEvent.click(ui.clearButton.get());
|
||||
await waitFor(() => expect(groupByWrapper).not.toHaveTextContent('appName'));
|
||||
@@ -136,8 +136,8 @@ describe('AlertGroups', () => {
|
||||
groups = await ui.group.findAll();
|
||||
|
||||
expect(groups).toHaveLength(2);
|
||||
expect(groups[0]).toHaveTextContent('env=production');
|
||||
expect(groups[1]).toHaveTextContent('env=staging');
|
||||
expect(groups[0]).toHaveTextContent('envproduction');
|
||||
expect(groups[1]).toHaveTextContent('envstaging');
|
||||
|
||||
await userEvent.click(ui.clearButton.get());
|
||||
await waitFor(() => expect(groupByWrapper).not.toHaveTextContent('env'));
|
||||
@@ -148,7 +148,7 @@ describe('AlertGroups', () => {
|
||||
groups = await ui.group.findAll();
|
||||
expect(groups).toHaveLength(2);
|
||||
expect(groups[0]).toHaveTextContent('No grouping');
|
||||
expect(groups[1]).toHaveTextContent('uniqueLabel=true');
|
||||
expect(groups[1]).toHaveTextContent('uniqueLabeltrue');
|
||||
});
|
||||
|
||||
it('should combine multiple ungrouped groups', async () => {
|
||||
|
||||
@@ -362,7 +362,7 @@ describe('RuleList', () => {
|
||||
|
||||
const ruleDetails = ui.expandedContent.get(ruleRows[1]);
|
||||
|
||||
expect(ruleDetails).toHaveTextContent('Labelsseverity=warningfoo=bar');
|
||||
expect(ruleDetails).toHaveTextContent('Labels severitywarning foobar');
|
||||
expect(ruleDetails).toHaveTextContent('Expressiontopk ( 5 , foo ) [ 5m ]');
|
||||
expect(ruleDetails).toHaveTextContent('messagegreat alert');
|
||||
expect(ruleDetails).toHaveTextContent('Matching instances');
|
||||
@@ -373,8 +373,8 @@ describe('RuleList', () => {
|
||||
const instanceRows = byTestId('row').getAll(instancesTable);
|
||||
expect(instanceRows).toHaveLength(2);
|
||||
|
||||
expect(instanceRows![0]).toHaveTextContent('Firing foo=barseverity=warning2021-03-18 08:47:05');
|
||||
expect(instanceRows![1]).toHaveTextContent('Firing foo=bazseverity=error2021-03-18 08:47:05');
|
||||
expect(instanceRows![0]).toHaveTextContent('Firing foobar severitywarning2021-03-18 08:47:05');
|
||||
expect(instanceRows![1]).toHaveTextContent('Firing foobaz severityerror2021-03-18 08:47:05');
|
||||
|
||||
// expand details of an instance
|
||||
await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0]));
|
||||
@@ -515,7 +515,7 @@ describe('RuleList', () => {
|
||||
await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0]));
|
||||
const ruleDetails = ui.expandedContent.get(ruleRows[0]);
|
||||
|
||||
expect(ruleDetails).toHaveTextContent('Labelsseverity=warningfoo=bar');
|
||||
expect(ruleDetails).toHaveTextContent('Labels severitywarning foobar');
|
||||
|
||||
// Check for different label matchers
|
||||
await userEvent.clear(filterInput);
|
||||
|
||||
@@ -1,17 +1,41 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { chain } from 'lodash';
|
||||
import React from 'react';
|
||||
|
||||
import { TagList } from '@grafana/ui';
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { getTagColorsFromName, useStyles2 } from '@grafana/ui';
|
||||
|
||||
import { Label, LabelSize } from './Label';
|
||||
|
||||
interface Props {
|
||||
labels: Record<string, string>;
|
||||
className?: string;
|
||||
size?: LabelSize;
|
||||
}
|
||||
|
||||
export const AlertLabels = ({ labels, className }: Props) => {
|
||||
const pairs = Object.entries(labels).filter(([key]) => !(key.startsWith('__') && key.endsWith('__')));
|
||||
export const AlertLabels = ({ labels, size }: Props) => {
|
||||
const styles = useStyles2((theme) => getStyles(theme, size));
|
||||
const pairs = chain(labels).toPairs().reject(isPrivateKey).value();
|
||||
|
||||
return (
|
||||
<div className={className}>
|
||||
<TagList tags={Object.values(pairs).map(([label, value]) => `${label}=${value}`)} className={className} />
|
||||
<div className={styles.wrapper} role="list" aria-label="Labels">
|
||||
{pairs.map(([label, value]) => (
|
||||
<Label key={label + value} size={size} label={label} value={value} color={getLabelColor(label)} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
function getLabelColor(input: string): string {
|
||||
return getTagColorsFromName(input).color;
|
||||
}
|
||||
|
||||
const isPrivateKey = ([key, _]: [string, string]) => key.startsWith('__') && key.endsWith('__');
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2, size?: LabelSize) => ({
|
||||
wrapper: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
|
||||
gap: ${size === 'md' ? theme.spacing() : theme.spacing(0.5)};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -1,61 +1,87 @@
|
||||
import { css } from '@emotion/css';
|
||||
import React, { ReactNode } from 'react';
|
||||
import tinycolor2 from 'tinycolor2';
|
||||
|
||||
import { GrafanaTheme2, IconName } from '@grafana/data';
|
||||
import { Stack } from '@grafana/experimental';
|
||||
import { Icon, useStyles2 } from '@grafana/ui';
|
||||
|
||||
export type LabelSize = 'md' | 'sm';
|
||||
|
||||
interface Props {
|
||||
icon?: IconName;
|
||||
label?: ReactNode;
|
||||
value: ReactNode;
|
||||
color?: string;
|
||||
size?: LabelSize;
|
||||
}
|
||||
|
||||
// TODO allow customization with color prop
|
||||
const Label = ({ label, value, icon }: Props) => {
|
||||
const styles = useStyles2(getStyles);
|
||||
const Label = ({ label, value, icon, color, size = 'md' }: Props) => {
|
||||
const styles = useStyles2((theme) => getStyles(theme, color, size));
|
||||
|
||||
return (
|
||||
<div className={styles.meta().wrapper}>
|
||||
<Stack direction="row" gap={0} alignItems="stretch">
|
||||
<div className={styles.meta().label}>
|
||||
<div className={styles.wrapper} role="listitem">
|
||||
<Stack direction="row" gap={0} alignItems="stretch" wrap={false}>
|
||||
<div className={styles.label}>
|
||||
<Stack direction="row" gap={0.5} alignItems="center">
|
||||
{icon && <Icon name={icon} />} {label ?? ''}
|
||||
</Stack>
|
||||
</div>
|
||||
<div className={styles.meta().value}>{value}</div>
|
||||
<div className={styles.value}>{value}</div>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getStyles = (theme: GrafanaTheme2) => ({
|
||||
meta: (color?: string) => ({
|
||||
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;
|
||||
|
||||
const padding =
|
||||
size === 'md' ? `${theme.spacing(0.33)} ${theme.spacing(1)}` : `${theme.spacing(0.2)} ${theme.spacing(0.6)}`;
|
||||
|
||||
return {
|
||||
wrapper: css`
|
||||
color: ${fontColor};
|
||||
font-size: ${theme.typography.bodySmall.fontSize};
|
||||
|
||||
border-radius: ${theme.shape.borderRadius(2)};
|
||||
`,
|
||||
label: css`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: inherit;
|
||||
|
||||
padding: ${theme.spacing(0.33)} ${theme.spacing(1)};
|
||||
background: ${theme.colors.secondary.transparent};
|
||||
padding: ${padding};
|
||||
background: ${backgroundColor};
|
||||
|
||||
border: solid 1px ${theme.colors.border.medium};
|
||||
border: solid 1px ${borderColor};
|
||||
border-top-left-radius: ${theme.shape.borderRadius(2)};
|
||||
border-bottom-left-radius: ${theme.shape.borderRadius(2)};
|
||||
`,
|
||||
value: css`
|
||||
padding: ${theme.spacing(0.33)} ${theme.spacing(1)};
|
||||
font-weight: ${theme.typography.fontWeightBold};
|
||||
color: inherit;
|
||||
padding: ${padding};
|
||||
background: ${valueBackgroundColor};
|
||||
|
||||
border: solid 1px ${theme.colors.border.medium};
|
||||
border: solid 1px ${borderColor};
|
||||
border-left: none;
|
||||
border-top-right-radius: ${theme.shape.borderRadius(2)};
|
||||
border-bottom-right-radius: ${theme.shape.borderRadius(2)};
|
||||
`,
|
||||
}),
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
export { Label };
|
||||
|
||||
@@ -30,7 +30,11 @@ export const AlertGroup = ({ alertManagerSourceName, group }: Props) => {
|
||||
onToggle={() => setIsCollapsed(!isCollapsed)}
|
||||
data-testid="alert-group-collapse-toggle"
|
||||
/>
|
||||
{Object.keys(group.labels).length ? <AlertLabels labels={group.labels} /> : <span>No grouping</span>}
|
||||
{Object.keys(group.labels).length ? (
|
||||
<AlertLabels labels={group.labels} size="sm" />
|
||||
) : (
|
||||
<span>No grouping</span>
|
||||
)}
|
||||
</div>
|
||||
<AlertGroupHeader group={group} />
|
||||
</div>
|
||||
|
||||
@@ -47,7 +47,7 @@ export const AlertGroupAlertsTable = ({ alerts, alertManagerSourceName }: Props)
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
// eslint-disable-next-line react/display-name
|
||||
renderCell: ({ data: { labels } }) => <AlertLabels labels={labels} />,
|
||||
renderCell: ({ data: { labels } }) => <AlertLabels labels={labels} size="sm" />,
|
||||
size: 1,
|
||||
},
|
||||
],
|
||||
|
||||
@@ -171,7 +171,7 @@ export function RuleViewer({ match }: RuleViewerProps) {
|
||||
)}
|
||||
{!!rule.labels && !!Object.keys(rule.labels).length && (
|
||||
<DetailsField label="Labels" horizontal={true}>
|
||||
<AlertLabels labels={rule.labels} className={styles.labels} />
|
||||
<AlertLabels labels={rule.labels} />
|
||||
</DetailsField>
|
||||
)}
|
||||
<RuleDetailsExpression rulesSource={rulesSource} rule={rule} annotations={annotations} />
|
||||
@@ -297,6 +297,7 @@ const getStyles = (theme: GrafanaTheme2) => {
|
||||
`,
|
||||
leftSide: css`
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
`,
|
||||
rightSide: css`
|
||||
padding-right: ${theme.spacing(3)};
|
||||
|
||||
@@ -53,7 +53,7 @@ const columns: AlertTableColumnProps[] = [
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
// eslint-disable-next-line react/display-name
|
||||
renderCell: ({ data: { labels } }) => <AlertLabels labels={labels} />,
|
||||
renderCell: ({ data: { labels } }) => <AlertLabels labels={labels} size="sm" />,
|
||||
},
|
||||
{
|
||||
id: 'created',
|
||||
|
||||
@@ -42,7 +42,7 @@ export const SilencedAlertsTableRow = ({ alert, className }: Props) => {
|
||||
<tr className={className}>
|
||||
<td></td>
|
||||
<td colSpan={5}>
|
||||
<AlertLabels labels={alert.labels} />
|
||||
<AlertLabels labels={alert.labels} size="sm" />
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
|
||||
@@ -90,7 +90,7 @@ function useColumns(): Array<DynamicTableColumnProps<AlertmanagerAlert>> {
|
||||
id: 'labels',
|
||||
label: 'Labels',
|
||||
renderCell: function renderName({ data }) {
|
||||
return <AlertLabels labels={data.labels} className={styles.alertLabels} />;
|
||||
return <AlertLabels labels={data.labels} size="sm" />;
|
||||
},
|
||||
size: 'auto',
|
||||
},
|
||||
@@ -123,7 +123,4 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`,
|
||||
alertLabels: css`
|
||||
justify-content: flex-start;
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -26,7 +26,7 @@ export const AlertGroup = ({ alertManagerSourceName, group, expandAll }: Props)
|
||||
return (
|
||||
<div className={styles.group} data-testid="alert-group">
|
||||
{Object.keys(group.labels).length > 0 ? (
|
||||
<AlertLabels labels={group.labels} />
|
||||
<AlertLabels labels={group.labels} size="sm" />
|
||||
) : (
|
||||
<div className={styles.noGroupingText}>No grouping</div>
|
||||
)}
|
||||
@@ -49,7 +49,7 @@ export const AlertGroup = ({ alertManagerSourceName, group, expandAll }: Props)
|
||||
<span className={textStyles[alert.status.state]}>{state} </span>for {interval}
|
||||
</div>
|
||||
<div>
|
||||
<AlertLabels labels={alert.labels} />
|
||||
<AlertLabels labels={alert.labels} size="sm" />
|
||||
</div>
|
||||
<div className={styles.actionsRow}>
|
||||
{alert.status.state === AlertState.Suppressed && (
|
||||
|
||||
@@ -123,7 +123,7 @@ describe('AlertGroupsPanel', () => {
|
||||
expect(groups).toHaveLength(2);
|
||||
|
||||
expect(groups[0]).toHaveTextContent('No grouping');
|
||||
expect(groups[1]).toHaveTextContent('severity=warningregion=US-Central');
|
||||
expect(groups[1]).toHaveTextContent('severitywarning regionUS-Central');
|
||||
|
||||
const alerts = ui.alert.queryAll();
|
||||
expect(alerts).toHaveLength(0);
|
||||
|
||||
@@ -188,8 +188,8 @@ describe('UnifiedAlertList', () => {
|
||||
|
||||
await user.click(expandElement);
|
||||
|
||||
const tagsElement = await byRole('list', { name: 'Tags' }).find();
|
||||
expect(await byRole('listitem').find(tagsElement)).toHaveTextContent('severity=critical');
|
||||
const labelsElement = await byRole('list', { name: 'Labels' }).find();
|
||||
expect(await byRole('listitem').find(labelsElement)).toHaveTextContent('severitycritical');
|
||||
|
||||
expect(replaceVarsSpy).toHaveBeenLastCalledWith('$label');
|
||||
expect(filterAlertsSpy).toHaveBeenLastCalledWith(
|
||||
|
||||
Reference in New Issue
Block a user