diff --git a/packages/grafana-ui/src/types/icon.ts b/packages/grafana-ui/src/types/icon.ts index 77642fabba7..b9fee639d7f 100644 --- a/packages/grafana-ui/src/types/icon.ts +++ b/packages/grafana-ui/src/types/icon.ts @@ -67,6 +67,7 @@ export const getAvailableIcons = () => 'file-copy-alt', 'filter', 'folder', + 'fire', 'folder-open', 'folder-plus', 'folder-upload', @@ -86,6 +87,7 @@ export const getAvailableIcons = () => 'heart-break', 'history', 'home-alt', + 'hourglass', 'import', 'info-circle', 'key-skeleton-alt', diff --git a/public/app/features/alerting/state/alertDef.ts b/public/app/features/alerting/state/alertDef.ts index dcfb434f202..79503b0eca2 100644 --- a/public/app/features/alerting/state/alertDef.ts +++ b/public/app/features/alerting/state/alertDef.ts @@ -19,10 +19,12 @@ const conditionTypes = [{ text: 'Query', value: 'query' }]; const alertStateSortScore = { alerting: 1, + firing: 1, no_data: 2, pending: 3, ok: 4, paused: 5, + inactive: 5, }; export enum EvalFunction { @@ -111,7 +113,7 @@ function getStateDisplayModel(state: string) { case 'pending': { return { text: 'PENDING', - iconClass: 'exclamation-triangle', + iconClass: 'hourglass', stateClass: 'alert-state-warning', }; } @@ -122,6 +124,22 @@ function getStateDisplayModel(state: string) { stateClass: 'alert-state-paused', }; } + + case 'firing': { + return { + text: 'FIRING', + iconClass: 'fire', + stateClass: '', + }; + } + + case 'inactive': { + return { + text: 'INACTIVE', + iconClass: 'check', + stateClass: '', + }; + } } throw { message: 'Unknown alert state' }; diff --git a/public/app/features/alerting/unified/api/prometheus.ts b/public/app/features/alerting/unified/api/prometheus.ts index d8379dc9d41..407dff5cdf9 100644 --- a/public/app/features/alerting/unified/api/prometheus.ts +++ b/public/app/features/alerting/unified/api/prometheus.ts @@ -1,7 +1,7 @@ import { getBackendSrv } from '@grafana/runtime'; import { RuleNamespace } from 'app/types/unified-alerting'; import { PromRulesResponse } from 'app/types/unified-alerting-dto'; -import { getDatasourceAPIId } from '../utils/datasource'; +import { getAllRulesSourceNames, getDatasourceAPIId } from '../utils/datasource'; export async function fetchRules(dataSourceName: string): Promise { const response = await getBackendSrv() @@ -30,3 +30,17 @@ export async function fetchRules(dataSourceName: string): Promise { + const namespaces = [] as Array>; + getAllRulesSourceNames().forEach(async (name) => { + namespaces.push( + fetchRules(name).catch((e) => { + return []; + // TODO add error comms + }) + ); + }); + const promises = await Promise.all(namespaces); + return promises.flat(); +} diff --git a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx index 216ae1b92fa..2f64c2b476d 100644 --- a/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx +++ b/public/app/features/alerting/unified/components/rules/AlertStateTag.tsx @@ -1,19 +1,7 @@ import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import React, { FC } from 'react'; -import { alertStateToReadable } from '../../utils/rules'; -import { State, StateTag } from '../StateTag'; - -const alertStateToState: Record = { - [PromAlertingRuleState.Inactive]: 'good', - [PromAlertingRuleState.Firing]: 'bad', - [PromAlertingRuleState.Pending]: 'warning', - [GrafanaAlertState.Alerting]: 'bad', - [GrafanaAlertState.Error]: 'bad', - [GrafanaAlertState.NoData]: 'info', - [GrafanaAlertState.Normal]: 'good', - [GrafanaAlertState.Pending]: 'warning', -}; - +import { alertStateToReadable, alertStateToState } from '../../utils/rules'; +import { StateTag } from '../StateTag'; interface Props { state: PromAlertingRuleState | GrafanaAlertState; } diff --git a/public/app/features/alerting/unified/components/rules/RuleState.tsx b/public/app/features/alerting/unified/components/rules/RuleState.tsx index 85427dc3a79..fd95b26ab61 100644 --- a/public/app/features/alerting/unified/components/rules/RuleState.tsx +++ b/public/app/features/alerting/unified/components/rules/RuleState.tsx @@ -2,9 +2,9 @@ import { css } from '@emotion/css'; import { GrafanaTheme2, intervalToAbbreviatedDurationString } from '@grafana/data'; import { HorizontalGroup, Spinner, useStyles2 } from '@grafana/ui'; import { CombinedRule } from 'app/types/unified-alerting'; -import { GrafanaAlertState, PromAlertingRuleState } from 'app/types/unified-alerting-dto'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import React, { FC, useMemo } from 'react'; -import { isAlertingRule, isRecordingRule } from '../../utils/rules'; +import { isAlertingRule, isRecordingRule, getFirstActiveAt } from '../../utils/rules'; import { AlertStateTag } from './AlertStateTag'; interface Props { @@ -26,15 +26,7 @@ export const RuleState: FC = ({ rule, isDeleting, isCreating }) => { promRule.state !== PromAlertingRuleState.Inactive ) { // find earliest alert - const firstActiveAt = promRule.alerts.reduce((prev, alert) => { - if (alert.activeAt && alert.state !== GrafanaAlertState.Normal) { - const activeAt = new Date(alert.activeAt); - if (prev === null || prev.getTime() > activeAt.getTime()) { - return activeAt; - } - } - return prev; - }, null as Date | null); + const firstActiveAt = getFirstActiveAt(promRule); // calculate time elapsed from earliest alert if (firstActiveAt) { diff --git a/public/app/features/alerting/unified/state/actions.ts b/public/app/features/alerting/unified/state/actions.ts index 279bd504936..04a907a833a 100644 --- a/public/app/features/alerting/unified/state/actions.ts +++ b/public/app/features/alerting/unified/state/actions.ts @@ -98,6 +98,17 @@ export function fetchAllPromAndRulerRulesAction(force = false): ThunkResult { + return (dispatch, getStore) => { + const { promRules } = getStore().unifiedAlerting; + getAllRulesSourceNames().map((name) => { + if (force || !promRules[name]?.loading) { + dispatch(fetchPromRulesAction(name)); + } + }); + }; +} + async function findExistingRule(ruleIdentifier: RuleIdentifier): Promise { if (isGrafanaRuleIdentifier(ruleIdentifier)) { const namespaces = await fetchRulerRules(GRAFANA_RULES_SOURCE_NAME); diff --git a/public/app/features/alerting/unified/utils/rules.ts b/public/app/features/alerting/unified/utils/rules.ts index 1eae1f31a09..40d74a7e350 100644 --- a/public/app/features/alerting/unified/utils/rules.ts +++ b/public/app/features/alerting/unified/utils/rules.ts @@ -14,15 +14,18 @@ import { AlertingRule, CloudRuleIdentifier, GrafanaRuleIdentifier, + PromRuleWithLocation, RecordingRule, Rule, RuleIdentifier, + RuleNamespace, RuleWithLocation, } from 'app/types/unified-alerting'; import { AsyncRequestState } from './redux'; import { RULER_NOT_SUPPORTED_MSG } from './constants'; import { hash } from './misc'; import { capitalize } from 'lodash'; +import { State } from '../components/StateTag'; export function isAlertingRule(rule: Rule): rule is AlertingRule { return rule.type === PromRuleType.Alerting; @@ -142,3 +145,42 @@ export function alertStateToReadable(state: PromAlertingRuleState | GrafanaAlert } return capitalize(state); } + +export const flattenRules = (rules: RuleNamespace[]) => { + return rules.reduce((acc, { dataSourceName, name: namespaceName, groups }) => { + groups.forEach(({ name: groupName, rules }) => { + rules.forEach((rule) => { + if (isAlertingRule(rule)) { + acc.push({ dataSourceName, namespaceName, groupName, rule }); + } + }); + }); + return acc; + }, []); +}; + +export const alertStateToState: Record = { + [PromAlertingRuleState.Inactive]: 'good', + [PromAlertingRuleState.Firing]: 'bad', + [PromAlertingRuleState.Pending]: 'warning', + [GrafanaAlertState.Alerting]: 'bad', + [GrafanaAlertState.Error]: 'bad', + [GrafanaAlertState.NoData]: 'info', + [GrafanaAlertState.Normal]: 'good', + [GrafanaAlertState.Pending]: 'warning', +}; + +export function getFirstActiveAt(promRule: AlertingRule) { + if (!promRule.alerts) { + return null; + } + return promRule.alerts.reduce((prev, alert) => { + if (alert.activeAt && alert.state !== GrafanaAlertState.Normal) { + const activeAt = new Date(alert.activeAt); + if (prev === null || prev.getTime() > activeAt.getTime()) { + return activeAt; + } + } + return prev; + }, null as Date | null); +} diff --git a/public/app/plugins/panel/alertlist/AlertInstances.tsx b/public/app/plugins/panel/alertlist/AlertInstances.tsx new file mode 100644 index 00000000000..51729730c82 --- /dev/null +++ b/public/app/plugins/panel/alertlist/AlertInstances.tsx @@ -0,0 +1,78 @@ +import React, { useEffect, useMemo, useState } from 'react'; +import pluralize from 'pluralize'; +import { Icon, useStyles2 } from '@grafana/ui'; +import { Alert, PromRuleWithLocation } from 'app/types/unified-alerting'; +import { AlertLabels } from 'app/features/alerting/unified/components/AlertLabels'; +import { AlertStateTag } from 'app/features/alerting/unified/components/rules/AlertStateTag'; +import { dateTime, GrafanaTheme2 } from '@grafana/data'; +import { css } from '@emotion/css'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; +import { omit } from 'lodash'; +import { alertInstanceKey } from 'app/features/alerting/unified/utils/rules'; + +interface Props { + ruleWithLocation: PromRuleWithLocation; + showInstances: boolean; +} + +export const AlertInstances = ({ ruleWithLocation, showInstances }: Props) => { + const { rule } = ruleWithLocation; + const [displayInstances, setDisplayInstances] = useState(showInstances); + const styles = useStyles2(getStyles); + + useEffect(() => { + setDisplayInstances(showInstances); + }, [showInstances]); + + // sort instances, because API returns them in random order every time + const sortedAlerts = useMemo( + (): Alert[] => + displayInstances + ? rule.alerts.slice().sort((a, b) => alertInstanceKey(a).localeCompare(alertInstanceKey(b))) + : [], + [rule, displayInstances] + ); + + return ( +
+ {rule.state !== PromAlertingRuleState.Inactive && ( +
setDisplayInstances(!displayInstances)}> + + {`${rule.alerts.length} ${pluralize('instance', rule.alerts.length)}`} +
+ )} + + {!!sortedAlerts.length && ( +
    + {sortedAlerts.map((alert, index) => { + return ( +
  1. +
    + + {dateTime(alert.activeAt).format('YYYY-MM-DD HH:mm:ss')} +
    + +
  2. + ); + })} +
+ )} +
+ ); +}; + +const getStyles = (theme: GrafanaTheme2) => ({ + instance: css` + cursor: pointer; + `, + list: css` + list-style-type: none; + `, + listItem: css` + margin-top: ${theme.spacing(1)}; + `, + date: css` + font-size: ${theme.typography.bodySmall.fontSize}; + padding-left: ${theme.spacing(0.5)}; + `, +}); diff --git a/public/app/plugins/panel/alertlist/UnifiedAlertList.tsx b/public/app/plugins/panel/alertlist/UnifiedAlertList.tsx new file mode 100644 index 00000000000..f475c490535 --- /dev/null +++ b/public/app/plugins/panel/alertlist/UnifiedAlertList.tsx @@ -0,0 +1,296 @@ +import React, { useEffect, useMemo } from 'react'; +import { sortBy } from 'lodash'; +import { useDispatch } from 'react-redux'; +import { GrafanaTheme, GrafanaTheme2, intervalToAbbreviatedDurationString, PanelProps } from '@grafana/data'; +import { CustomScrollbar, Icon, IconName, LoadingPlaceholder, useStyles, useStyles2 } from '@grafana/ui'; +import { css } from '@emotion/css'; + +import { AlertInstances } from './AlertInstances'; +import alertDef from 'app/features/alerting/state/alertDef'; +import { SortOrder, UnifiedAlertListOptions } from './types'; + +import { flattenRules, alertStateToState, getFirstActiveAt } from 'app/features/alerting/unified/utils/rules'; +import { PromRuleWithLocation } from 'app/types/unified-alerting'; +import { fetchAllPromRulesAction } from 'app/features/alerting/unified/state/actions'; +import { useUnifiedAlertingSelector } from 'app/features/alerting/unified/hooks/useUnifiedAlertingSelector'; +import { getAllRulesSourceNames } from 'app/features/alerting/unified/utils/datasource'; +import { getDashboardSrv } from 'app/features/dashboard/services/DashboardSrv'; +import { Annotation, RULE_LIST_POLL_INTERVAL_MS } from 'app/features/alerting/unified/utils/constants'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + +export function UnifiedAlertList(props: PanelProps) { + const dispatch = useDispatch(); + const rulesDataSourceNames = useMemo(getAllRulesSourceNames, []); + + useEffect(() => { + dispatch(fetchAllPromRulesAction()); + const interval = setInterval(() => dispatch(fetchAllPromRulesAction()), RULE_LIST_POLL_INTERVAL_MS); + return () => { + clearInterval(interval); + }; + }, [dispatch]); + + const promRulesRequests = useUnifiedAlertingSelector((state) => state.promRules); + + const dispatched = rulesDataSourceNames.some((name) => promRulesRequests[name]?.dispatched); + const loading = rulesDataSourceNames.some((name) => promRulesRequests[name]?.loading); + const haveResults = rulesDataSourceNames.some( + (name) => promRulesRequests[name]?.result?.length && !promRulesRequests[name]?.error + ); + + const styles = useStyles(getStyles); + const stateStyle = useStyles2(getStateTagStyles); + + const rules = useMemo( + () => + filterRules( + props.options, + sortRules( + props.options.sortOrder, + Object.values(promRulesRequests).flatMap(({ result = [] }) => flattenRules(result)) + ) + ), + [props.options, promRulesRequests] + ); + + const rulesToDisplay = rules.length <= props.options.maxItems ? rules : rules.slice(0, props.options.maxItems); + + const noAlertsMessage = rules.length ? '' : 'No alerts'; + + return ( + +
+ {dispatched && loading && !haveResults && } + {noAlertsMessage &&
{noAlertsMessage}
} +
+
    + {haveResults && + rulesToDisplay.map((ruleWithLocation, index) => { + const { rule, namespaceName, groupName } = ruleWithLocation; + const firstActiveAt = getFirstActiveAt(rule); + return ( +
  1. +
    + +
    +
    +
    +
    + {rule.name} +
    +
    + {rule.state.toUpperCase()}{' '} + {firstActiveAt && rule.state !== PromAlertingRuleState.Inactive && ( + <> + for{' '} + + {intervalToAbbreviatedDurationString({ + start: firstActiveAt, + end: Date.now(), + })} + + + )} +
    +
    + +
    +
  2. + ); + })} +
+
+
+
+ ); +} + +function sortRules(sortOrder: SortOrder, rules: PromRuleWithLocation[]) { + if (sortOrder === SortOrder.Importance) { + // @ts-ignore + return sortBy(rules, (rule) => alertDef.alertStateSortScore[rule.state]); + } else if (sortOrder === SortOrder.TimeAsc) { + return sortBy(rules, (rule) => getFirstActiveAt(rule.rule) || new Date()); + } else if (sortOrder === SortOrder.TimeDesc) { + return sortBy(rules, (rule) => getFirstActiveAt(rule.rule) || new Date()).reverse(); + } + const result = sortBy(rules, (rule) => rule.rule.name.toLowerCase()); + if (sortOrder === SortOrder.AlphaDesc) { + result.reverse(); + } + + return result; +} + +function filterRules(options: PanelProps['options'], rules: PromRuleWithLocation[]) { + let filteredRules = [...rules]; + if (options.dashboardAlerts) { + const dashboardUid = getDashboardSrv().getCurrent()?.uid; + filteredRules = filteredRules.filter(({ rule: { annotations = {} } }) => + Object.entries(annotations).some(([key, value]) => key === Annotation.dashboardUID && value === dashboardUid) + ); + } + if (options.alertName) { + filteredRules = filteredRules.filter(({ rule: { name } }) => + name.toLocaleLowerCase().includes(options.alertName.toLocaleLowerCase()) + ); + } + if (Object.values(options.stateFilter).some((value) => value)) { + filteredRules = filteredRules.filter((rule) => { + return ( + (options.stateFilter.firing && rule.rule.state === PromAlertingRuleState.Firing) || + (options.stateFilter.pending && rule.rule.state === PromAlertingRuleState.Pending) || + (options.stateFilter.inactive && rule.rule.state === PromAlertingRuleState.Inactive) + ); + }); + } + if (options.folder) { + filteredRules = filteredRules.filter((rule) => { + return rule.namespaceName === options.folder.title; + }); + } + + return filteredRules; +} + +const getStyles = (theme: GrafanaTheme) => ({ + cardContainer: css` + padding: ${theme.spacing.xs} 0 ${theme.spacing.xxs} 0; + line-height: ${theme.typography.lineHeight.md}; + margin-bottom: 0px; + `, + container: css` + overflow-y: auto; + height: 100%; + `, + alertRuleList: css` + display: flex; + flex-wrap: wrap; + justify-content: space-between; + list-style-type: none; + `, + alertRuleItem: css` + display: flex; + align-items: center; + width: 100%; + height: 100%; + background: ${theme.colors.bg2}; + padding: ${theme.spacing.xs} ${theme.spacing.sm}; + border-radius: ${theme.border.radius.md}; + margin-bottom: ${theme.spacing.xs}; + + & > * { + margin-right: ${theme.spacing.sm}; + } + `, + alertName: css` + font-size: ${theme.typography.size.md}; + font-weight: ${theme.typography.weight.bold}; + `, + alertDuration: css` + font-size: ${theme.typography.size.sm}; + `, + alertRuleItemText: css` + font-weight: ${theme.typography.weight.bold}; + font-size: ${theme.typography.size.sm}; + margin: 0; + `, + alertRuleItemTime: css` + color: ${theme.colors.textWeak}; + font-weight: normal; + white-space: nowrap; + `, + alertRuleItemInfo: css` + font-weight: normal; + flex-grow: 2; + display: flex; + align-items: flex-end; + `, + noAlertsMessage: css` + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; + `, + alertIcon: css` + margin-right: ${theme.spacing.xs}; + `, + instanceDetails: css` + min-width: 1px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + `, +}); + +const getStateTagStyles = (theme: GrafanaTheme2) => ({ + common: css` + width: 70px; + text-align: center; + align-self: stretch; + + display: inline-block; + color: white; + border-radius: ${theme.shape.borderRadius()}; + font-size: ${theme.typography.size.sm}; + /* padding: ${theme.spacing(2, 0)}; */ + text-transform: capitalize; + line-height: 1.2; + flex-shrink: 0; + + display: flex; + flex-direction: column; + justify-content: center; + `, + icon: css` + margin-top: ${theme.spacing(2.5)}; + align-self: flex-start; + `, + // good: css` + // background-color: ${theme.colors.success.main}; + // border: solid 1px ${theme.colors.success.main}; + // color: ${theme.colors.success.contrastText}; + // `, + // warning: css` + // background-color: ${theme.colors.warning.main}; + // border: solid 1px ${theme.colors.warning.main}; + // color: ${theme.colors.warning.contrastText}; + // `, + // bad: css` + // background-color: ${theme.colors.error.main}; + // border: solid 1px ${theme.colors.error.main}; + // color: ${theme.colors.error.contrastText}; + // `, + // neutral: css` + // background-color: ${theme.colors.secondary.main}; + // border: solid 1px ${theme.colors.secondary.main}; + // `, + // info: css` + // background-color: ${theme.colors.primary.main}; + // border: solid 1px ${theme.colors.primary.main}; + // color: ${theme.colors.primary.contrastText}; + // `, + good: css` + color: ${theme.colors.success.main}; + `, + bad: css` + color: ${theme.colors.error.main}; + `, + warning: css` + color: ${theme.colors.warning.main}; + `, + neutral: css` + color: ${theme.colors.secondary.main}; + `, + info: css` + color: ${theme.colors.primary.main}; + `, +}); diff --git a/public/app/plugins/panel/alertlist/module.tsx b/public/app/plugins/panel/alertlist/module.tsx index 87711fd11f4..42f8b39cfb0 100644 --- a/public/app/plugins/panel/alertlist/module.tsx +++ b/public/app/plugins/panel/alertlist/module.tsx @@ -2,15 +2,18 @@ import React from 'react'; import { PanelPlugin } from '@grafana/data'; import { TagsInput } from '@grafana/ui'; import { AlertList } from './AlertList'; +import { UnifiedAlertList } from './UnifiedAlertList'; import { FolderPicker } from 'app/core/components/Select/FolderPicker'; -import { AlertListOptions, ShowOption, SortOrder } from './types'; +import { AlertListOptions, UnifiedAlertListOptions, ShowOption, SortOrder } from './types'; import { alertListPanelMigrationHandler } from './AlertListMigrationHandler'; +import { config } from '@grafana/runtime'; +import { RuleFolderPicker } from 'app/features/alerting/unified/components/rule-editor/RuleFolderPicker'; function showIfCurrentState(options: AlertListOptions) { return options.showOptions === ShowOption.Current; } -export const plugin = new PanelPlugin(AlertList) +const alertList = new PanelPlugin(AlertList) .setPanelOptions((builder) => { builder .addSelect({ @@ -140,3 +143,84 @@ export const plugin = new PanelPlugin(AlertList) }); }) .setMigrationHandler(alertListPanelMigrationHandler); + +const unifiedAlertList = new PanelPlugin(UnifiedAlertList).setPanelOptions((builder) => { + builder + .addNumberInput({ + name: 'Max items', + path: 'maxItems', + defaultValue: 20, + category: ['Options'], + }) + .addSelect({ + name: 'Sort order', + path: 'sortOrder', + settings: { + options: [ + { label: 'Alphabetical (asc)', value: SortOrder.AlphaAsc }, + { label: 'Alphabetical (desc)', value: SortOrder.AlphaDesc }, + { label: 'Importance', value: SortOrder.Importance }, + { label: 'Time (asc)', value: SortOrder.TimeAsc }, + { label: 'Time (desc)', value: SortOrder.TimeDesc }, + ], + }, + defaultValue: SortOrder.AlphaAsc, + category: ['Options'], + }) + .addBooleanSwitch({ + path: 'dashboardAlerts', + name: 'Alerts from this dashboard', + defaultValue: false, + category: ['Options'], + }) + .addBooleanSwitch({ + path: 'showInstances', + name: 'Show alert instances', + defaultValue: false, + category: ['Options'], + }) + .addTextInput({ + path: 'alertName', + name: 'Alert name', + defaultValue: '', + category: ['Filter'], + }) + .addCustomEditor({ + path: 'folder', + name: 'Folder', + id: 'folder', + defaultValue: null, + editor: function RenderFolderPicker(props) { + return ( + { + return props.onChange({ title, id }); + }} + /> + ); + }, + category: ['Filter'], + }) + .addBooleanSwitch({ + path: 'stateFilter.firing', + name: 'Alerting', + defaultValue: true, + category: ['State filter'], + }) + .addBooleanSwitch({ + path: 'stateFilter.pending', + name: 'Pending', + defaultValue: true, + category: ['State filter'], + }) + .addBooleanSwitch({ + path: 'stateFilter.inactive', + name: 'Inactive', + defaultValue: false, + category: ['State filter'], + }); +}); + +export const plugin = config.featureToggles.ngalert ? unifiedAlertList : alertList; diff --git a/public/app/plugins/panel/alertlist/types.ts b/public/app/plugins/panel/alertlist/types.ts index cb074b49563..17d8e6699d3 100644 --- a/public/app/plugins/panel/alertlist/types.ts +++ b/public/app/plugins/panel/alertlist/types.ts @@ -1,3 +1,5 @@ +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; + export enum SortOrder { AlphaAsc = 1, AlphaDesc, @@ -29,3 +31,15 @@ export interface AlertListOptions { }; folderId: number; } + +export interface UnifiedAlertListOptions { + maxItems: number; + sortOrder: SortOrder; + dashboardAlerts: boolean; + alertName: string; + showInstances: boolean; + folder: { id: number; title: string }; + stateFilter: { + [K in PromAlertingRuleState]: boolean; + }; +} diff --git a/public/app/types/unified-alerting.ts b/public/app/types/unified-alerting.ts index d86ad06e33c..560b5ec9da3 100644 --- a/public/app/types/unified-alerting.ts +++ b/public/app/types/unified-alerting.ts @@ -101,6 +101,13 @@ export interface RuleWithLocation { rule: RulerRuleDTO; } +export interface PromRuleWithLocation { + rule: AlertingRule; + dataSourceName: string; + namespaceName: string; + groupName: string; +} + export interface CloudRuleIdentifier { ruleSourceName: string; namespace: string;