issuesTraceTree.spec.tsx 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. import {OrganizationFixture} from 'sentry-fixture/organization';
  2. import {EntryType} from 'sentry/types/event';
  3. import {
  4. isSpanNode,
  5. isTraceErrorNode,
  6. isTraceNode,
  7. isTransactionNode,
  8. } from 'sentry/views/performance/newTraceDetails/traceGuards';
  9. import {TraceTree} from 'sentry/views/performance/newTraceDetails/traceModels/traceTree';
  10. import type {TraceTreeNode} from 'sentry/views/performance/newTraceDetails/traceModels/traceTreeNode';
  11. import {DEFAULT_TRACE_VIEW_PREFERENCES} from 'sentry/views/performance/newTraceDetails/traceState/tracePreferences';
  12. import {IssuesTraceTree} from './issuesTraceTree';
  13. import {
  14. makeEventTransaction,
  15. makeSpan,
  16. makeTrace,
  17. makeTraceError,
  18. makeTransaction,
  19. } from './traceTreeTestUtils';
  20. const traceWithErrorInMiddle = makeTrace({
  21. transactions: [
  22. makeTransaction({transaction: 'transaction 1'}),
  23. makeTransaction({transaction: 'transaction 2'}),
  24. makeTransaction({transaction: 'transaction 3', errors: [makeTraceError({})]}),
  25. makeTransaction({transaction: 'transaction 4'}),
  26. makeTransaction({transaction: 'transaction 5'}),
  27. ],
  28. });
  29. const traceWithChildError = makeTrace({
  30. transactions: [
  31. makeTransaction({transaction: 'transaction 1'}),
  32. makeTransaction({
  33. transaction: 'transaction 2',
  34. children: [makeTransaction({errors: [makeTraceError({})]})],
  35. }),
  36. makeTransaction({transaction: 'transaction 4'}),
  37. ],
  38. });
  39. const errorsOnlyTrace = makeTrace({
  40. transactions: [],
  41. orphan_errors: new Array(20).fill(null).map(() => makeTraceError({})),
  42. });
  43. function mockSpansResponse(
  44. spans: TraceTree.Span[],
  45. project_slug: string,
  46. event_id: string
  47. ): jest.Mock<any, any> {
  48. return MockApiClient.addMockResponse({
  49. url: `/organizations/org-slug/events/${project_slug}:${event_id}/?averageColumn=span.self_time&averageColumn=span.duration`,
  50. method: 'GET',
  51. body: makeEventTransaction({
  52. entries: [{type: EntryType.SPANS, data: spans}],
  53. }),
  54. });
  55. }
  56. function hasErrors(n: TraceTreeNode<any>): boolean {
  57. return (
  58. (isTraceErrorNode(n) || n.errors.size > 0 || n.performance_issues.size > 0) &&
  59. !isTraceNode(n)
  60. );
  61. }
  62. describe('IssuesTraceTree', () => {
  63. it('collapsed nodes without errors', () => {
  64. const tree = IssuesTraceTree.FromTrace(traceWithErrorInMiddle, {
  65. meta: null,
  66. replay: null,
  67. });
  68. const issues = IssuesTraceTree.FindAll(tree.root, hasErrors);
  69. expect(tree.build().collapseList(issues).serialize()).toMatchSnapshot();
  70. });
  71. it('preserves path to child error', () => {
  72. const tree = IssuesTraceTree.FromTrace(traceWithChildError, {
  73. meta: null,
  74. replay: null,
  75. });
  76. const error = IssuesTraceTree.Find(tree.root, hasErrors);
  77. let node = error;
  78. const nodes: TraceTreeNode<any>[] = [];
  79. while (node && !isTraceNode(node)) {
  80. nodes.push(node);
  81. node = node.parent;
  82. }
  83. expect(tree.build().collapseList(nodes).serialize()).toMatchSnapshot();
  84. });
  85. it('errors only', () => {
  86. // has +100 issues at the end
  87. const tree = IssuesTraceTree.FromTrace(errorsOnlyTrace, {
  88. meta: null,
  89. replay: null,
  90. });
  91. const errors = IssuesTraceTree.FindAll(tree.root, hasErrors).slice(0, 10);
  92. expect(tree.build().collapseList(errors).serialize()).toMatchSnapshot();
  93. });
  94. describe('FromSpans', () => {
  95. const traceWithSpans = makeTrace({
  96. transactions: [
  97. makeTransaction({transaction: 'transaction 0'}),
  98. makeTransaction({transaction: 'transaction 0'}),
  99. makeTransaction({
  100. transaction: 'transaction 1',
  101. children: [
  102. makeTransaction({
  103. transaction: 'transaction 2',
  104. event_id: 'event-id',
  105. project_slug: 'project',
  106. errors: [
  107. makeTraceError({
  108. span: 'error-span-id',
  109. }),
  110. ],
  111. }),
  112. ],
  113. }),
  114. ],
  115. });
  116. it('collapses spans', async () => {
  117. const tree = IssuesTraceTree.FromTrace(traceWithSpans, {
  118. meta: null,
  119. replay: null,
  120. });
  121. mockSpansResponse(
  122. [
  123. makeSpan({op: 'cache', description: 'GET'}),
  124. makeSpan({op: 'http', description: 'GET /'}),
  125. makeSpan({op: 'db', description: 'SELECT'}),
  126. makeSpan({op: 'cache', description: 'GET'}),
  127. makeSpan({op: 'http', description: 'GET /', span_id: 'error-span-id'}),
  128. makeSpan({op: 'db', description: 'SELECT'}),
  129. ],
  130. 'project',
  131. 'event-id'
  132. );
  133. const txn = TraceTree.Find(
  134. tree.root,
  135. node => isTransactionNode(node) && node.value.transaction === 'transaction 2'
  136. )!;
  137. await tree.zoom(txn, true, {
  138. api: new MockApiClient(),
  139. organization: OrganizationFixture(),
  140. preferences: DEFAULT_TRACE_VIEW_PREFERENCES,
  141. });
  142. const span = TraceTree.Find(
  143. tree.root,
  144. node => isSpanNode(node) && node.value.span_id === 'error-span-id'
  145. )!;
  146. expect(tree.build().collapseList([span]).serialize()).toMatchSnapshot();
  147. });
  148. });
  149. });