123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708 |
- import {OrganizationFixture} from 'sentry-fixture/organization';
- import type {RawSpanType} from 'sentry/components/events/interfaces/spans/types';
- import {EntryType, type Event} from 'sentry/types';
- import type {
- TraceFullDetailed,
- TraceSplitResults,
- } from 'sentry/utils/performance/quickTrace/types';
- import {
- type VirtualizedList,
- VirtualizedViewManager,
- } from 'sentry/views/performance/newTraceDetails/virtualizedViewManager';
- import {TraceTree} from './traceTree';
- function makeEvent(overrides: Partial<Event> = {}, spans: RawSpanType[] = []): Event {
- return {
- entries: [{type: EntryType.SPANS, data: spans}],
- ...overrides,
- } as Event;
- }
- function makeTrace(
- overrides: Partial<TraceSplitResults<TraceFullDetailed>>
- ): TraceSplitResults<TraceFullDetailed> {
- return {
- transactions: [],
- orphan_errors: [],
- ...overrides,
- } as TraceSplitResults<TraceFullDetailed>;
- }
- function makeTransaction(overrides: Partial<TraceFullDetailed> = {}): TraceFullDetailed {
- return {
- children: [],
- start_timestamp: 0,
- timestamp: 1,
- transaction: 'transaction',
- 'transaction.op': '',
- 'transaction.status': '',
- errors: [],
- performance_issues: [],
- ...overrides,
- } as TraceFullDetailed;
- }
- function makeSpan(overrides: Partial<RawSpanType> = {}): RawSpanType {
- return {
- op: '',
- description: '',
- span_id: '',
- start_timestamp: 0,
- timestamp: 10,
- ...overrides,
- } as RawSpanType;
- }
- function makeParentAutogroupSpans(): RawSpanType[] {
- return [
- makeSpan({description: 'span', op: 'db', span_id: 'head_span'}),
- makeSpan({
- description: 'span',
- op: 'db',
- span_id: 'middle_span',
- parent_span_id: 'head_span',
- }),
- makeSpan({
- description: 'span',
- op: 'db',
- span_id: 'tail_span',
- parent_span_id: 'middle_span',
- }),
- ];
- }
- function makeSiblingAutogroupedSpans(): RawSpanType[] {
- return [
- makeSpan({description: 'span', op: 'db', span_id: 'first_span'}),
- makeSpan({description: 'span', op: 'db', span_id: 'middle_span'}),
- makeSpan({description: 'span', op: 'db', span_id: 'other_middle_span'}),
- makeSpan({description: 'span', op: 'db', span_id: 'another_middle_span'}),
- makeSpan({description: 'span', op: 'db', span_id: 'last_span'}),
- ];
- }
- function makeSingleTransactionTree(): TraceTree {
- return TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction({
- transaction: 'transaction',
- project_slug: 'project',
- event_id: 'event_id',
- }),
- ],
- })
- );
- }
- function makeList(): VirtualizedList {
- return {
- scrollToRow: jest.fn(),
- } as unknown as VirtualizedList;
- }
- const EVENT_REQUEST_URL =
- '/organizations/org-slug/events/project:event_id/?averageColumn=span.self_time';
- describe('VirtualizedViewManger', () => {
- it('initializes space', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0.5},
- span_list: {width: 0.5},
- });
- manager.initializeTraceSpace([10_000, 0, 1000, 1]);
- expect(manager.trace_space.serialize()).toEqual([0, 0, 1000, 1]);
- expect(manager.trace_view.serialize()).toEqual([0, 0, 1000, 1]);
- });
- it('initializes physical space', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0.5},
- span_list: {width: 0.5},
- });
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.container_physical_space.serialize()).toEqual([0, 0, 1000, 1]);
- expect(manager.trace_physical_space.serialize()).toEqual([0, 0, 500, 1]);
- });
- describe('computeSpanCSSMatrixTransform', () => {
- it('enforces min scaling', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 1000, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([0, 0.1])).toEqual([
- 0.001, 0, 0, 1, 0, 0,
- ]);
- });
- it('computes width scaling correctly', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([0, 100])).toEqual([1, 0, 0, 1, 0, 0]);
- });
- it('computes x position correctly', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 1000, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([50, 1000])).toEqual([
- 1, 0, 0, 1, 50, 0,
- ]);
- });
- it('computes span x position correctly', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 1000, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([50, 1000])).toEqual([
- 1, 0, 0, 1, 50, 0,
- ]);
- });
- describe('when start is not 0', () => {
- it('computes width scaling correctly', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([100, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([100, 100])).toEqual([
- 1, 0, 0, 1, 0, 0,
- ]);
- });
- it('computes x position correctly when view is offset', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([100, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeSpanCSSMatrixTransform([100, 100])).toEqual([
- 1, 0, 0, 1, 0, 0,
- ]);
- });
- });
- });
- describe('computeTransformXFromTimestamp', () => {
- it('computes x position correctly', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 1000, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.computeTransformXFromTimestamp(50)).toEqual(50);
- });
- it('computes x position correctly when view is offset', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([50, 0, 1000, 1]);
- manager.initializePhysicalSpace(1000, 1);
- manager.trace_view.x = 50;
- expect(manager.computeTransformXFromTimestamp(-50)).toEqual(-150);
- });
- it('when view is offset and scaled', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([50, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- manager.trace_view.width = 50;
- manager.trace_view.x = 50;
- expect(Math.round(manager.computeTransformXFromTimestamp(75))).toEqual(-250);
- });
- });
- describe('getConfigSpaceCursor', () => {
- it('returns the correct x position', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([50, 0]);
- });
- it('returns the correct x position when view scaled', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- manager.trace_view.x = 50;
- manager.trace_view.width = 50;
- expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([75, 0]);
- });
- it('returns the correct x position when view is offset', () => {
- const manager = new VirtualizedViewManager({
- list: {width: 0},
- span_list: {width: 1},
- });
- manager.initializeTraceSpace([0, 0, 100, 1]);
- manager.initializePhysicalSpace(1000, 1);
- manager.trace_view.x = 50;
- expect(manager.getConfigSpaceCursor({x: 500, y: 0})).toEqual([100, 0]);
- });
- });
- describe('scrollToPath', () => {
- const organization = OrganizationFixture();
- const api = new MockApiClient();
- const manager = new VirtualizedViewManager({
- list: {width: 0.5},
- span_list: {width: 0.5},
- });
- it('scrolls to root node', async () => {
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [makeTransaction()],
- orphan_errors: [],
- })
- );
- manager.list = makeList();
- const result = await manager.scrollToPath(tree, tree.list[0].path, () => void 0, {
- api: api,
- organization,
- });
- expect(result?.node).toBe(tree.list[0]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(0);
- });
- it('scrolls to transaction', async () => {
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction(),
- makeTransaction({
- event_id: 'event_id',
- children: [],
- }),
- ],
- })
- );
- manager.list = makeList();
- const result = await manager.scrollToPath(tree, ['txn-event_id'], () => void 0, {
- api: api,
- organization,
- });
- expect(result?.node).toBe(tree.list[2]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(2);
- });
- it('scrolls to nested transaction', async () => {
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction({
- event_id: 'root',
- children: [
- makeTransaction({
- event_id: 'child',
- children: [
- makeTransaction({
- event_id: 'event_id',
- children: [],
- }),
- ],
- }),
- ],
- }),
- ],
- })
- );
- manager.list = makeList();
- expect(tree.list[tree.list.length - 1].path).toEqual([
- 'txn-event_id',
- 'txn-child',
- 'txn-root',
- ]);
- const result = await manager.scrollToPath(
- tree,
- ['txn-event_id', 'txn-child', 'txn-root'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result?.node).toBe(tree.list[tree.list.length - 1]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(3);
- });
- it('scrolls to spans of expanded transaction', async () => {
- manager.list = makeList();
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction({
- event_id: 'event_id',
- project_slug: 'project',
- children: [],
- }),
- ],
- })
- );
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent(undefined, [makeSpan({span_id: 'span_id'})]),
- });
- const result = await manager.scrollToPath(
- tree,
- ['span-span_id', 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(tree.list[1].zoomedIn).toBe(true);
- expect(result?.node).toBe(tree.list[2]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(2);
- });
- it('scrolls to empty data node of expanded transaction', async () => {
- manager.list = makeList();
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction({
- event_id: 'event_id',
- project_slug: 'project',
- children: [],
- }),
- ],
- })
- );
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent(undefined, []),
- });
- const result = await manager.scrollToPath(
- tree,
- ['empty-node', 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(tree.list[1].zoomedIn).toBe(true);
- expect(result?.node).toBe(tree.list[2]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(2);
- });
- it('scrolls to span -> transaction -> span -> transaction', async () => {
- manager.list = makeList();
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [
- makeTransaction({
- event_id: 'event_id',
- project_slug: 'project_slug',
- children: [
- makeTransaction({
- parent_span_id: 'child_span',
- event_id: 'child_event_id',
- project_slug: 'project_slug',
- }),
- ],
- }),
- ],
- })
- );
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent(undefined, [
- makeSpan({span_id: 'other_child_span'}),
- makeSpan({span_id: 'child_span'}),
- ]),
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/project_slug:child_event_id/?averageColumn=span.self_time',
- method: 'GET',
- body: makeEvent(undefined, [makeSpan({span_id: 'other_child_span'})]),
- });
- const result = await manager.scrollToPath(
- tree,
- ['span-other_child_span', 'txn-child_event_id', 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(3);
- });
- describe('scrolls to directly autogrouped node', () => {
- for (const headOrTailId of ['head_span', 'tail_span']) {
- it('scrolls to directly autogrouped node head', async () => {
- manager.list = makeList();
- const tree = makeSingleTransactionTree();
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent({}, makeParentAutogroupSpans()),
- });
- const result = await manager.scrollToPath(
- tree,
- [`ag-${headOrTailId}`, 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(2);
- });
- }
- for (const headOrTailId of ['head_span', 'tail_span']) {
- it('scrolls to child of autogrouped node head or tail', async () => {
- manager.list = makeList();
- const tree = makeSingleTransactionTree();
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent({}, makeParentAutogroupSpans()),
- });
- const result = await manager.scrollToPath(
- tree,
- ['span-middle_span', `ag-${headOrTailId}`, 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(4);
- });
- }
- });
- describe('sibling autogrouping', () => {
- it('scrolls to child span of sibling autogrouped node', async () => {
- manager.list = makeList();
- const tree = makeSingleTransactionTree();
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent({}, makeSiblingAutogroupedSpans()),
- });
- const result = await manager.scrollToPath(
- tree,
- ['span-middle_span', `ag-first_span`, 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(4);
- });
- });
- describe('missing instrumentation', () => {
- it('scrolls to missing instrumentation via previous span_id', async () => {
- manager.list = makeList();
- const tree = makeSingleTransactionTree();
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent({}, [
- makeSpan({
- description: 'span',
- op: 'db',
- start_timestamp: 0,
- timestamp: 0.5,
- span_id: 'first_span',
- }),
- makeSpan({
- description: 'span',
- op: 'db',
- start_timestamp: 0.7,
- timestamp: 1,
- span_id: 'middle_span',
- }),
- ]),
- });
- const result = await manager.scrollToPath(
- tree,
- ['ms-first_span', 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(3);
- });
- it('scrolls to missing instrumentation via next span_id', async () => {
- manager.list = makeList();
- const tree = makeSingleTransactionTree();
- MockApiClient.addMockResponse({
- url: EVENT_REQUEST_URL,
- method: 'GET',
- body: makeEvent({}, [
- makeSpan({
- description: 'span',
- op: 'db',
- start_timestamp: 0,
- timestamp: 0.5,
- span_id: 'first_span',
- }),
- makeSpan({
- description: 'span',
- op: 'db',
- start_timestamp: 0.7,
- timestamp: 1,
- span_id: 'second_span',
- }),
- ]),
- });
- const result = await manager.scrollToPath(
- tree,
- ['ms-second_span', 'txn-event_id'],
- () => void 0,
- {
- api: api,
- organization,
- }
- );
- expect(result).toBeTruthy();
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(3);
- });
- });
- it('scrolls to orphan error', async () => {
- manager.list = makeList();
- const tree = TraceTree.FromTrace(
- makeTrace({
- transactions: [makeTransaction()],
- orphan_errors: [
- {
- event_id: 'ded',
- project_slug: 'project_slug',
- project_id: 1,
- issue: 'whoa rusty',
- issue_id: 0,
- span: '',
- level: 'error',
- title: 'ded fo good',
- timestamp: 1,
- },
- ],
- })
- );
- const result = await manager.scrollToPath(tree, ['error-ded'], () => void 0, {
- api: api,
- organization,
- });
- expect(result?.node).toBe(tree.list[2]);
- expect(manager.list.scrollToRow).toHaveBeenCalledWith(2);
- });
- });
- });
|