123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669 |
- import {mountWithTheme} from 'sentry-test/enzyme';
- import {initializeOrg} from 'sentry-test/initializeOrg';
- import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
- import {
- openAddDashboardWidgetModal,
- openAddToDashboardModal,
- } from 'sentry/actionCreators/modal';
- import {NewQuery} from 'sentry/types';
- import EventView from 'sentry/utils/discover/eventView';
- import {DisplayModes} from 'sentry/utils/discover/types';
- import {DashboardWidgetSource, DisplayType} from 'sentry/views/dashboardsV2/types';
- import DiscoverBanner from 'sentry/views/eventsV2/banner';
- import {ALL_VIEWS} from 'sentry/views/eventsV2/data';
- import SavedQueryButtonGroup from 'sentry/views/eventsV2/savedQuery';
- import * as utils from 'sentry/views/eventsV2/savedQuery/utils';
- const SELECTOR_BUTTON_SAVE_AS = 'button[aria-label="Save as"]';
- const SELECTOR_BUTTON_SAVED = '[data-test-id="discover2-savedquery-button-saved"]';
- const SELECTOR_BUTTON_UPDATE = '[data-test-id="discover2-savedquery-button-update"]';
- const SELECTOR_BUTTON_DELETE = '[data-test-id="discover2-savedquery-button-delete"]';
- const SELECTOR_BUTTON_CREATE_ALERT = '[data-test-id="discover2-create-from-discover"]';
- jest.mock('sentry/actionCreators/modal');
- function mount(
- location,
- organization,
- router,
- eventView,
- savedQuery,
- yAxis,
- disabled = false
- ) {
- return render(
- <SavedQueryButtonGroup
- location={location}
- organization={organization}
- eventView={eventView}
- savedQuery={savedQuery}
- disabled={disabled}
- updateCallback={() => {}}
- yAxis={yAxis}
- router={router}
- savedQueryLoading={false}
- />
- );
- }
- function generateWrappedComponent(
- location,
- organization,
- router,
- eventView,
- savedQuery,
- yAxis,
- disabled = false
- ) {
- return mountWithTheme(
- <SavedQueryButtonGroup
- location={location}
- organization={organization}
- eventView={eventView}
- savedQuery={savedQuery}
- disabled={disabled}
- updateCallback={() => {}}
- yAxis={yAxis}
- router={router}
- savedQueryLoading={false}
- />
- );
- }
- describe('EventsV2 > SaveQueryButtonGroup', function () {
- const organization = TestStubs.Organization({
- features: ['discover-query', 'dashboards-edit'],
- });
- const location = {
- pathname: '/organization/eventsv2/',
- query: {},
- };
- const router = {
- location: {query: {}},
- };
- const yAxis = ['count()', 'failure_count()'];
- const errorsQuery = {
- ...(ALL_VIEWS.find(view => view.name === 'Errors by Title') as NewQuery),
- yAxis: ['count()'],
- display: DisplayModes.DEFAULT,
- };
- const errorsView = EventView.fromSavedQuery(errorsQuery);
- const errorsViewSaved = EventView.fromSavedQuery(errorsQuery);
- errorsViewSaved.id = '1';
- const errorsViewModified = EventView.fromSavedQuery(errorsQuery);
- errorsViewModified.id = '1';
- errorsViewModified.name = 'Modified Name';
- const savedQuery = {
- ...errorsViewSaved.toNewQuery(),
- yAxis,
- dateCreated: '',
- dateUpdated: '',
- id: '1',
- };
- afterEach(() => {
- MockApiClient.clearMockResponses();
- jest.clearAllMocks();
- });
- describe('building on a new query', () => {
- const mockUtils = jest
- .spyOn(utils, 'handleCreateQuery')
- .mockImplementation(() => Promise.resolve(savedQuery));
- beforeEach(() => {
- mockUtils.mockClear();
- });
- it('renders disabled buttons when disabled prop is used', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsView,
- undefined,
- yAxis,
- true
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- expect(buttonSaveAs.props()['aria-disabled']).toBe(true);
- });
- it('renders the correct set of buttons', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsView,
- undefined,
- yAxis
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- const buttonSaved = wrapper.find(SELECTOR_BUTTON_SAVED);
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE);
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE);
- expect(buttonSaveAs.exists()).toBe(true);
- expect(buttonSaved.exists()).toBe(false);
- expect(buttonUpdate.exists()).toBe(false);
- expect(buttonDelete.exists()).toBe(false);
- });
- it('hides the banner when save is complete.', () => {
- mount(location, organization, router, errorsView, undefined, yAxis);
- // Click on ButtonSaveAs to open dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save as'}));
- // Fill in the Input
- userEvent.type(screen.getByPlaceholderText('Display name'), 'My New Query Name');
- // Click on Save in the Dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
- // The banner should not render
- mountWithTheme(<DiscoverBanner organization={organization} resultsUrl="" />);
- expect(screen.queryByText('Discover Trends')).not.toBeInTheDocument();
- });
- it('saves a well-formed query', () => {
- mount(location, organization, router, errorsView, undefined, yAxis);
- // Click on ButtonSaveAs to open dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save as'}));
- // Fill in the Input
- userEvent.type(screen.getByPlaceholderText('Display name'), 'My New Query Name');
- // Click on Save in the Dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
- expect(mockUtils).toHaveBeenCalledWith(
- expect.anything(), // api
- organization,
- expect.objectContaining({
- ...errorsView,
- name: 'My New Query Name',
- }),
- yAxis,
- true
- );
- });
- it('rejects if query.name is empty', () => {
- mount(location, organization, router, errorsView, undefined, yAxis);
- // Click on ButtonSaveAs to open dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save as'}));
- // Do not fill in Input
- // Click on Save in the Dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
- // Check that EventView has a name
- expect(errorsView.name).toBe('Errors by Title');
- expect(mockUtils).not.toHaveBeenCalled();
- });
- });
- describe('viewing a saved query', () => {
- let mockUtils;
- beforeEach(() => {
- mockUtils = jest
- .spyOn(utils, 'handleDeleteQuery')
- .mockImplementation(() => Promise.resolve());
- });
- afterEach(() => {
- mockUtils.mockClear();
- });
- it('renders the correct set of buttons', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewSaved,
- savedQuery,
- yAxis
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- const buttonSaved = wrapper.find(SELECTOR_BUTTON_SAVED);
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE);
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE);
- expect(buttonSaveAs.exists()).toBe(false);
- expect(buttonSaved.exists()).toBe(true);
- expect(buttonUpdate.exists()).toBe(false);
- expect(buttonDelete.exists()).toBe(true);
- });
- it('treats undefined yAxis the same as count() when checking for changes', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewSaved,
- {...savedQuery, yAxis: undefined},
- ['count()']
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- const buttonSaved = wrapper.find(SELECTOR_BUTTON_SAVED);
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE);
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE);
- expect(buttonSaveAs.exists()).toBe(false);
- expect(buttonSaved.exists()).toBe(true);
- expect(buttonUpdate.exists()).toBe(false);
- expect(buttonDelete.exists()).toBe(true);
- });
- it('converts string yAxis values to array when checking for changes', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewSaved,
- {...savedQuery, yAxis: 'count()'},
- ['count()']
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- const buttonSaved = wrapper.find(SELECTOR_BUTTON_SAVED);
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE);
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE);
- expect(buttonSaveAs.exists()).toBe(false);
- expect(buttonSaved.exists()).toBe(true);
- expect(buttonUpdate.exists()).toBe(false);
- expect(buttonDelete.exists()).toBe(true);
- });
- it('deletes the saved query', async () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewSaved,
- savedQuery,
- yAxis
- );
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE).first();
- await buttonDelete.simulate('click');
- expect(mockUtils).toHaveBeenCalledWith(
- expect.anything(), // api
- organization,
- expect.objectContaining({id: '1'})
- );
- });
- it('opens a modal with the correct params for top 5 display mode', async function () {
- const featuredOrganization = TestStubs.Organization({
- features: ['dashboards-edit', 'new-widget-builder-experience-design'],
- });
- const testData = initializeOrg({
- ...initializeOrg(),
- organization: featuredOrganization,
- });
- const savedTopNQuery = TestStubs.DiscoverSavedQuery({
- display: DisplayModes.TOP5,
- orderby: 'test',
- fields: ['test', 'count()'],
- topEvents: '2',
- });
- mount(
- testData.router.location,
- testData.organization,
- testData.router,
- EventView.fromSavedQuery(savedTopNQuery),
- savedTopNQuery,
- ['count()']
- );
- userEvent.click(screen.getByText('Add to Dashboard'));
- expect(openAddDashboardWidgetModal).not.toHaveBeenCalled();
- await waitFor(() => {
- expect(openAddToDashboardModal).toHaveBeenCalledWith(
- expect.objectContaining({
- widget: {
- title: 'Saved query #1',
- displayType: DisplayType.AREA,
- limit: 2,
- queries: [
- {
- aggregates: ['count()'],
- columns: ['test'],
- conditions: '',
- fields: ['test', 'count()', 'count()'],
- name: '',
- orderby: 'test',
- },
- ],
- },
- widgetAsQueryParams: expect.objectContaining({
- defaultTableColumns: ['test', 'count()'],
- defaultTitle: 'Saved query #1',
- defaultWidgetQuery:
- 'name=&aggregates=count()&columns=test&fields=test%2Ccount()%2Ccount()&conditions=&orderby=test',
- displayType: DisplayType.AREA,
- source: DashboardWidgetSource.DISCOVERV2,
- }),
- })
- );
- });
- });
- it('opens a modal with the correct params for default display mode', async function () {
- const featuredOrganization = TestStubs.Organization({
- features: ['dashboards-edit', 'new-widget-builder-experience-design'],
- });
- const testData = initializeOrg({
- ...initializeOrg(),
- organization: featuredOrganization,
- });
- const savedDefaultQuery = TestStubs.DiscoverSavedQuery({
- display: DisplayModes.DEFAULT,
- orderby: 'count()',
- fields: ['test', 'count()'],
- yAxis: ['count()'],
- });
- mount(
- testData.router.location,
- testData.organization,
- testData.router,
- EventView.fromSavedQuery(savedDefaultQuery),
- savedDefaultQuery,
- ['count()']
- );
- userEvent.click(screen.getByText('Add to Dashboard'));
- expect(openAddDashboardWidgetModal).not.toHaveBeenCalled();
- await waitFor(() => {
- expect(openAddToDashboardModal).toHaveBeenCalledWith(
- expect.objectContaining({
- widget: {
- title: 'Saved query #1',
- displayType: DisplayType.LINE,
- queries: [
- {
- aggregates: ['count()'],
- columns: [],
- conditions: '',
- fields: ['count()'],
- name: '',
- // Orderby gets dropped because ordering only applies to
- // Top-N and tables
- orderby: '',
- },
- ],
- },
- widgetAsQueryParams: expect.objectContaining({
- defaultTableColumns: ['test', 'count()'],
- defaultTitle: 'Saved query #1',
- defaultWidgetQuery:
- 'name=&aggregates=count()&columns=&fields=count()&conditions=&orderby=',
- displayType: DisplayType.LINE,
- source: DashboardWidgetSource.DISCOVERV2,
- }),
- })
- );
- });
- });
- });
- describe('modifying a saved query', () => {
- let mockUtils;
- it('renders the correct set of buttons', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewModified,
- errorsViewSaved.toNewQuery(),
- yAxis
- );
- const buttonSaveAs = wrapper.find(SELECTOR_BUTTON_SAVE_AS);
- const buttonSaved = wrapper.find(SELECTOR_BUTTON_SAVED);
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE);
- const buttonDelete = wrapper.find(SELECTOR_BUTTON_DELETE);
- expect(buttonSaveAs.exists()).toBe(true);
- expect(buttonSaved.exists()).toBe(false);
- expect(buttonUpdate.exists()).toBe(true);
- expect(buttonDelete.exists()).toBe(true);
- });
- describe('updates the saved query', () => {
- beforeEach(() => {
- mockUtils = jest
- .spyOn(utils, 'handleUpdateQuery')
- .mockImplementation(() => Promise.resolve(savedQuery));
- });
- afterEach(() => {
- mockUtils.mockClear();
- });
- it('accepts a well-formed query', async () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewModified,
- savedQuery,
- yAxis
- );
- // Click on Save in the Dropdown
- const buttonUpdate = wrapper.find(SELECTOR_BUTTON_UPDATE).first();
- await buttonUpdate.simulate('click');
- expect(mockUtils).toHaveBeenCalledWith(
- expect.anything(), // api
- organization,
- expect.objectContaining({
- ...errorsViewModified,
- }),
- yAxis
- );
- });
- });
- describe('creates a separate query', () => {
- beforeEach(() => {
- mockUtils = jest
- .spyOn(utils, 'handleCreateQuery')
- .mockImplementation(() => Promise.resolve(savedQuery));
- });
- afterEach(() => {
- mockUtils.mockClear();
- });
- it('checks that it is forked from a saved query', () => {
- mount(location, organization, router, errorsViewModified, savedQuery, yAxis);
- // Click on ButtonSaveAs to open dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save as'}));
- // Fill in the Input
- userEvent.type(screen.getByPlaceholderText('Display name'), 'Forked Query');
- // Click on Save in the Dropdown
- userEvent.click(screen.getByRole('button', {name: 'Save for Org'}));
- expect(mockUtils).toHaveBeenCalledWith(
- expect.anything(), // api
- organization,
- expect.objectContaining({
- ...errorsViewModified,
- name: 'Forked Query',
- }),
- yAxis,
- false
- );
- });
- });
- });
- describe('create alert from discover', () => {
- it('renders create alert button when metrics alerts is enabled', () => {
- const metricAlertOrg = {
- ...organization,
- features: ['incidents'],
- };
- const wrapper = generateWrappedComponent(
- location,
- metricAlertOrg,
- router,
- errorsViewModified,
- savedQuery,
- yAxis
- );
- const buttonCreateAlert = wrapper.find(SELECTOR_BUTTON_CREATE_ALERT);
- expect(buttonCreateAlert.exists()).toBe(true);
- });
- it('does not render create alert button without metric alerts', () => {
- const wrapper = generateWrappedComponent(
- location,
- organization,
- router,
- errorsViewModified,
- savedQuery,
- yAxis
- );
- const buttonCreateAlert = wrapper.find(SELECTOR_BUTTON_CREATE_ALERT);
- expect(buttonCreateAlert.exists()).toBe(false);
- });
- });
- describe('add dashboard widget', () => {
- let initialData;
- beforeEach(() => {
- initialData = initializeOrg({
- organization: {
- features: ['discover-query', 'widget-viewer-modal', 'dashboards-edit'],
- apdexThreshold: 400,
- },
- router: {
- location: {query: {}},
- },
- project: 1,
- projects: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-stats/',
- body: [],
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/dashboards/',
- body: [],
- });
- });
- afterEach(() => {
- MockApiClient.clearMockResponses();
- });
- it('opens widget modal when add to dashboard is clicked', () => {
- mount(
- initialData.router.location,
- initialData.organization,
- initialData.router,
- errorsViewModified,
- savedQuery,
- ['count()']
- );
- userEvent.click(screen.getByText('Add to Dashboard'));
- expect(openAddDashboardWidgetModal).toHaveBeenCalledWith(
- expect.objectContaining({
- defaultTableColumns: ['title', 'count()', 'count_unique(user)', 'project'],
- defaultTitle: 'Errors by Title',
- defaultWidgetQuery: {
- conditions: 'event.type:error',
- fields: ['count()'],
- aggregates: ['count()'],
- columns: [],
- name: '',
- orderby: '-count()',
- },
- displayType: 'line',
- })
- );
- });
- it('populates dashboard widget modal with saved query data if created from discover', () => {
- mount(
- initialData.router.location,
- initialData.organization,
- initialData.router,
- errorsViewModified,
- savedQuery,
- yAxis
- );
- userEvent.click(screen.getByText('Add to Dashboard'));
- expect(openAddDashboardWidgetModal).toHaveBeenCalledWith(
- expect.objectContaining({
- defaultTableColumns: ['title', 'count()', 'count_unique(user)', 'project'],
- defaultTitle: 'Errors by Title',
- defaultWidgetQuery: {
- conditions: 'event.type:error',
- fields: ['count()', 'failure_count()'],
- aggregates: ['count()', 'failure_count()'],
- columns: [],
- name: '',
- orderby: '-count()',
- },
- displayType: 'line',
- })
- );
- });
- it('adds equation to query fields if yAxis includes comprising functions', () => {
- mount(
- initialData.router.location,
- initialData.organization,
- initialData.router,
- errorsViewModified,
- savedQuery,
- [...yAxis, 'equation|count() + failure_count()']
- );
- userEvent.click(screen.getByText('Add to Dashboard'));
- expect(openAddDashboardWidgetModal).toHaveBeenCalledWith(
- expect.objectContaining({
- defaultTableColumns: ['title', 'count()', 'count_unique(user)', 'project'],
- defaultTitle: 'Errors by Title',
- defaultWidgetQuery: {
- conditions: 'event.type:error',
- fields: ['count()', 'failure_count()', 'equation|count() + failure_count()'],
- aggregates: [
- 'count()',
- 'failure_count()',
- 'equation|count() + failure_count()',
- ],
- columns: [],
- name: '',
- orderby: '-count()',
- },
- displayType: 'line',
- })
- );
- });
- });
- });
|