import React from 'react'; import {mountWithTheme} from 'sentry-test/enzyme'; import {initializeOrg} from 'sentry-test/initializeOrg'; import {Client} from 'app/api'; import WidgetQueries from 'app/views/dashboardsV2/widgetQueries'; describe('Dashboards > WidgetQueries', function () { const initialData = initializeOrg({ organization: TestStubs.Organization(), }); const multipleQueryWidget = { title: 'Errors', interval: '5m', displayType: 'line', queries: [ {conditions: 'event.type:error', fields: ['count()'], name: 'errors'}, {conditions: 'event.type:default', fields: ['count()'], name: 'default'}, ], }; const singleQueryWidget = { title: 'Errors', interval: '5m', displayType: 'line', queries: [{conditions: 'event.type:error', fields: ['count()'], name: 'errors'}], }; const tableWidget = { title: 'SDK', interval: '5m', displayType: 'table', queries: [{conditions: 'event.type:error', fields: ['sdk.name'], name: 'sdk'}], }; const selection = { projects: [1], environments: ['prod'], datetime: { period: '14d', }, }; const api = new Client(); afterEach(function () { MockApiClient.clearMockResponses(); }); it('can send multiple API requests', async function () { const errorMock = MockApiClient.addMockResponse( { url: '/organizations/org-slug/events-stats/', body: [], }, { predicate(_url, options) { return ( options.query.query === 'event.type:error' || options.query.query === 'event.type:default' ); }, } ); const wrapper = mountWithTheme( {() =>
} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 2 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(errorMock).toHaveBeenCalledTimes(2); }); it('sets errorMessage when the first request fails', async function () { const okMock = MockApiClient.addMockResponse( { url: '/organizations/org-slug/events-stats/', body: [], }, { predicate(_url, options) { return options.query.query === 'event.type:error'; }, } ); const failMock = MockApiClient.addMockResponse( { url: '/organizations/org-slug/events-stats/', statusCode: 400, body: {detail: 'Bad request data'}, }, { predicate(_url, options) { return options.query.query === 'event.type:default'; }, } ); let error = ''; const wrapper = mountWithTheme( {({errorMessage}) => { error = errorMessage; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 2 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(okMock).toHaveBeenCalledTimes(1); expect(failMock).toHaveBeenCalledTimes(1); expect(error).toEqual('Bad request data'); }); it('adjusts interval based on date window', async function () { const errorMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-stats/', body: [], }); const widget = {...singleQueryWidget, interval: '1m'}; const longSelection = { projects: [1], environments: ['prod', 'dev'], datetime: { period: '90d', }, }; const wrapper = mountWithTheme( {() =>
} , initialData.routerContext ); await tick(); // Child should be rendered and interval bumped up. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(errorMock).toHaveBeenCalledTimes(1); expect(errorMock).toHaveBeenCalledWith( '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({ interval: '4h', statsPeriod: '90d', environment: ['prod', 'dev'], project: [1], }), }) ); }); it('adjusts interval based on date window 14d', async function () { const errorMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-stats/', body: [], }); const widget = {...singleQueryWidget, interval: '1m'}; const wrapper = mountWithTheme( {() =>
} , initialData.routerContext ); await tick(); // Child should be rendered and interval bumped up. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(errorMock).toHaveBeenCalledTimes(1); expect(errorMock).toHaveBeenCalledWith( '/organizations/org-slug/events-stats/', expect.objectContaining({ query: expect.objectContaining({interval: '30m'}), }) ); }); it('can send table result queries', async function () { const tableMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/eventsv2/', body: { meta: {'sdk.name': 'string'}, data: [{'sdk.name': 'python'}], }, }); let childProps = undefined; const wrapper = mountWithTheme( {props => { childProps = props; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 1 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(tableMock).toHaveBeenCalledTimes(1); expect(tableMock).toHaveBeenCalledWith( '/organizations/org-slug/eventsv2/', expect.objectContaining({ query: expect.objectContaining({ query: 'event.type:error', name: 'SDK', field: ['sdk.name'], statsPeriod: '14d', environment: ['prod'], project: [1], }), }) ); expect(childProps.timeseriesResults).toBeUndefined(); expect(childProps.tableResults[0].data).toHaveLength(1); expect(childProps.tableResults[0].meta).toBeDefined(); }); it('can send multiple table queries', async function () { const firstQuery = MockApiClient.addMockResponse( { url: '/organizations/org-slug/eventsv2/', body: { meta: {'sdk.name': 'string'}, data: [{'sdk.name': 'python'}], }, }, { predicate(_url, options) { return options.query.query === 'event.type:error'; }, } ); const secondQuery = MockApiClient.addMockResponse( { url: '/organizations/org-slug/eventsv2/', body: { meta: {title: 'string'}, data: [{title: 'ValueError'}], }, }, { predicate(_url, options) { return options.query.query === 'title:ValueError'; }, } ); const widget = { title: 'SDK', interval: '5m', displayType: 'table', queries: [ {conditions: 'event.type:error', fields: ['sdk.name'], name: 'sdk'}, {conditions: 'title:ValueError', fields: ['title'], name: 'title'}, ], }; let childProps = undefined; const wrapper = mountWithTheme( {props => { childProps = props; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 2 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(firstQuery).toHaveBeenCalledTimes(1); expect(secondQuery).toHaveBeenCalledTimes(1); expect(childProps.tableResults).toHaveLength(2); expect(childProps.tableResults[0].data[0]['sdk.name']).toBeDefined(); expect(childProps.tableResults[1].data[0].title).toBeDefined(); }); it('can send big number result queries', async function () { const tableMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/eventsv2/', body: { meta: {'sdk.name': 'string'}, data: [{'sdk.name': 'python'}], }, }); let childProps = undefined; const wrapper = mountWithTheme( {props => { childProps = props; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 1 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(tableMock).toHaveBeenCalledTimes(1); expect(tableMock).toHaveBeenCalledWith( '/organizations/org-slug/eventsv2/', expect.objectContaining({ query: expect.objectContaining({ referrer: 'api.dashboards.bignumberwidget', query: 'event.type:error', name: 'SDK', field: ['sdk.name'], statsPeriod: '14d', environment: ['prod'], project: [1], }), }) ); expect(childProps.timeseriesResults).toBeUndefined(); expect(childProps.tableResults[0].data).toHaveLength(1); expect(childProps.tableResults[0].meta).toBeDefined(); }); it('can send world map result queries', async function () { const tableMock = MockApiClient.addMockResponse({ url: '/organizations/org-slug/events-geo/', body: { meta: {'sdk.name': 'string'}, data: [{'sdk.name': 'python'}], }, }); let childProps = undefined; const wrapper = mountWithTheme( {props => { childProps = props; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 1 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(tableMock).toHaveBeenCalledTimes(1); expect(tableMock).toHaveBeenCalledWith( '/organizations/org-slug/events-geo/', expect.objectContaining({ query: expect.objectContaining({ referrer: 'api.dashboards.worldmapwidget', query: 'event.type:error', name: 'SDK', field: ['count()'], statsPeriod: '14d', environment: ['prod'], project: [1], }), }) ); expect(childProps.timeseriesResults).toBeUndefined(); expect(childProps.tableResults[0].data).toHaveLength(1); expect(childProps.tableResults[0].meta).toBeDefined(); }); it('stops loading state once all queries finish even if some fail', async function () { const firstQuery = MockApiClient.addMockResponse( { statusCode: 500, url: '/organizations/org-slug/eventsv2/', body: {detail: 'it didnt work'}, }, { predicate(_url, options) { return options.query.query === 'event.type:error'; }, } ); const secondQuery = MockApiClient.addMockResponse( { url: '/organizations/org-slug/eventsv2/', body: { meta: {title: 'string'}, data: [{title: 'ValueError'}], }, }, { predicate(_url, options) { return options.query.query === 'title:ValueError'; }, } ); const widget = { title: 'SDK', interval: '5m', displayType: 'table', queries: [ {conditions: 'event.type:error', fields: ['sdk.name'], name: 'sdk'}, {conditions: 'title:ValueError', fields: ['title'], name: 'title'}, ], }; let childProps = undefined; const wrapper = mountWithTheme( {props => { childProps = props; return
; }} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 2 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(firstQuery).toHaveBeenCalledTimes(1); expect(secondQuery).toHaveBeenCalledTimes(1); expect(childProps.loading).toEqual(false); }); it('sets bar charts to 1d interval', async function () { const errorMock = MockApiClient.addMockResponse( { url: '/organizations/org-slug/events-stats/', body: [], }, { predicate(_url, options) { return options.query.interval === '1d'; }, } ); const barWidget = { ...singleQueryWidget, displayType: 'bar', // Should be ignored for bars. interval: '5m', }; const wrapper = mountWithTheme( {() =>
} , initialData.routerContext ); await tick(); await tick(); // Child should be rendered and 1 requests should be sent. expect(wrapper.find('[data-test-id="child"]')).toHaveLength(1); expect(errorMock).toHaveBeenCalledTimes(1); }); });