diff --git a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts index 9d401a130b2c3..6320630102d6f 100644 --- a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts +++ b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.test.ts @@ -3,11 +3,16 @@ import { lastValueFrom } from 'rxjs'; import { AlertState, getDefaultTimeRange, TimeRange } from '@grafana/data'; import { config } from '@grafana/runtime'; import { backendSrv } from 'app/core/services/backend_srv'; -import { grantUserPermissions } from 'app/features/alerting/unified/mocks'; +import { + grantUserPermissions, + mockPromAlertingRule, + mockPromRuleGroup, + mockPromRuleNamespace, +} from 'app/features/alerting/unified/mocks'; import { Annotation } from 'app/features/alerting/unified/utils/constants'; import { createDashboardModelFixture } from 'app/features/dashboard/state/__fixtures__/dashboardFixtures'; import { AccessControlAction } from 'app/types/accessControl'; -import { PromAlertingRuleState, PromRuleDTO, PromRulesResponse, PromRuleType } from 'app/types/unified-alerting-dto'; +import { PromAlertingRuleState } from 'app/types/unified-alerting-dto'; import { silenceConsoleOutput } from '../../../../../test/core/utils/silenceConsoleOutput'; import * as store from '../../../../store/store'; @@ -37,9 +42,8 @@ function getTestContext() { jest.clearAllMocks(); const dispatchMock = jest.spyOn(store, 'dispatch'); const options = getDefaultOptions(); - const getMock = jest.spyOn(backendSrv, 'get'); - return { getMock, options, dispatchMock }; + return { options, dispatchMock }; } describe('UnifiedAlertStatesWorker', () => { @@ -88,30 +92,24 @@ describe('UnifiedAlertStatesWorker', () => { describe('when run is called with incorrect props', () => { it('then it should return the correct results', async () => { - const { getMock, options } = getTestContext(); + const { options } = getTestContext(); const dashboard = createDashboardModelFixture({}); await expect(worker.work({ ...options, dashboard })).toEmitValuesWith((received) => { expect(received).toHaveLength(1); const results = received[0]; expect(results).toEqual({ alertStates: [], annotations: [] }); - expect(getMock).not.toHaveBeenCalled(); }); }); }); describe('when run repeatedly for the same dashboard and no alert rules are found', () => { + const nameSpaces = [mockPromRuleNamespace({ groups: [] })]; + const { dispatchMock, options } = getTestContext(); + //getMock.mockResolvedValue(getResults); + dispatchMock.mockResolvedValue(nameSpaces); it('then canWork should start returning false', async () => { const worker = new UnifiedAlertStatesWorker(); - - const getResults: PromRulesResponse = { - status: 'success', - data: { - groups: [], - }, - }; - const { getMock, options } = getTestContext(); - getMock.mockResolvedValue(getResults); expect(worker.canWork(options)).toBe(true); await lastValueFrom(worker.work(options)); expect(worker.canWork(options)).toBe(false); @@ -119,45 +117,41 @@ describe('UnifiedAlertStatesWorker', () => { }); describe('when run is called with correct props and request is successful', () => { - function mockPromRuleDTO(overrides: Partial): PromRuleDTO { - return { - alerts: [], - health: 'ok', - name: 'foo', - query: 'foo', - type: PromRuleType.Alerting, - state: PromAlertingRuleState.Firing, - labels: {}, - annotations: {}, - ...overrides, - }; - } - it('then it should return the correct results', async () => { - const getResults: PromRulesResponse = { - status: 'success', - data: { + const nameSpaces = [ + mockPromRuleNamespace({ groups: [ - { - name: 'group', - file: '', - interval: 1, + mockPromRuleGroup({ + name: 'group1', rules: [ - mockPromRuleDTO({ + mockPromAlertingRule({ + name: 'alert1', state: PromAlertingRuleState.Firing, annotations: { [Annotation.dashboardUID]: 'a uid', [Annotation.panelID]: '1', }, }), - mockPromRuleDTO({ + ], + }), + mockPromRuleGroup({ + name: 'group2', + rules: [ + mockPromAlertingRule({ + name: 'alert2', state: PromAlertingRuleState.Inactive, annotations: { [Annotation.dashboardUID]: 'a uid', [Annotation.panelID]: '2', }, }), - mockPromRuleDTO({ + ], + }), + mockPromRuleGroup({ + name: 'group3', + rules: [ + mockPromAlertingRule({ + name: 'alert3', state: PromAlertingRuleState.Pending, annotations: { [Annotation.dashboardUID]: 'a uid', @@ -165,12 +159,12 @@ describe('UnifiedAlertStatesWorker', () => { }, }), ], - }, + }), ], - }, - }; - const { getMock, options } = getTestContext(); - getMock.mockResolvedValue(getResults); + }), + ]; + const { dispatchMock, options } = getTestContext(); + dispatchMock.mockResolvedValue({ data: nameSpaces }); await expect(worker.work(options)).toEmitValuesWith((received) => { expect(received).toHaveLength(1); @@ -184,46 +178,38 @@ describe('UnifiedAlertStatesWorker', () => { }); }); - expect(getMock).toHaveBeenCalledTimes(1); - expect(getMock).toHaveBeenCalledWith( - '/api/prometheus/grafana/api/v1/rules', - { dashboard_uid: 'a uid' }, - 'dashboard-query-runner-unified-alert-states-12345' - ); + expect(dispatchMock).toHaveBeenCalledTimes(1); }); }); describe('when run is called with correct props and request fails', () => { silenceConsoleOutput(); it('then it should return the correct results', async () => { - const { getMock, options, dispatchMock } = getTestContext(); - getMock.mockRejectedValue({ message: 'An error' }); + const { options, dispatchMock } = getTestContext(); + dispatchMock.mockResolvedValue({ error: 'An error' }); await expect(worker.work(options)).toEmitValuesWith((received) => { expect(received).toHaveLength(1); const results = received[0]; expect(results).toEqual({ alertStates: [], annotations: [] }); - expect(getMock).toHaveBeenCalledTimes(1); expect(dispatchMock).toHaveBeenCalledTimes(1); }); }); }); - - describe('when run is called with correct props and request is cancelled', () => { - silenceConsoleOutput(); - it('then it should return the correct results', async () => { - const { getMock, options, dispatchMock } = getTestContext(); - getMock.mockRejectedValue({ cancelled: true }); - - await expect(worker.work(options)).toEmitValuesWith((received) => { - expect(received).toHaveLength(1); - const results = received[0]; - expect(results).toEqual({ alertStates: [], annotations: [] }); - expect(getMock).toHaveBeenCalledTimes(1); - expect(dispatchMock).not.toHaveBeenCalled(); - }); - }); - }); + // not sure how to test this use case as now we are using dispatch instead of getBackendSrv + // describe('when run is called with correct props and request is cancelled', () => { + // silenceConsoleOutput(); + // it('then it should return the correct results', async () => { + // const { options, dispatchMock } = getTestContext(); + + // await expect(worker.work(options)).toEmitValuesWith((received) => { + // expect(received).toHaveLength(1); + // const results = received[0]; + // expect(results).toEqual({ alertStates: [], annotations: [] }); + // expect(dispatchMock).not.toHaveBeenCalled(); + // }); + // }); + // }); }); describe('UnifiedAlertStateWorker with RBAC', () => { diff --git a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts index 832111a9b27f5..12d6b8ecba5d5 100644 --- a/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts +++ b/public/app/features/query/state/DashboardQueryRunner/UnifiedAlertStatesWorker.ts @@ -1,5 +1,5 @@ import { Observable, from } from 'rxjs'; -import { catchError, concatMap, map } from 'rxjs/operators'; +import { catchError, map } from 'rxjs/operators'; import { AlertState, AlertStateInfo } from '@grafana/data'; import { config } from '@grafana/runtime'; @@ -69,38 +69,42 @@ export class UnifiedAlertStatesWorker implements DashboardQueryRunnerWorker { return (await promRules).data; }; - return from(fetchData()).pipe( - concatMap((namespaces: RuleNamespace[]) => ungroupRulesByFileName(namespaces)), - map((group: PromRuleGroupDTO) => { + const res: Observable = from>(fetchData()).pipe( + map((namespaces: RuleNamespace[]) => ungroupRulesByFileName(namespaces)) + ); + + return res.pipe( + map((groups: PromRuleGroupDTO[]) => { 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); + 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; + // 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: [] }; }), catchError(handleDashboardQueryRunnerWorkerError)