index.spec.tsx 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726
  1. import {initializeOrg} from 'sentry-test/initializeOrg';
  2. import {
  3. render,
  4. renderGlobalModal,
  5. screen,
  6. userEvent,
  7. waitFor,
  8. } from 'sentry-test/reactTestingLibrary';
  9. import * as modal from 'sentry/actionCreators/modal';
  10. import {Client} from 'sentry/api';
  11. import * as LineChart from 'sentry/components/charts/lineChart';
  12. import SimpleTableChart from 'sentry/components/charts/simpleTableChart';
  13. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  14. import {DisplayType, Widget, WidgetType} from 'sentry/views/dashboards/types';
  15. import WidgetCard from 'sentry/views/dashboards/widgetCard';
  16. import ReleaseWidgetQueries from 'sentry/views/dashboards/widgetCard/releaseWidgetQueries';
  17. jest.mock('sentry/components/charts/simpleTableChart');
  18. jest.mock('sentry/views/dashboards/widgetCard/releaseWidgetQueries');
  19. describe('Dashboards > WidgetCard', function () {
  20. const {router, organization, routerContext} = initializeOrg({
  21. organization: TestStubs.Organization({
  22. features: ['dashboards-edit', 'discover-basic'],
  23. projects: [TestStubs.Project()],
  24. }),
  25. router: {orgId: 'orgId'},
  26. } as Parameters<typeof initializeOrg>[0]);
  27. const renderWithProviders = component =>
  28. render(
  29. <MEPSettingProvider forceTransactions={false}>{component}</MEPSettingProvider>,
  30. {organization, router, context: routerContext}
  31. );
  32. const multipleQueryWidget: Widget = {
  33. title: 'Errors',
  34. interval: '5m',
  35. displayType: DisplayType.LINE,
  36. widgetType: WidgetType.DISCOVER,
  37. queries: [
  38. {
  39. conditions: 'event.type:error',
  40. fields: ['count()', 'failure_count()'],
  41. aggregates: ['count()', 'failure_count()'],
  42. columns: [],
  43. name: 'errors',
  44. orderby: '',
  45. },
  46. {
  47. conditions: 'event.type:default',
  48. fields: ['count()', 'failure_count()'],
  49. aggregates: ['count()', 'failure_count()'],
  50. columns: [],
  51. name: 'default',
  52. orderby: '',
  53. },
  54. ],
  55. };
  56. const selection = {
  57. projects: [1],
  58. environments: ['prod'],
  59. datetime: {
  60. period: '14d',
  61. start: null,
  62. end: null,
  63. utc: false,
  64. },
  65. };
  66. const api = new Client();
  67. let eventsMock;
  68. beforeEach(function () {
  69. MockApiClient.addMockResponse({
  70. url: '/organizations/org-slug/events-stats/',
  71. body: {meta: {isMetricsData: false}},
  72. });
  73. MockApiClient.addMockResponse({
  74. url: '/organizations/org-slug/events-geo/',
  75. body: {meta: {isMetricsData: false}},
  76. });
  77. eventsMock = MockApiClient.addMockResponse({
  78. url: '/organizations/org-slug/events/',
  79. body: {
  80. meta: {fields: {title: 'string'}},
  81. data: [{title: 'title'}],
  82. },
  83. });
  84. });
  85. afterEach(function () {
  86. MockApiClient.clearMockResponses();
  87. });
  88. it('renders with Open in Discover button and opens the Query Selector Modal when clicked', async function () {
  89. const spy = jest.spyOn(modal, 'openDashboardWidgetQuerySelectorModal');
  90. renderWithProviders(
  91. <WidgetCard
  92. api={api}
  93. organization={organization}
  94. widget={multipleQueryWidget}
  95. selection={selection}
  96. isEditing={false}
  97. onDelete={() => undefined}
  98. onEdit={() => undefined}
  99. onDuplicate={() => undefined}
  100. renderErrorMessage={() => undefined}
  101. showContextMenu
  102. widgetLimitReached={false}
  103. />
  104. );
  105. await userEvent.click(await screen.findByLabelText('Widget actions'));
  106. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  107. await userEvent.click(screen.getByText('Open in Discover'));
  108. expect(spy).toHaveBeenCalledWith({
  109. isMetricsData: false,
  110. organization,
  111. widget: multipleQueryWidget,
  112. });
  113. });
  114. it('renders with Open in Discover button and opens in Discover when clicked', async function () {
  115. renderWithProviders(
  116. <WidgetCard
  117. api={api}
  118. organization={organization}
  119. widget={{...multipleQueryWidget, queries: [multipleQueryWidget.queries[0]]}}
  120. selection={selection}
  121. isEditing={false}
  122. onDelete={() => undefined}
  123. onEdit={() => undefined}
  124. onDuplicate={() => undefined}
  125. renderErrorMessage={() => undefined}
  126. showContextMenu
  127. widgetLimitReached={false}
  128. />
  129. );
  130. await userEvent.click(await screen.findByLabelText('Widget actions'));
  131. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  132. await userEvent.click(screen.getByText('Open in Discover'));
  133. expect(router.push).toHaveBeenCalledWith(
  134. '/organizations/org-slug/discover/results/?environment=prod&field=count%28%29&field=failure_count%28%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29&yAxis=failure_count%28%29'
  135. );
  136. });
  137. it('Opens in Discover with World Map', async function () {
  138. renderWithProviders(
  139. <WidgetCard
  140. api={api}
  141. organization={organization}
  142. widget={{
  143. ...multipleQueryWidget,
  144. displayType: DisplayType.WORLD_MAP,
  145. queries: [
  146. {
  147. ...multipleQueryWidget.queries[0],
  148. fields: ['count()'],
  149. aggregates: ['count()'],
  150. columns: [],
  151. },
  152. ],
  153. }}
  154. selection={selection}
  155. isEditing={false}
  156. onDelete={() => undefined}
  157. onEdit={() => undefined}
  158. onDuplicate={() => undefined}
  159. renderErrorMessage={() => undefined}
  160. showContextMenu
  161. widgetLimitReached={false}
  162. />
  163. );
  164. await userEvent.click(await screen.findByLabelText('Widget actions'));
  165. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  166. await userEvent.click(screen.getByText('Open in Discover'));
  167. expect(router.push).toHaveBeenCalledWith(
  168. '/organizations/org-slug/discover/results/?display=worldmap&environment=prod&field=geo.country_code&field=count%28%29&name=Errors&project=1&query=event.type%3Aerror%20has%3Ageo.country_code&statsPeriod=14d&yAxis=count%28%29'
  169. );
  170. });
  171. it('Opens in Discover with prepended fields pulled from equations', async function () {
  172. renderWithProviders(
  173. <WidgetCard
  174. api={api}
  175. organization={organization}
  176. widget={{
  177. ...multipleQueryWidget,
  178. queries: [
  179. {
  180. ...multipleQueryWidget.queries[0],
  181. fields: [
  182. 'equation|(count() + failure_count()) / count_if(transaction.duration,equals,300)',
  183. ],
  184. columns: [],
  185. aggregates: [
  186. 'equation|(count() + failure_count()) / count_if(transaction.duration,equals,300)',
  187. ],
  188. },
  189. ],
  190. }}
  191. selection={selection}
  192. isEditing={false}
  193. onDelete={() => undefined}
  194. onEdit={() => undefined}
  195. onDuplicate={() => undefined}
  196. renderErrorMessage={() => undefined}
  197. showContextMenu
  198. widgetLimitReached={false}
  199. />
  200. );
  201. await userEvent.click(await screen.findByLabelText('Widget actions'));
  202. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  203. await userEvent.click(screen.getByText('Open in Discover'));
  204. expect(router.push).toHaveBeenCalledWith(
  205. '/organizations/org-slug/discover/results/?environment=prod&field=count_if%28transaction.duration%2Cequals%2C300%29&field=failure_count%28%29&field=count%28%29&field=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=equation%7C%28count%28%29%20%2B%20failure_count%28%29%29%20%2F%20count_if%28transaction.duration%2Cequals%2C300%29'
  206. );
  207. });
  208. it('Opens in Discover with Top N', async function () {
  209. renderWithProviders(
  210. <WidgetCard
  211. api={api}
  212. organization={organization}
  213. widget={{
  214. ...multipleQueryWidget,
  215. displayType: DisplayType.TOP_N,
  216. queries: [
  217. {
  218. ...multipleQueryWidget.queries[0],
  219. fields: ['transaction', 'count()'],
  220. columns: ['transaction'],
  221. aggregates: ['count()'],
  222. },
  223. ],
  224. }}
  225. selection={selection}
  226. isEditing={false}
  227. onDelete={() => undefined}
  228. onEdit={() => undefined}
  229. onDuplicate={() => undefined}
  230. renderErrorMessage={() => undefined}
  231. showContextMenu
  232. widgetLimitReached={false}
  233. />
  234. );
  235. await userEvent.click(await screen.findByLabelText('Widget actions'));
  236. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  237. await userEvent.click(screen.getByText('Open in Discover'));
  238. expect(router.push).toHaveBeenCalledWith(
  239. '/organizations/org-slug/discover/results/?display=top5&environment=prod&field=transaction&field=count%28%29&name=Errors&project=1&query=event.type%3Aerror&statsPeriod=14d&yAxis=count%28%29'
  240. );
  241. });
  242. it('allows Open in Discover when the widget contains custom measurements', async function () {
  243. renderWithProviders(
  244. <WidgetCard
  245. api={api}
  246. organization={organization}
  247. widget={{
  248. ...multipleQueryWidget,
  249. displayType: DisplayType.LINE,
  250. queries: [
  251. {
  252. ...multipleQueryWidget.queries[0],
  253. conditions: '',
  254. fields: [],
  255. columns: [],
  256. aggregates: ['p99(measurements.custom.measurement)'],
  257. },
  258. ],
  259. }}
  260. selection={selection}
  261. isEditing={false}
  262. onDelete={() => undefined}
  263. onEdit={() => undefined}
  264. onDuplicate={() => undefined}
  265. renderErrorMessage={() => undefined}
  266. showContextMenu
  267. widgetLimitReached={false}
  268. />
  269. );
  270. await userEvent.click(await screen.findByLabelText('Widget actions'));
  271. expect(screen.getByText('Open in Discover')).toBeInTheDocument();
  272. await userEvent.click(screen.getByText('Open in Discover'));
  273. expect(router.push).toHaveBeenCalledWith(
  274. '/organizations/org-slug/discover/results/?environment=prod&field=p99%28measurements.custom.measurement%29&name=Errors&project=1&query=&statsPeriod=14d&yAxis=p99%28measurements.custom.measurement%29'
  275. );
  276. });
  277. it('calls onDuplicate when Duplicate Widget is clicked', async function () {
  278. const mock = jest.fn();
  279. renderWithProviders(
  280. <WidgetCard
  281. api={api}
  282. organization={organization}
  283. widget={{
  284. ...multipleQueryWidget,
  285. displayType: DisplayType.WORLD_MAP,
  286. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  287. }}
  288. selection={selection}
  289. isEditing={false}
  290. onDelete={() => undefined}
  291. onEdit={() => undefined}
  292. onDuplicate={mock}
  293. renderErrorMessage={() => undefined}
  294. showContextMenu
  295. widgetLimitReached={false}
  296. />
  297. );
  298. await userEvent.click(await screen.findByLabelText('Widget actions'));
  299. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  300. await userEvent.click(screen.getByText('Duplicate Widget'));
  301. expect(mock).toHaveBeenCalledTimes(1);
  302. });
  303. it('does not add duplicate widgets if max widget is reached', async function () {
  304. const mock = jest.fn();
  305. renderWithProviders(
  306. <WidgetCard
  307. api={api}
  308. organization={organization}
  309. widget={{
  310. ...multipleQueryWidget,
  311. displayType: DisplayType.WORLD_MAP,
  312. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  313. }}
  314. selection={selection}
  315. isEditing={false}
  316. onDelete={() => undefined}
  317. onEdit={() => undefined}
  318. onDuplicate={mock}
  319. renderErrorMessage={() => undefined}
  320. showContextMenu
  321. widgetLimitReached
  322. />
  323. );
  324. await userEvent.click(await screen.findByLabelText('Widget actions'));
  325. expect(screen.getByText('Duplicate Widget')).toBeInTheDocument();
  326. await userEvent.click(screen.getByText('Duplicate Widget'));
  327. expect(mock).toHaveBeenCalledTimes(0);
  328. });
  329. it('calls onEdit when Edit Widget is clicked', async function () {
  330. const mock = jest.fn();
  331. renderWithProviders(
  332. <WidgetCard
  333. api={api}
  334. organization={organization}
  335. widget={{
  336. ...multipleQueryWidget,
  337. displayType: DisplayType.WORLD_MAP,
  338. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  339. }}
  340. selection={selection}
  341. isEditing={false}
  342. onDelete={() => undefined}
  343. onEdit={mock}
  344. onDuplicate={() => undefined}
  345. renderErrorMessage={() => undefined}
  346. showContextMenu
  347. widgetLimitReached={false}
  348. />
  349. );
  350. await userEvent.click(await screen.findByLabelText('Widget actions'));
  351. expect(screen.getByText('Edit Widget')).toBeInTheDocument();
  352. await userEvent.click(screen.getByText('Edit Widget'));
  353. expect(mock).toHaveBeenCalledTimes(1);
  354. });
  355. it('renders delete widget option', async function () {
  356. const mock = jest.fn();
  357. renderWithProviders(
  358. <WidgetCard
  359. api={api}
  360. organization={organization}
  361. widget={{
  362. ...multipleQueryWidget,
  363. displayType: DisplayType.WORLD_MAP,
  364. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  365. }}
  366. selection={selection}
  367. isEditing={false}
  368. onDelete={mock}
  369. onEdit={() => undefined}
  370. onDuplicate={() => undefined}
  371. renderErrorMessage={() => undefined}
  372. showContextMenu
  373. widgetLimitReached={false}
  374. />
  375. );
  376. await userEvent.click(await screen.findByLabelText('Widget actions'));
  377. expect(screen.getByText('Delete Widget')).toBeInTheDocument();
  378. await userEvent.click(screen.getByText('Delete Widget'));
  379. // Confirm Modal
  380. renderGlobalModal();
  381. await screen.findByRole('dialog');
  382. await userEvent.click(screen.getByTestId('confirm-button'));
  383. expect(mock).toHaveBeenCalled();
  384. });
  385. it('calls events with a limit of 20 items', async function () {
  386. const mock = jest.fn();
  387. renderWithProviders(
  388. <WidgetCard
  389. api={api}
  390. organization={organization}
  391. widget={{
  392. ...multipleQueryWidget,
  393. displayType: DisplayType.TABLE,
  394. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  395. }}
  396. selection={selection}
  397. isEditing={false}
  398. onDelete={mock}
  399. onEdit={() => undefined}
  400. onDuplicate={() => undefined}
  401. renderErrorMessage={() => undefined}
  402. showContextMenu
  403. widgetLimitReached={false}
  404. tableItemLimit={20}
  405. />
  406. );
  407. await waitFor(() => {
  408. expect(eventsMock).toHaveBeenCalledWith(
  409. '/organizations/org-slug/events/',
  410. expect.objectContaining({
  411. query: expect.objectContaining({
  412. per_page: 20,
  413. }),
  414. })
  415. );
  416. });
  417. });
  418. it('calls events with a default limit of 5 items', async function () {
  419. const mock = jest.fn();
  420. renderWithProviders(
  421. <WidgetCard
  422. api={api}
  423. organization={organization}
  424. widget={{
  425. ...multipleQueryWidget,
  426. displayType: DisplayType.TABLE,
  427. queries: [{...multipleQueryWidget.queries[0], fields: ['count()']}],
  428. }}
  429. selection={selection}
  430. isEditing={false}
  431. onDelete={mock}
  432. onEdit={() => undefined}
  433. onDuplicate={() => undefined}
  434. renderErrorMessage={() => undefined}
  435. showContextMenu
  436. widgetLimitReached={false}
  437. />
  438. );
  439. await waitFor(() => {
  440. expect(eventsMock).toHaveBeenCalledWith(
  441. '/organizations/org-slug/events/',
  442. expect.objectContaining({
  443. query: expect.objectContaining({
  444. per_page: 5,
  445. }),
  446. })
  447. );
  448. });
  449. });
  450. it('has sticky table headers', async function () {
  451. const tableWidget: Widget = {
  452. title: 'Table Widget',
  453. interval: '5m',
  454. displayType: DisplayType.TABLE,
  455. widgetType: WidgetType.DISCOVER,
  456. queries: [
  457. {
  458. conditions: '',
  459. fields: ['transaction', 'count()'],
  460. columns: ['transaction'],
  461. aggregates: ['count()'],
  462. name: 'Table',
  463. orderby: '',
  464. },
  465. ],
  466. };
  467. renderWithProviders(
  468. <WidgetCard
  469. api={api}
  470. organization={organization}
  471. widget={tableWidget}
  472. selection={selection}
  473. isEditing={false}
  474. onDelete={() => undefined}
  475. onEdit={() => undefined}
  476. onDuplicate={() => undefined}
  477. renderErrorMessage={() => undefined}
  478. showContextMenu
  479. widgetLimitReached={false}
  480. tableItemLimit={20}
  481. />
  482. );
  483. await waitFor(() => expect(eventsMock).toHaveBeenCalled());
  484. expect(SimpleTableChart).toHaveBeenCalledWith(
  485. expect.objectContaining({stickyHeaders: true}),
  486. expect.anything()
  487. );
  488. });
  489. it('calls release queries', function () {
  490. const widget: Widget = {
  491. title: 'Release Widget',
  492. interval: '5m',
  493. displayType: DisplayType.LINE,
  494. widgetType: WidgetType.RELEASE,
  495. queries: [],
  496. };
  497. renderWithProviders(
  498. <WidgetCard
  499. api={api}
  500. organization={organization}
  501. widget={widget}
  502. selection={selection}
  503. isEditing={false}
  504. onDelete={() => undefined}
  505. onEdit={() => undefined}
  506. onDuplicate={() => undefined}
  507. renderErrorMessage={() => undefined}
  508. showContextMenu
  509. widgetLimitReached={false}
  510. tableItemLimit={20}
  511. />
  512. );
  513. expect(ReleaseWidgetQueries).toHaveBeenCalledTimes(1);
  514. });
  515. it('opens the widget viewer modal when a widget has no id', async () => {
  516. const widget: Widget = {
  517. title: 'Widget',
  518. interval: '5m',
  519. displayType: DisplayType.LINE,
  520. widgetType: WidgetType.DISCOVER,
  521. queries: [],
  522. };
  523. renderWithProviders(
  524. <WidgetCard
  525. api={api}
  526. organization={organization}
  527. widget={widget}
  528. selection={selection}
  529. isEditing={false}
  530. onDelete={() => undefined}
  531. onEdit={() => undefined}
  532. onDuplicate={() => undefined}
  533. renderErrorMessage={() => undefined}
  534. showContextMenu
  535. widgetLimitReached={false}
  536. index="10"
  537. isPreview
  538. />
  539. );
  540. await userEvent.click(await screen.findByLabelText('Open Widget Viewer'));
  541. expect(router.push).toHaveBeenCalledWith(
  542. expect.objectContaining({pathname: '/mock-pathname/widget/10/'})
  543. );
  544. });
  545. it('renders stored data disclaimer', async function () {
  546. MockApiClient.addMockResponse({
  547. url: '/organizations/org-slug/events/',
  548. body: {
  549. meta: {title: 'string', isMetricsData: false},
  550. data: [{title: 'title'}],
  551. },
  552. });
  553. renderWithProviders(
  554. <WidgetCard
  555. api={api}
  556. organization={{
  557. ...organization,
  558. features: [...organization.features, 'dashboards-mep'],
  559. }}
  560. widget={{
  561. ...multipleQueryWidget,
  562. displayType: DisplayType.TABLE,
  563. queries: [{...multipleQueryWidget.queries[0]}],
  564. }}
  565. selection={selection}
  566. isEditing={false}
  567. onDelete={() => undefined}
  568. onEdit={() => undefined}
  569. onDuplicate={() => undefined}
  570. renderErrorMessage={() => undefined}
  571. showContextMenu
  572. widgetLimitReached={false}
  573. showStoredAlert
  574. />
  575. );
  576. // Badge in the widget header
  577. expect(await screen.findByText('Indexed')).toBeInTheDocument();
  578. expect(
  579. // Alert below the widget
  580. await screen.findByText(/we've automatically adjusted your results/i)
  581. ).toBeInTheDocument();
  582. });
  583. it('renders chart using axis and tooltip formatters from custom measurement meta', async function () {
  584. const spy = jest.spyOn(LineChart, 'LineChart');
  585. const eventsStatsMock = MockApiClient.addMockResponse({
  586. url: '/organizations/org-slug/events-stats/',
  587. body: {
  588. data: [
  589. [
  590. 1658262600,
  591. [
  592. {
  593. count: 24,
  594. },
  595. ],
  596. ],
  597. ],
  598. meta: {
  599. fields: {
  600. time: 'date',
  601. p95_measurements_custom: 'duration',
  602. },
  603. units: {
  604. time: null,
  605. p95_measurements_custom: 'minute',
  606. },
  607. isMetricsData: true,
  608. tips: {},
  609. },
  610. },
  611. });
  612. renderWithProviders(
  613. <WidgetCard
  614. api={api}
  615. organization={organization}
  616. widget={{
  617. title: '',
  618. interval: '5m',
  619. widgetType: WidgetType.DISCOVER,
  620. displayType: DisplayType.LINE,
  621. queries: [
  622. {
  623. conditions: '',
  624. name: '',
  625. fields: [],
  626. columns: [],
  627. aggregates: ['p95(measurements.custom)'],
  628. orderby: '',
  629. },
  630. ],
  631. }}
  632. selection={selection}
  633. isEditing={false}
  634. onDelete={() => undefined}
  635. onEdit={() => undefined}
  636. onDuplicate={() => undefined}
  637. renderErrorMessage={() => undefined}
  638. showContextMenu
  639. widgetLimitReached={false}
  640. />
  641. );
  642. await waitFor(function () {
  643. expect(eventsStatsMock).toHaveBeenCalled();
  644. });
  645. const {tooltip, yAxis} = spy.mock.calls.pop()?.[0] ?? {};
  646. expect(tooltip).toBeDefined();
  647. expect(yAxis).toBeDefined();
  648. // @ts-ignore
  649. expect(tooltip.valueFormatter(24, 'p95(measurements.custom)')).toEqual('24.00ms');
  650. // @ts-ignore
  651. expect(yAxis.axisLabel.formatter(24, 'p95(measurements.custom)')).toEqual('24ms');
  652. });
  653. it('displays indexed badge in preview mode', async function () {
  654. renderWithProviders(
  655. <WidgetCard
  656. api={api}
  657. organization={{
  658. ...organization,
  659. features: [...organization.features, 'dashboards-mep'],
  660. }}
  661. widget={multipleQueryWidget}
  662. selection={selection}
  663. isEditing={false}
  664. onDelete={() => undefined}
  665. onEdit={() => undefined}
  666. onDuplicate={() => undefined}
  667. renderErrorMessage={() => undefined}
  668. showContextMenu
  669. widgetLimitReached={false}
  670. isPreview
  671. />
  672. );
  673. expect(await screen.findByText('Indexed')).toBeInTheDocument();
  674. });
  675. });