index.spec.tsx 22 KB

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