import {browserHistory} from 'react-router';
import moment from 'moment';
import {GroupFixture} from 'sentry-fixture/group';
import {MemberFixture} from 'sentry-fixture/member';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';
import {ProjectAlertRuleFixture} 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 = ProjectFixture();
const rule = ProjectAlertRuleFixture({
lastTriggered: moment().subtract(2, 'day').format(),
});
const member = MemberFixture();
const createWrapper = (props: any = {}, newContext?: any, org = organization) => {
const router = newContext ? newContext.router : context.router;
const routerContext = newContext ? newContext.routerContext : context.routerContext;
return render(
,
{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: GroupFixture(),
lastTriggered: moment('Apr 11, 2019 1:08:59 AM UTC').format(),
eventId: 'eventId',
},
],
headers: {
Link:
'; rel="previous"; results="false"; cursor="0:0:1", ' +
'; 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(await screen.findByText('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/${
GroupFixture().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 = ProjectAlertRuleFixture({
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: ProjectAlertRuleFixture({
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: ProjectAlertRuleFixture({
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 = ProjectAlertRuleFixture({
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 = ProjectAlertRuleFixture({
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 = OrganizationFixture({
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 = ProjectAlertRuleFixture({
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();
});
});