mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: miscllaneous UI fixes & improvements (#33734)
This commit is contained in:
@@ -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}
|
||||
|
||||
@@ -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.'}
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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)};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -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)};
|
||||
`,
|
||||
});
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />;
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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}>
|
||||
|
||||
@@ -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>;
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>;
|
||||
};
|
||||
@@ -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;
|
||||
|
||||
@@ -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>;
|
||||
@@ -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>
|
||||
);
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)}>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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',
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user