utils.ts 6.9 KB

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