transactionEvents.spec.tsx 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import {enforceActOnUseLegacyStoreHook, mountWithTheme} from 'sentry-test/enzyme';
  2. import {initializeOrg} from 'sentry-test/initializeOrg';
  3. import {act} from 'sentry-test/reactTestingLibrary';
  4. import {t} from 'sentry/locale';
  5. import ProjectsStore from 'sentry/stores/projectsStore';
  6. import {Organization} from 'sentry/types';
  7. import {WebVital} from 'sentry/utils/fields';
  8. import {OrganizationContext} from 'sentry/views/organizationContext';
  9. import TransactionEvents from 'sentry/views/performance/transactionSummary/transactionEvents';
  10. import {RouteContext} from 'sentry/views/routeContext';
  11. // XXX(epurkhiser): This appears to also be tested by ./transactionSummary/transactionEvents/index.spec.tsx
  12. type Data = {
  13. features?: string[];
  14. query?: {
  15. webVital?: WebVital;
  16. };
  17. };
  18. function initializeData({features: additionalFeatures = [], query = {}}: Data = {}) {
  19. const features = ['discover-basic', 'performance-view', ...additionalFeatures];
  20. const organization = TestStubs.Organization({
  21. features,
  22. projects: [TestStubs.Project()],
  23. apdexThreshold: 400,
  24. });
  25. const initialData = initializeOrg({
  26. organization,
  27. router: {
  28. location: {
  29. query: {
  30. transaction: '/performance',
  31. project: '1',
  32. transactionCursor: '1:0:0',
  33. ...query,
  34. },
  35. },
  36. },
  37. project: 1,
  38. projects: [],
  39. });
  40. act(() => ProjectsStore.loadInitialData(initialData.organization.projects));
  41. return initialData;
  42. }
  43. const WrappedComponent = ({
  44. organization,
  45. router,
  46. ...props
  47. }: Omit<React.ComponentProps<typeof TransactionEvents>, 'organization' | 'location'> & {
  48. organization: Organization;
  49. router: any;
  50. }) => {
  51. return (
  52. <RouteContext.Provider
  53. value={{
  54. router,
  55. location: router.location,
  56. params: {},
  57. routes: [],
  58. }}
  59. >
  60. <OrganizationContext.Provider value={organization}>
  61. <TransactionEvents
  62. organization={organization}
  63. location={router.location}
  64. {...props}
  65. />
  66. </OrganizationContext.Provider>
  67. </RouteContext.Provider>
  68. );
  69. };
  70. describe('Performance > TransactionSummary', function () {
  71. enforceActOnUseLegacyStoreHook();
  72. beforeEach(function () {
  73. // @ts-ignore no-console
  74. // eslint-disable-next-line no-console
  75. jest.spyOn(console, 'error').mockImplementation(jest.fn());
  76. MockApiClient.addMockResponse({
  77. url: '/organizations/org-slug/projects/',
  78. body: [],
  79. });
  80. MockApiClient.addMockResponse({
  81. url: '/prompts-activity/',
  82. body: {},
  83. });
  84. MockApiClient.addMockResponse({
  85. url: '/organizations/org-slug/sdk-updates/',
  86. body: [],
  87. });
  88. MockApiClient.addMockResponse({
  89. url: '/organizations/org-slug/events/',
  90. body: {
  91. data: [
  92. {
  93. 'p100()': 9502,
  94. 'p99()': 9285.7,
  95. 'p95()': 7273.6,
  96. 'p75()': 3639.5,
  97. 'p50()': 755.5,
  98. },
  99. ],
  100. meta: {
  101. fields: {
  102. 'p100()': 'duration',
  103. 'p99()': 'duration',
  104. 'p95()': 'duration',
  105. 'p75()': 'duration',
  106. 'p50()': 'duration',
  107. },
  108. },
  109. },
  110. match: [
  111. (_, options) => {
  112. return options.query?.field?.includes('p95()');
  113. },
  114. ],
  115. });
  116. // Transaction list response
  117. MockApiClient.addMockResponse({
  118. url: '/organizations/org-slug/events/',
  119. headers: {
  120. Link:
  121. '<http://localhost/api/0/organizations/org-slug/events/?cursor=2:0:0>; rel="next"; results="true"; cursor="2:0:0",' +
  122. '<http://localhost/api/0/organizations/org-slug/events/?cursor=1:0:0>; rel="previous"; results="false"; cursor="1:0:0"',
  123. },
  124. body: {
  125. meta: {
  126. fields: {
  127. id: 'string',
  128. 'user.display': 'string',
  129. 'transaction.duration': 'duration',
  130. 'project.id': 'integer',
  131. timestamp: 'date',
  132. },
  133. },
  134. data: [
  135. {
  136. id: 'deadbeef',
  137. 'user.display': 'uhoh@example.com',
  138. 'transaction.duration': 400,
  139. 'project.id': 1,
  140. timestamp: '2020-05-21T15:31:18+00:00',
  141. trace: '1234',
  142. 'measurements.lcp': 200,
  143. },
  144. {
  145. id: 'moredeadbeef',
  146. 'user.display': 'moreuhoh@example.com',
  147. 'transaction.duration': 600,
  148. 'project.id': 1,
  149. timestamp: '2020-05-22T15:31:18+00:00',
  150. trace: '4321',
  151. 'measurements.lcp': 300,
  152. },
  153. ],
  154. },
  155. match: [
  156. (_url, options) => {
  157. return options.query?.field?.includes('user.display');
  158. },
  159. ],
  160. });
  161. MockApiClient.addMockResponse({
  162. url: '/organizations/org-slug/events/',
  163. body: {
  164. data: [{'count()': 5161}],
  165. },
  166. match: [
  167. (_url, options) => {
  168. return options.query?.field?.includes('count()');
  169. },
  170. ],
  171. });
  172. MockApiClient.addMockResponse({
  173. url: '/organizations/org-slug/events-has-measurements/',
  174. body: {measurements: false},
  175. });
  176. });
  177. afterEach(function () {
  178. MockApiClient.clearMockResponses();
  179. // @ts-ignore no-console
  180. // eslint-disable-next-line no-console
  181. console.error.mockRestore();
  182. act(() => ProjectsStore.reset());
  183. jest.clearAllMocks();
  184. });
  185. it('renders basic UI elements', async function () {
  186. const initialData = initializeData();
  187. const wrapper = mountWithTheme(
  188. <WrappedComponent
  189. organization={initialData.organization}
  190. router={initialData.router}
  191. />,
  192. initialData.routerContext
  193. );
  194. await tick();
  195. wrapper.update();
  196. expect(wrapper.find('TabList').find("TabWrap[data-key='events']")).toHaveLength(1);
  197. expect(wrapper.find('SentryDocumentTitle')).toHaveLength(1);
  198. expect(wrapper.find('SearchBar')).toHaveLength(1);
  199. expect(wrapper.find('GridEditable')).toHaveLength(1);
  200. expect(wrapper.find('Pagination')).toHaveLength(1);
  201. expect(wrapper.find('EventsContent')).toHaveLength(1);
  202. expect(wrapper.find('TransactionHeader')).toHaveLength(1);
  203. });
  204. it('renders relative span breakdown header when no filter selected', async function () {
  205. const initialData = initializeData();
  206. const wrapper = mountWithTheme(
  207. <WrappedComponent
  208. organization={initialData.organization}
  209. router={initialData.router}
  210. />,
  211. initialData.routerContext
  212. );
  213. await tick();
  214. wrapper.update();
  215. expect(wrapper.find('GridHeadCell')).toHaveLength(6);
  216. expect(
  217. wrapper.find('OperationTitle').children().children().children().at(0).html()
  218. ).toEqual(t('operation duration'));
  219. });
  220. it('renders event column results correctly', async function () {
  221. const initialData = initializeData();
  222. const wrapper = mountWithTheme(
  223. <WrappedComponent
  224. organization={initialData.organization}
  225. router={initialData.router}
  226. />,
  227. initialData.routerContext
  228. );
  229. await tick();
  230. wrapper.update();
  231. function keyAt(index) {
  232. return wrapper.find('CellAction').at(index).props().column.key;
  233. }
  234. function valueAt(index, element = 'div') {
  235. return wrapper.find('CellAction').at(index).find(element).last().children().html();
  236. }
  237. expect(wrapper.find('CellAction')).toHaveLength(12);
  238. expect(keyAt(0)).toEqual('id');
  239. expect(valueAt(0)).toEqual('deadbeef');
  240. expect(keyAt(1)).toEqual('user.display');
  241. expect(valueAt(1, 'span')).toEqual('uhoh@example.com');
  242. expect(keyAt(2)).toEqual('span_ops_breakdown.relative');
  243. expect(valueAt(2, 'span')).toEqual('(no value)');
  244. expect(keyAt(3)).toEqual('transaction.duration');
  245. expect(valueAt(3, 'span')).toEqual('400.00ms');
  246. expect(keyAt(4)).toEqual('trace');
  247. expect(valueAt(4)).toEqual('1234');
  248. expect(keyAt(5)).toEqual('timestamp');
  249. expect(valueAt(5, 'time')).toEqual('May 21, 2020 3:31:18 PM UTC');
  250. });
  251. it('renders additional Web Vital column', async function () {
  252. const initialData = initializeData({
  253. query: {webVital: WebVital.LCP},
  254. });
  255. const wrapper = mountWithTheme(
  256. <WrappedComponent
  257. organization={initialData.organization}
  258. router={initialData.router}
  259. />,
  260. initialData.routerContext
  261. );
  262. await tick();
  263. wrapper.update();
  264. function keyAt(index) {
  265. return wrapper.find('CellAction').at(index).props().column.key;
  266. }
  267. function valueAt(index, element = 'div') {
  268. return wrapper.find('CellAction').at(index).find(element).last().children().html();
  269. }
  270. expect(wrapper.find('CellAction')).toHaveLength(14);
  271. expect(keyAt(0)).toEqual('id');
  272. expect(valueAt(0)).toEqual('deadbeef');
  273. expect(keyAt(1)).toEqual('user.display');
  274. expect(valueAt(1, 'span')).toEqual('uhoh@example.com');
  275. expect(keyAt(2)).toEqual('span_ops_breakdown.relative');
  276. expect(valueAt(2, 'span')).toEqual('(no value)');
  277. expect(keyAt(3)).toEqual('measurements.lcp');
  278. expect(valueAt(3)).toEqual('200');
  279. expect(keyAt(4)).toEqual('transaction.duration');
  280. expect(valueAt(4, 'span')).toEqual('400.00ms');
  281. expect(keyAt(5)).toEqual('trace');
  282. expect(valueAt(5)).toEqual('1234');
  283. expect(keyAt(6)).toEqual('timestamp');
  284. expect(valueAt(6, 'time')).toEqual('May 21, 2020 3:31:18 PM UTC');
  285. });
  286. });