grafana_bak/public/app/features/alerting/unified/PanelAlertTabContent.test.tsx
2025-04-01 10:38:02 +09:00

363 lines
9.8 KiB
TypeScript

import { render } from 'test/test-utils';
import { byTestId, byText } from 'testing-library-selector';
import { PromOptions } from '@grafana/prometheus';
import { setPluginLinksHook } from '@grafana/runtime';
import config from 'app/core/config';
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources';
import { DashboardModel } from 'app/features/dashboard/state/DashboardModel';
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { AccessControlAction } from 'app/types';
import { AlertQuery, PromRulesResponse } from 'app/types/unified-alerting-dto';
import { PanelAlertTabContent } from './PanelAlertTabContent';
import * as apiRuler from './api/ruler';
import * as alertingAbilities from './hooks/useAbilities';
import { mockAlertRuleApi, setupMswServer } from './mockApi';
import {
grantUserPermissions,
mockDataSource,
mockPromAlert,
mockPromAlertingRule,
mockRulerAlertingRule,
mockRulerRuleGroup,
} from './mocks';
import { captureRequests } from './mocks/server/events';
import { RuleFormValues } from './types/rule-form';
import { Annotation } from './utils/constants';
import { DataSourceType, GRAFANA_RULES_SOURCE_NAME } from './utils/datasource';
jest.mock('./api/ruler');
jest.spyOn(alertingAbilities, 'useAlertRuleAbility');
const prometheusModuleSettings = { alerting: true, module: 'core:plugin/prometheus' };
const dataSources = {
prometheus: mockDataSource<PromOptions>(
{
name: 'Prometheus',
type: DataSourceType.Prometheus,
isDefault: false,
jsonData: { manageAlerts: true },
},
prometheusModuleSettings
),
default: mockDataSource<PromOptions>(
{
name: 'Default',
type: DataSourceType.Prometheus,
isDefault: true,
jsonData: { manageAlerts: true },
},
prometheusModuleSettings
),
prometheusMinInterval: mockDataSource<PromOptions>(
{
name: 'Prometheus Min Interval',
type: DataSourceType.Prometheus,
isDefault: false,
jsonData: { manageAlerts: true, timeInterval: '7m' },
},
prometheusModuleSettings
),
};
const mocks = {
useAlertRuleAbilityMock: jest.mocked(alertingAbilities.useAlertRuleAbility),
rulerBuilderMock: jest.mocked(apiRuler.rulerUrlBuilder),
};
const renderAlertTabContent = (dashboard: DashboardModel, panel: PanelModel) =>
render(<PanelAlertTabContent dashboard={dashboard} panel={panel} />);
const promResponse: PromRulesResponse = {
status: 'success',
data: {
groups: [
{
name: 'mygroup',
file: 'default',
rules: [
mockPromAlertingRule({
name: 'dashboardrule1',
alerts: [
mockPromAlert({
labels: { severity: 'critical' },
annotations: {
[Annotation.dashboardUID]: '12',
[Annotation.panelID]: '34',
},
}),
],
totals: { alerting: 1 },
totalsFiltered: { alerting: 1 },
}),
],
interval: 20,
},
{
name: 'othergroup',
file: 'default',
rules: [
mockPromAlertingRule({
name: 'dashboardrule2',
alerts: [
mockPromAlert({
labels: { severity: 'critical' },
annotations: {
[Annotation.dashboardUID]: '121',
[Annotation.panelID]: '341',
},
}),
],
totals: { alerting: 1 },
totalsFiltered: { alerting: 1 },
}),
],
interval: 20,
},
],
totals: {
alerting: 2,
},
},
};
const rulerResponse = {
default: [
mockRulerRuleGroup({
name: 'mygroup',
rules: [
mockRulerAlertingRule({
alert: 'dashboardrule1',
annotations: {
[Annotation.dashboardUID]: '12',
[Annotation.panelID]: '34',
},
}),
],
}),
{
name: 'othergroup',
rules: [
mockRulerAlertingRule({
alert: 'dashboardrule2',
annotations: {
[Annotation.dashboardUID]: '121',
[Annotation.panelID]: '341',
},
}),
],
},
],
};
const dashboard = {
uid: '12',
time: {
from: 'now-6h',
to: 'now',
},
meta: {
canSave: true,
folderUid: 'abc',
folderTitle: 'super folder',
},
} as DashboardModel;
const panel = new PanelModel({
datasource: {
type: 'prometheus',
uid: dataSources.prometheus.uid,
},
title: 'mypanel',
id: 34,
targets: [
{
expr: 'sum(some_metric [$__interval])) by (app)',
refId: 'A',
},
],
});
const ui = {
row: byTestId('row'),
createButton: byTestId<HTMLAnchorElement>('create-alert-rule-button'),
notSavedYet: byText('Dashboard not saved'),
};
const server = setupMswServer();
describe('PanelAlertTabContent', () => {
beforeEach(() => {
grantUserPermissions([
AccessControlAction.AlertingRuleRead,
AccessControlAction.AlertingRuleUpdate,
AccessControlAction.AlertingRuleDelete,
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleExternalRead,
AccessControlAction.AlertingRuleExternalWrite,
]);
setupDataSources(...Object.values(dataSources));
setPluginLinksHook(() => ({
links: [],
isLoading: false,
}));
mocks.rulerBuilderMock.mockReturnValue({
rules: () => ({ path: `api/ruler/${GRAFANA_RULES_SOURCE_NAME}/api/v1/rules` }),
namespace: () => ({ path: 'ruler' }),
namespaceGroup: () => ({ path: 'ruler' }),
});
mocks.useAlertRuleAbilityMock.mockReturnValue([true, true]);
mockAlertRuleApi(server).prometheusRuleNamespaces(GRAFANA_RULES_SOURCE_NAME, promResponse);
mockAlertRuleApi(server).rulerRules(GRAFANA_RULES_SOURCE_NAME, rulerResponse);
config.unifiedAlertingEnabled = true;
});
it('Will take into account panel maxDataPoints', async () => {
renderAlertTabContent(
dashboard,
new PanelModel({
...panel,
maxDataPoints: 100,
interval: '10s',
})
);
const button = await ui.createButton.find();
const href = button.href;
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
expect(match).toHaveLength(2);
const defaults = JSON.parse(decodeURIComponent(match![1]));
expect(defaults.queries[0].model).toEqual({
expr: 'sum(some_metric [5m])) by (app)',
refId: 'A',
datasource: {
type: 'prometheus',
uid: 'mock-ds-2',
},
interval: '',
intervalMs: 300000,
maxDataPoints: 100,
});
});
it('Will work with default datasource', async () => {
renderAlertTabContent(
dashboard,
new PanelModel({
...panel,
datasource: undefined,
maxDataPoints: 100,
interval: '10s',
})
);
const button = await ui.createButton.find();
const href = button.href;
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
expect(match).toHaveLength(2);
const defaults = JSON.parse(decodeURIComponent(match![1]));
expect(defaults.queries[0].model).toEqual({
expr: 'sum(some_metric [5m])) by (app)',
refId: 'A',
datasource: {
type: 'prometheus',
uid: 'mock-ds-3',
},
interval: '',
intervalMs: 300000,
maxDataPoints: 100,
});
});
it('should not make requests for unsaved dashboard', async () => {
const capture = captureRequests();
const unsavedDashboard = {
...dashboard,
uid: null,
} as DashboardModel;
renderAlertTabContent(
unsavedDashboard,
new PanelModel({
...panel,
datasource: undefined,
maxDataPoints: 100,
interval: '10s',
})
);
expect(await ui.notSavedYet.find()).toBeInTheDocument();
const requests = await capture;
expect(requests.length).toBe(0);
});
it('Will take into account datasource minInterval', async () => {
renderAlertTabContent(
dashboard,
new PanelModel({
...panel,
maxDataPoints: 100,
datasource: {
type: 'prometheus',
uid: dataSources.prometheusMinInterval.uid,
},
})
);
const button = await ui.createButton.find();
const href = button.href;
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
expect(match).toHaveLength(2);
const defaults = JSON.parse(decodeURIComponent(match![1]));
expect(defaults.queries[0].model).toEqual({
expr: 'sum(some_metric [7m])) by (app)',
refId: 'A',
datasource: {
type: 'prometheus',
uid: 'mock-ds-4',
},
interval: '',
intervalMs: 420000,
maxDataPoints: 100,
});
});
it('Will render alerts belonging to panel and a button to create alert from panel queries', async () => {
config.unifiedAlertingEnabled = true;
renderAlertTabContent(dashboard, panel);
const rows = await ui.row.findAll();
// after updating to RTKQ, the response is already returning the alerts belonging to the panel
expect(rows).toHaveLength(2);
expect(rows[0]).toHaveTextContent(/dashboardrule1/);
expect(rows[0]).not.toHaveTextContent(/dashboardrule2/);
const button = await ui.createButton.find();
const href = button.href;
const match = href.match(/alerting\/new\?defaults=(.*)&returnTo=/);
expect(match).toHaveLength(2);
const defaults = JSON.parse(decodeURIComponent(match![1]));
const defaultsWithDeterministicTime: Partial<RuleFormValues> = {
...defaults,
queries: defaults.queries.map((q: AlertQuery) => {
return {
...q,
// Fix computed time stamp to avoid assertion flakiness
...(q.relativeTimeRange ? { relativeTimeRange: { from: 21600, to: 0 } } : {}),
};
}),
};
expect(defaultsWithDeterministicTime).toMatchSnapshot();
});
});