mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: a11y improvements (#63072)
This commit is contained in:
parent
c70571c536
commit
53a8998c85
@ -4,20 +4,22 @@ import React from 'react';
|
|||||||
import { PanelModel } from '../dashboard/state';
|
import { PanelModel } from '../dashboard/state';
|
||||||
import { createDashboardModelFixture, createPanelJSONFixture } from '../dashboard/state/__fixtures__/dashboardFixtures';
|
import { createDashboardModelFixture, createPanelJSONFixture } from '../dashboard/state/__fixtures__/dashboardFixtures';
|
||||||
|
|
||||||
import { TestRuleResult, Props } from './TestRuleResult';
|
import { TestRuleResult } from './TestRuleResult';
|
||||||
|
|
||||||
|
const backendSrv = {
|
||||||
|
post: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
jest.mock('@grafana/runtime', () => {
|
jest.mock('@grafana/runtime', () => {
|
||||||
const original = jest.requireActual('@grafana/runtime');
|
const original = jest.requireActual('@grafana/runtime');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...original,
|
...original,
|
||||||
getBackendSrv: () => ({
|
getBackendSrv: () => backendSrv,
|
||||||
post: jest.fn(),
|
|
||||||
}),
|
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
const props: Props = {
|
const props: React.ComponentProps<typeof TestRuleResult> = {
|
||||||
panel: new PanelModel({ id: 1 }),
|
panel: new PanelModel({ id: 1 }),
|
||||||
dashboard: createDashboardModelFixture({
|
dashboard: createDashboardModelFixture({
|
||||||
panels: [createPanelJSONFixture({ id: 1 })],
|
panels: [createPanelJSONFixture({ id: 1 })],
|
||||||
@ -30,9 +32,14 @@ describe('TestRuleResult', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should call testRule when mounting', () => {
|
it('should call testRule when mounting', () => {
|
||||||
jest.spyOn(TestRuleResult.prototype, 'testRule');
|
jest.spyOn(backendSrv, 'post');
|
||||||
render(<TestRuleResult {...props} />);
|
render(<TestRuleResult {...props} />);
|
||||||
|
|
||||||
expect(TestRuleResult.prototype.testRule).toHaveBeenCalled();
|
expect(backendSrv.post).toHaveBeenCalledWith(
|
||||||
|
'/api/alerts/test',
|
||||||
|
expect.objectContaining({
|
||||||
|
panelId: 1,
|
||||||
|
})
|
||||||
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,20 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
|
|
||||||
import { getBackendSrv } from '@grafana/runtime';
|
import { getBackendSrv } from '@grafana/runtime';
|
||||||
import { LoadingPlaceholder, JSONFormatter, Icon, HorizontalGroup, ClipboardButton } from '@grafana/ui';
|
import {
|
||||||
|
LoadingPlaceholder,
|
||||||
|
JSONFormatter,
|
||||||
|
Icon,
|
||||||
|
HorizontalGroup,
|
||||||
|
ClipboardButton,
|
||||||
|
clearButtonStyles,
|
||||||
|
withTheme2,
|
||||||
|
Themeable2,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { DashboardModel, PanelModel } from '../dashboard/state';
|
import { DashboardModel, PanelModel } from '../dashboard/state';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props extends Themeable2 {
|
||||||
dashboard: DashboardModel;
|
dashboard: DashboardModel;
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
}
|
}
|
||||||
@ -16,7 +25,7 @@ interface State {
|
|||||||
testRuleResponse: {};
|
testRuleResponse: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TestRuleResult extends PureComponent<Props, State> {
|
class UnThemedTestRuleResult extends PureComponent<Props, State> {
|
||||||
readonly state: State = {
|
readonly state: State = {
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
allNodesExpanded: null,
|
allNodesExpanded: null,
|
||||||
@ -90,6 +99,7 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { testRuleResponse, isLoading } = this.state;
|
const { testRuleResponse, isLoading } = this.state;
|
||||||
|
const clearButton = clearButtonStyles(this.props.theme);
|
||||||
|
|
||||||
if (isLoading === true) {
|
if (isLoading === true) {
|
||||||
return <LoadingPlaceholder text="Evaluating rule" />;
|
return <LoadingPlaceholder text="Evaluating rule" />;
|
||||||
@ -101,7 +111,9 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
<>
|
<>
|
||||||
<div className="pull-right">
|
<div className="pull-right">
|
||||||
<HorizontalGroup spacing="md">
|
<HorizontalGroup spacing="md">
|
||||||
<div onClick={this.onToggleExpand}>{this.renderExpandCollapse()}</div>
|
<button type="button" className={clearButton} onClick={this.onToggleExpand}>
|
||||||
|
{this.renderExpandCollapse()}
|
||||||
|
</button>
|
||||||
<ClipboardButton getText={this.getTextForClipboard} icon="copy">
|
<ClipboardButton getText={this.getTextForClipboard} icon="copy">
|
||||||
Copy to Clipboard
|
Copy to Clipboard
|
||||||
</ClipboardButton>
|
</ClipboardButton>
|
||||||
@ -113,3 +125,5 @@ export class TestRuleResult extends PureComponent<Props, State> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const TestRuleResult = withTheme2(UnThemedTestRuleResult);
|
||||||
|
@ -2,7 +2,7 @@ import { css } from '@emotion/css';
|
|||||||
import React, { FC } from 'react';
|
import React, { FC } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data';
|
import { GrafanaTheme2 } from '@grafana/data';
|
||||||
import { Badge, useStyles2 } from '@grafana/ui';
|
import { Badge, clearButtonStyles, useStyles2 } from '@grafana/ui';
|
||||||
|
|
||||||
interface AlertConditionProps {
|
interface AlertConditionProps {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
@ -33,22 +33,27 @@ export const AlertConditionIndicator: FC<AlertConditionProps> = ({
|
|||||||
|
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return (
|
return (
|
||||||
<div className={styles.actionLink} onClick={() => onSetCondition && onSetCondition()}>
|
<button type="button" className={styles.actionLink} onClick={() => onSetCondition && onSetCondition()}>
|
||||||
Make this the alert condition
|
Make this the alert condition
|
||||||
</div>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
const getStyles = (theme: GrafanaTheme2) => ({
|
const getStyles = (theme: GrafanaTheme2) => {
|
||||||
actionLink: css`
|
const clearButton = clearButtonStyles(theme);
|
||||||
color: ${theme.colors.text.link};
|
|
||||||
cursor: pointer;
|
|
||||||
|
|
||||||
&:hover {
|
return {
|
||||||
text-decoration: underline;
|
actionLink: css`
|
||||||
}
|
${clearButton};
|
||||||
`,
|
color: ${theme.colors.text.link};
|
||||||
});
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -5,7 +5,7 @@ import React, { FC, useCallback, useState } from 'react';
|
|||||||
import { DataFrame, dateTimeFormat, GrafanaTheme2, LoadingState, PanelData } from '@grafana/data';
|
import { DataFrame, dateTimeFormat, GrafanaTheme2, LoadingState, PanelData } from '@grafana/data';
|
||||||
import { isTimeSeries } from '@grafana/data/src/dataframe/utils';
|
import { isTimeSeries } from '@grafana/data/src/dataframe/utils';
|
||||||
import { Stack } from '@grafana/experimental';
|
import { Stack } from '@grafana/experimental';
|
||||||
import { AutoSizeInput, Icon, IconButton, Select, useStyles2 } from '@grafana/ui';
|
import { AutoSizeInput, clearButtonStyles, Icon, IconButton, Select, useStyles2 } from '@grafana/ui';
|
||||||
import { ClassicConditions } from 'app/features/expressions/components/ClassicConditions';
|
import { ClassicConditions } from 'app/features/expressions/components/ClassicConditions';
|
||||||
import { Math } from 'app/features/expressions/components/Math';
|
import { Math } from 'app/features/expressions/components/Math';
|
||||||
import { Reduce } from 'app/features/expressions/components/Reduce';
|
import { Reduce } from 'app/features/expressions/components/Reduce';
|
||||||
@ -175,6 +175,7 @@ interface HeaderProps {
|
|||||||
|
|
||||||
const Header: FC<HeaderProps> = ({ refId, queryType, onUpdateRefId, onUpdateExpressionType, onRemoveExpression }) => {
|
const Header: FC<HeaderProps> = ({ refId, queryType, onUpdateRefId, onUpdateExpressionType, onRemoveExpression }) => {
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const clearButton = useStyles2(clearButtonStyles);
|
||||||
/**
|
/**
|
||||||
* There are 3 edit modes:
|
* There are 3 edit modes:
|
||||||
*
|
*
|
||||||
@ -195,9 +196,9 @@ const Header: FC<HeaderProps> = ({ refId, queryType, onUpdateRefId, onUpdateExpr
|
|||||||
<Stack direction="row" gap={0.5} alignItems="center">
|
<Stack direction="row" gap={0.5} alignItems="center">
|
||||||
<Stack direction="row" gap={1} alignItems="center" wrap={false}>
|
<Stack direction="row" gap={1} alignItems="center" wrap={false}>
|
||||||
{!editingRefId && (
|
{!editingRefId && (
|
||||||
<div className={styles.editable} onClick={() => setEditMode('refId')}>
|
<button type="button" className={cx(clearButton, styles.editable)} onClick={() => setEditMode('refId')}>
|
||||||
<div className={styles.expression.refId}>{refId}</div>
|
<div className={styles.expression.refId}>{refId}</div>
|
||||||
</div>
|
</button>
|
||||||
)}
|
)}
|
||||||
{editingRefId && (
|
{editingRefId && (
|
||||||
<AutoSizeInput
|
<AutoSizeInput
|
||||||
@ -216,10 +217,14 @@ const Header: FC<HeaderProps> = ({ refId, queryType, onUpdateRefId, onUpdateExpr
|
|||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!editingType && (
|
{!editingType && (
|
||||||
<div className={styles.editable} onClick={() => setEditMode('expressionType')}>
|
<button
|
||||||
|
type="button"
|
||||||
|
className={cx(clearButton, styles.editable)}
|
||||||
|
onClick={() => setEditMode('expressionType')}
|
||||||
|
>
|
||||||
<div className={styles.mutedText}>{capitalize(queryType)}</div>
|
<div className={styles.mutedText}>{capitalize(queryType)}</div>
|
||||||
<Icon size="xs" name="pen" className={styles.mutedIcon} onClick={() => setEditMode('expressionType')} />
|
<Icon size="xs" name="pen" className={styles.mutedIcon} onClick={() => setEditMode('expressionType')} />
|
||||||
</div>
|
</button>
|
||||||
)}
|
)}
|
||||||
{editingType && (
|
{editingType && (
|
||||||
<Select
|
<Select
|
||||||
|
@ -27,12 +27,15 @@ export const CollapsibleSection = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx(styles.wrapper, className)}>
|
<div className={cx(styles.wrapper, className)}>
|
||||||
<div className={styles.heading} onClick={toggleCollapse}>
|
<CollapseToggle
|
||||||
<CollapseToggle className={styles.caret} size={size} onToggle={toggleCollapse} isCollapsed={isCollapsed} />
|
className={styles.toggle}
|
||||||
<h6>{label}</h6>
|
size={size}
|
||||||
</div>
|
onToggle={toggleCollapse}
|
||||||
|
isCollapsed={isCollapsed}
|
||||||
|
text={label}
|
||||||
|
/>
|
||||||
{description && <p className={styles.description}>{description}</p>}
|
{description && <p className={styles.description}>{description}</p>}
|
||||||
<div className={isCollapsed ? styles.hidden : undefined}>{children}</div>
|
<div className={isCollapsed ? styles.hidden : styles.content}>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@ -42,14 +45,9 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
margin-top: ${theme.spacing(1)};
|
margin-top: ${theme.spacing(1)};
|
||||||
padding-bottom: ${theme.spacing(1)};
|
padding-bottom: ${theme.spacing(1)};
|
||||||
`,
|
`,
|
||||||
caret: css`
|
toggle: css`
|
||||||
margin-left: -${theme.spacing(0.5)}; // make it align with fields despite icon size
|
margin: ${theme.spacing(1, 0)};
|
||||||
`,
|
padding: 0;
|
||||||
heading: css`
|
|
||||||
cursor: pointer;
|
|
||||||
h6 {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
`,
|
`,
|
||||||
hidden: css`
|
hidden: css`
|
||||||
display: none;
|
display: none;
|
||||||
@ -60,4 +58,7 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
|||||||
font-weight: ${theme.typography.fontWeightRegular};
|
font-weight: ${theme.typography.fontWeightRegular};
|
||||||
margin: 0;
|
margin: 0;
|
||||||
`,
|
`,
|
||||||
|
content: css`
|
||||||
|
padding-left: ${theme.spacing(3)};
|
||||||
|
`,
|
||||||
});
|
});
|
||||||
|
@ -5,7 +5,16 @@ import AutoSizer from 'react-virtualized-auto-sizer';
|
|||||||
import { FixedSizeList } from 'react-window';
|
import { FixedSizeList } from 'react-window';
|
||||||
|
|
||||||
import { GrafanaTheme2 } from '@grafana/data/src';
|
import { GrafanaTheme2 } from '@grafana/data/src';
|
||||||
import { FilterInput, LoadingPlaceholder, useStyles2, Icon, Modal, Button, Alert } from '@grafana/ui';
|
import {
|
||||||
|
FilterInput,
|
||||||
|
LoadingPlaceholder,
|
||||||
|
useStyles2,
|
||||||
|
Icon,
|
||||||
|
Modal,
|
||||||
|
Button,
|
||||||
|
Alert,
|
||||||
|
clearButtonStyles,
|
||||||
|
} from '@grafana/ui';
|
||||||
|
|
||||||
import { dashboardApi } from '../../api/dashboardApi';
|
import { dashboardApi } from '../../api/dashboardApi';
|
||||||
|
|
||||||
@ -99,17 +108,18 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
|||||||
const isSelected = selectedDashboardUid === dashboard.uid;
|
const isSelected = selectedDashboardUid === dashboard.uid;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
title={dashboard.title}
|
title={dashboard.title}
|
||||||
style={style}
|
style={style}
|
||||||
className={cx(styles.row, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
className={cx(styles.rowButton, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
||||||
onClick={() => handleDashboardChange(dashboard.uid)}
|
onClick={() => handleDashboardChange(dashboard.uid)}
|
||||||
>
|
>
|
||||||
<div className={styles.dashboardTitle}>{dashboard.title}</div>
|
<div className={styles.dashboardTitle}>{dashboard.title}</div>
|
||||||
<div className={styles.dashboardFolder}>
|
<div className={styles.dashboardFolder}>
|
||||||
<Icon name="folder" /> {dashboard.folderTitle ?? 'General'}
|
<Icon name="folder" /> {dashboard.folderTitle ?? 'General'}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -118,13 +128,14 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
|||||||
const isSelected = selectedPanelId === panel.id.toString();
|
const isSelected = selectedPanelId === panel.id.toString();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<button
|
||||||
|
type="button"
|
||||||
style={style}
|
style={style}
|
||||||
className={cx(styles.row, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
className={cx(styles.rowButton, { [styles.rowOdd]: index % 2 === 1, [styles.rowSelected]: isSelected })}
|
||||||
onClick={() => setSelectedPanelId(panel.id.toString())}
|
onClick={() => setSelectedPanelId(panel.id.toString())}
|
||||||
>
|
>
|
||||||
{panel.title || '<No title>'}
|
{panel.title || '<No title>'}
|
||||||
</div>
|
</button>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -221,60 +232,66 @@ export const DashboardPicker = ({ dashboardUid, panelId, isOpen, onChange, onDis
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPickerStyles = (theme: GrafanaTheme2) => ({
|
const getPickerStyles = (theme: GrafanaTheme2) => {
|
||||||
container: css`
|
const clearButton = clearButtonStyles(theme);
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
return {
|
||||||
grid-template-rows: min-content auto;
|
container: css`
|
||||||
gap: ${theme.spacing(2)};
|
display: grid;
|
||||||
flex: 1;
|
grid-template-columns: 1fr 1fr;
|
||||||
`,
|
grid-template-rows: min-content auto;
|
||||||
column: css`
|
gap: ${theme.spacing(2)};
|
||||||
flex: 1 1 auto;
|
flex: 1;
|
||||||
`,
|
`,
|
||||||
dashboardTitle: css`
|
column: css`
|
||||||
height: 22px;
|
flex: 1 1 auto;
|
||||||
font-weight: ${theme.typography.fontWeightBold};
|
`,
|
||||||
`,
|
dashboardTitle: css`
|
||||||
dashboardFolder: css`
|
height: 22px;
|
||||||
height: 20px;
|
font-weight: ${theme.typography.fontWeightBold};
|
||||||
font-size: ${theme.typography.bodySmall.fontSize};
|
`,
|
||||||
color: ${theme.colors.text.secondary};
|
dashboardFolder: css`
|
||||||
display: flex;
|
height: 20px;
|
||||||
flex-direction: row;
|
font-size: ${theme.typography.bodySmall.fontSize};
|
||||||
justify-content: flex-start;
|
color: ${theme.colors.text.secondary};
|
||||||
column-gap: ${theme.spacing(1)};
|
display: flex;
|
||||||
align-items: center;
|
flex-direction: row;
|
||||||
`,
|
justify-content: flex-start;
|
||||||
row: css`
|
column-gap: ${theme.spacing(1)};
|
||||||
padding: ${theme.spacing(0.5)};
|
align-items: center;
|
||||||
overflow: hidden;
|
`,
|
||||||
text-overflow: ellipsis;
|
rowButton: css`
|
||||||
white-space: nowrap;
|
${clearButton};
|
||||||
cursor: pointer;
|
padding: ${theme.spacing(0.5)};
|
||||||
border: 2px solid transparent;
|
overflow: hidden;
|
||||||
`,
|
text-overflow: ellipsis;
|
||||||
rowSelected: css`
|
text-align: left;
|
||||||
border-color: ${theme.colors.primary.border};
|
white-space: nowrap;
|
||||||
`,
|
cursor: pointer;
|
||||||
rowOdd: css`
|
border: 2px solid transparent;
|
||||||
background-color: ${theme.colors.background.secondary};
|
`,
|
||||||
`,
|
rowSelected: css`
|
||||||
loadingPlaceholder: css`
|
border-color: ${theme.colors.primary.border};
|
||||||
height: 100%;
|
`,
|
||||||
display: flex;
|
rowOdd: css`
|
||||||
justify-content: center;
|
background-color: ${theme.colors.background.secondary};
|
||||||
align-items: center;
|
`,
|
||||||
`,
|
loadingPlaceholder: css`
|
||||||
modal: css`
|
height: 100%;
|
||||||
height: 100%;
|
display: flex;
|
||||||
`,
|
justify-content: center;
|
||||||
modalContent: css`
|
align-items: center;
|
||||||
flex: 1;
|
`,
|
||||||
display: flex;
|
modal: css`
|
||||||
flex-direction: column;
|
height: 100%;
|
||||||
`,
|
`,
|
||||||
modalAlert: css`
|
modalContent: css`
|
||||||
flex-grow: 0;
|
flex: 1;
|
||||||
`,
|
display: flex;
|
||||||
});
|
flex-direction: column;
|
||||||
|
`,
|
||||||
|
modalAlert: css`
|
||||||
|
flex-grow: 0;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import { css } from '@emotion/css';
|
import { css, cx } from '@emotion/css';
|
||||||
import { noop } from 'lodash';
|
import { noop } from 'lodash';
|
||||||
import pluralize from 'pluralize';
|
import pluralize from 'pluralize';
|
||||||
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
import React, { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
|
||||||
import { GrafanaTheme2, PanelProps } from '@grafana/data';
|
import { GrafanaTheme2, PanelProps } from '@grafana/data';
|
||||||
import { Icon, useStyles2 } from '@grafana/ui';
|
import { clearButtonStyles, Icon, useStyles2 } from '@grafana/ui';
|
||||||
import { AlertInstancesTable } from 'app/features/alerting/unified/components/rules/AlertInstancesTable';
|
import { AlertInstancesTable } from 'app/features/alerting/unified/components/rules/AlertInstancesTable';
|
||||||
import { sortAlerts } from 'app/features/alerting/unified/utils/misc';
|
import { sortAlerts } from 'app/features/alerting/unified/utils/misc';
|
||||||
import { Alert } from 'app/types/unified-alerting';
|
import { Alert } from 'app/types/unified-alerting';
|
||||||
@ -24,6 +24,7 @@ export const AlertInstances = ({ alerts, options }: Props) => {
|
|||||||
const defaultShowInstances = options.groupMode === GroupMode.Custom ? true : options.showInstances;
|
const defaultShowInstances = options.groupMode === GroupMode.Custom ? true : options.showInstances;
|
||||||
const [displayInstances, setDisplayInstances] = useState<boolean>(defaultShowInstances);
|
const [displayInstances, setDisplayInstances] = useState<boolean>(defaultShowInstances);
|
||||||
const styles = useStyles2(getStyles);
|
const styles = useStyles2(getStyles);
|
||||||
|
const clearButton = useStyles2(clearButtonStyles);
|
||||||
|
|
||||||
const toggleDisplayInstances = useCallback(() => {
|
const toggleDisplayInstances = useCallback(() => {
|
||||||
setDisplayInstances((display) => !display);
|
setDisplayInstances((display) => !display);
|
||||||
@ -50,11 +51,14 @@ export const AlertInstances = ({ alerts, options }: Props) => {
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{options.groupMode === GroupMode.Default && (
|
{options.groupMode === GroupMode.Default && (
|
||||||
<div className={uncollapsible ? styles.clickable : ''} onClick={() => toggleShowInstances()}>
|
<button
|
||||||
|
className={cx(clearButton, uncollapsible ? styles.clickable : '')}
|
||||||
|
onClick={() => toggleShowInstances()}
|
||||||
|
>
|
||||||
{uncollapsible && <Icon name={displayInstances ? 'angle-down' : 'angle-right'} size={'md'} />}
|
{uncollapsible && <Icon name={displayInstances ? 'angle-down' : 'angle-right'} size={'md'} />}
|
||||||
<span>{`${filteredAlerts.length} ${pluralize('instance', filteredAlerts.length)}`}</span>
|
<span>{`${filteredAlerts.length} ${pluralize('instance', filteredAlerts.length)}`}</span>
|
||||||
{hiddenInstances > 0 && <span>, {`${hiddenInstances} hidden by filters`}</span>}
|
{hiddenInstances > 0 && <span>, {`${hiddenInstances} hidden by filters`}</span>}
|
||||||
</div>
|
</button>
|
||||||
)}
|
)}
|
||||||
{displayInstances && (
|
{displayInstances && (
|
||||||
<AlertInstancesTable
|
<AlertInstancesTable
|
||||||
|
Loading…
Reference in New Issue
Block a user