import { from, Observable } from 'rxjs'; import { catchError, map } from 'rxjs/operators'; import { AlertState, AlertStateInfo } from '@grafana/data'; import { getBackendSrv } from '@grafana/runtime'; import { contextSrv } from 'app/core/services/context_srv'; import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { isAlertingRule } from 'app/features/alerting/unified/utils/rules'; import { AccessControlAction } from 'app/types'; import { PromAlertingRuleState, PromRulesResponse } from 'app/types/unified-alerting-dto'; import { DashboardQueryRunnerOptions, DashboardQueryRunnerWorker, DashboardQueryRunnerWorkerResult } from './types'; import { emptyResult, handleDashboardQueryRunnerWorkerError } from './utils'; export class UnifiedAlertStatesWorker implements DashboardQueryRunnerWorker { // maps dashboard uid to wether it has alert rules. // if it is determined that a dashboard does not have alert rules, // further attempts to get alert states for it will not be made private hasAlertRules: Record = {}; canWork({ dashboard, range }: DashboardQueryRunnerOptions): boolean { if (!dashboard.uid) { return false; } if (range.raw.to !== 'now') { return false; } if (this.hasAlertRules[dashboard.uid] === false) { return false; } const hasRuleReadPermission = contextSrv.hasPermission(AccessControlAction.AlertingRuleRead) && contextSrv.hasPermission(AccessControlAction.AlertingRuleExternalRead); if (!hasRuleReadPermission) { return false; } return true; } work(options: DashboardQueryRunnerOptions): Observable { if (!this.canWork(options)) { return emptyResult(); } const { dashboard } = options; return from( getBackendSrv().get( '/api/prometheus/grafana/api/v1/rules', { dashboard_uid: dashboard.uid, }, `dashboard-query-runner-unified-alert-states-${dashboard.id}` ) ).pipe( map((result: PromRulesResponse) => { if (result.status === 'success') { this.hasAlertRules[dashboard.uid] = false; const panelIdToAlertState: Record = {}; result.data.groups.forEach((group) => group.rules.forEach((rule) => { if (isAlertingRule(rule) && rule.annotations && rule.annotations[Annotation.panelID]) { this.hasAlertRules[dashboard.uid] = true; const panelId = Number(rule.annotations[Annotation.panelID]); const state = promAlertStateToAlertState(rule.state); // there can be multiple alerts per panel, so we make sure we get the most severe state: // alerting > pending > ok if (!panelIdToAlertState[panelId]) { panelIdToAlertState[panelId] = { state, id: Object.keys(panelIdToAlertState).length, panelId, dashboardId: dashboard.id, }; } else if ( state === AlertState.Alerting && panelIdToAlertState[panelId].state !== AlertState.Alerting ) { panelIdToAlertState[panelId].state = AlertState.Alerting; } else if ( state === AlertState.Pending && panelIdToAlertState[panelId].state !== AlertState.Alerting && panelIdToAlertState[panelId].state !== AlertState.Pending ) { panelIdToAlertState[panelId].state = AlertState.Pending; } } }) ); return { alertStates: Object.values(panelIdToAlertState), annotations: [] }; } throw new Error(`Unexpected alert rules response.`); }), catchError(handleDashboardQueryRunnerWorkerError) ); } } function promAlertStateToAlertState(state: PromAlertingRuleState): AlertState { if (state === PromAlertingRuleState.Firing) { return AlertState.Alerting; } else if (state === PromAlertingRuleState.Pending) { return AlertState.Pending; } return AlertState.OK; }