messageSpanSamplesPanel.spec.tsx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
  3. import {useLocation} from 'sentry/utils/useLocation';
  4. import usePageFilters from 'sentry/utils/usePageFilters';
  5. import {MessageSpanSamplesPanel} from 'sentry/views/insights/queues/components/messageSpanSamplesPanel';
  6. jest.mock('sentry/utils/useLocation');
  7. jest.mock('sentry/utils/usePageFilters');
  8. describe('messageSpanSamplesPanel', () => {
  9. const organization = OrganizationFixture();
  10. let eventsRequestMock: jest.Mock;
  11. let eventsStatsRequestMock: jest.Mock;
  12. let samplesRequestMock: jest.Mock;
  13. let spanFieldTagsMock: jest.Mock;
  14. jest.mocked(usePageFilters).mockReturnValue({
  15. isReady: true,
  16. desyncedFilters: new Set(),
  17. pinnedFilters: new Set(),
  18. shouldPersist: true,
  19. selection: {
  20. datetime: {
  21. period: '10d',
  22. start: null,
  23. end: null,
  24. utc: false,
  25. },
  26. environments: [],
  27. projects: [],
  28. },
  29. });
  30. jest.mocked(useLocation).mockReturnValue({
  31. pathname: '',
  32. search: '',
  33. query: {transaction: 'sentry.tasks.store.save_event', destination: 'event-queue'},
  34. hash: '',
  35. state: undefined,
  36. action: 'PUSH',
  37. key: '',
  38. });
  39. beforeEach(() => {
  40. eventsStatsRequestMock = MockApiClient.addMockResponse({
  41. url: `/organizations/${organization.slug}/events-stats/`,
  42. method: 'GET',
  43. body: {
  44. data: [[1699907700, [{count: 7810}]]],
  45. meta: {},
  46. },
  47. });
  48. eventsRequestMock = MockApiClient.addMockResponse({
  49. url: `/organizations/${organization.slug}/events/`,
  50. method: 'GET',
  51. body: {
  52. data: [
  53. {
  54. 'sum(span.duration)': 10.0,
  55. 'trace_status_rate(ok)': 0.8,
  56. 'count_op(queue.publish)': 222,
  57. 'count_op(queue.process)': 333,
  58. 'avg_if(span.duration,span.op,queue.publish)': 3.0,
  59. 'avg_if(span.duration,span.op,queue.process)': 4.0,
  60. 'count()': 555,
  61. 'avg(messaging.message.receive.latency)': 2.0,
  62. 'avg(span.duration)': 3.5,
  63. },
  64. ],
  65. meta: {
  66. fields: {
  67. 'sum(span.duration)': 'duration',
  68. 'trace_status_rate(ok)': 'percentage',
  69. 'count_op(queue.publish)': 'integer',
  70. 'count_op(queue.process)': 'integer',
  71. 'avg_if(span.duration,span.op,queue.publish)': 'duration',
  72. 'avg_if(span.duration,span.op,queue.process)': 'duration',
  73. 'count()': 'integer',
  74. 'avg(messaging.message.receive.latency)': 'number',
  75. 'avg(span.duration)': 'duration',
  76. },
  77. units: {
  78. 'sum(span.duration)': 'millisecond',
  79. 'trace_status_rate(ok)': null,
  80. 'count_op(queue.publish)': null,
  81. 'count_op(queue.process)': null,
  82. 'avg_if(span.duration,span.op,queue.publish)': 'millisecond',
  83. 'avg_if(span.duration,span.op,queue.process)': 'millisecond',
  84. 'count()': null,
  85. 'avg(messaging.message.receive.latency)': null,
  86. 'avg(span.duration)': 'millisecond',
  87. },
  88. },
  89. },
  90. });
  91. samplesRequestMock = MockApiClient.addMockResponse({
  92. url: `/api/0/organizations/${organization.slug}/spans-samples/`,
  93. method: 'GET',
  94. body: {
  95. data: [
  96. {
  97. span_id: '123',
  98. trace: 'abc',
  99. project: 'project',
  100. timestamp: '2024-03-25T20:31:36+00:00',
  101. 'span.duration': 320.300102,
  102. },
  103. ],
  104. },
  105. });
  106. spanFieldTagsMock = MockApiClient.addMockResponse({
  107. url: `/organizations/${organization.slug}/spans/fields/`,
  108. method: 'GET',
  109. body: [
  110. {
  111. key: 'api_key',
  112. name: 'Api Key',
  113. },
  114. {
  115. key: 'bytes.size',
  116. name: 'Bytes.Size',
  117. },
  118. ],
  119. });
  120. MockApiClient.addMockResponse({
  121. url: `/organizations/${organization.slug}/recent-searches/`,
  122. body: [],
  123. });
  124. });
  125. afterAll(() => {
  126. jest.resetAllMocks();
  127. });
  128. it('renders consumer panel', async () => {
  129. jest.mocked(useLocation).mockReturnValue({
  130. pathname: '',
  131. search: '',
  132. query: {
  133. transaction: 'sentry.tasks.store.save_event',
  134. destination: 'event-queue',
  135. 'span.op': 'queue.process',
  136. },
  137. hash: '',
  138. state: undefined,
  139. action: 'PUSH',
  140. key: '',
  141. });
  142. render(<MessageSpanSamplesPanel />, {organization});
  143. await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
  144. expect(eventsStatsRequestMock).toHaveBeenCalled();
  145. expect(eventsRequestMock).toHaveBeenCalledWith(
  146. `/organizations/${organization.slug}/events/`,
  147. expect.objectContaining({
  148. method: 'GET',
  149. query: expect.objectContaining({
  150. dataset: 'spansMetrics',
  151. environment: [],
  152. field: [
  153. 'count()',
  154. 'count_op(queue.publish)',
  155. 'count_op(queue.process)',
  156. 'sum(span.duration)',
  157. 'avg(span.duration)',
  158. 'avg_if(span.duration,span.op,queue.publish)',
  159. 'avg_if(span.duration,span.op,queue.process)',
  160. 'avg(messaging.message.receive.latency)',
  161. 'trace_status_rate(ok)',
  162. 'time_spent_percentage(app,span.duration)',
  163. ],
  164. per_page: 10,
  165. project: [],
  166. query:
  167. 'span.op:[queue.process,queue.publish] messaging.destination.name:event-queue transaction:sentry.tasks.store.save_event',
  168. statsPeriod: '10d',
  169. }),
  170. })
  171. );
  172. expect(samplesRequestMock).toHaveBeenCalledWith(
  173. `/api/0/organizations/${organization.slug}/spans-samples/`,
  174. expect.objectContaining({
  175. query: expect.objectContaining({
  176. additionalFields: [
  177. 'trace',
  178. 'transaction.id',
  179. 'span.description',
  180. 'measurements.messaging.message.body.size',
  181. 'measurements.messaging.message.receive.latency',
  182. 'measurements.messaging.message.retry.count',
  183. 'messaging.message.id',
  184. 'trace.status',
  185. 'span.duration',
  186. ],
  187. firstBound: 2666.6666666666665,
  188. lowerBound: 0,
  189. project: [],
  190. query:
  191. 'span.op:queue.process transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
  192. referrer: undefined,
  193. secondBound: 5333.333333333333,
  194. statsPeriod: '10d',
  195. upperBound: 8000,
  196. }),
  197. })
  198. );
  199. expect(spanFieldTagsMock).toHaveBeenNthCalledWith(
  200. 1,
  201. `/organizations/${organization.slug}/spans/fields/`,
  202. expect.objectContaining({
  203. method: 'GET',
  204. query: {
  205. project: [],
  206. environment: [],
  207. statsPeriod: '1h',
  208. },
  209. })
  210. );
  211. expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
  212. expect(screen.getByText('Consumer')).toBeInTheDocument();
  213. // Metrics Ribbon
  214. expect(screen.getByText('Processed')).toBeInTheDocument();
  215. expect(screen.getByText('Error Rate')).toBeInTheDocument();
  216. expect(screen.getByText('Avg Time In Queue')).toBeInTheDocument();
  217. expect(screen.getByText('Avg Processing Time')).toBeInTheDocument();
  218. expect(screen.getByText('333')).toBeInTheDocument();
  219. expect(screen.getByText('20%')).toBeInTheDocument();
  220. expect(screen.getByText('2.00ms')).toBeInTheDocument();
  221. expect(screen.getByText('4.00ms')).toBeInTheDocument();
  222. });
  223. it('renders producer panel', async () => {
  224. jest.mocked(useLocation).mockReturnValue({
  225. pathname: '',
  226. search: '',
  227. query: {
  228. transaction: 'sentry.tasks.store.save_event',
  229. destination: 'event-queue',
  230. 'span.op': 'queue.publish',
  231. },
  232. hash: '',
  233. state: undefined,
  234. action: 'PUSH',
  235. key: '',
  236. });
  237. render(<MessageSpanSamplesPanel />, {organization});
  238. await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
  239. expect(eventsStatsRequestMock).toHaveBeenCalled();
  240. expect(eventsRequestMock).toHaveBeenCalledWith(
  241. `/organizations/${organization.slug}/events/`,
  242. expect.objectContaining({
  243. method: 'GET',
  244. query: expect.objectContaining({
  245. dataset: 'spansMetrics',
  246. environment: [],
  247. field: [
  248. 'count()',
  249. 'count_op(queue.publish)',
  250. 'count_op(queue.process)',
  251. 'sum(span.duration)',
  252. 'avg(span.duration)',
  253. 'avg_if(span.duration,span.op,queue.publish)',
  254. 'avg_if(span.duration,span.op,queue.process)',
  255. 'avg(messaging.message.receive.latency)',
  256. 'trace_status_rate(ok)',
  257. 'time_spent_percentage(app,span.duration)',
  258. ],
  259. per_page: 10,
  260. project: [],
  261. query:
  262. 'span.op:[queue.process,queue.publish] messaging.destination.name:event-queue transaction:sentry.tasks.store.save_event',
  263. statsPeriod: '10d',
  264. }),
  265. })
  266. );
  267. expect(samplesRequestMock).toHaveBeenCalledWith(
  268. `/api/0/organizations/${organization.slug}/spans-samples/`,
  269. expect.objectContaining({
  270. query: expect.objectContaining({
  271. additionalFields: [
  272. 'trace',
  273. 'transaction.id',
  274. 'span.description',
  275. 'measurements.messaging.message.body.size',
  276. 'measurements.messaging.message.receive.latency',
  277. 'measurements.messaging.message.retry.count',
  278. 'messaging.message.id',
  279. 'trace.status',
  280. 'span.duration',
  281. ],
  282. firstBound: 2666.6666666666665,
  283. lowerBound: 0,
  284. project: [],
  285. query:
  286. 'span.op:queue.publish transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
  287. referrer: undefined,
  288. secondBound: 5333.333333333333,
  289. statsPeriod: '10d',
  290. upperBound: 8000,
  291. }),
  292. })
  293. );
  294. expect(spanFieldTagsMock).toHaveBeenCalledWith(
  295. `/organizations/${organization.slug}/spans/fields/`,
  296. expect.objectContaining({
  297. method: 'GET',
  298. query: {
  299. project: [],
  300. environment: [],
  301. statsPeriod: '1h',
  302. },
  303. })
  304. );
  305. expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
  306. expect(screen.getByText('Producer')).toBeInTheDocument();
  307. // Metrics Ribbon
  308. expect(screen.getByText('Published')).toBeInTheDocument();
  309. expect(screen.getByText('Error Rate')).toBeInTheDocument();
  310. expect(screen.getByText('222')).toBeInTheDocument();
  311. expect(screen.getByText('20%')).toBeInTheDocument();
  312. });
  313. });