index.spec.tsx 24 KB

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