visualizationStep.spec.tsx 8.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349
  1. import {DashboardFixture} from 'sentry-fixture/dashboard';
  2. import {LocationFixture} from 'sentry-fixture/locationFixture';
  3. import {PageFiltersFixture} from 'sentry-fixture/pageFilters';
  4. import {TagsFixture} from 'sentry-fixture/tags';
  5. import {initializeOrg} from 'sentry-test/initializeOrg';
  6. import {render, screen, userEvent, waitFor} from 'sentry-test/reactTestingLibrary';
  7. import ProjectsStore from 'sentry/stores/projectsStore';
  8. import type {Organization} from 'sentry/types/organization';
  9. import {MEPSettingProvider} from 'sentry/utils/performance/contexts/metricsEnhancedSetting';
  10. import {
  11. DashboardWidgetSource,
  12. DisplayType,
  13. WidgetType,
  14. } from 'sentry/views/dashboards/types';
  15. import WidgetBuilder from 'sentry/views/dashboards/widgetBuilder';
  16. import {VisualizationStep} from 'sentry/views/dashboards/widgetBuilder/buildSteps/visualizationStep';
  17. import {DashboardsMEPProvider} from '../../widgetCard/dashboardsMEPContext';
  18. import WidgetLegendSelectionState from '../../widgetLegendSelectionState';
  19. jest.unmock('lodash/debounce');
  20. function mockRequests(orgSlug: Organization['slug']) {
  21. const eventsMock = MockApiClient.addMockResponse({
  22. url: `/organizations/${orgSlug}/events/`,
  23. method: 'GET',
  24. statusCode: 200,
  25. body: {
  26. meta: {},
  27. data: [],
  28. },
  29. });
  30. MockApiClient.addMockResponse({
  31. url: '/organizations/org-slug/tags/',
  32. method: 'GET',
  33. body: TagsFixture(),
  34. });
  35. MockApiClient.addMockResponse({
  36. url: '/organizations/org-slug/users/',
  37. body: [],
  38. });
  39. MockApiClient.addMockResponse({
  40. url: '/organizations/org-slug/projects/',
  41. method: 'GET',
  42. body: [],
  43. });
  44. MockApiClient.addMockResponse({
  45. url: '/organizations/org-slug/measurements-meta/',
  46. method: 'GET',
  47. body: {'measurements.custom.measurement': {functions: ['p99']}},
  48. });
  49. MockApiClient.addMockResponse({
  50. url: '/organizations/org-slug/metrics-compatibility/',
  51. method: 'GET',
  52. body: {
  53. incompatible_projects: [],
  54. compatible_projects: [1],
  55. },
  56. });
  57. MockApiClient.addMockResponse({
  58. url: '/organizations/org-slug/metrics-compatibility-sums/',
  59. method: 'GET',
  60. body: {
  61. sum: {
  62. metrics: 988803,
  63. metrics_null: 0,
  64. metrics_unparam: 132,
  65. },
  66. },
  67. });
  68. MockApiClient.addMockResponse({
  69. url: '/organizations/org-slug/releases/',
  70. body: [],
  71. });
  72. MockApiClient.addMockResponse({
  73. url: '/organizations/org-slug/recent-searches/',
  74. method: 'GET',
  75. body: [],
  76. });
  77. MockApiClient.addMockResponse({
  78. url: `/organizations/org-slug/spans/fields/`,
  79. body: [],
  80. });
  81. return {eventsMock};
  82. }
  83. describe('VisualizationStep', function () {
  84. const {organization, projects, router} = initializeOrg({
  85. organization: {
  86. features: ['performance-view', 'dashboards-edit', 'global-views', 'dashboards-mep'],
  87. },
  88. router: {
  89. location: {
  90. query: {
  91. source: DashboardWidgetSource.DASHBOARDS,
  92. },
  93. },
  94. },
  95. });
  96. const widgetLegendState = new WidgetLegendSelectionState({
  97. location: LocationFixture(),
  98. dashboard: DashboardFixture([], {id: 'new', title: 'Dashboard'}),
  99. organization,
  100. router,
  101. });
  102. beforeEach(function () {
  103. ProjectsStore.loadInitialData(projects);
  104. });
  105. it('debounce works as expected and requests are not triggered often', async function () {
  106. const {eventsMock} = mockRequests(organization.slug);
  107. render(
  108. <WidgetBuilder
  109. route={{}}
  110. router={router}
  111. routes={router.routes}
  112. routeParams={router.params}
  113. location={router.location}
  114. dashboard={{
  115. id: 'new',
  116. title: 'Dashboard',
  117. createdBy: undefined,
  118. dateCreated: '2020-01-01T00:00:00.000Z',
  119. widgets: [],
  120. projects: [],
  121. filters: {},
  122. }}
  123. onSave={jest.fn()}
  124. params={{
  125. orgId: organization.slug,
  126. dashboardId: 'new',
  127. }}
  128. widgetLegendState={widgetLegendState}
  129. />,
  130. {
  131. router,
  132. organization,
  133. }
  134. );
  135. await waitFor(() => expect(eventsMock).toHaveBeenCalledTimes(1));
  136. await userEvent.type(await screen.findByPlaceholderText('Alias'), 'abc', {
  137. delay: null,
  138. });
  139. await waitFor(() => expect(eventsMock).toHaveBeenCalledTimes(1));
  140. });
  141. it('displays stored data alert', async function () {
  142. mockRequests(organization.slug);
  143. MockApiClient.addMockResponse({
  144. url: `/organizations/${organization.slug}/events/`,
  145. method: 'GET',
  146. statusCode: 200,
  147. body: {
  148. meta: {isMetricsData: false},
  149. data: [],
  150. },
  151. });
  152. render(
  153. <WidgetBuilder
  154. route={{}}
  155. router={router}
  156. routes={router.routes}
  157. routeParams={router.params}
  158. location={router.location}
  159. dashboard={{
  160. id: 'new',
  161. title: 'Dashboard',
  162. createdBy: undefined,
  163. dateCreated: '2020-01-01T00:00:00.000Z',
  164. widgets: [],
  165. projects: [],
  166. filters: {},
  167. }}
  168. onSave={jest.fn()}
  169. params={{
  170. orgId: organization.slug,
  171. dashboardId: 'new',
  172. }}
  173. widgetLegendState={widgetLegendState}
  174. />,
  175. {
  176. router,
  177. organization: {
  178. ...organization,
  179. features: [...organization.features, 'dynamic-sampling', 'mep-rollout-flag'],
  180. },
  181. }
  182. );
  183. await screen.findByText(/we've automatically adjusted your results/i);
  184. });
  185. it('uses release from URL params when querying', async function () {
  186. const {eventsMock} = mockRequests(organization.slug);
  187. render(
  188. <WidgetBuilder
  189. route={{}}
  190. router={router}
  191. routes={router.routes}
  192. routeParams={router.params}
  193. location={{
  194. ...router.location,
  195. query: {
  196. ...router.location.query,
  197. release: ['v1'],
  198. },
  199. }}
  200. dashboard={{
  201. id: 'new',
  202. title: 'Dashboard',
  203. createdBy: undefined,
  204. dateCreated: '2020-01-01T00:00:00.000Z',
  205. widgets: [],
  206. projects: [],
  207. filters: {},
  208. }}
  209. onSave={jest.fn()}
  210. params={{
  211. orgId: organization.slug,
  212. dashboardId: 'new',
  213. }}
  214. widgetLegendState={widgetLegendState}
  215. />,
  216. {
  217. router,
  218. organization,
  219. }
  220. );
  221. await waitFor(() =>
  222. expect(eventsMock).toHaveBeenCalledWith(
  223. '/organizations/org-slug/events/',
  224. expect.objectContaining({
  225. query: expect.objectContaining({query: ' release:"v1" '}),
  226. })
  227. )
  228. );
  229. });
  230. it('does not trigger an extra events request when adding a column', async function () {
  231. const {eventsMock} = mockRequests(organization.slug);
  232. render(
  233. <WidgetBuilder
  234. route={{}}
  235. router={router}
  236. routes={router.routes}
  237. routeParams={router.params}
  238. location={{
  239. ...router.location,
  240. query: {
  241. ...router.location.query,
  242. release: ['v1'],
  243. },
  244. }}
  245. dashboard={{
  246. id: 'new',
  247. title: 'Dashboard',
  248. createdBy: undefined,
  249. dateCreated: '2020-01-01T00:00:00.000Z',
  250. widgets: [],
  251. projects: [],
  252. filters: {},
  253. }}
  254. onSave={jest.fn()}
  255. params={{
  256. orgId: organization.slug,
  257. dashboardId: 'new',
  258. }}
  259. widgetLegendState={widgetLegendState}
  260. />,
  261. {
  262. router,
  263. organization,
  264. }
  265. );
  266. await userEvent.click(screen.getByText('Add a Column'));
  267. // Only called once on the initial render
  268. await waitFor(() => expect(eventsMock).toHaveBeenCalledTimes(1));
  269. });
  270. it('makes a request to the spans dataset for a table widget', async function () {
  271. const {eventsMock} = mockRequests(organization.slug);
  272. const mockSpanWidget = {
  273. interval: '1d',
  274. title: 'Title',
  275. widgetType: WidgetType.SPANS,
  276. displayType: DisplayType.TABLE,
  277. queries: [
  278. {
  279. conditions: '',
  280. name: '',
  281. aggregates: ['count()'],
  282. columns: [],
  283. fields: [],
  284. orderby: '',
  285. },
  286. ],
  287. };
  288. render(
  289. <MEPSettingProvider>
  290. <DashboardsMEPProvider>
  291. <VisualizationStep
  292. pageFilters={PageFiltersFixture()}
  293. displayType={DisplayType.TABLE}
  294. error={undefined}
  295. onChange={jest.fn()}
  296. widget={mockSpanWidget}
  297. isWidgetInvalid={false}
  298. location={router.location}
  299. widgetLegendState={widgetLegendState}
  300. />
  301. </DashboardsMEPProvider>
  302. </MEPSettingProvider>,
  303. {organization}
  304. );
  305. await waitFor(() =>
  306. expect(eventsMock).toHaveBeenCalledWith(
  307. '/organizations/org-slug/events/',
  308. expect.objectContaining({
  309. query: expect.objectContaining({
  310. dataset: 'spans',
  311. }),
  312. })
  313. )
  314. );
  315. });
  316. });