import {Fragment} from 'react'; import {initializeData as _initializeData} from 'sentry-test/performance/initializePerformanceData'; import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary'; import {GenericQueryBatcher} from 'sentry/utils/performance/contexts/genericQueryBatcher'; import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting'; import {PerformanceDisplayProvider} from 'sentry/utils/performance/contexts/performanceDisplayContext'; import {OrganizationContext} from 'sentry/views/organizationContext'; import WidgetContainer from 'sentry/views/performance/landing/widgets/components/widgetContainer'; import {PerformanceWidgetSetting} from 'sentry/views/performance/landing/widgets/widgetDefinitions'; import {ProjectPerformanceType} from 'sentry/views/performance/utils'; const initializeData = () => { const data = _initializeData({ query: {statsPeriod: '7d', environment: ['prod'], project: [-42]}, }); data.eventView.additionalConditions.addFilterValues('transaction.op', ['pageload']); return data; }; const BASIC_QUERY_PARAMS = { interval: '1h', partial: '1', query: 'transaction.op:pageload', statsPeriod: '14d', }; function WrappedComponent({data, ...rest}: any) { return ( <OrganizationContext.Provider value={data.organization}> <MEPSettingProvider> <PerformanceDisplayProvider value={{performanceType: ProjectPerformanceType.ANY}}> <WidgetContainer chartHeight={100} allowedCharts={[ PerformanceWidgetSetting.TPM_AREA, PerformanceWidgetSetting.FAILURE_RATE_AREA, PerformanceWidgetSetting.USER_MISERY_AREA, ]} rowChartSettings={[]} forceDefaultChartSetting {...data} {...rest} /> </PerformanceDisplayProvider> </MEPSettingProvider> </OrganizationContext.Provider> ); } describe('Performance > Widgets > Query Batching', function () { let eventsMock: jest.Mock; let eventStatsMock: jest.Mock; beforeEach(function () { eventsMock = MockApiClient.addMockResponse({ method: 'GET', url: '/organizations/org-slug/events/', body: { data: [ { 'tpm()': 53.12, 'user_misery()': 0.023, 'failure_rate()': 0.012, }, ], meta: { isMetricsData: false, }, }, }); eventStatsMock = MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/events-stats/`, body: { 'tpm()': { data: [ [ 1636822800, [ { count: 30.0, }, ], ], [ 1636995600, [ { count: 60.1, }, ], ], ], order: 1, start: 1636822800, end: 1636995600, }, 'user_misery()': { data: [ [ 1636822800, [ { count: 0.02, }, ], ], [ 1636995600, [ { count: 0.03, }, ], ], ], order: 1, start: 1636822800, end: 1636995600, }, 'failure_rate()': { data: [ [ 1636822800, [ { count: 0.002, }, ], ], [ 1636995600, [ { count: 0.001, }, ], ], ], order: 2, start: 1636822800, end: 1636995600, }, }, }); }); it('EventsRequest and DiscoverQuery based component fires queries without provider', async function () { const data = initializeData(); render( <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.TPM_AREA} isMEPEnabled={false} />, { organization: data.organization, } ); expect(await screen.findByTestId('performance-widget-title')).toBeInTheDocument(); expect(eventStatsMock).toHaveBeenCalledTimes(1); expect(eventStatsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ ...BASIC_QUERY_PARAMS, yAxis: 'tpm()', }), }) ); expect(eventsMock).toHaveBeenCalledTimes(1); expect(eventsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ environment: ['prod'], statsPeriod: '7d', field: ['tpm()'], }), }) ); }); it('Multiple EventsRequest and DiscoverQuery based components fire individual queries without provider', async function () { const data = initializeData(); render( <Fragment> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.TPM_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA} isMEPEnabled={false} /> </Fragment>, { organization: data.organization, } ); expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3); expect(eventStatsMock).toHaveBeenCalledTimes(3); expect(eventsMock).toHaveBeenCalledTimes(3); expect(eventStatsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ ...BASIC_QUERY_PARAMS, yAxis: 'tpm()', }), }) ); }); it('Multiple EventsRequest and DiscoverQuery based components merge queries with provider', async function () { const data = initializeData(); render( <GenericQueryBatcher> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.TPM_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA} isMEPEnabled={false} /> </GenericQueryBatcher>, { organization: data.organization, } ); await waitFor(() => expect(eventStatsMock).toHaveBeenCalled()); await waitFor(() => expect(eventsMock).toHaveBeenCalled()); expect(eventsMock).toHaveBeenNthCalledWith( 1, '/organizations/org-slug/events/', expect.objectContaining({ query: expect.objectContaining({ environment: ['prod'], field: ['tpm()', 'failure_rate()', 'user_misery()'], statsPeriod: '7d', }), }) ); expect(eventsMock).toHaveBeenCalledTimes(1); expect(eventStatsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ ...BASIC_QUERY_PARAMS, yAxis: ['tpm()', 'failure_rate()', 'user_misery()'], }), }) ); expect(eventStatsMock).toHaveBeenCalledTimes(1); expect(await screen.findAllByTestId('widget-state-has-data')).toHaveLength(3); }); it('Multiple EventsRequest and DiscoverQuery based components merge queries with provider and add MEP', async function () { const data = initializeData(); render( <GenericQueryBatcher> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.TPM_AREA} isMEPEnabled /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA} isMEPEnabled /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA} isMEPEnabled /> </GenericQueryBatcher>, { organization: data.organization, } ); expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3); expect(eventStatsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ ...BASIC_QUERY_PARAMS, yAxis: ['tpm()', 'failure_rate()', 'user_misery()'], }), }) ); expect(eventStatsMock).toHaveBeenCalledTimes(1); expect(await screen.findAllByTestId('widget-state-has-data')).toHaveLength(3); }); it('Errors work correctly', async function () { eventStatsMock = MockApiClient.addMockResponse({ method: 'GET', url: `/organizations/org-slug/events-stats/`, statusCode: 404, body: {}, }); const data = initializeData(); render( <GenericQueryBatcher> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.TPM_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.FAILURE_RATE_AREA} isMEPEnabled={false} /> <WrappedComponent data={data} defaultChartSetting={PerformanceWidgetSetting.USER_MISERY_AREA} isMEPEnabled={false} /> </GenericQueryBatcher>, { organization: data.organization, } ); expect(await screen.findAllByTestId('performance-widget-title')).toHaveLength(3); expect(eventStatsMock).toHaveBeenNthCalledWith( 1, expect.anything(), expect.objectContaining({ query: expect.objectContaining({ ...BASIC_QUERY_PARAMS, yAxis: ['tpm()', 'failure_rate()', 'user_misery()'], }), }) ); expect(eventStatsMock).toHaveBeenCalledTimes(1); expect(await screen.findAllByTestId('widget-state-is-errored')).toHaveLength(3); }); });