Alerting: miscllaneous UI fixes & improvements (#33734)

This commit is contained in:
Domas
2021-05-06 11:21:58 +03:00
committed by GitHub
parent d994d0e762
commit d2d13ea39a
27 changed files with 217 additions and 157 deletions

View File

@@ -1,7 +1,7 @@
import React, { FC, useCallback, useEffect, useMemo, useState } from 'react';
import { css } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { Alert, Field, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { Alert, LoadingPlaceholder, useStyles2 } from '@grafana/ui';
import { useDispatch } from 'react-redux';
import { Redirect } from 'react-router-dom';
import { Receiver } from 'app/plugins/datasource/alertmanager/types';
@@ -96,9 +96,7 @@ const AmRoutes: FC = () => {
return (
<AlertingPageWrapper pageId="am-routes">
<Field label="Choose alert manager">
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
</Field>
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
{savingError && !saving && (
<Alert severity="error" title="Error saving alert manager config">
{savingError.message || 'Unknown error.'}
@@ -112,7 +110,6 @@ const AmRoutes: FC = () => {
{resultLoading && <LoadingPlaceholder text="Loading alert manager config..." />}
{result && !resultLoading && !resultError && (
<>
<div className={styles.break} />
<AmRootRoute
alertManagerSourceName={alertManagerSourceName}
isEditMode={isRootRouteEditMode}

View File

@@ -1,4 +1,4 @@
import { Field, Alert, LoadingPlaceholder } from '@grafana/ui';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import React, { FC, useEffect } from 'react';
import { useDispatch } from 'react-redux';
import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom';
@@ -50,9 +50,11 @@ const Receivers: FC = () => {
return (
<AlertingPageWrapper pageId="receivers">
<Field label={disableAmSelect ? 'Alert manager' : 'Choose alert manager'} disabled={disableAmSelect}>
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
</Field>
<AlertManagerPicker
current={alertManagerSourceName}
disabled={disableAmSelect}
onChange={setAlertManagerSourceName}
/>
{error && !loading && (
<Alert severity="error" title="Error loading alert manager config">
{error.message || 'Unknown error.'}

View File

@@ -2,23 +2,29 @@ import React, { FC, useEffect, useCallback } from 'react';
import { Alert, LoadingPlaceholder } from '@grafana/ui';
import { useDispatch } from 'react-redux';
import { Redirect, Route, RouteChildrenProps, Switch } from 'react-router-dom';
import { Redirect, Route, RouteChildrenProps, Switch, useLocation } from 'react-router-dom';
import { AlertingPageWrapper } from './components/AlertingPageWrapper';
import SilencesTable from './components/silences/SilencesTable';
import { useAlertManagerSourceName } from './hooks/useAlertManagerSourceName';
import { useUnifiedAlertingSelector } from './hooks/useUnifiedAlertingSelector';
import { fetchAmAlertsAction, fetchSilencesAction } from './state/actions';
import { SILENCES_POLL_INTERVAL_MS } from './utils/constants';
import { initialAsyncRequestState } from './utils/redux';
import { AsyncRequestState, initialAsyncRequestState } from './utils/redux';
import SilencesEditor from './components/silences/SilencesEditor';
import { AlertManagerPicker } from './components/AlertManagerPicker';
import { Silence } from 'app/plugins/datasource/alertmanager/types';
const Silences: FC = () => {
const [alertManagerSourceName = '', setAlertManagerSourceName] = useAlertManagerSourceName();
const [alertManagerSourceName, setAlertManagerSourceName] = useAlertManagerSourceName();
const dispatch = useDispatch();
const silences = useUnifiedAlertingSelector((state) => state.silences);
const alertsRequests = useUnifiedAlertingSelector((state) => state.amAlerts);
const alertsRequest = alertManagerSourceName
? alertsRequests[alertManagerSourceName] || initialAsyncRequestState
: undefined;
const alerts =
useUnifiedAlertingSelector((state) => state.amAlerts)[alertManagerSourceName] || initialAsyncRequestState;
const location = useLocation();
const isRoot = location.pathname.endsWith('/alerting/silences');
useEffect(() => {
function fetchAll() {
@@ -34,7 +40,9 @@ const Silences: FC = () => {
};
}, [alertManagerSourceName, dispatch]);
const { result, loading, error } = silences[alertManagerSourceName] || initialAsyncRequestState;
const { result, loading, error }: AsyncRequestState<Silence[]> =
(alertManagerSourceName && silences[alertManagerSourceName]) || initialAsyncRequestState;
const getSilenceById = useCallback((id: string) => result && result.find((silence) => silence.id === id), [result]);
if (!alertManagerSourceName) {
@@ -43,20 +51,20 @@ const Silences: FC = () => {
return (
<AlertingPageWrapper pageId="silences">
<AlertManagerPicker disabled={!isRoot} current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
{error && !loading && (
<Alert severity="error" title="Error loading silences">
{error.message || 'Unknown error.'}
</Alert>
)}
{loading && <LoadingPlaceholder text="loading silences..." />}
{result && !error && alerts.result && (
{result && !error && alertsRequest?.result && (
<Switch>
<Route exact path="/alerting/silences">
<SilencesTable
silences={result}
alertManagerAlerts={alerts.result}
alertManagerAlerts={alertsRequest.result}
alertManagerSourceName={alertManagerSourceName}
setAlertManagerSourceName={setAlertManagerSourceName}
/>
</Route>
<Route exact path="/alerting/silence/new">

View File

@@ -1,8 +1,9 @@
import { SelectableValue } from '@grafana/data';
import { SelectableValue, GrafanaTheme2 } from '@grafana/data';
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from '../utils/datasource';
import React, { FC, useMemo } from 'react';
import { Select } from '@grafana/ui';
import { Field, Select, useStyles2 } from '@grafana/ui';
import { getAllDataSources } from '../utils/config';
import { css } from '@emotion/css';
interface Props {
onChange: (alertManagerSourceName: string) => void;
@@ -11,6 +12,8 @@ interface Props {
}
export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = false }) => {
const styles = useStyles2(getStyles);
const options: Array<SelectableValue<string>> = useMemo(() => {
return [
{
@@ -30,20 +33,30 @@ export const AlertManagerPicker: FC<Props> = ({ onChange, current, disabled = fa
];
}, []);
// no need to show the picker if there's only one option
if (options.length === 1) {
return null;
}
return (
<Select
disabled={disabled}
width={29}
className="ds-picker select-container"
isMulti={false}
isClearable={false}
backspaceRemovesValue={false}
onChange={(value) => value.value && onChange(value.value)}
options={options}
maxMenuHeight={500}
noOptionsMessage="No datasources found"
value={current}
getOptionLabel={(o) => o.label}
/>
<Field className={styles.field} label={disabled ? 'Alert manager' : 'Choose alert manager'} disabled={disabled}>
<Select
width={29}
className="ds-picker select-container"
backspaceRemovesValue={false}
onChange={(value) => value.value && onChange(value.value)}
options={options}
maxMenuHeight={500}
noOptionsMessage="No datasources found"
value={current}
getOptionLabel={(o) => o.label}
/>
</Field>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
field: css`
margin-bottom: ${theme.spacing(4)};
`,
});

View File

@@ -1,18 +1,18 @@
import { GrafanaTheme2 } from '@grafana/data';
import { useStyles2 } from '@grafana/ui';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { SilenceState, AlertState } from 'app/plugins/datasource/alertmanager/types';
import { css, cx } from '@emotion/css';
import React, { FC } from 'react';
export type State = 'good' | 'bad' | 'warning' | 'neutral' | 'info';
type Props = {
status: PromAlertingRuleState | SilenceState | AlertState;
state: State;
};
export const StateTag: FC<Props> = ({ children, status }) => {
export const StateTag: FC<Props> = ({ children, state }) => {
const styles = useStyles2(getStyles);
return <span className={cx(styles.common, styles[status])}>{children || status}</span>;
return <span className={cx(styles.common, styles[state])}>{children || state}</span>;
};
const getStyles = (theme: GrafanaTheme2) => ({
@@ -25,37 +25,27 @@ const getStyles = (theme: GrafanaTheme2) => ({
text-transform: capitalize;
line-height: 1.2;
`,
[PromAlertingRuleState.Inactive]: css`
good: css`
background-color: ${theme.colors.success.main};
border: solid 1px ${theme.colors.success.main};
color: ${theme.colors.success.contrastText};
`,
[PromAlertingRuleState.Pending]: css`
warning: css`
background-color: ${theme.colors.warning.main};
border: solid 1px ${theme.colors.warning.main};
color: ${theme.colors.warning.contrastText};
`,
[PromAlertingRuleState.Firing]: css`
bad: css`
background-color: ${theme.colors.error.main};
border: solid 1px ${theme.colors.error.main};
color: ${theme.colors.error.contrastText};
`,
[SilenceState.Expired]: css`
neutral: css`
background-color: ${theme.colors.secondary.main};
border: solid 1px ${theme.colors.secondary.main};
color: ${theme.colors.secondary.contrastText};
`,
[SilenceState.Active]: css`
background-color: ${theme.colors.success.main};
border: solid 1px ${theme.colors.success.main};
color: ${theme.colors.success.contrastText};
`,
[AlertState.Unprocessed]: css`
background-color: ${theme.colors.secondary.main};
border: solid 1px ${theme.colors.secondary.main};
color: ${theme.colors.secondary.contrastText};
`,
[AlertState.Suppressed]: css`
info: css`
background-color: ${theme.colors.primary.main};
border: solid 1px ${theme.colors.primary.main};
color: ${theme.colors.primary.contrastText};

View File

@@ -187,7 +187,7 @@ export const AmRootRouteForm: FC<AmRootRouteFormProps> = ({
</Collapse>
<div className={styles.container}>
<Button type="submit">Save</Button>
<Button onClick={onCancel} type="reset" variant="secondary">
<Button onClick={onCancel} type="reset" variant="secondary" fill="outline">
Cancel
</Button>
</div>

View File

@@ -257,7 +257,7 @@ export const AmRoutesExpandedForm: FC<AmRoutesExpandedFormProps> = ({ onCancel,
)}
<div className={styles.buttonGroup}>
<Button type="submit">Save policy</Button>
<Button onClick={onCancel} type="button" variant="secondary">
<Button onClick={onCancel} fill="outline" type="button" variant="secondary">
Cancel
</Button>
</div>

View File

@@ -1,6 +1,6 @@
import { css } from '@emotion/css';
import { GrafanaTheme } from '@grafana/data';
import { LinkButton, useStyles } from '@grafana/ui';
import { css, cx } from '@emotion/css';
import { GrafanaTheme2 } from '@grafana/data';
import { LinkButton, useStyles2 } from '@grafana/ui';
import React, { FC } from 'react';
interface Props {
@@ -8,13 +8,21 @@ interface Props {
description: string;
addButtonLabel: string;
addButtonTo: string;
className?: string;
}
export const ReceiversSection: FC<Props> = ({ title, description, addButtonLabel, addButtonTo, children }) => {
const styles = useStyles(getStyles);
export const ReceiversSection: FC<Props> = ({
className,
title,
description,
addButtonLabel,
addButtonTo,
children,
}) => {
const styles = useStyles2(getStyles);
return (
<>
<div className={styles.heading}>
<div className={cx(styles.heading, className)}>
<div>
<h4>{title}</h4>
<p className={styles.description}>{description}</p>
@@ -28,13 +36,12 @@ export const ReceiversSection: FC<Props> = ({ title, description, addButtonLabel
);
};
const getStyles = (theme: GrafanaTheme) => ({
const getStyles = (theme: GrafanaTheme2) => ({
heading: css`
margin-top: ${theme.spacing.xl};
display: flex;
justify-content: space-between;
`,
description: css`
color: ${theme.colors.textSemiWeak};
color: ${theme.colors.text.secondary};
`,
});

View File

@@ -7,6 +7,8 @@ import { extractReadableNotifierTypes } from '../../utils/receivers';
import { ActionIcon } from '../rules/ActionIcon';
import { ReceiversSection } from './ReceiversSection';
import { makeAMLink } from '../../utils/misc';
import { GrafanaTheme2 } from '@grafana/data';
import { css } from '@emotion/css';
interface Props {
config: AlertManagerCortexConfig;
@@ -15,6 +17,7 @@ interface Props {
export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
const tableStyles = useStyles2(getAlertTableStyles);
const styles = useStyles2(getStyles);
const grafanaNotifiers = useUnifiedAlertingSelector((state) => state.grafanaNotifiers);
@@ -29,6 +32,7 @@ export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
return (
<ReceiversSection
className={styles.section}
title="Contact points"
description="Define where the notifications will be sent to, for example email or Slack."
addButtonLabel="New contact point"
@@ -75,3 +79,9 @@ export const ReceiversTable: FC<Props> = ({ config, alertManagerName }) => {
</ReceiversSection>
);
};
const getStyles = (theme: GrafanaTheme2) => ({
section: css`
margin-top: ${theme.spacing(4)};
`,
});

View File

@@ -153,6 +153,7 @@ export const TemplateForm: FC<Props> = ({ existing, alertManagerSourceName, conf
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
variant="secondary"
type="button"
fill="outline"
>
Cancel
</LinkButton>

View File

@@ -120,7 +120,8 @@ export function ReceiverForm<R extends ChannelValues>({
<LinkButton
disabled={loading}
variant="secondary"
href={makeAMLink('/alerting/notifications', alertManagerSourceName)}
fill="outline"
href={makeAMLink('alerting/notifications', alertManagerSourceName)}
>
Cancel
</LinkButton>

View File

@@ -1,16 +1,16 @@
import { SelectableValue } from '@grafana/data';
import { Select } from '@grafana/ui';
import { SelectBaseProps } from '@grafana/ui/src/components/Select/types';
import { GrafanaAlertState } from 'app/types/unified-alerting-dto';
import { GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto';
import React, { FC } from 'react';
type Props = Omit<SelectBaseProps<GrafanaAlertState>, 'options'>;
type Props = Omit<SelectBaseProps<GrafanaAlertStateDecision>, 'options'>;
const options: SelectableValue[] = [
{ value: GrafanaAlertState.Alerting, label: 'Alerting' },
{ value: GrafanaAlertState.NoData, label: 'No Data' },
{ value: GrafanaAlertState.KeepLastState, label: 'Keep Last State' },
{ value: GrafanaAlertState.OK, label: 'OK' },
{ value: GrafanaAlertStateDecision.Alerting, label: 'Alerting' },
{ value: GrafanaAlertStateDecision.NoData, label: 'No Data' },
{ value: GrafanaAlertStateDecision.KeepLastState, label: 'Keep Last State' },
{ value: GrafanaAlertStateDecision.OK, label: 'OK' },
];
export const GrafanaAlertStatePicker: FC<Props> = (props) => <Select options={options} {...props} />;

View File

@@ -12,9 +12,11 @@ export const AlertInstanceDetails: FC<Props> = ({ instance }) => {
return (
<div>
<DetailsField label="Value" horizontal={true}>
{instance.value}
</DetailsField>
{instance.value && (
<DetailsField label="Value" horizontal={true}>
{instance.value}
</DetailsField>
)}
{annotations.map(([key, value]) => (
<DetailsField key={key} label={key} horizontal={true}>
<Annotation annotationKey={key} value={value} />

View File

@@ -7,8 +7,8 @@ import { getAlertTableStyles } from '../../styles/table';
import { alertInstanceKey } from '../../utils/rules';
import { AlertLabels } from '../AlertLabels';
import { CollapseToggle } from '../CollapseToggle';
import { StateTag } from '../StateTag';
import { AlertInstanceDetails } from './AlertInstanceDetails';
import { AlertStateTag } from './AlertStateTag';
interface Props {
instances: AlertingRule['alerts'];
@@ -45,25 +45,32 @@ export const AlertInstancesTable: FC<Props> = ({ instances }) => {
{instances.map((instance, idx) => {
const key = alertInstanceKey(instance);
const isExpanded = expandedKeys.includes(key);
// don't allow expanding if there's nothing to show
const isExpandable = instance.value || !!Object.keys(instance.annotations ?? {}).length;
return (
<Fragment key={key}>
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
<td>
<CollapseToggle
isCollapsed={!isExpanded}
onToggle={() => toggleExpandedState(key)}
data-testid="alert-collapse-toggle"
/>
{isExpandable && (
<CollapseToggle
isCollapsed={!isExpanded}
onToggle={() => toggleExpandedState(key)}
data-testid="alert-collapse-toggle"
/>
)}
</td>
<td>
<StateTag status={instance.state} />
<AlertStateTag state={instance.state} />
</td>
<td className={styles.labelsCell}>
<AlertLabels labels={instance.labels} />
</td>
<td className={styles.createdCell}>{instance.activeAt.substr(0, 19).replace('T', ' ')}</td>
<td className={styles.createdCell}>
{instance.activeAt.startsWith('0001') ? '-' : instance.activeAt.substr(0, 19).replace('T', ' ')}
</td>
</tr>
{isExpanded && (
{isExpanded && isExpandable && (
<tr className={idx % 2 === 0 ? tableStyles.evenRow : undefined}>
<td></td>
<td colSpan={3}>

View File

@@ -0,0 +1,20 @@
import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import React, { FC } from 'react';
import { State, StateTag } from '../StateTag';
const alertStateToState: Record<PromAlertingRuleState | GrafanaAlertState, State> = {
[PromAlertingRuleState.Inactive]: 'good',
[PromAlertingRuleState.Firing]: 'bad',
[PromAlertingRuleState.Pending]: 'warning',
[GrafanaAlertState.Alerting]: 'bad',
[GrafanaAlertState.Error]: 'bad',
[GrafanaAlertState.NoData]: 'info',
[GrafanaAlertState.Normal]: 'good',
[GrafanaAlertState.Pending]: 'warning',
};
interface Props {
state: PromAlertingRuleState | GrafanaAlertState;
}
export const AlertStateTag: FC<Props> = ({ state }) => <StateTag state={alertStateToState[state]}>{state}</StateTag>;

View File

@@ -4,14 +4,14 @@ import { useStyles } from '@grafana/ui';
import { css, cx } from '@emotion/css';
import { GrafanaTheme } from '@grafana/data';
import { isAlertingRule, isGrafanaRulerRule } from '../../utils/rules';
import { isCloudRulesSource, isGrafanaRulesSource } from '../../utils/datasource';
import { isCloudRulesSource } from '../../utils/datasource';
import { Annotation } from '../Annotation';
import { AlertLabels } from '../AlertLabels';
import { AlertInstancesTable } from './AlertInstancesTable';
import { DetailsField } from '../DetailsField';
import { RuleQuery } from './RuleQuery';
import { getDatasourceSrv } from 'app/features/plugins/datasource_srv';
import { ExpressionDatasourceUID } from 'app/features/expressions/ExpressionDatasource';
import { Expression } from '../Expression';
interface Props {
rule: CombinedRule;
@@ -57,13 +57,15 @@ export const RuleDetails: FC<Props> = ({ rule, rulesSource }) => {
<AlertLabels labels={rule.labels} />
</DetailsField>
)}
<DetailsField
label={isGrafanaRulesSource(rulesSource) ? 'Query' : 'Expression'}
className={cx({ [styles.exprRow]: !!annotations.length })}
horizontal={true}
>
<RuleQuery rule={rule} rulesSource={rulesSource} />
</DetailsField>
{isCloudRulesSource(rulesSource) && (
<DetailsField
label="Expression"
className={cx({ [styles.exprRow]: !!annotations.length })}
horizontal={true}
>
<Expression expression={rule.query} rulesSource={rulesSource} />
</DetailsField>
)}
{annotations.map(([key, value]) => (
<DetailsField key={key} label={key} horizontal={true}>
<Annotation annotationKey={key} value={value} />

View File

@@ -1,34 +0,0 @@
import { useTheme2 } from '@grafana/ui';
import { CombinedRule, RulesSource } from 'app/types/unified-alerting';
import React, { FC, useState } from 'react';
import { GRAFANA_RULES_SOURCE_NAME } from '../../utils/datasource';
import { isGrafanaRulerRule } from '../../utils/rules';
import { Expression } from '../Expression';
interface Props {
rule: CombinedRule;
rulesSource: RulesSource;
}
export const RuleQuery: FC<Props> = ({ rule, rulesSource }) => {
const { rulerRule } = rule;
const theme = useTheme2();
const [isHidden, setIsHidden] = useState(true);
if (rulesSource !== GRAFANA_RULES_SOURCE_NAME) {
return <Expression expression={rule.query} rulesSource={rulesSource} />;
}
if (rulerRule && isGrafanaRulerRule(rulerRule)) {
// @TODO: better grafana queries vizualization read-only
if (isHidden) {
return (
<a style={{ color: theme.colors.text.link }} onClick={() => setIsHidden(false)}>
Show raw query JSON
</a>
);
}
return <pre>{JSON.stringify(rulerRule.grafana_alert.data, null, 2)}</pre>;
}
return <pre>@TODO: handle grafana prom rule case</pre>;
};

View File

@@ -4,7 +4,6 @@ import React, { FC, Fragment, useState } from 'react';
import { getRuleIdentifier, isAlertingRule, stringifyRuleIdentifier } from '../../utils/rules';
import { CollapseToggle } from '../CollapseToggle';
import { css, cx } from '@emotion/css';
import { StateTag } from '../StateTag';
import { RuleDetails } from './RuleDetails';
import { getAlertTableStyles } from '../../styles/table';
import { ActionIcon } from './ActionIcon';
@@ -14,6 +13,7 @@ import { useDispatch } from 'react-redux';
import { deleteRuleAction } from '../../state/actions';
import { useHasRuler } from '../../hooks/useHasRuler';
import { CombinedRule } from 'app/types/unified-alerting';
import { AlertStateTag } from './AlertStateTag';
interface Props {
rules: CombinedRule[];
@@ -63,7 +63,7 @@ export const RulesTable: FC<Props> = ({
const wrapperClass = cx(styles.wrapper, { [styles.wrapperMargin]: showGuidelines });
if (!rules.length) {
return <div className={wrapperClass}>{emptyMessage}</div>;
return <div className={cx(wrapperClass, styles.emptyMessage)}>{emptyMessage}</div>;
}
return (
@@ -126,7 +126,7 @@ export const RulesTable: FC<Props> = ({
data-testid="rule-collapse-toggle"
/>
</td>
<td>{promRule && isAlertingRule(promRule) ? <StateTag status={promRule.state} /> : 'n/a'}</td>
<td>{promRule && isAlertingRule(promRule) ? <AlertStateTag state={promRule.state} /> : 'n/a'}</td>
<td>{rule.name}</td>
{showGroupColumn && (
<td>{isCloudRulesSource(rulesSource) ? `${namespace.name} > ${group.name}` : namespace.name}</td>
@@ -194,6 +194,9 @@ export const getStyles = (theme: GrafanaTheme2) => ({
wrapperMargin: css`
margin-left: 36px;
`,
emptyMessage: css`
padding: ${theme.spacing(1)};
`,
wrapper: css`
margin-top: ${theme.spacing(3)};
width: auto;

View File

@@ -0,0 +1,15 @@
import { AlertState } from 'app/plugins/datasource/alertmanager/types';
import React, { FC } from 'react';
import { State, StateTag } from '../StateTag';
const alertStateToState: Record<AlertState, State> = {
[AlertState.Active]: 'bad',
[AlertState.Unprocessed]: 'neutral',
[AlertState.Suppressed]: 'info',
};
interface Props {
state: AlertState;
}
export const AmAlertStateTag: FC<Props> = ({ state }) => <StateTag state={alertStateToState[state]}>{state}</StateTag>;

View File

@@ -0,0 +1,17 @@
import { SilenceState } from 'app/plugins/datasource/alertmanager/types';
import React, { FC } from 'react';
import { State, StateTag } from '../StateTag';
const silenceStateToState: Record<SilenceState, State> = {
[SilenceState.Active]: 'good',
[SilenceState.Expired]: 'neutral',
[SilenceState.Pending]: 'neutral',
};
interface Props {
state: SilenceState;
}
export const SilenceStateTag: FC<Props> = ({ state }) => (
<StateTag state={silenceStateToState[state]}>{state}</StateTag>
);

View File

@@ -2,7 +2,6 @@ import React, { FC, Fragment, useState } from 'react';
import { dateMath, GrafanaTheme, toDuration } from '@grafana/data';
import { css, cx } from '@emotion/css';
import { Silence, AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
import { StateTag } from '../StateTag';
import { CollapseToggle } from '../CollapseToggle';
import { ActionButton } from '../rules/ActionButton';
import { ActionIcon } from '../rules/ActionIcon';
@@ -11,6 +10,7 @@ import SilencedAlertsTable from './SilencedAlertsTable';
import { expireSilenceAction } from '../../state/actions';
import { useDispatch } from 'react-redux';
import { Matchers } from './Matchers';
import { SilenceStateTag } from './SilenceStateTag';
interface Props {
className?: string;
silence: Silence;
@@ -41,7 +41,7 @@ const SilenceTableRow: FC<Props> = ({ silence, className, silencedAlerts, alertM
<CollapseToggle isCollapsed={isCollapsed} onToggle={(value) => setIsCollapsed(value)} />
</td>
<td>
<StateTag status={status.state}>{status.state}</StateTag>
<SilenceStateTag state={status.state} />
</td>
<td className={styles.matchersCell}>
<Matchers matchers={matchers} />

View File

@@ -1,12 +1,12 @@
import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
import React, { FC, useState } from 'react';
import { CollapseToggle } from '../CollapseToggle';
import { StateTag } from '../StateTag';
import { ActionIcon } from '../rules/ActionIcon';
import { getAlertTableStyles } from '../../styles/table';
import { useStyles2 } from '@grafana/ui';
import { dateTimeAsMoment, toDuration } from '@grafana/data';
import { AlertLabels } from '../AlertLabels';
import { AmAlertStateTag } from './AmAlertStateTag';
interface Props {
alert: AlertmanagerAlert;
@@ -30,7 +30,7 @@ export const SilencedAlertsTableRow: FC<Props> = ({ alert, className }) => {
<CollapseToggle isCollapsed={isCollapsed} onToggle={(collapsed) => setIsCollapsed(collapsed)} />
</td>
<td>
<StateTag status={alert.status.state}>{alert.status.state}</StateTag>
<AmAlertStateTag state={alert.status.state} />
</td>
<td>for {alertDuration} seconds</td>
<td>{alertName}</td>

View File

@@ -1,26 +1,19 @@
import React, { FC } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, useStyles2, Link, Button, Field } from '@grafana/ui';
import { Icon, useStyles2, Link, Button } from '@grafana/ui';
import { css } from '@emotion/css';
import { AlertmanagerAlert, Silence } from 'app/plugins/datasource/alertmanager/types';
import SilenceTableRow from './SilenceTableRow';
import { getAlertTableStyles } from '../../styles/table';
import { NoSilencesSplash } from './NoSilencesCTA';
import { AlertManagerPicker } from '../AlertManagerPicker';
import { makeAMLink } from '../../utils/misc';
interface Props {
silences: Silence[];
alertManagerAlerts: AlertmanagerAlert[];
alertManagerSourceName: string;
setAlertManagerSourceName(name: string): void;
}
const SilencesTable: FC<Props> = ({
silences,
alertManagerAlerts,
alertManagerSourceName,
setAlertManagerSourceName,
}) => {
const SilencesTable: FC<Props> = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
const styles = useStyles2(getStyles);
const tableStyles = useStyles2(getAlertTableStyles);
@@ -30,9 +23,6 @@ const SilencesTable: FC<Props> = ({
return (
<>
<Field label="Choose alert manager">
<AlertManagerPicker current={alertManagerSourceName} onChange={setAlertManagerSourceName} />
</Field>
{!!silences.length && (
<>
<Link href={makeAMLink('/alerting/silence/new', alertManagerSourceName)}>

View File

@@ -1,4 +1,4 @@
import { GrafanaQuery, GrafanaAlertState } from 'app/types/unified-alerting-dto';
import { GrafanaQuery, GrafanaAlertStateDecision } from 'app/types/unified-alerting-dto';
export enum RuleFormType {
threshold = 'threshold',
@@ -17,8 +17,8 @@ export interface RuleFormValues {
// threshold alerts
queries: GrafanaQuery[];
condition: string | null; // refId of the query that gets alerted on
noDataState: GrafanaAlertState;
execErrState: GrafanaAlertState;
noDataState: GrafanaAlertStateDecision;
execErrState: GrafanaAlertStateDecision;
folder: { title: string; id: number } | null;
evaluateEvery: string;
evaluateFor: string;

View File

@@ -5,7 +5,7 @@ import { ExpressionQuery, ExpressionQueryType } from 'app/features/expressions/t
import { RuleWithLocation } from 'app/types/unified-alerting';
import {
Annotations,
GrafanaAlertState,
GrafanaAlertStateDecision,
GrafanaQuery,
Labels,
PostableRuleGrafanaRuleDTO,
@@ -28,8 +28,8 @@ export const defaultFormValues: RuleFormValues = Object.freeze({
folder: null,
queries: [],
condition: '',
noDataState: GrafanaAlertState.NoData,
execErrState: GrafanaAlertState.Alerting,
noDataState: GrafanaAlertStateDecision.NoData,
execErrState: GrafanaAlertStateDecision.Alerting,
evaluateEvery: '1m',
evaluateFor: '5m',

View File

@@ -11,6 +11,14 @@ export enum PromAlertingRuleState {
Pending = 'pending',
}
export enum GrafanaAlertState {
Normal = 'Normal',
Alerting = 'Alerting',
Pending = 'Pending',
NoData = 'NoData',
Error = 'Error',
}
export enum PromRuleType {
Alerting = 'alerting',
Recording = 'recording',
@@ -29,7 +37,7 @@ export interface PromAlertingRuleDTO extends PromRuleDTOBase {
alerts: Array<{
labels: Labels;
annotations: Annotations;
state: Exclude<PromAlertingRuleState, PromAlertingRuleState.Inactive>;
state: Exclude<PromAlertingRuleState | GrafanaAlertState, PromAlertingRuleState.Inactive>;
activeAt: string;
value: string;
}>;
@@ -86,7 +94,7 @@ export interface RulerAlertingRuleDTO extends RulerRuleBaseDTO {
annotations?: Annotations;
}
export enum GrafanaAlertState {
export enum GrafanaAlertStateDecision {
Alerting = 'Alerting',
NoData = 'NoData',
KeepLastState = 'KeepLastState',
@@ -104,8 +112,8 @@ export interface PostableGrafanaRuleDefinition {
uid?: string;
title: string;
condition: string;
no_data_state: GrafanaAlertState;
exec_err_state: GrafanaAlertState;
no_data_state: GrafanaAlertStateDecision;
exec_err_state: GrafanaAlertStateDecision;
data: GrafanaQuery[];
}
export interface GrafanaRuleDefinition extends PostableGrafanaRuleDefinition {

View File

@@ -8,13 +8,14 @@ import {
Labels,
Annotations,
RulerRuleGroupDTO,
GrafanaAlertState,
} from './unified-alerting-dto';
export type Alert = {
activeAt: string;
annotations: { [key: string]: string };
labels: { [key: string]: string };
state: PromAlertingRuleState;
state: PromAlertingRuleState | GrafanaAlertState;
value: string;
};