mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
741 lines
24 KiB
TypeScript
741 lines
24 KiB
TypeScript
import selectEvent from 'react-select-event';
|
||
import {EnvironmentsFixture} from 'sentry-fixture/environments';
|
||
import {GroupsFixture} from 'sentry-fixture/groups';
|
||
import {LocationFixture} from 'sentry-fixture/locationFixture';
|
||
import {OrganizationFixture} from 'sentry-fixture/organization';
|
||
import {ProjectAlertRuleFixture} from 'sentry-fixture/projectAlertRule';
|
||
import {ProjectAlertRuleConfigurationFixture} from 'sentry-fixture/projectAlertRuleConfiguration';
|
||
import {RouteComponentPropsFixture} from 'sentry-fixture/routeComponentPropsFixture';
|
||
|
||
import {initializeOrg} from 'sentry-test/initializeOrg';
|
||
import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
|
||
|
||
import ProjectsStore from 'sentry/stores/projectsStore';
|
||
import TeamStore from 'sentry/stores/teamStore';
|
||
import {metric, trackAnalytics} from 'sentry/utils/analytics';
|
||
import AlertsContainer from 'sentry/views/alerts';
|
||
import AlertBuilderProjectProvider from 'sentry/views/alerts/builder/projectProvider';
|
||
import ProjectAlertsCreate from 'sentry/views/alerts/create';
|
||
|
||
jest.unmock('sentry/utils/recreateRoute');
|
||
// updateOnboardingTask triggers an out of band state update
|
||
jest.mock('sentry/actionCreators/onboardingTasks');
|
||
jest.mock('sentry/actionCreators/members', () => ({
|
||
fetchOrgMembers: jest.fn(() => Promise.resolve([])),
|
||
indexMembersByProject: jest.fn(() => {
|
||
return {};
|
||
}),
|
||
}));
|
||
jest.mock('react-router');
|
||
jest.mock('sentry/utils/analytics', () => ({
|
||
metric: {
|
||
startTransaction: jest.fn(() => ({
|
||
setTag: jest.fn(),
|
||
setData: jest.fn(),
|
||
})),
|
||
endTransaction: jest.fn(),
|
||
mark: jest.fn(),
|
||
measure: jest.fn(),
|
||
},
|
||
trackAnalytics: jest.fn(),
|
||
}));
|
||
|
||
describe('ProjectAlertsCreate', function () {
|
||
beforeEach(function () {
|
||
TeamStore.init();
|
||
TeamStore.loadInitialData([], false, null);
|
||
MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/configuration/',
|
||
body: ProjectAlertRuleConfigurationFixture(),
|
||
});
|
||
MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/1/',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/environments/',
|
||
body: EnvironmentsFixture(),
|
||
});
|
||
MockApiClient.addMockResponse({
|
||
url: `/projects/org-slug/project-slug/?expand=hasAlertIntegration`,
|
||
body: {},
|
||
});
|
||
MockApiClient.addMockResponse({
|
||
url: `/projects/org-slug/project-slug/ownership/`,
|
||
method: 'GET',
|
||
body: {
|
||
fallthrough: false,
|
||
autoAssignment: false,
|
||
},
|
||
});
|
||
MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/preview/',
|
||
method: 'POST',
|
||
body: [],
|
||
});
|
||
});
|
||
|
||
afterEach(function () {
|
||
MockApiClient.clearMockResponses();
|
||
jest.clearAllMocks();
|
||
});
|
||
|
||
const createWrapper = (props = {}, location = {}) => {
|
||
const {organization, project, router, routerContext} = initializeOrg(props);
|
||
ProjectsStore.loadInitialData([project]);
|
||
const params = {orgId: organization.slug, projectId: project.slug};
|
||
const wrapper = render(
|
||
<AlertsContainer>
|
||
<AlertBuilderProjectProvider
|
||
{...RouteComponentPropsFixture()}
|
||
params={params}
|
||
organization={organization}
|
||
hasMetricAlerts={false}
|
||
>
|
||
<ProjectAlertsCreate
|
||
{...RouteComponentPropsFixture()}
|
||
hasMetricAlerts={false}
|
||
members={[]}
|
||
params={params}
|
||
organization={organization}
|
||
project={project}
|
||
location={LocationFixture({
|
||
pathname: `/organizations/org-slug/alerts/rules/${project.slug}/new/`,
|
||
query: {createFromWizard: 'true'},
|
||
...location,
|
||
})}
|
||
router={router}
|
||
/>
|
||
</AlertBuilderProjectProvider>
|
||
</AlertsContainer>,
|
||
{organization, context: routerContext}
|
||
);
|
||
|
||
return {
|
||
wrapper,
|
||
organization,
|
||
project,
|
||
router,
|
||
};
|
||
};
|
||
|
||
it('adds default parameters if wizard was skipped', async function () {
|
||
const location = {query: {}};
|
||
const wrapper = createWrapper(undefined, location);
|
||
await waitFor(() => {
|
||
expect(wrapper.router.replace).toHaveBeenCalledWith(
|
||
expect.objectContaining({
|
||
pathname: '/organizations/org-slug/alerts/new/metric',
|
||
query: {
|
||
aggregate: 'count()',
|
||
dataset: 'events',
|
||
eventTypes: 'error',
|
||
project: 'project-slug',
|
||
},
|
||
})
|
||
);
|
||
});
|
||
});
|
||
|
||
describe('Issue Alert', function () {
|
||
it('loads default values', async function () {
|
||
createWrapper();
|
||
expect(await screen.findByText('All Environments')).toBeInTheDocument();
|
||
expect(await screen.findByText('any')).toBeInTheDocument();
|
||
expect(await screen.findByText('all')).toBeInTheDocument();
|
||
expect(await screen.findByText('24 hours')).toBeInTheDocument();
|
||
});
|
||
|
||
it('can remove filters', async function () {
|
||
createWrapper();
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
|
||
// Change name of alert rule
|
||
await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname');
|
||
|
||
// Add a filter and remove it
|
||
await selectEvent.select(screen.getByText('Add optional filter...'), [
|
||
'The issue is older or newer than...',
|
||
]);
|
||
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[1]);
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
actions: [],
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
filterMatch: 'all',
|
||
filters: [],
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
});
|
||
});
|
||
|
||
it('can remove triggers', async function () {
|
||
const {organization} = createWrapper();
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
// delete node
|
||
await userEvent.click(screen.getByLabelText('Delete Node'));
|
||
|
||
// Change name of alert rule
|
||
await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname');
|
||
|
||
// Add a trigger and remove it
|
||
await selectEvent.select(screen.getByText('Add optional trigger...'), [
|
||
'A new issue is created',
|
||
]);
|
||
|
||
await userEvent.click(screen.getByLabelText('Delete Node'));
|
||
|
||
await waitFor(() => {
|
||
expect(trackAnalytics).toHaveBeenCalledWith('edit_alert_rule.add_row', {
|
||
name: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
organization,
|
||
project_id: '2',
|
||
type: 'conditions',
|
||
});
|
||
});
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
actions: [],
|
||
conditions: [],
|
||
filterMatch: 'all',
|
||
filters: [],
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
});
|
||
});
|
||
|
||
it('can remove actions', async function () {
|
||
createWrapper();
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
|
||
// Change name of alert rule
|
||
await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname');
|
||
|
||
// Add an action and remove it
|
||
await selectEvent.select(screen.getByText('Add action...'), [
|
||
'Send a notification to all legacy integrations',
|
||
]);
|
||
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[1]);
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
actions: [],
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
filterMatch: 'all',
|
||
filters: [],
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
});
|
||
});
|
||
|
||
describe('updates and saves', function () {
|
||
let mock;
|
||
|
||
beforeEach(function () {
|
||
mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
});
|
||
|
||
afterEach(function () {
|
||
jest.clearAllMocks();
|
||
});
|
||
|
||
it('environment, async action and filter match', async function () {
|
||
const wrapper = createWrapper();
|
||
|
||
// Change target environment
|
||
await selectEvent.select(screen.getByText('All Environments'), ['production']);
|
||
|
||
// Change actionMatch and filterMatch dropdown
|
||
const anyDropdown = screen.getByText('any');
|
||
expect(anyDropdown).toBeInTheDocument();
|
||
const allDropdown = screen.getByText('all');
|
||
expect(allDropdown).toBeInTheDocument();
|
||
|
||
await selectEvent.select(anyDropdown, ['all']);
|
||
await selectEvent.select(allDropdown, ['any']);
|
||
|
||
// Change name of alert rule
|
||
await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname');
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'all',
|
||
filterMatch: 'any',
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
actions: [],
|
||
filters: [],
|
||
environment: 'production',
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
expect(metric.startTransaction).toHaveBeenCalledWith({name: 'saveAlertRule'});
|
||
|
||
await waitFor(() => {
|
||
expect(wrapper.router.push).toHaveBeenCalledWith({
|
||
pathname: '/organizations/org-slug/alerts/rules/project-slug/1/details/',
|
||
});
|
||
});
|
||
});
|
||
|
||
it('new condition', async function () {
|
||
const wrapper = createWrapper();
|
||
|
||
// Change name of alert rule
|
||
await userEvent.click(screen.getByPlaceholderText('Enter Alert Name'));
|
||
await userEvent.paste('myname');
|
||
|
||
// Add another condition
|
||
await selectEvent.select(screen.getByText('Add optional filter...'), [
|
||
"The event's tags match {key} {match} {value}",
|
||
]);
|
||
// Edit new Condition
|
||
await userEvent.click(screen.getByPlaceholderText('key'));
|
||
await userEvent.paste('conditionKey');
|
||
await userEvent.click(screen.getByPlaceholderText('value'));
|
||
await userEvent.paste('conditionValue');
|
||
await selectEvent.select(screen.getByText('contains'), ['does not equal']);
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
actions: [],
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
filterMatch: 'all',
|
||
filters: [
|
||
{
|
||
id: 'sentry.rules.filters.tagged_event.TaggedEventFilter',
|
||
key: 'conditionKey',
|
||
match: 'ne',
|
||
value: 'conditionValue',
|
||
},
|
||
],
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
expect(metric.startTransaction).toHaveBeenCalledWith({name: 'saveAlertRule'});
|
||
|
||
await waitFor(() => {
|
||
expect(wrapper.router.push).toHaveBeenCalledWith({
|
||
pathname: '/organizations/org-slug/alerts/rules/project-slug/1/details/',
|
||
});
|
||
});
|
||
});
|
||
|
||
it('new filter', async function () {
|
||
const wrapper = createWrapper();
|
||
|
||
// Change name of alert rule
|
||
await userEvent.click(screen.getByPlaceholderText('Enter Alert Name'));
|
||
await userEvent.paste('myname');
|
||
// delete one condition
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[0]);
|
||
|
||
// Add a new filter
|
||
await selectEvent.select(screen.getByText('Add optional filter...'), [
|
||
'The issue is older or newer than...',
|
||
]);
|
||
await userEvent.click(screen.getByPlaceholderText('10'));
|
||
await userEvent.paste('12');
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
filterMatch: 'all',
|
||
filters: [
|
||
{
|
||
id: 'sentry.rules.filters.age_comparison.AgeComparisonFilter',
|
||
comparison_type: 'older',
|
||
time: 'minute',
|
||
value: '12',
|
||
},
|
||
],
|
||
actions: [],
|
||
conditions: [],
|
||
frequency: 60 * 24,
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
expect(metric.startTransaction).toHaveBeenCalledWith({name: 'saveAlertRule'});
|
||
|
||
await waitFor(() => {
|
||
expect(wrapper.router.push).toHaveBeenCalledWith({
|
||
pathname: '/organizations/org-slug/alerts/rules/project-slug/1/details/',
|
||
});
|
||
});
|
||
});
|
||
|
||
it('new action', async function () {
|
||
const wrapper = createWrapper();
|
||
|
||
// Change name of alert rule
|
||
await userEvent.type(screen.getByPlaceholderText('Enter Alert Name'), 'myname');
|
||
|
||
// Add a new action
|
||
await selectEvent.select(screen.getByText('Add action...'), [
|
||
'Suggested Assignees, Team, or Member',
|
||
]);
|
||
|
||
// Update action interval
|
||
await selectEvent.select(screen.getByText('24 hours'), ['60 minutes']);
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
actions: [
|
||
{id: 'sentry.mail.actions.NotifyEmailAction', targetType: 'IssueOwners'},
|
||
],
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
filterMatch: 'all',
|
||
filters: [],
|
||
frequency: '60',
|
||
name: 'myname',
|
||
owner: null,
|
||
},
|
||
})
|
||
);
|
||
expect(metric.startTransaction).toHaveBeenCalledWith({name: 'saveAlertRule'});
|
||
|
||
await waitFor(() => {
|
||
expect(wrapper.router.push).toHaveBeenCalledWith({
|
||
pathname: '/organizations/org-slug/alerts/rules/project-slug/1/details/',
|
||
});
|
||
});
|
||
});
|
||
});
|
||
});
|
||
|
||
describe('test preview chart', () => {
|
||
it('valid preview table', async () => {
|
||
const groups = GroupsFixture();
|
||
const date = new Date();
|
||
for (let i = 0; i < groups.length; i++) {
|
||
groups[i].lastTriggered = String(date);
|
||
}
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/preview/',
|
||
method: 'POST',
|
||
body: groups,
|
||
headers: {
|
||
'X-Hits': String(groups.length),
|
||
Endpoint: 'endpoint',
|
||
},
|
||
});
|
||
createWrapper();
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalledWith(
|
||
expect.any(String),
|
||
expect.objectContaining({
|
||
data: {
|
||
actionMatch: 'any',
|
||
conditions: [
|
||
expect.objectContaining({
|
||
id: 'sentry.rules.conditions.first_seen_event.FirstSeenEventCondition',
|
||
}),
|
||
],
|
||
filterMatch: 'all',
|
||
filters: [],
|
||
frequency: 60 * 24,
|
||
endpoint: null,
|
||
},
|
||
})
|
||
);
|
||
});
|
||
expect(
|
||
screen.getByText('4 issues would have triggered this rule in the past 14 days', {
|
||
exact: false,
|
||
})
|
||
).toBeInTheDocument();
|
||
for (const group of groups) {
|
||
expect(screen.getByText(group.shortId)).toBeInTheDocument();
|
||
}
|
||
expect(screen.getAllByText('3mo ago')[0]).toBeInTheDocument();
|
||
});
|
||
|
||
it('invalid preview alert', async () => {
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/preview/',
|
||
method: 'POST',
|
||
statusCode: 400,
|
||
});
|
||
createWrapper();
|
||
// delete existion condition
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[0]);
|
||
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalled();
|
||
});
|
||
expect(
|
||
screen.getByText('Select a condition to generate a preview')
|
||
).toBeInTheDocument();
|
||
|
||
await selectEvent.select(screen.getByText('Add optional trigger...'), [
|
||
'A new issue is created',
|
||
]);
|
||
expect(
|
||
screen.getByText('Preview is not supported for these conditions')
|
||
).toBeInTheDocument();
|
||
});
|
||
|
||
it('empty preview table', async () => {
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/preview/',
|
||
method: 'POST',
|
||
body: [],
|
||
headers: {
|
||
'X-Hits': '0',
|
||
Endpoint: 'endpoint',
|
||
},
|
||
});
|
||
createWrapper();
|
||
await waitFor(() => {
|
||
expect(mock).toHaveBeenCalled();
|
||
});
|
||
expect(
|
||
screen.getByText("We couldn't find any issues that would've triggered your rule")
|
||
).toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
describe('test incompatible conditions', () => {
|
||
const errorText =
|
||
'The conditions highlighted in red are in conflict. They may prevent the alert from ever being triggered.';
|
||
|
||
it('shows error for incompatible conditions', async () => {
|
||
createWrapper();
|
||
const anyDropdown = screen.getByText('any');
|
||
expect(anyDropdown).toBeInTheDocument();
|
||
await selectEvent.select(anyDropdown, ['all']);
|
||
|
||
await selectEvent.select(screen.getByText('Add optional trigger...'), [
|
||
'The issue changes state from resolved to unresolved',
|
||
]);
|
||
expect(screen.getByText(errorText)).toBeInTheDocument();
|
||
|
||
expect(screen.getByRole('button', {name: 'Save Rule'})).toHaveAttribute(
|
||
'aria-disabled',
|
||
'true'
|
||
);
|
||
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[0]);
|
||
expect(screen.queryByText(errorText)).not.toBeInTheDocument();
|
||
});
|
||
|
||
it('test any filterMatch', async () => {
|
||
createWrapper();
|
||
const allDropdown = screen.getByText('all');
|
||
await selectEvent.select(allDropdown, ['any']);
|
||
await selectEvent.select(screen.getByText('Add optional filter...'), [
|
||
'The issue is older or newer than...',
|
||
]);
|
||
|
||
await userEvent.type(screen.getByPlaceholderText('10'), '10');
|
||
await userEvent.click(document.body);
|
||
|
||
await selectEvent.select(screen.getByText('Add optional filter...'), [
|
||
'The issue has happened at least {x} times (Note: this is approximate)',
|
||
]);
|
||
|
||
expect(screen.getByText(errorText)).toBeInTheDocument();
|
||
|
||
await userEvent.click(screen.getAllByLabelText('Delete Node')[1]);
|
||
await userEvent.clear(screen.getByDisplayValue('10'));
|
||
await userEvent.click(document.body);
|
||
|
||
expect(screen.queryByText(errorText)).not.toBeInTheDocument();
|
||
});
|
||
});
|
||
|
||
it('shows archived to escalating instead of ignored to unresolved', async () => {
|
||
createWrapper({
|
||
organization: OrganizationFixture(),
|
||
});
|
||
await selectEvent.select(screen.getByText('Add optional trigger...'), [
|
||
'The issue changes state from archived to escalating',
|
||
]);
|
||
|
||
expect(
|
||
screen.getByText('The issue changes state from archived to escalating')
|
||
).toBeInTheDocument();
|
||
});
|
||
|
||
it('displays noisy alert checkbox for no conditions + filters', async function () {
|
||
const mock = MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
body: ProjectAlertRuleFixture(),
|
||
});
|
||
|
||
createWrapper({organization: {features: ['noisy-alert-warning']}});
|
||
await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]);
|
||
|
||
await selectEvent.select(screen.getByText('Add action...'), [
|
||
'Suggested Assignees, Team, or Member',
|
||
]);
|
||
|
||
expect(
|
||
screen.getByText(/Alerts without conditions can fire too frequently/)
|
||
).toBeInTheDocument();
|
||
expect(trackAnalytics).toHaveBeenCalledWith(
|
||
'alert_builder.noisy_warning_viewed',
|
||
expect.anything()
|
||
);
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).not.toHaveBeenCalled();
|
||
|
||
await userEvent.click(
|
||
screen.getByRole('checkbox', {name: 'Yes, I don’t mind if this alert gets noisy'})
|
||
);
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
expect(mock).toHaveBeenCalled();
|
||
expect(trackAnalytics).toHaveBeenCalledWith(
|
||
'alert_builder.noisy_warning_agreed',
|
||
expect.anything()
|
||
);
|
||
});
|
||
|
||
it('does not display noisy alert banner for legacy integrations', async function () {
|
||
createWrapper({organization: {features: ['noisy-alert-warning']}});
|
||
await userEvent.click((await screen.findAllByLabelText('Delete Node'))[0]);
|
||
|
||
await selectEvent.select(screen.getByText('Add action...'), [
|
||
'Send a notification to all legacy integrations',
|
||
]);
|
||
|
||
expect(
|
||
screen.queryByText(/Alerts without conditions can fire too frequently/)
|
||
).not.toBeInTheDocument();
|
||
|
||
await selectEvent.select(screen.getByText('Add action...'), [
|
||
'Suggested Assignees, Team, or Member',
|
||
]);
|
||
|
||
expect(
|
||
screen.getByText(/Alerts without conditions can fire too frequently/)
|
||
).toBeInTheDocument();
|
||
});
|
||
|
||
it('displays duplicate error banner with link', async function () {
|
||
MockApiClient.addMockResponse({
|
||
url: '/projects/org-slug/project-slug/rules/',
|
||
method: 'POST',
|
||
statusCode: 400,
|
||
body: {
|
||
name: [
|
||
"This rule is an exact duplicate of 'test alert' in this project and may not be created.",
|
||
],
|
||
ruleId: [1337],
|
||
},
|
||
});
|
||
|
||
createWrapper();
|
||
|
||
await userEvent.click(screen.getByText('Save Rule'));
|
||
|
||
const bannerLink = await screen.findByRole('link', {
|
||
name: /rule fully duplicates "test alert"/,
|
||
});
|
||
expect(bannerLink).toBeInTheDocument();
|
||
expect(bannerLink).toHaveAttribute(
|
||
'href',
|
||
'/organizations/org-slug/alerts/rules/project-slug/1337/details/'
|
||
);
|
||
});
|
||
});
|