|
|
|
@ -1,6 +1,4 @@ |
|
|
|
|
import { SerializedError } from '@reduxjs/toolkit'; |
|
|
|
|
import userEvent from '@testing-library/user-event'; |
|
|
|
|
import { SetupServer } from 'msw/node'; |
|
|
|
|
import { TestProvider } from 'test/helpers/TestProvider'; |
|
|
|
|
import { render, screen, waitFor, within } from 'test/test-utils'; |
|
|
|
|
import { byRole, byTestId, byText } from 'testing-library-selector'; |
|
|
|
@ -9,11 +7,10 @@ import { PluginExtensionTypes } from '@grafana/data'; |
|
|
|
|
import { selectors } from '@grafana/e2e-selectors'; |
|
|
|
|
import { locationService, setAppEvents, usePluginLinks } from '@grafana/runtime'; |
|
|
|
|
import appEvents from 'app/core/app_events'; |
|
|
|
|
import { mockUserApi, setupMswServer } from 'app/features/alerting/unified/mockApi'; |
|
|
|
|
import { setupMswServer } from 'app/features/alerting/unified/mockApi'; |
|
|
|
|
import { setAlertmanagerChoices } from 'app/features/alerting/unified/mocks/server/configure'; |
|
|
|
|
import * as actions from 'app/features/alerting/unified/state/actions'; |
|
|
|
|
import { setupDataSources } from 'app/features/alerting/unified/testSetup/datasources'; |
|
|
|
|
import { getMockUser } from 'app/features/users/__mocks__/userMocks'; |
|
|
|
|
import { AlertmanagerChoice } from 'app/plugins/datasource/alertmanager/types'; |
|
|
|
|
import { AccessControlAction } from 'app/types'; |
|
|
|
|
import { PromAlertingRuleState, PromApplication } from 'app/types/unified-alerting-dto'; |
|
|
|
@ -134,16 +131,11 @@ const ui = { |
|
|
|
|
}, |
|
|
|
|
}; |
|
|
|
|
|
|
|
|
|
const server = setupMswServer(); |
|
|
|
|
|
|
|
|
|
const configureMockServer = (server: SetupServer) => { |
|
|
|
|
mockUserApi(server).user(getMockUser()); |
|
|
|
|
setAlertmanagerChoices(AlertmanagerChoice.All, 1); |
|
|
|
|
}; |
|
|
|
|
setupMswServer(); |
|
|
|
|
|
|
|
|
|
describe('RuleList', () => { |
|
|
|
|
beforeEach(() => { |
|
|
|
|
configureMockServer(server); |
|
|
|
|
setAlertmanagerChoices(AlertmanagerChoice.All, 1); |
|
|
|
|
grantUserPermissions([ |
|
|
|
|
AccessControlAction.AlertingRuleRead, |
|
|
|
|
AccessControlAction.AlertingRuleUpdate, |
|
|
|
@ -242,7 +234,7 @@ describe('RuleList', () => { |
|
|
|
|
|
|
|
|
|
mocks.api.fetchRulerRules.mockRejectedValue({ status: 500, data: { message: 'Server error' } }); |
|
|
|
|
|
|
|
|
|
await renderRuleList(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(mocks.api.fetchRules).toHaveBeenCalledTimes(4)); |
|
|
|
|
const groups = await ui.ruleGroup.findAll(); |
|
|
|
@ -259,7 +251,7 @@ describe('RuleList', () => { |
|
|
|
|
expect(errors).not.toHaveTextContent( |
|
|
|
|
'Failed to load rules state from Prometheus-broken: this datasource is broken' |
|
|
|
|
); |
|
|
|
|
await userEvent.click(ui.moreErrorsButton.get()); |
|
|
|
|
await user.click(ui.moreErrorsButton.get()); |
|
|
|
|
expect(errors).toHaveTextContent('Failed to load rules state from Prometheus-broken: this datasource is broken'); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -338,7 +330,7 @@ describe('RuleList', () => { |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
await renderRuleList(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
const groups = await ui.ruleGroup.findAll(); |
|
|
|
|
expect(groups).toHaveLength(2); |
|
|
|
@ -354,7 +346,7 @@ describe('RuleList', () => { |
|
|
|
|
|
|
|
|
|
// expand second group to see rules table
|
|
|
|
|
expect(ui.rulesTable.query()).not.toBeInTheDocument(); |
|
|
|
|
await userEvent.click(ui.groupCollapseToggle.get(groups[1])); |
|
|
|
|
await user.click(ui.groupCollapseToggle.get(groups[1])); |
|
|
|
|
const table = await ui.rulesTable.find(groups[1]); |
|
|
|
|
|
|
|
|
|
// check that rule rows are rendered properly
|
|
|
|
@ -376,7 +368,7 @@ describe('RuleList', () => { |
|
|
|
|
expect(byText('Labels').query()).not.toBeInTheDocument(); |
|
|
|
|
|
|
|
|
|
// expand alert details
|
|
|
|
|
await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[1])); |
|
|
|
|
await user.click(ui.ruleCollapseToggle.get(ruleRows[1])); |
|
|
|
|
|
|
|
|
|
const ruleDetails = ui.expandedContent.get(ruleRows[1]); |
|
|
|
|
const labels = byTestId('label-value').getAll(ruleDetails); |
|
|
|
@ -397,17 +389,17 @@ describe('RuleList', () => { |
|
|
|
|
expect(instanceRows![1]).toHaveTextContent('Firingfoobazseverityerror2021-03-18 08:47:05'); |
|
|
|
|
|
|
|
|
|
// expand details of an instance
|
|
|
|
|
await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); |
|
|
|
|
await user.click(ui.ruleCollapseToggle.get(instanceRows![0])); |
|
|
|
|
|
|
|
|
|
const alertDetails = byTestId(selectors.components.AlertRules.expandedContent).get(instanceRows[0]); |
|
|
|
|
expect(alertDetails).toHaveTextContent('Value2e+10'); |
|
|
|
|
expect(alertDetails).toHaveTextContent('messagefirst alert message'); |
|
|
|
|
|
|
|
|
|
// collapse everything again
|
|
|
|
|
await userEvent.click(ui.ruleCollapseToggle.get(instanceRows![0])); |
|
|
|
|
await user.click(ui.ruleCollapseToggle.get(instanceRows![0])); |
|
|
|
|
expect(byTestId(selectors.components.AlertRules.expandedContent).query(instanceRows[0])).not.toBeInTheDocument(); |
|
|
|
|
await userEvent.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]); |
|
|
|
|
await userEvent.click(ui.groupCollapseToggle.get(groups[1])); |
|
|
|
|
await user.click(ui.ruleCollapseToggle.getAll(ruleRows[1])[0]); |
|
|
|
|
await user.click(ui.groupCollapseToggle.get(groups[1])); |
|
|
|
|
expect(ui.rulesTable.query()).not.toBeInTheDocument(); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
@ -511,49 +503,49 @@ describe('RuleList', () => { |
|
|
|
|
} |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
await renderRuleList(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
const groups = await ui.ruleGroup.findAll(); |
|
|
|
|
expect(groups).toHaveLength(2); |
|
|
|
|
|
|
|
|
|
const filterInput = ui.rulesFilterInput.get(); |
|
|
|
|
await userEvent.type(filterInput, 'label:foo=bar{Enter}'); |
|
|
|
|
await user.type(filterInput, 'label:foo=bar{Enter}'); |
|
|
|
|
|
|
|
|
|
// Input is debounced so wait for it to be visible
|
|
|
|
|
await waitFor(() => expect(filterInput).toHaveValue('label:foo=bar')); |
|
|
|
|
// Group doesn't contain matching labels
|
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); |
|
|
|
|
|
|
|
|
|
await userEvent.click(ui.groupCollapseToggle.get(groups[0])); |
|
|
|
|
await user.click(ui.groupCollapseToggle.get(groups[0])); |
|
|
|
|
|
|
|
|
|
const ruleRows = ui.ruleRow.getAll(groups[0]); |
|
|
|
|
expect(ruleRows).toHaveLength(1); |
|
|
|
|
|
|
|
|
|
await userEvent.click(ui.ruleCollapseToggle.get(ruleRows[0])); |
|
|
|
|
await user.click(ui.ruleCollapseToggle.get(ruleRows[0])); |
|
|
|
|
const ruleDetails = ui.expandedContent.get(ruleRows[0]); |
|
|
|
|
const labels = byTestId('label-value').getAll(ruleDetails); |
|
|
|
|
expect(labels[0]).toHaveTextContent('severitywarning'); |
|
|
|
|
expect(labels[1]).toHaveTextContent('foobar'); |
|
|
|
|
|
|
|
|
|
// Check for different label matchers
|
|
|
|
|
await userEvent.clear(filterInput); |
|
|
|
|
await userEvent.type(filterInput, 'label:foo!=bar label:foo!=baz{Enter}'); |
|
|
|
|
await user.clear(filterInput); |
|
|
|
|
await user.type(filterInput, 'label:foo!=bar label:foo!=baz{Enter}'); |
|
|
|
|
// Group doesn't contain matching labels
|
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); |
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2')); |
|
|
|
|
|
|
|
|
|
await userEvent.clear(filterInput); |
|
|
|
|
await userEvent.type(filterInput, 'label:"foo=~b.+"{Enter}'); |
|
|
|
|
await user.clear(filterInput); |
|
|
|
|
await user.type(filterInput, 'label:"foo=~b.+"{Enter}'); |
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(2)); |
|
|
|
|
|
|
|
|
|
await userEvent.clear(filterInput); |
|
|
|
|
await userEvent.type(filterInput, 'label:region=US{Enter}'); |
|
|
|
|
await user.clear(filterInput); |
|
|
|
|
await user.type(filterInput, 'label:region=US{Enter}'); |
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.queryAll()).toHaveLength(1)); |
|
|
|
|
await waitFor(() => expect(ui.ruleGroup.get()).toHaveTextContent('group-2')); |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
it.skip('uses entire group when reordering after filtering', async () => { |
|
|
|
|
const user = userEvent.setup(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
mocks.api.discoverFeaturesByUid.mockResolvedValue({ |
|
|
|
|
application: PromApplication.Cortex, |
|
|
|
@ -591,7 +583,7 @@ describe('RuleList', () => { |
|
|
|
|
const [firstReorderButton] = await screen.findAllByLabelText(/reorder/i); |
|
|
|
|
|
|
|
|
|
const filterInput = ui.rulesFilterInput.get(); |
|
|
|
|
await userEvent.type(filterInput, 'alert1a{Enter}'); |
|
|
|
|
await user.type(filterInput, 'alert1a{Enter}'); |
|
|
|
|
|
|
|
|
|
await user.click(firstReorderButton); |
|
|
|
|
|
|
|
|
@ -627,9 +619,7 @@ describe('RuleList', () => { |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
test('resuming paused alert rule', async () => { |
|
|
|
|
const user = userEvent.setup(); |
|
|
|
|
|
|
|
|
|
renderRuleList(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
// Expand the paused rule group so we can assert the rule state
|
|
|
|
|
await user.click(await ui.pausedRuleGroup.find()); |
|
|
|
@ -675,7 +665,7 @@ describe('RuleList', () => { |
|
|
|
|
Promise.resolve(dataSourceName === testDatasources.prom.name ? someRulerRules : {}) |
|
|
|
|
); |
|
|
|
|
|
|
|
|
|
await renderRuleList(); |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
expect(await ui.rulesFilterInput.find()).toHaveValue(''); |
|
|
|
|
|
|
|
|
@ -685,7 +675,7 @@ describe('RuleList', () => { |
|
|
|
|
expect(groups).toHaveLength(3); |
|
|
|
|
|
|
|
|
|
// open edit dialog
|
|
|
|
|
await userEvent.click(ui.editCloudGroupIcon.get(groups[0])); |
|
|
|
|
await user.click(ui.editCloudGroupIcon.get(groups[0])); |
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(ui.editGroupModal.dialog.get()).toBeInTheDocument()); |
|
|
|
|
|
|
|
|
@ -696,18 +686,20 @@ describe('RuleList', () => { |
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
testCase('rename both lotex namespace and group', async () => { |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
// make changes to form
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.namespaceInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.namespaceInput.get(), 'super namespace'); |
|
|
|
|
await user.clear(ui.editGroupModal.namespaceInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.namespaceInput.get(), 'super namespace'); |
|
|
|
|
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); |
|
|
|
|
await user.clear(ui.editGroupModal.ruleGroupInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); |
|
|
|
|
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
await user.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
|
|
|
|
|
// submit, check that appropriate calls were made
|
|
|
|
|
await userEvent.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
await user.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); |
|
|
|
|
|
|
|
|
@ -715,15 +707,17 @@ describe('RuleList', () => { |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
testCase('rename just the lotex group', async () => { |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
// make changes to form
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.ruleGroupInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); |
|
|
|
|
await user.clear(ui.editGroupModal.ruleGroupInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.ruleGroupInput.get(), 'super group'); |
|
|
|
|
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
await user.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
|
|
|
|
|
// submit, check that appropriate calls were made
|
|
|
|
|
await userEvent.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
await user.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); |
|
|
|
|
|
|
|
|
@ -731,12 +725,14 @@ describe('RuleList', () => { |
|
|
|
|
}); |
|
|
|
|
|
|
|
|
|
testCase('edit lotex group eval interval, no renaming', async () => { |
|
|
|
|
const { user } = await renderRuleList(); |
|
|
|
|
|
|
|
|
|
// make changes to form
|
|
|
|
|
await userEvent.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await userEvent.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
await user.clear(ui.editGroupModal.intervalInput.get()); |
|
|
|
|
await user.type(ui.editGroupModal.intervalInput.get(), '5m'); |
|
|
|
|
|
|
|
|
|
// submit, check that appropriate calls were made
|
|
|
|
|
await userEvent.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
await user.click(ui.editGroupModal.saveButton.get()); |
|
|
|
|
|
|
|
|
|
await waitFor(() => expect(ui.editGroupModal.namespaceInput.query()).not.toBeInTheDocument()); |
|
|
|
|
|
|
|
|
|