123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278 |
- import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
- import type {EventTransaction} from 'sentry/types';
- import {EntryType, EventOrGroupType, IssueType} from 'sentry/types';
- export enum ProblemSpan {
- PARENT = 'parent',
- OFFENDER = 'offender',
- CAUSE = 'cause',
- }
- export const EXAMPLE_TRANSACTION_TITLE = '/api/0/transaction-test-endpoint/';
- type AddSpanOpts = {
- endTimestamp: number;
- startTimestamp: number;
- data?: Record<string, any>;
- description?: string;
- hash?: string;
- op?: string;
- problemSpan?: ProblemSpan | ProblemSpan[];
- status?: string;
- };
- interface TransactionSettings {
- duration?: number;
- fcp?: number;
- }
- export class TransactionEventBuilder {
- TRACE_ID = '8cbbc19c0f54447ab702f00263262726';
- ROOT_SPAN_ID = '0000000000000000';
- #event: EventTransaction;
- #spans: RawSpanType[] = [];
- constructor(
- id?: string,
- title?: string,
- problemType?: IssueType,
- transactionSettings?: TransactionSettings,
- occurenceBasedEvent?: boolean
- ) {
- const perfEvidenceData = {
- causeSpanIds: [],
- offenderSpanIds: [],
- parentSpanIds: [],
- };
- this.#event = {
- id: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
- eventID: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
- title: title ?? EXAMPLE_TRANSACTION_TITLE,
- type: EventOrGroupType.TRANSACTION,
- startTimestamp: 0,
- endTimestamp: transactionSettings?.duration ?? 0,
- contexts: {
- trace: {
- trace_id: this.TRACE_ID,
- span_id: this.ROOT_SPAN_ID,
- op: 'pageload',
- status: 'ok',
- type: 'trace',
- },
- },
- entries: [
- {
- data: this.#spans,
- type: EntryType.SPANS,
- },
- ],
- // For the purpose of mock data, we don't care as much about the properties below.
- // They're here to satisfy the type constraints, but in the future if we need actual values here
- // for testing purposes, we can add methods on the builder to set them.
- crashFile: null,
- culprit: '',
- dateReceived: '',
- dist: null,
- errors: [],
- fingerprints: [],
- location: null,
- message: '',
- measurements: {
- fcp: {
- value: transactionSettings?.fcp ?? 0,
- unit: 'millisecond',
- },
- },
- perfProblem: undefined,
- metadata: {
- current_level: undefined,
- current_tree_label: undefined,
- directive: undefined,
- display_title_with_tree_label: undefined,
- filename: undefined,
- finest_tree_label: undefined,
- function: undefined,
- message: undefined,
- origin: undefined,
- stripped_crash: undefined,
- title: undefined,
- type: undefined,
- uri: undefined,
- value: undefined,
- },
- occurrence: null,
- projectID: '',
- size: 0,
- tags: [],
- user: null,
- };
- if (occurenceBasedEvent) {
- this.#event.occurrence = {
- evidenceData: perfEvidenceData,
- eventId: id ?? 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
- detectionTime: '100',
- evidenceDisplay: [],
- fingerprint: ['fingerprint123'],
- id: 'id123',
- issueTitle: 'N + 1 Query',
- resourceId: '',
- subtitle: 'SELECT * FROM TABLE',
- type: 1006,
- };
- } else {
- this.#event.perfProblem = perfEvidenceData;
- this.#event.perfProblem.issueType =
- problemType ?? IssueType.PERFORMANCE_N_PLUS_ONE_DB_QUERIES;
- }
- }
- generateSpanId() {
- // Convert the num of spans to a hex string to get its ID
- return (this.#spans.length + 1).toString(16).padStart(16, '0');
- }
- addEntry(entry: EventTransaction['entries'][number]) {
- this.#event.entries.push(entry);
- }
- addSpan(mockSpan: MockSpan, numSpans = 1, parentSpanId?: string) {
- for (let i = 0; i < numSpans; i++) {
- const spanId = this.generateSpanId();
- const {span} = mockSpan;
- const clonedSpan = {...span};
- clonedSpan.span_id = spanId;
- clonedSpan.trace_id = this.TRACE_ID;
- clonedSpan.parent_span_id = parentSpanId ?? this.ROOT_SPAN_ID;
- this.#spans.push(clonedSpan);
- const problemSpans = Array.isArray(mockSpan.problemSpan)
- ? mockSpan.problemSpan
- : [mockSpan.problemSpan];
- const perfEvidenceData =
- this.#event.perfProblem ?? this.#event.occurrence?.evidenceData;
- problemSpans.forEach(problemSpan => {
- switch (problemSpan) {
- case ProblemSpan.PARENT:
- perfEvidenceData?.parentSpanIds.push(spanId);
- break;
- case ProblemSpan.OFFENDER:
- perfEvidenceData?.offenderSpanIds.push(spanId);
- break;
- case ProblemSpan.CAUSE:
- perfEvidenceData?.causeSpanIds.push(spanId);
- break;
- default:
- break;
- }
- });
- if (clonedSpan.timestamp > this.#event.endTimestamp) {
- this.#event.endTimestamp = clonedSpan.timestamp;
- }
- mockSpan.children.forEach(child => this.addSpan(child, 1, spanId));
- }
- return this;
- }
- getEventFixture() {
- return this.#event;
- }
- }
- /**
- * A MockSpan object to be used for testing. This object is intended to be used in tandem with `TransactionEventBuilder`
- */
- export class MockSpan {
- span: RawSpanType;
- children: MockSpan[] = [];
- problemSpan: ProblemSpan | ProblemSpan[] | undefined;
- /**
- *
- * @param opts.startTimestamp
- * @param opts.endTimestamp
- * @param opts.op The operation of the span
- * @param opts.description The description of the span
- * @param opts.status Optional span specific status, defaults to 'ok'
- * @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)
- * @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,
- * this will be handled automatically and you do not need to provide an ID. Defaults to the root span's ID.
- */
- constructor(opts: AddSpanOpts) {
- const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
- opts;
- this.span = {
- start_timestamp: startTimestamp,
- timestamp: endTimestamp,
- op,
- description,
- hash,
- status: status ?? 'ok',
- data: opts.data || {},
- // These values are automatically assigned by the TransactionEventBuilder when the spans are added
- span_id: '',
- trace_id: '',
- parent_span_id: '',
- };
- this.problemSpan = problemSpan;
- }
- /**
- *
- * @param opts.numSpans If provided, will create the same span numSpan times
- */
- addChild(opts: AddSpanOpts, numSpans = 1) {
- const {startTimestamp, endTimestamp, op, description, hash, status, problemSpan} =
- opts;
- for (let i = 0; i < numSpans; i++) {
- const span = new MockSpan({
- startTimestamp,
- endTimestamp,
- op,
- description,
- hash,
- status,
- problemSpan,
- });
- this.children.push(span);
- }
- return this;
- }
- /**
- * 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.
- * Will create `depth` spans, each span being a child of the previous.
- * @param depth
- */
- addDuplicateNestedChildren(depth = 1) {
- let currentSpan: MockSpan = this;
- for (let i = 0; i < depth; i++) {
- currentSpan.addChild(currentSpan.getOpts());
- currentSpan = currentSpan.children[0];
- }
- return this;
- }
- getOpts() {
- return {
- startTimestamp: this.span.start_timestamp,
- endTimestamp: this.span.timestamp,
- op: this.span.op,
- description: this.span.description,
- status: this.span.status,
- problemSpan: this.problemSpan,
- };
- }
- }
|