import {LocationFixture} from 'sentry-fixture/locationFixture'; import {OrganizationFixture} from 'sentry-fixture/organization'; import {ProjectFixture} from 'sentry-fixture/project'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary'; import selectEvent from 'sentry-test/selectEvent'; import * as PageFilterPersistence from 'sentry/components/organizations/pageFilters/persistence'; import ProjectsStore from 'sentry/stores/projectsStore'; import {SavedSearchType} from 'sentry/types/group'; import {browserHistory} from 'sentry/utils/browserHistory'; import EventView from 'sentry/utils/discover/eventView'; import Results from 'sentry/views/discover/results'; import {DEFAULT_EVENT_VIEW, getTransactionViews} from './data'; const FIELDS = [ { field: 'title', }, { field: 'timestamp', }, { field: 'user', }, { field: 'count()', }, ]; const generateFields = () => ({ field: FIELDS.map(i => i.field), }); const eventTitle = 'Oh no something bad'; function renderMockRequests() { MockApiClient.addMockResponse({ url: '/organizations/org-slug/projects/', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/projects-count/', body: {myProjects: 10, allProjects: 300}, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/tags/', body: [], }); const eventsStatsMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-stats/', body: {data: [[123, []]]}, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', method: 'POST', body: [], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/releases/stats/', body: [], }); const measurementsMetaMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/measurements-meta/', method: 'GET', body: {}, }); const eventsResultsMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events/', body: { meta: { fields: { id: 'string', title: 'string', 'project.name': 'string', timestamp: 'date', 'user.id': 'string', }, discoverSplitDecision: 'transaction-like', }, data: [ { id: 'deadbeef', 'user.id': 'alberto leal', title: eventTitle, 'project.name': 'project-slug', timestamp: '2019-05-23T22:12:48+00:00', }, ], }, }); const eventsMetaMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-meta/', body: { count: 2, }, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/events/project-slug:deadbeef/', method: 'GET', body: { id: '1234', size: 1200, eventID: 'deadbeef', title: 'Oh no something bad', message: 'It was not good', dateCreated: '2019-05-23T22:12:48+00:00', entries: [ { type: 'message', message: 'bad stuff', data: {}, }, ], tags: [{key: 'browser', value: 'Firefox'}], }, }); const eventFacetsMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-facets/', body: [ { key: 'release', topValues: [{count: 3, value: 'abcd123', name: 'abcd123'}], }, { key: 'environment', topValues: [ {count: 2, value: 'dev', name: 'dev'}, {count: 1, value: 'prod', name: 'prod'}, ], }, { key: 'foo', topValues: [ {count: 2, value: 'bar', name: 'bar'}, {count: 1, value: 'baz', name: 'baz'}, ], }, ], }); const mockVisit = MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/visit/', method: 'POST', body: [], statusCode: 200, }); const mockSaved = MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/', method: 'GET', statusCode: 200, body: { id: '1', name: 'new', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'discover', }, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/homepage/', method: 'GET', statusCode: 200, body: { id: '2', name: '', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'discover', }, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/dynamic-sampling/custom-rules/', method: 'GET', statusCode: 204, body: '', }); return { eventsStatsMock, eventsMetaMock, eventsResultsMock, mockVisit, mockSaved, eventFacetsMock, measurementsMetaMock, }; } describe('Results', function () { afterEach(function () { MockApiClient.clearMockResponses(); ProjectsStore.reset(); }); describe('Events', function () { const features = ['discover-basic']; it('loads data when moving from an invalid to valid EventView', function () { const organization = OrganizationFixture({ features, }); // Start off with an invalid view (empty is invalid) const {router} = initializeOrg({ organization, router: { location: {query: {query: 'tag:value'}}, }, }); const mockRequests = renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); render( , { router: router, organization, } ); // No request as eventview was invalid. expect(mockRequests.eventsStatsMock).not.toHaveBeenCalled(); // Should redirect and retain the old query value expect(browserHistory.replace).toHaveBeenCalledWith( expect.objectContaining({ pathname: '/organizations/org-slug/discover/results/', query: expect.objectContaining({ query: 'tag:value', }), }) ); }); it('pagination cursor should be cleared when making a search', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: { query: { ...generateFields(), cursor: '0%3A50%3A0', }, }, }, }); const mockRequests = renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); render( , { router: router, organization, } ); // ensure cursor query string is initially present in the location expect(router.location).toEqual({ query: { ...generateFields(), cursor: '0%3A50%3A0', }, }); await waitFor(() => expect(screen.queryByTestId('loading-indicator')).not.toBeInTheDocument() ); // perform a search await userEvent.click( screen.getByPlaceholderText('Search for events, users, tags, and more') ); await userEvent.paste('geo:canada'); await userEvent.keyboard('{enter}'); // should only be called with saved queries expect(mockRequests.mockVisit).not.toHaveBeenCalled(); // cursor query string should be omitted from the query string expect(router.push).toHaveBeenCalledWith({ pathname: undefined, query: { ...generateFields(), query: 'geo:canada', statsPeriod: '14d', }, }); }); it('renders a y-axis selector', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), yAxis: 'count()'}}, }, }); renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); render( , { router: router, organization, } ); // Click the 'default' option. await selectEvent.select( await screen.findByRole('button', {name: 'Y-Axis count()'}), 'count_unique(user)' ); }); it('renders a display selector', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'default', yAxis: 'count'}}, }, }); renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); render( , { router: router, organization, } ); // Click the 'default' option. await selectEvent.select( await screen.findByRole('button', {name: /Display/}), 'Total Period' ); }); it('excludes top5 options when plan does not include discover-query', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'previous'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); render( , { router: router, organization, } ); await userEvent.click(await screen.findByRole('button', {name: /Display/})); expect(screen.queryByText('Top 5 Daily')).not.toBeInTheDocument(); expect(screen.queryByText('Top 5 Period')).not.toBeInTheDocument(); }); it('needs confirmation on long queries', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), statsPeriod: '60d', project: '-1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(0); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); }); it('needs confirmation on long query with explicit projects', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: { query: { ...generateFields(), statsPeriod: '60d', project: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11].map(String), }, }, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(0); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); }); it('does not need confirmation on short queries', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), statsPeriod: '30d', project: '-1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1); }); it('does not need confirmation with to few projects', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: { query: { ...generateFields(), statsPeriod: '90d', project: [1, 2, 3, 4].map(String), }, }, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1); }); it('creates event view from saved query', async function () { const organization = OrganizationFixture({ features, slug: 'org-slug', }); const {router} = initializeOrg({ organization, router: { location: {pathname: '/', query: {id: '1', statsPeriod: '24h'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); await waitFor(() => expect(mockRequests.mockVisit).toHaveBeenCalled()); expect(screen.getByRole('link', {name: 'timestamp'})).toHaveAttribute( 'href', '/?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-timestamp&statsPeriod=24h&topEvents=5' ); expect(screen.getByRole('link', {name: 'project'})).toHaveAttribute( 'href', '/?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-project&statsPeriod=24h&topEvents=5' ); // NOTE: This uses a legacy redirect for project event to the issue group event link expect(screen.getByRole('link', {name: 'deadbeef'})).toHaveAttribute( 'href', '/org-slug/project-slug/events/deadbeef/?id=1&referrer=discover-events-table&statsPeriod=24h' ); expect(screen.getByRole('link', {name: 'user.display'})).toHaveAttribute( 'href', '/?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=user.display&statsPeriod=24h&topEvents=5' ); expect(screen.getByRole('link', {name: 'title'})).toHaveAttribute( 'href', '/?field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&query=&sort=-title&statsPeriod=24h&topEvents=5' ); }); it('overrides saved query params with location query params', async function () { const organization = OrganizationFixture({ features, slug: 'org-slug', }); const {router} = initializeOrg({ organization, router: { location: { pathname: '/', query: { id: '1', statsPeriod: '7d', project: ['2'], environment: ['production'], }, }, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); await waitFor(() => expect(mockRequests.mockVisit).toHaveBeenCalled()); expect(screen.getByRole('link', {name: 'timestamp'})).toHaveAttribute( 'href', '/?environment=production&field=title&field=event.type&field=project&field=user.display&field=timestamp&id=1&name=new&project=2&query=&sort=-timestamp&statsPeriod=7d&topEvents=5' ); }); it('updates chart whenever yAxis parameter changes', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), yAxis: 'count()'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const {eventsStatsMock, measurementsMetaMock} = renderMockRequests(); const {rerender} = render( , { router: router, organization, } ); // Should load events once expect(eventsStatsMock).toHaveBeenCalledTimes(1); expect(eventsStatsMock).toHaveBeenNthCalledWith( 1, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '14d', yAxis: ['count()'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); // Update location simulating a browser back button action rerender( ); // Should load events again expect(eventsStatsMock).toHaveBeenCalledTimes(2); expect(eventsStatsMock).toHaveBeenNthCalledWith( 2, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '14d', yAxis: ['count_unique(user)'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); }); it('updates chart whenever display parameter changes', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}}, }, }); const {eventsStatsMock, measurementsMetaMock} = renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); const {rerender} = render( , { router: router, organization, } ); // Should load events once expect(eventsStatsMock).toHaveBeenCalledTimes(1); expect(eventsStatsMock).toHaveBeenNthCalledWith( 1, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '14d', yAxis: ['count()'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); // Update location simulating a browser back button action rerender( ); // Should load events again expect(eventsStatsMock).toHaveBeenCalledTimes(2); expect(eventsStatsMock).toHaveBeenNthCalledWith( 2, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '28d', yAxis: ['count()'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); }); it('updates chart whenever display and yAxis parameters change', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'default', yAxis: 'count()'}}, }, }); const {eventsStatsMock, measurementsMetaMock} = renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); const {rerender} = render( , { router: router, organization, } ); // Should load events once expect(eventsStatsMock).toHaveBeenCalledTimes(1); expect(eventsStatsMock).toHaveBeenNthCalledWith( 1, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '14d', yAxis: ['count()'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); // Update location simulating a browser back button action rerender( ); // Should load events again expect(eventsStatsMock).toHaveBeenCalledTimes(2); expect(eventsStatsMock).toHaveBeenNthCalledWith( 2, '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ statsPeriod: '28d', yAxis: ['count_unique(user)'], }), }) ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); }); it('appends tag value to existing query when clicked', async function () { const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'default', yAxis: 'count'}}, }, }); const mockRequests = renderMockRequests(); ProjectsStore.loadInitialData([ProjectFixture()]); render( , { router: router, organization, } ); await userEvent.click(await screen.findByRole('button', {name: 'Show Tags'})); await waitFor(() => expect(mockRequests.eventFacetsMock).toHaveBeenCalled()); // TODO(edward): update this to be less generic await userEvent.click(screen.getByText('environment')); await userEvent.click(screen.getByText('foo')); // since environment collides with the environment field, it is wrapped with `tags[...]` expect( await screen.findByRole('link', { name: 'environment, dev, 100% of all events. View events with this tag value.', }) ).toBeInTheDocument(); expect( screen.getByRole('link', { name: 'foo, bar, 100% of all events. View events with this tag value.', }) ).toBeInTheDocument(); }); it('respects pinned filters for prebuilt queries', async function () { const organization = OrganizationFixture({ features: [...features, 'global-views'], }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), display: 'default', yAxis: 'count'}}, }, }); renderMockRequests(); jest.spyOn(PageFilterPersistence, 'getPageFilterStorage').mockReturnValue({ state: { project: [1], environment: [], start: null, end: null, period: '14d', utc: null, }, pinnedFilters: new Set(['projects']), }); ProjectsStore.loadInitialData([ProjectFixture({id: '1', slug: 'Pinned Project'})]); render( , {router: router, organization} ); const projectPageFilter = await screen.findByTestId('page-filter-project-selector'); expect(projectPageFilter).toHaveTextContent('All Projects'); }); it('displays tip when events response contains a tip', async function () { renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/events/', body: { meta: { fields: {}, tips: {query: 'this is a tip'}, }, data: [], }, }); const organization = OrganizationFixture({ features, }); const {router} = initializeOrg({ organization, router: { location: {query: {...generateFields(), yAxis: 'count()'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); render( , {router: router, organization} ); expect(await screen.findByText('this is a tip')).toBeInTheDocument(); }); it('renders metric fallback alert', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: {query: {fromMetric: 'true', id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); render( , { router: router, organization, } ); expect( await screen.findByText( /You've navigated to this page from a performance metric widget generated from processed events/ ) ).toBeInTheDocument(); }); it('renders unparameterized data banner', async function () { const organization = OrganizationFixture({ features: ['discover-basic'], }); const {router} = initializeOrg({ organization, router: { location: {query: {showUnparameterizedBanner: 'true', id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); render( , { router: router, organization, } ); expect( await screen.findByText(/These are unparameterized transactions/) ).toBeInTheDocument(); }); it('updates the homepage query with up to date eventView when Set as Default is clicked', async () => { const mockHomepageUpdate = MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/homepage/', method: 'PUT', statusCode: 200, }); const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { // These fields take priority and should be sent in the request location: {query: {field: ['title', 'user'], id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); render( , {router: router, organization} ); await waitFor(() => expect(screen.getByRole('button', {name: /set as default/i})).toBeEnabled() ); await userEvent.click(screen.getByText('Set as Default')); expect(mockHomepageUpdate).toHaveBeenCalledWith( '/organizations/org-slug/discover/homepage/', expect.objectContaining({ data: expect.objectContaining({ fields: ['title', 'user'], }), }) ); }); it('Changes the Use as Discover button to a reset button for saved query', async () => { renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/homepage/', method: 'PUT', statusCode: 200, body: { id: '2', name: '', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'discover', }, }); const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); const {rerender} = render( , {router: router, organization} ); await waitFor(() => expect(screen.getByRole('button', {name: /set as default/i})).toBeEnabled() ); await userEvent.click(screen.getByText('Set as Default')); expect(await screen.findByText('Remove Default')).toBeInTheDocument(); await userEvent.click(screen.getByText('Total Period')); await userEvent.click(screen.getByText('Previous Period')); const rerenderData = initializeOrg({ organization, router: { location: {query: {...router.location.query, display: 'previous'}}, }, }); rerender( ); screen.getByText('Previous Period'); expect(await screen.findByText('Set as Default')).toBeInTheDocument(); }); it('Changes the Use as Discover button to a reset button for prebuilt query', async () => { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/homepage/', method: 'PUT', statusCode: 200, body: {...getTransactionViews(organization)[0], name: ''}, }); const {router} = initializeOrg({ organization, router: { location: { ...LocationFixture(), query: { ...EventView.fromNewQueryWithLocation( getTransactionViews(organization)[0], LocationFixture() ).generateQueryStringObject(), }, }, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); const {rerender} = render( , {router: router, organization} ); await screen.findAllByText(getTransactionViews(organization)[0].name); await userEvent.click(screen.getByText('Set as Default')); expect(await screen.findByText('Remove Default')).toBeInTheDocument(); await userEvent.click(screen.getByText('Total Period')); await userEvent.click(screen.getByText('Previous Period')); const rerenderData = initializeOrg({ organization, router: { location: {query: {...router.location.query, display: 'previous'}}, }, }); rerender( ); screen.getByText('Previous Period'); expect(await screen.findByText('Set as Default')).toBeInTheDocument(); }); it('links back to the homepage through the Discover breadcrumb', async () => { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const {measurementsMetaMock} = renderMockRequests(); render( , {router: router, organization} ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); expect(screen.getByText('Discover')).toHaveAttribute( 'href', expect.stringMatching(new RegExp('^/organizations/org-slug/discover/homepage/')) ); }); it('links back to the Saved Queries through the Saved Queries breadcrumb', async () => { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); const {measurementsMetaMock} = renderMockRequests(); render( , {router: router, organization} ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); expect(screen.getByRole('link', {name: 'Saved Queries'})).toHaveAttribute( 'href', expect.stringMatching(new RegExp('^/organizations/org-slug/discover/queries/')) ); }); it('allows users to Set As Default on the All Events query', async () => { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: { ...LocationFixture(), query: { ...EventView.fromNewQueryWithLocation( DEFAULT_EVENT_VIEW, LocationFixture() ).generateQueryStringObject(), }, }, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const {measurementsMetaMock} = renderMockRequests(); render( , {router: router, organization} ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); expect(screen.getByTestId('set-as-default')).toBeEnabled(); }); it("doesn't render sample data alert", async function () { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: { ...LocationFixture(), query: { ...EventView.fromNewQueryWithLocation( {...DEFAULT_EVENT_VIEW, query: 'event.type:error'}, LocationFixture() ).generateQueryStringObject(), }, }, }, }); const {measurementsMetaMock} = renderMockRequests(); render( , {router: router, organization} ); await waitFor(() => { expect(measurementsMetaMock).toHaveBeenCalled(); }); expect(screen.queryByText(/Based on your search criteria/)).not.toBeInTheDocument(); }); it('uses split decision to populate dataset selector', async function () { const organization = OrganizationFixture({ features: [ 'discover-basic', 'discover-query', 'performance-discover-dataset-selector', ], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); render( , { router: router, organization, } ); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1); await waitFor(() => { expect(screen.getByRole('tab', {name: 'Transactions'})).toHaveAttribute( 'aria-selected', 'true' ); }); expect( screen.getByText( "We're splitting our datasets up to make it a bit easier to digest. We defaulted this query to Transactions. Edit as you see fit." ) ).toBeInTheDocument(); expect(screen.queryByText('Save Changes')).not.toBeInTheDocument(); }); it('calls events endpoint with the right dataset', async function () { const organization = OrganizationFixture({ features: [ 'discover-basic', 'discover-query', 'performance-discover-dataset-selector', ], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/', method: 'GET', statusCode: 200, body: { id: '1', name: 'new', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'error-events', }, }); render( , { router: router, organization, } ); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1); expect(screen.getByRole('tab', {name: 'Errors'})).toHaveAttribute( 'aria-selected', 'true' ); expect(mockRequests.eventsStatsMock).toHaveBeenCalledWith( '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ dataset: 'errors', }), }) ); expect(mockRequests.eventsResultsMock).toHaveBeenCalledWith( '/organizations/org-slug/events/', expect.objectContaining({ query: expect.objectContaining({ dataset: 'errors', }), }) ); expect(mockRequests.eventsMetaMock).toHaveBeenCalledWith( '/organizations/org-slug/events-meta/', expect.objectContaining({ query: expect.objectContaining({ dataset: 'errors', }), }) ); }); it('does not automatically append dataset with selector feature disabled', async function () { const organization = OrganizationFixture({ features: ['discover-basic', 'discover-query'], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); const mockRequests = renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/', method: 'GET', statusCode: 200, body: { id: '1', name: 'new', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'error-events', }, }); render( , { router: router, organization, } ); await waitFor(() => { expect(mockRequests.measurementsMetaMock).toHaveBeenCalled(); }); expect(mockRequests.eventsResultsMock).toHaveBeenCalledTimes(1); expect( screen.queryByRole('button', {name: 'Dataset Errors'}) ).not.toBeInTheDocument(); expect(mockRequests.eventsStatsMock).toHaveBeenCalledWith( '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.not.objectContaining({ dataset: 'errors', }), }) ); expect(mockRequests.eventsResultsMock).toHaveBeenCalledWith( '/organizations/org-slug/events/', expect.objectContaining({ query: expect.not.objectContaining({ dataset: 'errors', }), }) ); expect(mockRequests.eventsMetaMock).toHaveBeenCalledWith( '/organizations/org-slug/events-meta/', expect.objectContaining({ query: expect.not.objectContaining({ dataset: 'errors', }), }) ); }); it('shows the search history for the error dataset', async function () { const organization = OrganizationFixture({ features: [ 'discover-basic', 'discover-query', 'performance-discover-dataset-selector', ], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [ { query: 'event.type:error', }, ], match: [ (_url, options) => { return options.query?.type === SavedSearchType.ERROR; }, ], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [ { query: 'transaction.status:ok', }, ], match: [ (_url, options) => { return options.query?.type === SavedSearchType.TRANSACTION; }, ], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/', method: 'GET', statusCode: 200, body: { id: '1', name: 'new', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'error-events', }, }); render( , { router: router, organization, } ); await userEvent.click( screen.getByPlaceholderText('Search for events, users, tags, and more') ); expect(screen.getByTestId('filter-token')).toHaveTextContent('event.type:error'); }); it('shows the search history for the transaction dataset', async function () { const organization = OrganizationFixture({ features: [ 'discover-basic', 'discover-query', 'performance-discover-dataset-selector', ], }); const {router} = initializeOrg({ organization, router: { location: {query: {id: '1'}}, }, }); ProjectsStore.loadInitialData([ProjectFixture()]); renderMockRequests(); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [ { query: 'event.type:error', }, ], match: [ (_url, options) => { return options.query?.type === SavedSearchType.ERROR; }, ], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/recent-searches/', body: [ { query: 'transaction.status:ok', }, ], match: [ (_url, options) => { return options.query?.type === SavedSearchType.TRANSACTION; }, ], }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/events/', body: { meta: { fields: { id: 'string', title: 'string', 'project.name': 'string', timestamp: 'date', 'user.id': 'string', }, discoverSplitDecision: 'transaction-like', }, data: [ { trace: 'test', id: 'deadbeef', 'user.id': 'alberto leal', title: eventTitle, 'project.name': 'project-slug', timestamp: '2019-05-23T22:12:48+00:00', }, ], }, }); MockApiClient.addMockResponse({ url: '/organizations/org-slug/discover/saved/1/', method: 'GET', statusCode: 200, body: { id: '1', name: 'new', projects: [], version: 2, expired: false, dateCreated: '2021-04-08T17:53:25.195782Z', dateUpdated: '2021-04-09T12:13:18.567264Z', createdBy: { id: '2', }, environment: [], fields: ['title', 'event.type', 'project', 'user.display', 'timestamp'], widths: ['-1', '-1', '-1', '-1', '-1'], range: '24h', orderby: '-user.display', queryDataset: 'transaction-like', }, }); render( , { router: router, organization, } ); await userEvent.click( screen.getByPlaceholderText('Search for events, users, tags, and more') ); expect(screen.getByTestId('filter-token')).toHaveTextContent( 'transaction.status:ok' ); }); }); });