{pairs.map(([key, value], index) => (
))}
@@ -22,7 +22,7 @@ export const AlertLabels = ({ labels }: Props) => {
const getStyles = (theme: GrafanaTheme) => ({
wrapper: css`
& > * {
- margin-top: ${theme.spacing.xs};
+ margin-bottom: ${theme.spacing.xs};
margin-right: ${theme.spacing.xs};
}
padding-bottom: ${theme.spacing.xs};
diff --git a/public/app/features/alerting/unified/components/DynamicTable.tsx b/public/app/features/alerting/unified/components/DynamicTable.tsx
index fb36cb3523d..e26bf183ca2 100644
--- a/public/app/features/alerting/unified/components/DynamicTable.tsx
+++ b/public/app/features/alerting/unified/components/DynamicTable.tsx
@@ -28,10 +28,18 @@ export interface DynamicTableProps
{
onExpand?: (item: DynamicTableItemProps) => void;
isExpanded?: (item: DynamicTableItemProps) => boolean;
- renderExpandedContent?: (item: DynamicTableItemProps, index: number) => ReactNode;
+ renderExpandedContent?: (
+ item: DynamicTableItemProps,
+ index: number,
+ items: Array>
+ ) => ReactNode;
testIdGenerator?: (item: DynamicTableItemProps, index: number) => string;
renderPrefixHeader?: () => ReactNode;
- renderPrefixCell?: (item: DynamicTableItemProps, index: number) => ReactNode;
+ renderPrefixCell?: (
+ item: DynamicTableItemProps,
+ index: number,
+ items: Array>
+ ) => ReactNode;
}
export const DynamicTable = ({
@@ -84,7 +92,7 @@ export const DynamicTable = ({
const isItemExpanded = isExpanded ? isExpanded(item) : expandedIds.includes(item.id);
return (
- {renderPrefixCell && renderPrefixCell(item, index)}
+ {renderPrefixCell && renderPrefixCell(item, index, items)}
{isExpandable && (
({
))}
{isItemExpanded && renderExpandedContent && (
- {renderExpandedContent(item, index)}
+ {renderExpandedContent(item, index, items)}
)}
@@ -223,6 +231,7 @@ const getStyles =
(
`,
expandButton: css`
margin-right: 0;
+ display: block;
`,
});
};
diff --git a/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx b/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx
new file mode 100644
index 00000000000..65f98b1524b
--- /dev/null
+++ b/public/app/features/alerting/unified/components/DynamicTableWithGuidelines.tsx
@@ -0,0 +1,76 @@
+import { css, cx } from '@emotion/css';
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
+import React from 'react';
+import { DynamicTable, DynamicTableProps } from './DynamicTable';
+
+export type DynamicTableWithGuidelinesProps = Omit, 'renderPrefixHeader, renderPrefixCell'>;
+
+// DynamicTable, but renders visual guidelines on the left, for larger screen widths
+export const DynamicTableWithGuidelines = ({
+ renderExpandedContent,
+ ...props
+}: DynamicTableWithGuidelinesProps) => {
+ const styles = useStyles2(getStyles);
+ return (
+ (
+ <>
+ {!(index === items.length - 1) && }
+ {renderExpandedContent(item, index, items)}
+ >
+ )
+ : undefined
+ }
+ renderPrefixHeader={() => (
+
+ )}
+ renderPrefixCell={(_, index, items) => (
+
+
+ {!(index === items.length - 1) &&
}
+
+ )}
+ {...props}
+ />
+ );
+};
+
+export const getStyles = (theme: GrafanaTheme2) => ({
+ relative: css`
+ position: relative;
+ height: 100%;
+ `,
+ guideline: css`
+ left: -19px;
+ border-left: 1px solid ${theme.colors.border.medium};
+ position: absolute;
+
+ ${theme.breakpoints.down('md')} {
+ display: none;
+ }
+ `,
+ topGuideline: css`
+ width: 18px;
+ border-bottom: 1px solid ${theme.colors.border.medium};
+ top: 0;
+ bottom: 50%;
+ `,
+ bottomGuideline: css`
+ top: 50%;
+ bottom: 0;
+ `,
+ contentGuideline: css`
+ top: 0;
+ bottom: 0;
+ left: -49px !important;
+ `,
+ headerGuideline: css`
+ top: -25px;
+ bottom: 0;
+ `,
+});
diff --git a/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertDetails.tsx b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertDetails.tsx
new file mode 100644
index 00000000000..e70d37426a1
--- /dev/null
+++ b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertDetails.tsx
@@ -0,0 +1,92 @@
+import { css } from '@emotion/css';
+import { GrafanaTheme2 } from '@grafana/data';
+import { LinkButton, useStyles2 } from '@grafana/ui';
+import { AlertmanagerAlert, AlertState } from 'app/plugins/datasource/alertmanager/types';
+import { Labels } from 'app/types/unified-alerting-dto';
+import React, { FC } from 'react';
+import { makeAMLink } from '../../utils/misc';
+import { AnnotationDetailsField } from '../AnnotationDetailsField';
+
+interface AmNotificationsAlertDetailsProps {
+ alertManagerSourceName: string;
+ alert: AlertmanagerAlert;
+}
+
+export const AmNotificationsAlertDetails: FC = ({
+ alert,
+ alertManagerSourceName,
+}) => {
+ const styles = useStyles2(getStyles);
+ return (
+ <>
+
+ {alert.status.state === AlertState.Suppressed && (
+
+ Manage silences
+
+ )}
+ {alert.status.state === AlertState.Active && (
+
+ Silence
+
+ )}
+ {alert.generatorURL && (
+
+ See source
+
+ )}
+
+ {Object.entries(alert.annotations).map(([annotationKey, annotationValue]) => (
+
+ ))}
+
+ Receivers:{' '}
+ {alert.receivers
+ .map(({ name }) => name)
+ .filter((name) => !!name)
+ .join(', ')}
+
+ >
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ button: css`
+ & + & {
+ margin-left: ${theme.spacing(1)};
+ }
+ `,
+ actionsRow: css`
+ padding: ${theme.spacing(2, 0)} !important;
+ border-bottom: 1px solid ${theme.colors.border.medium};
+ `,
+ receivers: css`
+ padding: ${theme.spacing(1, 0)};
+ `,
+});
+
+const getMatcherQueryParams = (labels: Labels) => {
+ return `matchers=${encodeURIComponent(
+ Object.entries(labels)
+ .filter(([labelKey]) => !(labelKey.startsWith('__') && labelKey.endsWith('__')))
+ .map(([labelKey, labelValue]) => {
+ return `${labelKey}=${labelValue}`;
+ })
+ .join(',')
+ )}`;
+};
diff --git a/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertsTable.tsx b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertsTable.tsx
new file mode 100644
index 00000000000..ea04eb4cb01
--- /dev/null
+++ b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsAlertsTable.tsx
@@ -0,0 +1,91 @@
+import { AlertmanagerAlert } from 'app/plugins/datasource/alertmanager/types';
+import React, { useMemo } from 'react';
+import { useStyles2 } from '@grafana/ui';
+import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data';
+import { css } from '@emotion/css';
+import { DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
+import { AmAlertStateTag } from '../silences/AmAlertStateTag';
+import { AlertLabels } from '../AlertLabels';
+import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
+import { AmNotificationsAlertDetails } from './AmNotificationsAlertDetails';
+
+interface Props {
+ alerts: AlertmanagerAlert[];
+ alertManagerSourceName: string;
+}
+
+type AmNotificationsAlertsTableColumnProps = DynamicTableColumnProps;
+type AmNotificationsAlertsTableItemProps = DynamicTableItemProps;
+
+export const AmNotificationsAlertsTable = ({ alerts, alertManagerSourceName }: Props) => {
+ const styles = useStyles2(getStyles);
+
+ const columns = useMemo(
+ (): AmNotificationsAlertsTableColumnProps[] => [
+ {
+ id: 'state',
+ label: 'State',
+ // eslint-disable-next-line react/display-name
+ renderCell: ({ data: alert }) => (
+ <>
+
+
+ for{' '}
+ {intervalToAbbreviatedDurationString({
+ start: new Date(alert.startsAt),
+ end: new Date(alert.endsAt),
+ })}
+
+ >
+ ),
+ size: '190px',
+ },
+ {
+ id: 'labels',
+ label: 'Labels',
+ // eslint-disable-next-line react/display-name
+ renderCell: ({ data: { labels } }) => ,
+ size: 1,
+ },
+ ],
+ [styles]
+ );
+
+ const items = useMemo(
+ (): AmNotificationsAlertsTableItemProps[] =>
+ alerts.map((alert) => ({
+ id: alert.fingerprint,
+ data: alert,
+ })),
+ [alerts]
+ );
+
+ return (
+
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ tableWrapper: css`
+ margin-top: ${theme.spacing(3)};
+ ${theme.breakpoints.up('md')} {
+ margin-left: ${theme.spacing(4.5)};
+ }
+ `,
+ duration: css`
+ margin-left: ${theme.spacing(1)};
+ font-size: ${theme.typography.bodySmall.fontSize};
+ `,
+ labels: css`
+ padding-bottom: 0;
+ `,
+});
diff --git a/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroup.tsx b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroup.tsx
new file mode 100644
index 00000000000..5db45a22d91
--- /dev/null
+++ b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroup.tsx
@@ -0,0 +1,82 @@
+import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types';
+import React, { useState } from 'react';
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
+import { css } from '@emotion/css';
+import { AlertLabels } from '../AlertLabels';
+import { AmNotificationsAlertsTable } from './AmNotificationsAlertsTable';
+import { CollapseToggle } from '../CollapseToggle';
+import { AmNotificationsGroupHeader } from './AmNotificationsGroupHeader';
+
+interface Props {
+ group: AlertmanagerGroup;
+ alertManagerSourceName: string;
+}
+
+export const AmNotificationsGroup = ({ alertManagerSourceName, group }: Props) => {
+ const [isCollapsed, setIsCollapsed] = useState(true);
+ const styles = useStyles2(getStyles);
+
+ return (
+
+
+
+
setIsCollapsed(!isCollapsed)}
+ data-testid="notifications-group-collapse-toggle"
+ />
+ {Object.keys(group.labels).length ? (
+
+ ) : (
+ No grouping
+ )}
+
+
+
+ {!isCollapsed && (
+
+ )}
+
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ wrapper: css`
+ & + & {
+ margin-top: ${theme.spacing(2)};
+ }
+ `,
+ headerLabels: css`
+ padding-bottom: 0 !important;
+ margin-bottom: -${theme.spacing(0.5)};
+ `,
+ header: css`
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: center;
+ justify-content: space-between;
+ padding: ${theme.spacing(1, 1, 1, 0)};
+ background-color: ${theme.colors.background.secondary};
+ width: 100%;
+ `,
+ group: css`
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ `,
+ summary: css``,
+ spanElement: css`
+ margin-left: ${theme.spacing(0.5)};
+ `,
+ [AlertState.Active]: css`
+ color: ${theme.colors.error.main};
+ `,
+ [AlertState.Suppressed]: css`
+ color: ${theme.colors.primary.main};
+ `,
+ [AlertState.Unprocessed]: css`
+ color: ${theme.colors.secondary.main};
+ `,
+});
diff --git a/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroupHeader.tsx b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroupHeader.tsx
new file mode 100644
index 00000000000..6fb481b9a59
--- /dev/null
+++ b/public/app/features/alerting/unified/components/amnotifications/AmNotificationsGroupHeader.tsx
@@ -0,0 +1,49 @@
+import { AlertmanagerGroup, AlertState } from 'app/plugins/datasource/alertmanager/types';
+import React from 'react';
+import { GrafanaTheme2 } from '@grafana/data';
+import { useStyles2 } from '@grafana/ui';
+import { css } from '@emotion/css';
+
+interface Props {
+ group: AlertmanagerGroup;
+}
+
+export const AmNotificationsGroupHeader = ({ group }: Props) => {
+ const styles = useStyles2(getStyles);
+ const total = group.alerts.length;
+ const countByStatus = group.alerts.reduce((statusObj, alert) => {
+ if (statusObj[alert.status.state]) {
+ statusObj[alert.status.state] += 1;
+ } else {
+ statusObj[alert.status.state] = 1;
+ }
+ return statusObj;
+ }, {} as Record);
+
+ return (
+
+ {`${total} alerts: `}
+ {Object.entries(countByStatus).map(([state, count], index) => {
+ return (
+
+ {index > 0 && ', '}
+ {`${count} ${state}`}
+
+ );
+ })}
+
+ );
+};
+
+const getStyles = (theme: GrafanaTheme2) => ({
+ summary: css``,
+ [AlertState.Active]: css`
+ color: ${theme.colors.error.main};
+ `,
+ [AlertState.Suppressed]: css`
+ color: ${theme.colors.primary.main};
+ `,
+ [AlertState.Unprocessed]: css`
+ color: ${theme.colors.secondary.main};
+ `,
+});
diff --git a/public/app/features/alerting/unified/components/rules/RuleState.tsx b/public/app/features/alerting/unified/components/rules/RuleState.tsx
index 04b8da8c167..85e85a2d448 100644
--- a/public/app/features/alerting/unified/components/rules/RuleState.tsx
+++ b/public/app/features/alerting/unified/components/rules/RuleState.tsx
@@ -80,5 +80,6 @@ const getStyle = (theme: GrafanaTheme2) => ({
font-size: ${theme.typography.bodySmall.fontSize};
color: ${theme.colors.text.secondary};
white-space: nowrap;
+ padding-top: 2px;
`,
});
diff --git a/public/app/features/alerting/unified/components/rules/RulesTable.tsx b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
index aa8df099d37..ce59280e7d2 100644
--- a/public/app/features/alerting/unified/components/rules/RulesTable.tsx
+++ b/public/app/features/alerting/unified/components/rules/RulesTable.tsx
@@ -9,7 +9,8 @@ import { CombinedRule } from 'app/types/unified-alerting';
import { Annotation } from '../../utils/constants';
import { RuleState } from './RuleState';
import { RuleHealth } from './RuleHealth';
-import { DynamicTable, DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
+import { DynamicTableColumnProps, DynamicTableItemProps } from '../DynamicTable';
+import { DynamicTableWithGuidelines } from '../DynamicTableWithGuidelines';
type RuleTableColumnProps = DynamicTableColumnProps;
type RuleTableItemProps = DynamicTableItemProps;
@@ -50,7 +51,7 @@ export const RulesTable: FC = ({
});
}, [rules]);
- const columns = useColumns(showSummaryColumn, showGroupColumn, showGuidelines, items.length);
+ const columns = useColumns(showSummaryColumn, showGroupColumn);
if (!rules.length) {
return {emptyMessage}
;
@@ -58,39 +59,11 @@ export const RulesTable: FC = ({
return (
-
(
- <>
- {!(index === rules.length - 1) && showGuidelines ? (
-
- ) : null}
-
- >
- )}
- renderPrefixHeader={
- showGuidelines
- ? () => (
-
- )
- : undefined
- }
- renderPrefixCell={
- showGuidelines
- ? (_, index) => (
-
-
- {!(index === rules.length - 1) && (
-
- )}
-
- )
- : undefined
- }
+ renderExpandedContent={({ data: rule }) => }
/>
);
@@ -131,46 +104,13 @@ export const getStyles = (theme: GrafanaTheme2) => ({
evenRow: css`
background-color: ${theme.colors.background.primary};
`,
- relative: css`
- position: relative;
- height: 100%;
- `,
- guideline: css`
- left: -19px;
- border-left: 1px solid ${theme.colors.border.medium};
- position: absolute;
-
- ${theme.breakpoints.down('md')} {
- display: none;
- }
- `,
- ruleTopGuideline: css`
- width: 18px;
- border-bottom: 1px solid ${theme.colors.border.medium};
- top: 0;
- bottom: 50%;
- `,
- ruleBottomGuideline: css`
- top: 50%;
- bottom: 0;
- `,
- ruleContentGuideline: css`
- top: 0;
- bottom: 0;
- left: -49px !important;
- `,
- headerGuideline: css`
- top: -24px;
- bottom: 0;
- `,
state: css`
width: 110px;
`,
});
-function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showGuidelines: boolean, totalRules: number) {
+function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean) {
const hasRuler = useHasRuler();
- const styles = useStyles2(getStyles);
return useMemo((): RuleTableColumnProps[] => {
const columns: RuleTableColumnProps[] = [
@@ -178,25 +118,13 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showGu
id: 'state',
label: 'State',
// eslint-disable-next-line react/display-name
- renderCell: ({ data: rule }, ruleIdx) => {
+ renderCell: ({ data: rule }) => {
const { namespace } = rule;
const { rulesSource } = namespace;
const { promRule, rulerRule } = rule;
const isDeleting = !!(hasRuler(rulesSource) && promRule && !rulerRule);
const isCreating = !!(hasRuler(rulesSource) && rulerRule && !promRule);
- return (
- <>
- {showGuidelines && (
- <>
-
- {!(ruleIdx === totalRules - 1) && (
-
- )}
- >
- )}
-
- >
- );
+ return ;
},
size: '165px',
},
@@ -238,5 +166,5 @@ function useColumns(showSummaryColumn: boolean, showGroupColumn: boolean, showGu
});
}
return columns;
- }, [hasRuler, showSummaryColumn, showGroupColumn, showGuidelines, totalRules, styles]);
+ }, [hasRuler, showSummaryColumn, showGroupColumn]);
}
diff --git a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
index 5a3ccd90e84..4832d5ffa89 100644
--- a/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
+++ b/public/app/features/alerting/unified/components/silences/SilencesTable.tsx
@@ -1,4 +1,4 @@
-import React, { FC } from 'react';
+import React, { FC, useMemo } from 'react';
import { GrafanaTheme2 } from '@grafana/data';
import { Icon, useStyles2, Link, Button } from '@grafana/ui';
import { css } from '@emotion/css';
@@ -8,6 +8,7 @@ import { getAlertTableStyles } from '../../styles/table';
import { NoSilencesSplash } from './NoSilencesCTA';
import { makeAMLink } from '../../utils/misc';
import { contextSrv } from 'app/core/services/context_srv';
+import { useQueryParams } from 'app/core/hooks/useQueryParams';
interface Props {
silences: Silence[];
alertManagerAlerts: AlertmanagerAlert[];
@@ -17,6 +18,15 @@ interface Props {
const SilencesTable: FC = ({ silences, alertManagerAlerts, alertManagerSourceName }) => {
const styles = useStyles2(getStyles);
const tableStyles = useStyles2(getAlertTableStyles);
+ const [queryParams] = useQueryParams();
+
+ const filteredSilences = useMemo(() => {
+ const silenceIdsString = queryParams?.silenceIds;
+ if (typeof silenceIdsString === 'string') {
+ return silences.filter((silence) => silenceIdsString.split(',').includes(silence.id));
+ }
+ return silences;
+ }, [queryParams, silences]);
const findSilencedAlerts = (id: string) => {
return alertManagerAlerts.filter((alert) => alert.status.silencedBy.includes(id));
@@ -55,7 +65,7 @@ const SilencesTable: FC = ({ silences, alertManagerAlerts, alertManagerSo
- {silences.map((silence, index) => {
+ {filteredSilences.map((silence, index) => {
const silencedAlerts = findSilencedAlerts(silence.id);
return (
= {}): Rul
};
};
+export const mockAlertmanagerAlert = (partial: Partial = {}): AlertmanagerAlert => {
+ return {
+ annotations: {
+ summary: 'US-Central region is on fire',
+ },
+ endsAt: '2021-06-22T21:49:28.562Z',
+ fingerprint: '88e013643c3df34ac3',
+ receivers: [{ name: 'pagerduty' }],
+ startsAt: '2021-06-21T17:25:28.562Z',
+ status: { inhibitedBy: [], silencedBy: [], state: AlertState.Active },
+ updatedAt: '2021-06-22T21:45:28.564Z',
+ generatorURL: 'https://play.grafana.com/explore',
+ labels: { severity: 'warning', region: 'US-Central' },
+ ...partial,
+ };
+};
+
+export const mockAlertGroup = (partial: Partial = {}): AlertmanagerGroup => {
+ return {
+ labels: {
+ severity: 'warning',
+ region: 'US-Central',
+ },
+ receiver: {
+ name: 'pagerduty',
+ },
+ alerts: [
+ mockAlertmanagerAlert(),
+ mockAlertmanagerAlert({
+ status: { state: AlertState.Suppressed, silencedBy: ['123456abcdef'], inhibitedBy: [] },
+ labels: { severity: 'warning', region: 'US-Central', foo: 'bar' },
+ }),
+ ],
+ ...partial,
+ };
+};
+
export class MockDataSourceSrv implements DataSourceSrv {
// @ts-ignore
private settingsMapByName: Record = {};
diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts
index 4fbd81eeca6..b2b3f6a1aa5 100644
--- a/public/app/features/alerting/unified/state/actions.ts
+++ b/public/app/features/alerting/unified/state/actions.ts
@@ -3,6 +3,7 @@ import { createAsyncThunk } from '@reduxjs/toolkit';
import {
AlertmanagerAlert,
AlertManagerCortexConfig,
+ AlertmanagerGroup,
Silence,
SilenceCreatePayload,
} from 'app/plugins/datasource/alertmanager/types';
@@ -19,6 +20,7 @@ import {
expireSilence,
fetchAlertManagerConfig,
fetchAlerts,
+ fetchAlertGroups,
fetchSilences,
createOrUpdateSilence,
updateAlertManagerConfig,
@@ -534,3 +536,10 @@ export const fetchFolderIfNotFetchedAction = (uid: string): ThunkResult =>
}
};
};
+
+export const fetchAlertGroupsAction = createAsyncThunk(
+ 'unifiedalerting/fetchAlertGroups',
+ (alertManagerSourceName: string): Promise => {
+ return withSerializedError(fetchAlertGroups(alertManagerSourceName));
+ }
+);
diff --git a/public/app/features/alerting/unified/state/reducers.ts b/public/app/features/alerting/unified/state/reducers.ts
index 6e2bcf45114..26e0a51537a 100644
--- a/public/app/features/alerting/unified/state/reducers.ts
+++ b/public/app/features/alerting/unified/state/reducers.ts
@@ -12,6 +12,7 @@ import {
updateAlertManagerConfigAction,
createOrUpdateSilenceAction,
fetchFolderAction,
+ fetchAlertGroupsAction,
} from './actions';
export const reducer = combineReducers({
@@ -34,6 +35,11 @@ export const reducer = combineReducers({
amAlerts: createAsyncMapSlice('amAlerts', fetchAmAlertsAction, (alertManagerSourceName) => alertManagerSourceName)
.reducer,
folders: createAsyncMapSlice('folders', fetchFolderAction, (uid) => uid).reducer,
+ amAlertGroups: createAsyncMapSlice(
+ 'amAlertGroups',
+ fetchAlertGroupsAction,
+ (alertManagerSourceName) => alertManagerSourceName
+ ).reducer,
});
export type UnifiedAlertingState = ReturnType;
diff --git a/public/app/features/alerting/unified/utils/constants.ts b/public/app/features/alerting/unified/utils/constants.ts
index efeea1c9341..6f4332cb281 100644
--- a/public/app/features/alerting/unified/utils/constants.ts
+++ b/public/app/features/alerting/unified/utils/constants.ts
@@ -5,6 +5,7 @@ export const RULE_LIST_POLL_INTERVAL_MS = 20000;
export const ALERTMANAGER_NAME_QUERY_KEY = 'alertmanager';
export const ALERTMANAGER_NAME_LOCAL_STORAGE_KEY = 'alerting-alertmanager';
export const SILENCES_POLL_INTERVAL_MS = 20000;
+export const NOTIFICATIONS_POLL_INTERVAL_MS = 20000;
export const TIMESERIES = 'timeseries';
export const TABLE = 'table';
diff --git a/public/app/plugins/datasource/alertmanager/types.ts b/public/app/plugins/datasource/alertmanager/types.ts
index ca953310c5d..cac8e670e25 100644
--- a/public/app/plugins/datasource/alertmanager/types.ts
+++ b/public/app/plugins/datasource/alertmanager/types.ts
@@ -208,7 +208,6 @@ export type AlertmanagerGroup = {
labels: { [key: string]: string };
receiver: { name: string };
alerts: AlertmanagerAlert[];
- id: string;
};
export interface AlertmanagerStatus {
diff --git a/public/app/routes/routes.tsx b/public/app/routes/routes.tsx
index aa2cd750594..3db699be012 100644
--- a/public/app/routes/routes.tsx
+++ b/public/app/routes/routes.tsx
@@ -439,6 +439,13 @@ export function getAppRoutes(): RouteDescriptor[] {
import(/* webpackChunkName: "EditNotificationChannel"*/ 'app/features/alerting/EditNotificationChannelPage')
),
},
+ {
+ path: '/alerting/alertmanager/',
+ component: SafeDynamicImport(
+ () =>
+ import(/* webpackChunkName: "AlertManagerNotifications" */ 'app/features/alerting/unified/AmNotifications')
+ ),
+ },
{
path: '/alerting/new',
pageClass: 'page-alerting',