The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/public/app/features/alerting/unified/components/rules/RuleActionsButtons.test.tsx

197 lines
7.1 KiB

import { render, screen, userEvent } from 'test/test-utils';
import { byLabelText, byRole } from 'testing-library-selector';
import { config, locationService, setPluginLinksHook } from '@grafana/runtime';
import { interceptLinkClicks } from 'app/core/navigation/patch/interceptLinkClicks';
import { contextSrv } from 'app/core/services/context_srv';
import { RuleActionsButtons } from 'app/features/alerting/unified/components/rules/RuleActionsButtons';
import { setupMswServer } from 'app/features/alerting/unified/mockApi';
import {
getCloudRule,
getGrafanaRule,
grantUserPermissions,
mockDataSource,
mockGrafanaRulerRule,
mockPromAlertingRule,
} from 'app/features/alerting/unified/mocks';
import { MIMIR_DATASOURCE_UID } from 'app/features/alerting/unified/mocks/server/constants';
import { AccessControlAction } from 'app/types';
import { PromAlertingRuleState } from 'app/types/unified-alerting-dto';
import { setupDataSources } from '../../testSetup/datasources';
import { fromCombinedRule, stringifyIdentifier } from '../../utils/rule-id';
setupMswServer();
jest.mock('app/core/services/context_srv');
const mockContextSrv = jest.mocked(contextSrv);
const ui = {
detailsButton: byRole('link', { name: /View/ }),
menu: byRole('menu'),
moreButton: byLabelText(/More/),
pauseButton: byRole('menuitem', { name: /Pause evaluation/ }),
};
const grantAllPermissions = () => {
grantUserPermissions([
AccessControlAction.AlertingRuleCreate,
AccessControlAction.AlertingRuleRead,
AccessControlAction.AlertingRuleUpdate,
AccessControlAction.AlertingRuleDelete,
AccessControlAction.AlertingInstanceCreate,
AccessControlAction.AlertingRuleExternalRead,
AccessControlAction.AlertingRuleExternalWrite,
]);
mockContextSrv.hasPermissionInMetadata.mockImplementation(() => true);
mockContextSrv.hasPermission.mockImplementation(() => true);
};
const grantNoPermissions = () => {
grantUserPermissions([]);
mockContextSrv.hasPermissionInMetadata.mockImplementation(() => false);
mockContextSrv.hasPermission.mockImplementation(() => false);
};
const getMenuContents = async () => {
await screen.findByRole('menu');
const allMenuItems = screen.queryAllByRole('menuitem').map((el) => el.textContent);
const allLinkItems = screen.queryAllByRole('link').map((el) => el.textContent);
return [...allMenuItems, ...allLinkItems];
};
setPluginLinksHook(() => ({
links: [],
isLoading: false,
}));
const mimirDs = mockDataSource({ uid: MIMIR_DATASOURCE_UID, name: 'Mimir' });
const prometheusDs = mockDataSource({ uid: 'prometheus', name: 'Prometheus' });
setupDataSources(mimirDs, prometheusDs);
const clickCopyLink = async () => {
const user = userEvent.setup();
await user.click(await ui.moreButton.find());
await user.click(await screen.findByText(/copy link/i));
};
describe('RuleActionsButtons', () => {
it('renders correct options for grafana managed rule', async () => {
const user = userEvent.setup();
grantAllPermissions();
const mockRule = getGrafanaRule();
render(<RuleActionsButtons rule={mockRule} rulesSource="grafana" />);
await user.click(await ui.moreButton.find());
expect(await getMenuContents()).toMatchSnapshot();
});
it('should be able to pause a Grafana rule', async () => {
const user = userEvent.setup();
grantAllPermissions();
const mockRule = getGrafanaRule();
render(<RuleActionsButtons rule={mockRule} rulesSource="grafana" />);
await user.click(await ui.moreButton.find());
await user.click(await ui.pauseButton.find());
expect(ui.menu.query()).not.toBeInTheDocument();
});
it('renders correct options for Cloud rule', async () => {
const user = userEvent.setup();
grantAllPermissions();
const mockRule = getCloudRule(undefined, { rulesSource: mimirDs });
render(<RuleActionsButtons rule={mockRule} rulesSource={mimirDs} />);
await user.click(await ui.moreButton.find());
expect(await getMenuContents()).toMatchSnapshot();
});
it('view rule button should properly handle special characters in rule name', async () => {
// Production setup uses the link interceptor to push all link clicks through the location service
// and history object under the hood
// It causes issues due to the bug in history library that causes the pathname to be decoded
// https://github.com/remix-run/history/issues/505#issuecomment-453175833
document.addEventListener('click', interceptLinkClicks);
grantAllPermissions();
const mockRule = getCloudRule({ name: 'special !@#$%^&*() chars' }, { rulesSource: mimirDs });
const { user } = render(<RuleActionsButtons rule={mockRule} rulesSource={mimirDs} showViewButton />, {
renderWithRouter: true,
});
const locationPushSpy = jest.spyOn(locationService, 'push');
await user.click(await ui.detailsButton.find());
const ruleId = fromCombinedRule(mimirDs.name, mockRule);
const stringifiedRuleId = stringifyIdentifier(ruleId);
const expectedPath = `/alerting/${encodeURIComponent(mimirDs.name)}/${encodeURIComponent(stringifiedRuleId)}/view`;
// Check if the interceptor worked
expect(locationPushSpy).toHaveBeenCalledWith(expectedPath);
// Check if the location service has the correct pathname
expect(locationService.getLocation().pathname).toBe(expectedPath);
});
it('renders minimal "More" menu when appropriate', async () => {
const user = userEvent.setup();
grantNoPermissions();
const mockRule = getGrafanaRule({ promRule: mockPromAlertingRule({ state: PromAlertingRuleState.Inactive }) });
render(<RuleActionsButtons rule={mockRule} rulesSource="grafana" />);
await user.click(await ui.moreButton.find());
expect(await getMenuContents()).toMatchSnapshot();
});
it('does not allow deletion when rule is provisioned', async () => {
const user = userEvent.setup();
grantAllPermissions();
const mockRule = getGrafanaRule({ rulerRule: mockGrafanaRulerRule({ provenance: 'file' }) });
render(<RuleActionsButtons rule={mockRule} rulesSource="grafana" />);
await user.click(await ui.moreButton.find());
expect(screen.queryByText(/delete/i)).not.toBeInTheDocument();
});
describe('copy link', () => {
beforeEach(() => {
grantAllPermissions();
config.appUrl = 'http://localhost:3000/';
config.appSubUrl = '/sub';
});
it('copies correct URL for grafana managed alert rule', async () => {
const mockRule = getGrafanaRule({ rulerRule: mockGrafanaRulerRule({ uid: 'foo', provenance: 'file' }) });
render(<RuleActionsButtons rule={mockRule} rulesSource="grafana" />);
await clickCopyLink();
expect(await navigator.clipboard.readText()).toBe('http://localhost:3000/sub/alerting/grafana/foo/view');
});
it('copies correct URL for cloud rule', async () => {
const mockRule = getCloudRule({ name: 'pod-1-cpu-firing' }, { rulesSource: prometheusDs });
render(<RuleActionsButtons rule={mockRule} rulesSource={prometheusDs} />);
await clickCopyLink();
expect(await navigator.clipboard.readText()).toBe(
'http://localhost:3000/sub/alerting/Prometheus/pod-1-cpu-firing/find'
);
});
});
});