utils.ts 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227
  1. import {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
  2. import {EntryType, EventOrGroupType, EventTransaction, IssueType} from 'sentry/types';
  3. export enum ProblemSpan {
  4. PARENT = 'parent',
  5. OFFENDER = 'offender',
  6. CAUSE = 'cause',
  7. }
  8. export const EXAMPLE_TRANSACTION_TITLE = '/api/0/transaction-test-endpoint/';
  9. type AddSpanOpts = {
  10. endTimestamp: number;
  11. startTimestamp: number;
  12. description?: string;
  13. op?: string;
  14. problemSpan?: ProblemSpan;
  15. status?: string;
  16. };
  17. export class TransactionEventBuilder {
  18. TRACE_ID = '8cbbc19c0f54447ab702f00263262726';
  19. ROOT_SPAN_ID = '0000000000000000';
  20. #event: EventTransaction;
  21. #spans: RawSpanType[] = [];
  22. constructor(id?: string, title?: string, problemType?: IssueType) {
  23. this.#event = {
  24. id: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  25. eventID: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
  26. title: title ?? EXAMPLE_TRANSACTION_TITLE,
  27. type: EventOrGroupType.TRANSACTION,
  28. startTimestamp: 0,
  29. endTimestamp: 0,
  30. contexts: {
  31. trace: {
  32. trace_id: this.TRACE_ID,
  33. span_id: this.ROOT_SPAN_ID,
  34. op: 'pageload',
  35. status: 'ok',
  36. type: 'trace',
  37. },
  38. },
  39. entries: [
  40. {
  41. data: this.#spans,
  42. type: EntryType.SPANS,
  43. },
  44. ],
  45. perfProblem: {
  46. causeSpanIds: [],
  47. offenderSpanIds: [],
  48. parentSpanIds: [],
  49. issueType: problemType ?? IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES,
  50. },
  51. // For the purpose of mock data, we don't care as much about the properties below.
  52. // They're here to satisfy the type constraints, but in the future if we need actual values here
  53. // for testing purposes, we can add methods on the builder to set them.
  54. crashFile: null,
  55. culprit: '',
  56. dateReceived: '',
  57. dist: null,
  58. errors: [],
  59. fingerprints: [],
  60. location: null,
  61. message: '',
  62. metadata: {
  63. current_level: undefined,
  64. current_tree_label: undefined,
  65. directive: undefined,
  66. display_title_with_tree_label: undefined,
  67. filename: undefined,
  68. finest_tree_label: undefined,
  69. function: undefined,
  70. message: undefined,
  71. origin: undefined,
  72. stripped_crash: undefined,
  73. title: undefined,
  74. type: undefined,
  75. uri: undefined,
  76. value: undefined,
  77. },
  78. occurrence: null,
  79. projectID: '',
  80. size: 0,
  81. tags: [],
  82. user: null,
  83. };
  84. }
  85. generateSpanId() {
  86. // Convert the num of spans to a hex string to get its ID
  87. return (this.#spans.length + 1).toString(16).padStart(16, '0');
  88. }
  89. addEntry(entry: EventTransaction['entries'][number]) {
  90. this.#event.entries.push(entry);
  91. }
  92. addSpan(mockSpan: MockSpan, numSpans = 1, parentSpanId?: string) {
  93. for (let i = 0; i < numSpans; i++) {
  94. const spanId = this.generateSpanId();
  95. const {span} = mockSpan;
  96. const clonedSpan = {...span};
  97. clonedSpan.span_id = spanId;
  98. clonedSpan.trace_id = this.TRACE_ID;
  99. clonedSpan.parent_span_id = parentSpanId ?? this.ROOT_SPAN_ID;
  100. this.#spans.push(clonedSpan);
  101. switch (mockSpan.problemSpan) {
  102. case ProblemSpan.PARENT:
  103. this.#event.perfProblem?.parentSpanIds.push(spanId);
  104. break;
  105. case ProblemSpan.OFFENDER:
  106. this.#event.perfProblem?.offenderSpanIds.push(spanId);
  107. break;
  108. case ProblemSpan.CAUSE:
  109. this.#event.perfProblem?.causeSpanIds.push(spanId);
  110. break;
  111. default:
  112. break;
  113. }
  114. if (clonedSpan.timestamp > this.#event.endTimestamp) {
  115. this.#event.endTimestamp = clonedSpan.timestamp;
  116. }
  117. mockSpan.children.forEach(child => this.addSpan(child, 1, spanId));
  118. }
  119. return this;
  120. }
  121. getEvent() {
  122. return this.#event;
  123. }
  124. }
  125. /**
  126. * A MockSpan object to be used for testing. This object is intended to be used in tandem with `TransactionEventBuilder`
  127. */
  128. export class MockSpan {
  129. span: RawSpanType;
  130. children: MockSpan[] = [];
  131. problemSpan: ProblemSpan | undefined;
  132. /**
  133. *
  134. * @param opts.startTimestamp
  135. * @param opts.endTimestamp
  136. * @param opts.op The operation of the span
  137. * @param opts.description The description of the span
  138. * @param opts.status Optional span specific status, defaults to 'ok'
  139. * @param opts.problemSpan If this span should be part of a performance problem, indicates the type of problem span (i.e ProblemSpan.OFFENDER, ProblemSpan.PARENT)
  140. * @param opts.parentSpanId When provided, will explicitly set this span's parent ID. If you are creating nested spans via `addChild` on the `MockSpan` object,
  141. * this will be handled automatically and you do not need to provide an ID. Defaults to the root span's ID.
  142. */
  143. constructor(opts: AddSpanOpts) {
  144. const {startTimestamp, endTimestamp, op, description, status, problemSpan} = opts;
  145. this.span = {
  146. start_timestamp: startTimestamp,
  147. timestamp: endTimestamp,
  148. op,
  149. description,
  150. status: status ?? 'ok',
  151. data: {},
  152. // These values are automatically assigned by the TransactionEventBuilder when the spans are added
  153. span_id: '',
  154. trace_id: '',
  155. parent_span_id: '',
  156. };
  157. this.problemSpan = problemSpan;
  158. }
  159. /**
  160. *
  161. * @param opts.numSpans If provided, will create the same span numSpan times
  162. */
  163. addChild(opts: AddSpanOpts, numSpans = 1) {
  164. const {startTimestamp, endTimestamp, op, description, status, problemSpan} = opts;
  165. for (let i = 0; i < numSpans; i++) {
  166. const span = new MockSpan({
  167. startTimestamp,
  168. endTimestamp,
  169. op,
  170. description,
  171. status,
  172. problemSpan,
  173. });
  174. this.children.push(span);
  175. }
  176. return this;
  177. }
  178. /**
  179. * Allows you to create a nested group of duplicate mock spans by duplicating the current span. This is useful for simulating the nested 'autogrouped' condition on the span tree.
  180. * Will create `depth` spans, each span being a child of the previous.
  181. * @param depth
  182. */
  183. addDuplicateNestedChildren(depth = 1) {
  184. let currentSpan: MockSpan = this;
  185. for (let i = 0; i < depth; i++) {
  186. currentSpan.addChild(currentSpan.getOpts());
  187. currentSpan = currentSpan.children[0];
  188. }
  189. return this;
  190. }
  191. getOpts() {
  192. return {
  193. startTimestamp: this.span.start_timestamp,
  194. endTimestamp: this.span.timestamp,
  195. op: this.span.op,
  196. description: this.span.description,
  197. status: this.span.status,
  198. problemSpan: this.problemSpan,
  199. };
  200. }
  201. }