widgetQueries.spec.tsx 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885
  1. import {EventsStatsFixture} from 'sentry-fixture/events';
  2. import {OrganizationFixture} from 'sentry-fixture/organization';
  3. import {initializeOrg} from 'sentry-test/initializeOrg';
  4. import {render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
  5. import type {PageFilters} from 'sentry/types/core';
  6. import {MetricsResultsMetaProvider} from 'sentry/utils/performance/contexts/metricsEnhancedPerformanceDataContext';
  7. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  8. import {DashboardFilterKeys, DisplayType} from 'sentry/views/dashboards/types';
  9. import {
  10. DashboardsMEPContext,
  11. DashboardsMEPProvider,
  12. } from 'sentry/views/dashboards/widgetCard/dashboardsMEPContext';
  13. import type {GenericWidgetQueriesChildrenProps} from 'sentry/views/dashboards/widgetCard/genericWidgetQueries';
  14. import WidgetQueries from 'sentry/views/dashboards/widgetCard/widgetQueries';
  15. describe('Dashboards > WidgetQueries', function () {
  16. const initialData = initializeOrg();
  17. const renderWithProviders = (component: React.ReactNode) =>
  18. render(
  19. <MetricsResultsMetaProvider>
  20. <DashboardsMEPProvider>
  21. <MEPSettingProvider forceTransactions={false}>{component}</MEPSettingProvider>
  22. </DashboardsMEPProvider>
  23. </MetricsResultsMetaProvider>
  24. );
  25. const multipleQueryWidget = {
  26. title: 'Errors',
  27. interval: '5m',
  28. displayType: DisplayType.LINE,
  29. queries: [
  30. {
  31. conditions: 'event.type:error',
  32. fields: ['count()'],
  33. aggregates: ['count()'],
  34. columns: [],
  35. name: 'errors',
  36. orderby: '',
  37. },
  38. {
  39. conditions: 'event.type:default',
  40. fields: ['count()'],
  41. aggregates: ['count()'],
  42. columns: [],
  43. name: 'default',
  44. orderby: '',
  45. },
  46. ],
  47. };
  48. const singleQueryWidget = {
  49. title: 'Errors',
  50. interval: '5m',
  51. displayType: DisplayType.LINE,
  52. queries: [
  53. {
  54. conditions: 'event.type:error',
  55. fields: ['count()'],
  56. aggregates: ['count()'],
  57. columns: [],
  58. name: 'errors',
  59. orderby: '',
  60. },
  61. ],
  62. };
  63. const tableWidget = {
  64. title: 'SDK',
  65. interval: '5m',
  66. displayType: DisplayType.TABLE,
  67. queries: [
  68. {
  69. conditions: 'event.type:error',
  70. fields: ['sdk.name'],
  71. aggregates: [],
  72. columns: ['sdk.name'],
  73. name: 'sdk',
  74. orderby: '',
  75. },
  76. ],
  77. };
  78. const selection: PageFilters = {
  79. projects: [1],
  80. environments: ['prod'],
  81. datetime: {
  82. period: '14d',
  83. start: null,
  84. end: null,
  85. utc: false,
  86. },
  87. };
  88. afterEach(function () {
  89. MockApiClient.clearMockResponses();
  90. });
  91. it('can send multiple API requests', async function () {
  92. const errorMock = MockApiClient.addMockResponse({
  93. url: '/organizations/org-slug/events-stats/',
  94. body: [],
  95. match: [MockApiClient.matchQuery({query: 'event.type:error'})],
  96. });
  97. const defaultMock = MockApiClient.addMockResponse({
  98. url: '/organizations/org-slug/events-stats/',
  99. body: [],
  100. match: [MockApiClient.matchQuery({query: 'event.type:default'})],
  101. });
  102. renderWithProviders(
  103. <WidgetQueries
  104. api={new MockApiClient()}
  105. widget={multipleQueryWidget}
  106. organization={initialData.organization}
  107. selection={selection}
  108. >
  109. {() => <div data-test-id="child" />}
  110. </WidgetQueries>
  111. );
  112. // Child should be rendered and 2 requests should be sent.
  113. await screen.findByTestId('child');
  114. expect(errorMock).toHaveBeenCalledTimes(1);
  115. expect(defaultMock).toHaveBeenCalledTimes(1);
  116. });
  117. it('appends dashboard filters to events series request', async function () {
  118. const mock = MockApiClient.addMockResponse({
  119. url: '/organizations/org-slug/events-stats/',
  120. body: [],
  121. });
  122. renderWithProviders(
  123. <WidgetQueries
  124. api={new MockApiClient()}
  125. widget={singleQueryWidget}
  126. organization={initialData.organization}
  127. selection={selection}
  128. dashboardFilters={{[DashboardFilterKeys.RELEASE]: ['abc@1.2.0', 'abc@1.3.0']}}
  129. >
  130. {() => <div data-test-id="child" />}
  131. </WidgetQueries>
  132. );
  133. await screen.findByTestId('child');
  134. expect(mock).toHaveBeenCalledWith(
  135. '/organizations/org-slug/events-stats/',
  136. expect.objectContaining({
  137. query: expect.objectContaining({
  138. query: '(event.type:error) release:["abc@1.2.0","abc@1.3.0"] ',
  139. }),
  140. })
  141. );
  142. });
  143. it('appends dashboard filters to events table request', async function () {
  144. const mock = MockApiClient.addMockResponse({
  145. url: '/organizations/org-slug/events/',
  146. body: [],
  147. });
  148. renderWithProviders(
  149. <WidgetQueries
  150. api={new MockApiClient()}
  151. widget={tableWidget}
  152. organization={initialData.organization}
  153. selection={selection}
  154. dashboardFilters={{[DashboardFilterKeys.RELEASE]: ['abc@1.3.0']}}
  155. >
  156. {() => <div data-test-id="child" />}
  157. </WidgetQueries>
  158. );
  159. await screen.findByTestId('child');
  160. expect(mock).toHaveBeenCalledWith(
  161. '/organizations/org-slug/events/',
  162. expect.objectContaining({
  163. query: expect.objectContaining({
  164. query: '(event.type:error) release:"abc@1.3.0" ',
  165. }),
  166. })
  167. );
  168. });
  169. it('sets errorMessage when the first request fails', async function () {
  170. const okMock = MockApiClient.addMockResponse({
  171. url: '/organizations/org-slug/events-stats/',
  172. match: [MockApiClient.matchQuery({query: 'event.type:error'})],
  173. body: [],
  174. });
  175. const failMock = MockApiClient.addMockResponse({
  176. url: '/organizations/org-slug/events-stats/',
  177. statusCode: 400,
  178. body: {detail: 'Bad request data'},
  179. match: [MockApiClient.matchQuery({query: 'event.type:default'})],
  180. });
  181. let error: string | undefined;
  182. renderWithProviders(
  183. <WidgetQueries
  184. api={new MockApiClient()}
  185. widget={multipleQueryWidget}
  186. organization={initialData.organization}
  187. selection={selection}
  188. >
  189. {({errorMessage}: {errorMessage?: string}) => {
  190. error = errorMessage;
  191. return <div data-test-id="child" />;
  192. }}
  193. </WidgetQueries>
  194. );
  195. // Child should be rendered and 2 requests should be sent.
  196. expect(await screen.findByTestId('child')).toBeInTheDocument();
  197. await waitFor(() => {
  198. expect(error).toBe('Bad request data');
  199. });
  200. expect(okMock).toHaveBeenCalledTimes(1);
  201. expect(failMock).toHaveBeenCalledTimes(1);
  202. });
  203. it('adjusts interval based on date window', async function () {
  204. const errorMock = MockApiClient.addMockResponse({
  205. url: '/organizations/org-slug/events-stats/',
  206. body: [],
  207. });
  208. const widget = {...singleQueryWidget, interval: '1m'};
  209. const longSelection: PageFilters = {
  210. projects: [1],
  211. environments: ['prod', 'dev'],
  212. datetime: {
  213. period: '90d',
  214. start: null,
  215. end: null,
  216. utc: false,
  217. },
  218. };
  219. renderWithProviders(
  220. <WidgetQueries
  221. api={new MockApiClient()}
  222. widget={widget}
  223. organization={initialData.organization}
  224. selection={longSelection}
  225. >
  226. {() => <div data-test-id="child" />}
  227. </WidgetQueries>
  228. );
  229. // Child should be rendered and interval bumped up.
  230. await screen.findByTestId('child');
  231. expect(errorMock).toHaveBeenCalledTimes(1);
  232. expect(errorMock).toHaveBeenCalledWith(
  233. '/organizations/org-slug/events-stats/',
  234. expect.objectContaining({
  235. query: expect.objectContaining({
  236. interval: '4h',
  237. statsPeriod: '90d',
  238. environment: ['prod', 'dev'],
  239. project: [1],
  240. }),
  241. })
  242. );
  243. });
  244. it('adjusts interval based on date window 14d', async function () {
  245. const errorMock = MockApiClient.addMockResponse({
  246. url: '/organizations/org-slug/events-stats/',
  247. body: [],
  248. });
  249. const widget = {...singleQueryWidget, interval: '1m'};
  250. renderWithProviders(
  251. <WidgetQueries
  252. api={new MockApiClient()}
  253. widget={widget}
  254. organization={initialData.organization}
  255. selection={selection}
  256. >
  257. {() => <div data-test-id="child" />}
  258. </WidgetQueries>
  259. );
  260. // Child should be rendered and interval bumped up.
  261. await screen.findByTestId('child');
  262. expect(errorMock).toHaveBeenCalledTimes(1);
  263. expect(errorMock).toHaveBeenCalledWith(
  264. '/organizations/org-slug/events-stats/',
  265. expect.objectContaining({
  266. query: expect.objectContaining({interval: '30m'}),
  267. })
  268. );
  269. });
  270. it('can send table result queries', async function () {
  271. const tableMock = MockApiClient.addMockResponse({
  272. url: '/organizations/org-slug/events/',
  273. body: {
  274. meta: {'sdk.name': 'string'},
  275. data: [{'sdk.name': 'python'}],
  276. },
  277. });
  278. let childProps: GenericWidgetQueriesChildrenProps | undefined;
  279. renderWithProviders(
  280. <WidgetQueries
  281. api={new MockApiClient()}
  282. widget={tableWidget}
  283. organization={initialData.organization}
  284. selection={selection}
  285. >
  286. {props => {
  287. childProps = props;
  288. return <div data-test-id="child" />;
  289. }}
  290. </WidgetQueries>
  291. );
  292. // Child should be rendered and 1 requests should be sent.
  293. await screen.findByTestId('child');
  294. expect(tableMock).toHaveBeenCalledTimes(1);
  295. expect(tableMock).toHaveBeenCalledWith(
  296. '/organizations/org-slug/events/',
  297. expect.objectContaining({
  298. query: expect.objectContaining({
  299. query: 'event.type:error',
  300. field: 'sdk.name',
  301. statsPeriod: '14d',
  302. environment: 'prod',
  303. project: '1',
  304. }),
  305. })
  306. );
  307. expect(childProps?.timeseriesResults).toBeUndefined();
  308. await waitFor(() => expect(childProps?.tableResults?.[0]!.data).toHaveLength(1));
  309. expect(childProps?.tableResults?.[0]!.meta).toBeDefined();
  310. });
  311. it('can send multiple table queries', async function () {
  312. const firstQuery = MockApiClient.addMockResponse({
  313. url: '/organizations/org-slug/events/',
  314. body: {
  315. meta: {'sdk.name': 'string'},
  316. data: [{'sdk.name': 'python'}],
  317. },
  318. match: [MockApiClient.matchQuery({query: 'event.type:error'})],
  319. });
  320. const secondQuery = MockApiClient.addMockResponse({
  321. url: '/organizations/org-slug/events/',
  322. body: {
  323. meta: {title: 'string'},
  324. data: [{title: 'ValueError'}],
  325. },
  326. match: [MockApiClient.matchQuery({query: 'title:ValueError'})],
  327. });
  328. const widget = {
  329. title: 'SDK',
  330. interval: '5m',
  331. displayType: DisplayType.TABLE,
  332. queries: [
  333. {
  334. conditions: 'event.type:error',
  335. fields: ['sdk.name'],
  336. aggregates: [],
  337. columns: ['sdk.name'],
  338. name: 'sdk',
  339. orderby: '',
  340. },
  341. {
  342. conditions: 'title:ValueError',
  343. fields: ['title'],
  344. aggregates: [],
  345. columns: ['sdk.name'],
  346. name: 'title',
  347. orderby: '',
  348. },
  349. ],
  350. };
  351. let childProps: GenericWidgetQueriesChildrenProps | undefined;
  352. renderWithProviders(
  353. <WidgetQueries
  354. api={new MockApiClient()}
  355. widget={widget}
  356. organization={initialData.organization}
  357. selection={selection}
  358. >
  359. {props => {
  360. childProps = props;
  361. return <div data-test-id="child" />;
  362. }}
  363. </WidgetQueries>
  364. );
  365. // Child should be rendered and 2 requests should be sent.
  366. await screen.findByTestId('child');
  367. expect(firstQuery).toHaveBeenCalledTimes(1);
  368. expect(secondQuery).toHaveBeenCalledTimes(1);
  369. await waitFor(() => expect(childProps?.tableResults).toHaveLength(2));
  370. expect(childProps?.tableResults?.[0]!.data[0]!['sdk.name']).toBeDefined();
  371. expect(childProps?.tableResults?.[1]!.data[0]!.title).toBeDefined();
  372. });
  373. it('can send big number result queries', async function () {
  374. const tableMock = MockApiClient.addMockResponse({
  375. url: '/organizations/org-slug/events/',
  376. body: {
  377. meta: {'sdk.name': 'string'},
  378. data: [{'sdk.name': 'python'}],
  379. },
  380. });
  381. let childProps: GenericWidgetQueriesChildrenProps | undefined;
  382. renderWithProviders(
  383. <WidgetQueries
  384. api={new MockApiClient()}
  385. widget={{
  386. title: 'SDK',
  387. interval: '5m',
  388. displayType: DisplayType.BIG_NUMBER,
  389. queries: [
  390. {
  391. conditions: 'event.type:error',
  392. fields: ['sdk.name'],
  393. aggregates: [],
  394. columns: ['sdk.name'],
  395. name: 'sdk',
  396. orderby: '',
  397. },
  398. ],
  399. }}
  400. organization={initialData.organization}
  401. selection={selection}
  402. >
  403. {props => {
  404. childProps = props;
  405. return <div data-test-id="child" />;
  406. }}
  407. </WidgetQueries>
  408. );
  409. // Child should be rendered and 1 requests should be sent.
  410. await screen.findByTestId('child');
  411. expect(tableMock).toHaveBeenCalledTimes(1);
  412. expect(tableMock).toHaveBeenCalledWith(
  413. '/organizations/org-slug/events/',
  414. expect.objectContaining({
  415. query: expect.objectContaining({
  416. referrer: 'api.dashboards.bignumberwidget',
  417. query: 'event.type:error',
  418. field: 'sdk.name',
  419. statsPeriod: '14d',
  420. environment: 'prod',
  421. project: '1',
  422. }),
  423. })
  424. );
  425. expect(childProps?.timeseriesResults).toBeUndefined();
  426. await waitFor(() => expect(childProps?.tableResults?.[0]?.data).toHaveLength(1));
  427. expect(childProps?.tableResults?.[0]?.meta).toBeDefined();
  428. });
  429. it('stops loading state once all queries finish even if some fail', async function () {
  430. const firstQuery = MockApiClient.addMockResponse({
  431. statusCode: 500,
  432. url: '/organizations/org-slug/events/',
  433. body: {detail: 'it didnt work'},
  434. match: [MockApiClient.matchQuery({query: 'event.type:error'})],
  435. });
  436. const secondQuery = MockApiClient.addMockResponse({
  437. url: '/organizations/org-slug/events/',
  438. body: {
  439. meta: {title: 'string'},
  440. data: [{title: 'ValueError'}],
  441. },
  442. match: [MockApiClient.matchQuery({query: 'title:ValueError'})],
  443. });
  444. const widget = {
  445. title: 'SDK',
  446. interval: '5m',
  447. displayType: DisplayType.TABLE,
  448. queries: [
  449. {
  450. conditions: 'event.type:error',
  451. fields: ['sdk.name'],
  452. aggregates: [],
  453. columns: ['sdk.name'],
  454. name: 'sdk',
  455. orderby: '',
  456. },
  457. {
  458. conditions: 'title:ValueError',
  459. fields: ['sdk.name'],
  460. aggregates: [],
  461. columns: ['sdk.name'],
  462. name: 'title',
  463. orderby: '',
  464. },
  465. ],
  466. };
  467. let childProps: GenericWidgetQueriesChildrenProps | undefined;
  468. renderWithProviders(
  469. <WidgetQueries
  470. api={new MockApiClient()}
  471. widget={widget}
  472. organization={initialData.organization}
  473. selection={selection}
  474. >
  475. {props => {
  476. childProps = props;
  477. return <div data-test-id="child" />;
  478. }}
  479. </WidgetQueries>
  480. );
  481. // Child should be rendered and 2 requests should be sent.
  482. await screen.findByTestId('child');
  483. expect(firstQuery).toHaveBeenCalledTimes(1);
  484. expect(secondQuery).toHaveBeenCalledTimes(1);
  485. await waitFor(() => expect(childProps?.loading).toBe(false));
  486. });
  487. it('sets bar charts to 1d interval', async function () {
  488. const errorMock = MockApiClient.addMockResponse({
  489. url: '/organizations/org-slug/events-stats/',
  490. body: [],
  491. match: [MockApiClient.matchQuery({interval: '1d'})],
  492. });
  493. const barWidget = {
  494. ...singleQueryWidget,
  495. displayType: DisplayType.BAR,
  496. // Should be ignored for bars.
  497. interval: '5m',
  498. };
  499. renderWithProviders(
  500. <WidgetQueries
  501. api={new MockApiClient()}
  502. widget={barWidget}
  503. organization={initialData.organization}
  504. selection={selection}
  505. >
  506. {() => <div data-test-id="child" />}
  507. </WidgetQueries>
  508. );
  509. // Child should be rendered and 1 requests should be sent.
  510. await screen.findByTestId('child');
  511. expect(errorMock).toHaveBeenCalledTimes(1);
  512. });
  513. it('returns timeseriesResults in the same order as widgetQuery', async function () {
  514. MockApiClient.clearMockResponses();
  515. const defaultMock = MockApiClient.addMockResponse({
  516. url: '/organizations/org-slug/events-stats/',
  517. method: 'GET',
  518. body: {
  519. data: [
  520. [
  521. 1000,
  522. [
  523. {
  524. count: 100,
  525. },
  526. ],
  527. ],
  528. ],
  529. start: 1000,
  530. end: 2000,
  531. },
  532. match: [MockApiClient.matchQuery({query: 'event.type:default'})],
  533. });
  534. const errorMock = MockApiClient.addMockResponse({
  535. url: '/organizations/org-slug/events-stats/',
  536. method: 'GET',
  537. body: {
  538. data: [
  539. [
  540. 1000,
  541. [
  542. {
  543. count: 200,
  544. },
  545. ],
  546. ],
  547. ],
  548. start: 1000,
  549. end: 2000,
  550. },
  551. match: [MockApiClient.matchQuery({query: 'event.type:error'})],
  552. });
  553. const barWidget = {
  554. ...multipleQueryWidget,
  555. displayType: DisplayType.BAR,
  556. // Should be ignored for bars.
  557. interval: '5m',
  558. };
  559. const child = jest.fn(() => <div data-test-id="child" />);
  560. renderWithProviders(
  561. <WidgetQueries
  562. api={new MockApiClient()}
  563. widget={barWidget}
  564. organization={initialData.organization}
  565. selection={selection}
  566. >
  567. {child}
  568. </WidgetQueries>
  569. );
  570. await screen.findByTestId('child');
  571. expect(defaultMock).toHaveBeenCalledTimes(1);
  572. expect(errorMock).toHaveBeenCalledTimes(1);
  573. await waitFor(() =>
  574. expect(child).toHaveBeenLastCalledWith(
  575. expect.objectContaining({
  576. timeseriesResults: [
  577. {data: [{name: 1000000, value: 200}], seriesName: 'errors : count()'},
  578. {data: [{name: 1000000, value: 100}], seriesName: 'default : count()'},
  579. ],
  580. })
  581. )
  582. );
  583. });
  584. it('calls events-stats with 4h interval when interval buckets would exceed 66', async function () {
  585. const eventsStatsMock = MockApiClient.addMockResponse({
  586. url: '/organizations/org-slug/events-stats/',
  587. body: [],
  588. });
  589. const areaWidget = {
  590. ...singleQueryWidget,
  591. displayType: DisplayType.AREA,
  592. interval: '5m',
  593. };
  594. renderWithProviders(
  595. <WidgetQueries
  596. api={new MockApiClient()}
  597. widget={areaWidget}
  598. organization={initialData.organization}
  599. selection={{
  600. ...selection,
  601. datetime: {
  602. period: '90d',
  603. start: null,
  604. end: null,
  605. utc: false,
  606. },
  607. }}
  608. >
  609. {() => <div data-test-id="child" />}
  610. </WidgetQueries>
  611. );
  612. // Child should be rendered and 1 requests should be sent.
  613. await screen.findByTestId('child');
  614. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  615. expect(eventsStatsMock).toHaveBeenCalledWith(
  616. '/organizations/org-slug/events-stats/',
  617. expect.objectContaining({query: expect.objectContaining({interval: '4h'})})
  618. );
  619. });
  620. it('does not re-query events and sets name in widgets', async function () {
  621. const eventsStatsMock = MockApiClient.addMockResponse({
  622. url: '/organizations/org-slug/events-stats/',
  623. body: EventsStatsFixture(),
  624. });
  625. const lineWidget = {
  626. ...singleQueryWidget,
  627. displayType: DisplayType.LINE,
  628. interval: '5m',
  629. };
  630. let childProps!: GenericWidgetQueriesChildrenProps;
  631. const {rerender} = renderWithProviders(
  632. <WidgetQueries
  633. api={new MockApiClient()}
  634. widget={lineWidget}
  635. organization={initialData.organization}
  636. selection={selection}
  637. >
  638. {props => {
  639. childProps = props;
  640. return <div data-test-id="child" />;
  641. }}
  642. </WidgetQueries>
  643. );
  644. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  645. await waitFor(() => expect(childProps.loading).toBe(false));
  646. // Simulate a re-render with a new query alias
  647. rerender(
  648. <MetricsResultsMetaProvider>
  649. <DashboardsMEPProvider>
  650. <MEPSettingProvider forceTransactions={false}>
  651. <WidgetQueries
  652. api={new MockApiClient()}
  653. widget={{
  654. ...lineWidget,
  655. queries: [
  656. {
  657. conditions: 'event.type:error',
  658. fields: ['count()'],
  659. aggregates: ['count()'],
  660. columns: [],
  661. name: 'this query alias changed',
  662. orderby: '',
  663. },
  664. ],
  665. }}
  666. organization={initialData.organization}
  667. selection={selection}
  668. >
  669. {props => {
  670. childProps = props;
  671. return <div data-test-id="child" />;
  672. }}
  673. </WidgetQueries>
  674. </MEPSettingProvider>
  675. </DashboardsMEPProvider>
  676. </MetricsResultsMetaProvider>
  677. );
  678. // Did not re-query
  679. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  680. expect(childProps.timeseriesResults![0]!.seriesName).toBe(
  681. 'this query alias changed : count()'
  682. );
  683. });
  684. it('charts send metricsEnhanced requests', async function () {
  685. const {organization} = initialData;
  686. const mock = MockApiClient.addMockResponse({
  687. url: '/organizations/org-slug/events-stats/',
  688. body: {
  689. data: [
  690. [
  691. 1000,
  692. [
  693. {
  694. count: 100,
  695. },
  696. ],
  697. ],
  698. ],
  699. isMetricsData: false,
  700. start: 1000,
  701. end: 2000,
  702. },
  703. });
  704. const setIsMetricsMock = jest.fn();
  705. const children = jest.fn(() => <div />);
  706. renderWithProviders(
  707. <DashboardsMEPContext.Provider
  708. value={{
  709. isMetricsData: undefined,
  710. setIsMetricsData: setIsMetricsMock,
  711. }}
  712. >
  713. <WidgetQueries
  714. api={new MockApiClient()}
  715. widget={singleQueryWidget}
  716. organization={{
  717. ...organization,
  718. features: [...organization.features, 'dashboards-mep'],
  719. }}
  720. selection={selection}
  721. >
  722. {children}
  723. </WidgetQueries>
  724. </DashboardsMEPContext.Provider>
  725. );
  726. expect(mock).toHaveBeenCalledWith(
  727. '/organizations/org-slug/events-stats/',
  728. expect.objectContaining({
  729. query: expect.objectContaining({dataset: 'metricsEnhanced'}),
  730. })
  731. );
  732. await waitFor(() => {
  733. expect(setIsMetricsMock).toHaveBeenCalledWith(false);
  734. });
  735. });
  736. it('tables send metricsEnhanced requests', async function () {
  737. const {organization} = initialData;
  738. const mock = MockApiClient.addMockResponse({
  739. url: '/organizations/org-slug/events/',
  740. body: {
  741. meta: {title: 'string', isMetricsData: true},
  742. data: [{title: 'ValueError'}],
  743. },
  744. });
  745. const setIsMetricsMock = jest.fn();
  746. const children = jest.fn(() => <div />);
  747. renderWithProviders(
  748. <DashboardsMEPContext.Provider
  749. value={{
  750. isMetricsData: undefined,
  751. setIsMetricsData: setIsMetricsMock,
  752. }}
  753. >
  754. <WidgetQueries
  755. api={new MockApiClient()}
  756. widget={{...singleQueryWidget, displayType: DisplayType.TABLE}}
  757. organization={{
  758. ...organization,
  759. features: [...organization.features, 'dashboards-mep'],
  760. }}
  761. selection={selection}
  762. >
  763. {children}
  764. </WidgetQueries>
  765. </DashboardsMEPContext.Provider>
  766. );
  767. expect(mock).toHaveBeenCalledWith(
  768. '/organizations/org-slug/events/',
  769. expect.objectContaining({
  770. query: expect.objectContaining({dataset: 'metricsEnhanced'}),
  771. })
  772. );
  773. await waitFor(() => {
  774. expect(setIsMetricsMock).toHaveBeenCalledWith(true);
  775. });
  776. });
  777. it('does not inject equation aliases for top N requests', async function () {
  778. const testData = initializeOrg({
  779. organization: {
  780. ...OrganizationFixture(),
  781. },
  782. });
  783. const eventsStatsMock = MockApiClient.addMockResponse({
  784. url: '/organizations/org-slug/events-stats/',
  785. body: [],
  786. });
  787. const areaWidget = {
  788. title: 'Errors',
  789. displayType: DisplayType.AREA,
  790. interval: '5m',
  791. queries: [
  792. {
  793. conditions: 'event.type:error',
  794. fields: [],
  795. aggregates: ['count()', 'equation|count() * 2'],
  796. columns: ['project'],
  797. orderby: 'equation[0]',
  798. name: '',
  799. },
  800. ],
  801. };
  802. renderWithProviders(
  803. <WidgetQueries
  804. api={new MockApiClient()}
  805. widget={areaWidget}
  806. organization={testData.organization}
  807. selection={selection}
  808. >
  809. {() => <div data-test-id="child" />}
  810. </WidgetQueries>
  811. );
  812. // Child should be rendered and 1 requests should be sent.
  813. await screen.findByTestId('child');
  814. expect(eventsStatsMock).toHaveBeenCalledTimes(1);
  815. expect(eventsStatsMock).toHaveBeenCalledWith(
  816. '/organizations/org-slug/events-stats/',
  817. expect.objectContaining({
  818. query: expect.objectContaining({
  819. field: ['project', 'count()', 'equation|count() * 2'],
  820. orderby: 'equation[0]',
  821. }),
  822. })
  823. );
  824. });
  825. });