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();
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();
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();
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(, {
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();
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();
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();
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();
await clickCopyLink();
expect(await navigator.clipboard.readText()).toBe(
'http://localhost:3000/sub/alerting/Prometheus/pod-1-cpu-firing/find'
);
});
});
});