123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370 |
- import {browserHistory} from 'react-router';
- import moment from 'moment';
- import {Organization} from 'sentry-fixture/organization';
- import {ProjectAlertRule} from 'sentry-fixture/projectAlertRule';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import AlertRuleDetails from './ruleDetails';
- describe('AlertRuleDetails', () => {
- const context = initializeOrg();
- const organization = context.organization;
- const project = TestStubs.Project();
- const rule = ProjectAlertRule({
- lastTriggered: moment().subtract(2, 'day').format(),
- });
- const member = TestStubs.Member();
- const createWrapper = (props: any = {}, newContext?: any, org = organization) => {
- const router = newContext ? newContext.router : context.router;
- const routerContext = newContext ? newContext.routerContext : context.routerContext;
- return render(
- <AlertRuleDetails
- params={{
- orgId: org.slug,
- projectId: project.slug,
- ruleId: rule.id,
- }}
- location={{...router.location, query: {}}}
- router={router}
- {...props}
- />,
- {context: routerContext, organization: org}
- );
- };
- beforeEach(() => {
- browserHistory.push = jest.fn();
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- body: rule,
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/stats/`,
- body: [],
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/group-history/`,
- body: [
- {
- count: 1,
- group: TestStubs.Group(),
- lastTriggered: moment('Apr 11, 2019 1:08:59 AM UTC').format(),
- eventId: 'eventId',
- },
- ],
- headers: {
- Link:
- '<https://sentry.io/api/0/projects/org-slug/project-slug/rules/1/group-history/?cursor=0:0:1>; rel="previous"; results="false"; cursor="0:0:1", ' +
- '<https://sentry.io/api/0/projects/org-slug/project-slug/rules/1/group-history/?cursor=0:100:0>; rel="next"; results="true"; cursor="0:100:0"',
- },
- });
- MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/projects/`,
- body: [project],
- });
- MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/users/`,
- body: [member],
- });
- ProjectsStore.init();
- ProjectsStore.loadInitialData([project]);
- });
- afterEach(() => {
- ProjectsStore.reset();
- MockApiClient.clearMockResponses();
- });
- it('displays alert rule with list of issues', async () => {
- createWrapper();
- expect(await screen.findAllByText('My alert rule')).toHaveLength(2);
- expect(screen.getByText('RequestError:')).toBeInTheDocument();
- expect(screen.getByText('Apr 11, 2019 1:08:59 AM UTC')).toBeInTheDocument();
- expect(screen.getByText('RequestError:')).toHaveAttribute(
- 'href',
- expect.stringMatching(
- RegExp(
- `/organizations/${organization.slug}/issues/${
- TestStubs.Group().id
- }/events/eventId.*`
- )
- )
- );
- });
- it('should allow paginating results', async () => {
- createWrapper();
- expect(await screen.findByLabelText('Next')).toBeEnabled();
- await userEvent.click(screen.getByLabelText('Next'));
- expect(browserHistory.push).toHaveBeenCalledWith({
- pathname: '/mock-pathname/',
- query: {
- cursor: '0:100:0',
- },
- });
- });
- it('should reset pagination cursor on date change', async () => {
- createWrapper();
- const dateSelector = await screen.findByText('7D');
- expect(dateSelector).toBeInTheDocument();
- await userEvent.click(dateSelector);
- await userEvent.click(screen.getByRole('option', {name: 'Last 24 hours'}));
- expect(context.router.push).toHaveBeenCalledWith(
- expect.objectContaining({
- query: {
- pageStatsPeriod: '24h',
- cursor: undefined,
- pageEnd: undefined,
- pageStart: undefined,
- pageUtc: undefined,
- },
- })
- );
- });
- it('should show the time since last triggered in sidebar', async () => {
- createWrapper();
- expect(await screen.findAllByText('Last Triggered')).toHaveLength(2);
- expect(screen.getByText('2 days ago')).toBeInTheDocument();
- });
- it('renders not found on 404', async () => {
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- statusCode: 404,
- body: {},
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- createWrapper();
- expect(
- await screen.findByText('The alert rule you were looking for was not found.')
- ).toBeInTheDocument();
- });
- it('renders incompatible rule filter', async () => {
- const incompatibleRule = TestStubs.ProjectAlertRule({
- conditions: [
- {id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition'},
- {id: 'sentry.rules.conditions.regression_event.RegressionEventCondition'},
- ],
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- body: incompatibleRule,
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- createWrapper();
- expect(
- await screen.findByText(
- 'The conditions in this alert rule conflict and might not be working properly.'
- )
- ).toBeInTheDocument();
- });
- it('incompatible rule banner hidden for good rule', async () => {
- createWrapper();
- expect(await screen.findAllByText('My alert rule')).toHaveLength(2);
- expect(
- screen.queryByText(
- 'The conditions in this alert rule conflict and might not be working properly.'
- )
- ).not.toBeInTheDocument();
- });
- it('rule disabled banner because of missing actions and hides some actions', async () => {
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- body: ProjectAlertRule({
- actions: [],
- status: 'disabled',
- }),
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- createWrapper();
- expect(
- await screen.findByText(
- 'This alert is disabled due to missing actions. Please edit the alert rule to enable this alert.'
- )
- ).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Edit to enable'})).toBeInTheDocument();
- expect(screen.getByRole('button', {name: 'Duplicate'})).toBeDisabled();
- expect(screen.getByRole('button', {name: 'Mute for me'})).toBeDisabled();
- });
- it('rule disabled banner generic', async () => {
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- body: ProjectAlertRule({
- status: 'disabled',
- }),
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- createWrapper();
- expect(
- await screen.findByText(
- 'This alert is disabled due to its configuration and needs to be edited to be enabled.'
- )
- ).toBeInTheDocument();
- });
- it('rule to be disabled can opt out', async () => {
- const disabledRule = ProjectAlertRule({
- disableDate: moment().add(1, 'day').format(),
- disableReason: 'noisy',
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${disabledRule.id}/`,
- body: disabledRule,
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- const updateMock = MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${disabledRule.id}/`,
- method: 'PUT',
- });
- createWrapper();
- expect(
- await screen.findByText(/This alert is scheduled to be disabled/)
- ).toBeInTheDocument();
- await userEvent.click(screen.getByRole('button', {name: 'click here'}));
- expect(updateMock).toHaveBeenCalledWith(
- expect.anything(),
- expect.objectContaining({data: {...disabledRule, optOutExplicit: true}})
- );
- expect(
- screen.queryByText(/This alert is scheduled to be disabled/)
- ).not.toBeInTheDocument();
- });
- it('disabled rule can be re-enabled', async () => {
- const disabledRule = ProjectAlertRule({
- status: 'disabled',
- disableDate: moment().subtract(1, 'day').format(),
- disableReason: 'noisy',
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${disabledRule.id}/`,
- body: disabledRule,
- match: [MockApiClient.matchQuery({expand: 'lastTriggered'})],
- });
- const enableMock = MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${disabledRule.id}/enable/`,
- method: 'PUT',
- });
- createWrapper();
- expect(
- await screen.findByText(/This alert was disabled due to lack of activity/)
- ).toBeInTheDocument();
- await userEvent.click(screen.getByRole('button', {name: 'click here'}));
- expect(enableMock).toHaveBeenCalled();
- expect(
- screen.queryByText(/This alert was disabled due to lack of activity/)
- ).not.toBeInTheDocument();
- expect(screen.queryByText(/This alert is disabled/)).not.toBeInTheDocument();
- });
- it('renders the mute button and can mute/unmute alerts', async () => {
- const postRequest = MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/snooze/`,
- method: 'POST',
- });
- const deleteRequest = MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/snooze/`,
- method: 'DELETE',
- });
- createWrapper();
- expect(await screen.findByText('Mute for everyone')).toBeInTheDocument();
- await userEvent.click(screen.getByRole('button', {name: 'Mute for everyone'}));
- expect(postRequest).toHaveBeenCalledWith(
- expect.anything(),
- expect.objectContaining({data: {target: 'everyone'}})
- );
- expect(await screen.findByText('Unmute')).toBeInTheDocument();
- await userEvent.click(screen.getByRole('button', {name: 'Unmute'}));
- expect(deleteRequest).toHaveBeenCalledTimes(1);
- });
- it('mutes alert if query parameter is set', async () => {
- const request = MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/snooze/`,
- method: 'POST',
- });
- const contextWithQueryParam = initializeOrg({
- router: {
- location: {query: {mute: '1'}},
- },
- });
- createWrapper({}, contextWithQueryParam);
- expect(await screen.findByText('Unmute')).toBeInTheDocument();
- expect(request).toHaveBeenCalledWith(
- expect.anything(),
- expect.objectContaining({
- data: {target: 'everyone'},
- })
- );
- });
- it('mute button is disabled if no alerts:write permission', async () => {
- const orgWithoutAccess = Organization({
- access: [],
- });
- const contextWithoutAccess = initializeOrg({
- organization: orgWithoutAccess,
- });
- createWrapper({}, contextWithoutAccess, orgWithoutAccess);
- expect(await screen.findByRole('button', {name: 'Mute for everyone'})).toBeDisabled();
- });
- it('inserts user email into rule notify action', async () => {
- // Alert rule with "send a notification to member" action
- const sendNotificationRule = TestStubs.ProjectAlertRule({
- actions: [
- {
- id: 'sentry.mail.actions.NotifyEmailAction',
- name: 'Send a notification to Member and if none can be found then send a notification to ActiveMembers',
- targetIdentifier: member.id,
- targetType: 'Member',
- },
- ],
- });
- MockApiClient.addMockResponse({
- url: `/projects/${organization.slug}/${project.slug}/rules/${rule.id}/`,
- body: sendNotificationRule,
- });
- createWrapper();
- expect(
- await screen.findByText(`Send a notification to ${member.email}`)
- ).toBeInTheDocument();
- });
- });
|