123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- import {
- generateSampleEvent,
- generateSampleSpan,
- initializeData as _initializeData,
- } from 'sentry-test/performance/initializePerformanceData';
- import {MockSpan, TransactionEventBuilder} from 'sentry-test/performance/utils';
- import {
- render,
- screen,
- userEvent,
- waitFor,
- within,
- } from 'sentry-test/reactTestingLibrary';
- import * as AnchorLinkManager from 'sentry/components/events/interfaces/spans/spanContext';
- import TraceView from 'sentry/components/events/interfaces/spans/traceView';
- import {spanTargetHash} from 'sentry/components/events/interfaces/spans/utils';
- import WaterfallModel from 'sentry/components/events/interfaces/spans/waterfallModel';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import {QuickTraceContext} from 'sentry/utils/performance/quickTrace/quickTraceContext';
- import QuickTraceQuery from 'sentry/utils/performance/quickTrace/quickTraceQuery';
- function initializeData(settings) {
- const data = _initializeData(settings);
- ProjectsStore.loadInitialData(data.projects);
- return data;
- }
- describe('TraceView', () => {
- let data;
- beforeEach(() => {
- data = initializeData({});
- });
- afterEach(() => {
- MockApiClient.clearMockResponses();
- });
- describe('Autogrouped spans tests', () => {
- it('should render siblings with the same op and description as a grouped span in the minimap and span tree', async () => {
- const builder = new TransactionEventBuilder();
- builder.addSpan(
- new MockSpan({
- startTimestamp: 0,
- endTimestamp: 100,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- expect(await screen.findByTestId('minimap-sibling-group-bar')).toBeInTheDocument();
- expect(await screen.findByTestId('span-row-2')).toHaveTextContent('Autogrouped');
- expect(screen.queryByTestId('span-row-3')).not.toBeInTheDocument();
- });
- it('should expand grouped siblings when clicked, async and then regroup when clicked again', async () => {
- const builder = new TransactionEventBuilder();
- builder.addSpan(
- new MockSpan({
- startTimestamp: 0,
- endTimestamp: 100,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- const groupedSiblingsSpan = await screen.findByText('Autogrouped — http —');
- await userEvent.click(groupedSiblingsSpan);
- await waitFor(() =>
- expect(screen.queryByText('Autogrouped — http —')).not.toBeInTheDocument()
- );
- for (let i = 1; i < 7; i++) {
- expect(await screen.findByTestId(`span-row-${i}`)).toBeInTheDocument();
- }
- const regroupButton = await screen.findByText('Regroup');
- expect(regroupButton).toBeInTheDocument();
- await userEvent.click(regroupButton);
- await waitFor(() =>
- expect(screen.queryByTestId('span-row-6')).not.toBeInTheDocument()
- );
- expect(await screen.findByText('Autogrouped — http —')).toBeInTheDocument();
- });
- it("should not group sibling spans that don't have the same op or description", async () => {
- const builder = new TransactionEventBuilder();
- builder.addSpan(
- new MockSpan({
- startTimestamp: 10,
- endTimestamp: 100,
- op: 'http',
- description: 'test',
- })
- );
- builder.addSpan(
- new MockSpan({
- startTimestamp: 100,
- endTimestamp: 200,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- builder.addSpan(
- new MockSpan({
- startTimestamp: 200,
- endTimestamp: 300,
- op: 'http',
- description: 'test',
- })
- );
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- expect(await screen.findByText('group me')).toBeInTheDocument();
- expect(await screen.findAllByText('test')).toHaveLength(2);
- });
- it('should autogroup similar nested spans', async () => {
- const builder = new TransactionEventBuilder();
- const span = new MockSpan({
- startTimestamp: 50,
- endTimestamp: 100,
- op: 'http',
- description: 'group me',
- }).addDuplicateNestedChildren(5);
- builder.addSpan(span);
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- const grouped = await screen.findByText('group me');
- expect(grouped).toBeInTheDocument();
- });
- it('should expand/collapse only the sibling group that is clicked, async even if multiple groups have the same op and description', async () => {
- const builder = new TransactionEventBuilder();
- builder.addSpan(
- new MockSpan({
- startTimestamp: 100,
- endTimestamp: 200,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- builder.addSpan(
- new MockSpan({
- startTimestamp: 200,
- endTimestamp: 300,
- op: 'http',
- description: 'not me',
- })
- );
- builder.addSpan(
- new MockSpan({
- startTimestamp: 300,
- endTimestamp: 400,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- expect(screen.queryAllByText('group me')).toHaveLength(2);
- const firstGroup = screen.queryAllByText('Autogrouped — http —')[0];
- await userEvent.click(firstGroup);
- expect(await screen.findAllByText('group me')).toHaveLength(6);
- const secondGroup = await screen.findByText('Autogrouped — http —');
- await userEvent.click(secondGroup);
- expect(await screen.findAllByText('group me')).toHaveLength(10);
- const firstRegroup = screen.queryAllByText('Regroup')[0];
- await userEvent.click(firstRegroup);
- expect(await screen.findAllByText('group me')).toHaveLength(6);
- const secondRegroup = await screen.findByText('Regroup');
- await userEvent.click(secondRegroup);
- expect(await screen.findAllByText('group me')).toHaveLength(2);
- });
- // TODO: This test can be converted later to use the TransactionEventBuilder instead
- it('should allow expanding of embedded transactions', async () => {
- const {organization, project, location} = initializeData({});
- const event = generateSampleEvent();
- generateSampleSpan(
- 'parent span',
- 'db',
- 'b000000000000000',
- 'a000000000000000',
- event
- );
- const waterfallModel = new WaterfallModel(event);
- const mockResponse = {
- method: 'GET',
- statusCode: 200,
- body: {
- transactions: [
- event,
- {
- errors: [],
- event_id: '998d7e2c304c45729545e4434e2967cb',
- generation: 1,
- parent_event_id: '2b658a829a21496b87fd1f14a61abf65',
- parent_span_id: 'b000000000000000',
- project_id: project.id,
- project_slug: project.slug,
- span_id: '8596e2795f88471d',
- transaction:
- '/api/0/organizations/{organization_slug}/events/{project_slug}:{event_id}/',
- 'transaction.duration': 159,
- 'transaction.op': 'http.server',
- },
- ],
- orphan_errors: [],
- },
- };
- const eventsTraceMock = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events-trace/${event.contexts.trace?.trace_id}/`,
- ...mockResponse,
- });
- const eventsTraceLightMock = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events-trace-light/${event.contexts.trace?.trace_id}/`,
- ...mockResponse,
- });
- const embeddedEvent = {
- ...generateSampleEvent(),
- id: '998d7e2c304c45729545e4434e2967cb',
- eventID: '998d7e2c304c45729545e4434e2967cb',
- };
- embeddedEvent.contexts.trace!.span_id = 'a111111111111111';
- const embeddedSpan = generateSampleSpan(
- 'i am embedded :)',
- 'test',
- 'b111111111111111',
- 'b000000000000000',
- embeddedEvent
- );
- embeddedSpan.trace_id = '8cbbc19c0f54447ab702f00263262726';
- const fetchEmbeddedTransactionMock = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events/${project.slug}:998d7e2c304c45729545e4434e2967cb/`,
- method: 'GET',
- statusCode: 200,
- body: embeddedEvent,
- });
- render(
- <QuickTraceQuery event={event} location={location} orgSlug={organization.slug}>
- {results => (
- <QuickTraceContext.Provider value={results}>
- <TraceView organization={organization} waterfallModel={waterfallModel} />
- </QuickTraceContext.Provider>
- )}
- </QuickTraceQuery>
- );
- expect(eventsTraceMock).toHaveBeenCalled();
- expect(eventsTraceLightMock).toHaveBeenCalled();
- const embeddedTransactionBadge = await screen.findByTestId(
- 'embedded-transaction-badge'
- );
- expect(embeddedTransactionBadge).toBeInTheDocument();
- await userEvent.click(embeddedTransactionBadge);
- expect(fetchEmbeddedTransactionMock).toHaveBeenCalled();
- expect(await screen.findByText(/i am embedded :\)/i)).toBeInTheDocument();
- });
- it('should allow expanding of multiple embedded transactions with the same parent span', async () => {
- const {organization, project, location} = initializeData({});
- const event = generateSampleEvent();
- generateSampleSpan(
- 'GET /api/transitive-edge',
- 'http.client',
- 'b000000000000000',
- 'a000000000000000',
- event
- );
- const waterfallModel = new WaterfallModel(event);
- const mockResponse = {
- method: 'GET',
- statusCode: 200,
- body: {
- transactions: [
- event,
- {
- errors: [],
- event_id: '998d7e2c304c45729545e4434e2967cb',
- generation: 1,
- parent_event_id: '2b658a829a21496b87fd1f14a61abf65',
- parent_span_id: 'b000000000000000',
- project_id: project.id,
- project_slug: project.slug,
- span_id: '8596e2795f88471d',
- transaction:
- '/api/0/organizations/{organization_slug}/events/{project_slug}:{event_id}/',
- 'transaction.duration': 159,
- 'transaction.op': 'http.server',
- },
- {
- errors: [],
- event_id: '59e1fe369528499b87dab7221ce6b8a9',
- generation: 1,
- parent_event_id: '2b658a829a21496b87fd1f14a61abf65',
- parent_span_id: 'b000000000000000',
- project_id: project.id,
- project_slug: project.slug,
- span_id: 'aa5abb302ad5b9e1',
- transaction:
- '/api/0/organizations/{organization_slug}/events/{project_slug}:{event_id}/',
- 'transaction.duration': 159,
- 'transaction.op': 'middleware.nextjs',
- },
- ],
- orphan_errors: [],
- },
- };
- const eventsTraceMock = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events-trace/${event.contexts.trace?.trace_id}/`,
- ...mockResponse,
- });
- const eventsTraceLightMock = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events-trace-light/${event.contexts.trace?.trace_id}/`,
- ...mockResponse,
- });
- const embeddedEvent1 = {
- ...generateSampleEvent(),
- id: '998d7e2c304c45729545e4434e2967cb',
- eventID: '998d7e2c304c45729545e4434e2967cb',
- };
- embeddedEvent1.contexts.trace!.span_id = 'a111111111111111';
- const embeddedSpan1 = generateSampleSpan(
- 'i am embedded :)',
- 'test',
- 'b111111111111111',
- 'b000000000000000',
- embeddedEvent1
- );
- embeddedSpan1.trace_id = '8cbbc19c0f54447ab702f00263262726';
- const embeddedEvent2 = {
- ...generateSampleEvent(),
- id: '59e1fe369528499b87dab7221ce6b8a9',
- eventID: '59e1fe369528499b87dab7221ce6b8a9',
- };
- embeddedEvent2.contexts.trace!.span_id = 'a222222222222222';
- const embeddedSpan2 = generateSampleSpan(
- 'i am also embedded :o',
- 'middleware.nextjs',
- 'c111111111111111',
- 'b000000000000000',
- embeddedEvent2
- );
- embeddedSpan2.trace_id = '8cbbc19c0f54447ab702f00263262726';
- const fetchEmbeddedTransactionMock1 = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events/${project.slug}:998d7e2c304c45729545e4434e2967cb/`,
- method: 'GET',
- statusCode: 200,
- body: embeddedEvent1,
- });
- const fetchEmbeddedTransactionMock2 = MockApiClient.addMockResponse({
- url: `/organizations/${organization.slug}/events/${project.slug}:59e1fe369528499b87dab7221ce6b8a9/`,
- method: 'GET',
- statusCode: 200,
- body: embeddedEvent2,
- });
- render(
- <QuickTraceQuery event={event} location={location} orgSlug={organization.slug}>
- {results => (
- <QuickTraceContext.Provider value={results}>
- <TraceView organization={organization} waterfallModel={waterfallModel} />
- </QuickTraceContext.Provider>
- )}
- </QuickTraceQuery>
- );
- expect(eventsTraceMock).toHaveBeenCalled();
- expect(eventsTraceLightMock).toHaveBeenCalled();
- const embeddedTransactionBadge = await screen.findByTestId(
- 'embedded-transaction-badge'
- );
- expect(embeddedTransactionBadge).toBeInTheDocument();
- await userEvent.click(embeddedTransactionBadge);
- expect(fetchEmbeddedTransactionMock1).toHaveBeenCalled();
- expect(fetchEmbeddedTransactionMock2).toHaveBeenCalled();
- expect(await screen.findByText(/i am embedded :\)/i)).toBeInTheDocument();
- expect(await screen.findByText(/i am also embedded :o/i)).toBeInTheDocument();
- });
- it('should correctly render sibling autogroup text when op and/or description is not provided', async () => {
- data = initializeData({});
- const builder1 = new TransactionEventBuilder();
- // Autogroup without span ops
- builder1.addSpan(
- new MockSpan({
- startTimestamp: 50,
- endTimestamp: 100,
- description: 'group me',
- }),
- 5
- );
- const {rerender} = render(
- <TraceView
- organization={data.organization}
- waterfallModel={new WaterfallModel(builder1.getEventFixture())}
- />
- );
- expect(await screen.findByTestId('span-row-2')).toHaveTextContent(
- /Autogrouped — group me/
- );
- // Autogroup without span descriptions
- const builder2 = new TransactionEventBuilder();
- builder2.addSpan(
- new MockSpan({
- startTimestamp: 100,
- endTimestamp: 200,
- op: 'http',
- }),
- 5
- );
- rerender(
- <TraceView
- organization={data.organization}
- waterfallModel={new WaterfallModel(builder2.getEventFixture())}
- />
- );
- expect(await screen.findByTestId('span-row-2')).toHaveTextContent(
- /Autogrouped — http/
- );
- // Autogroup without span ops or descriptions
- const builder3 = new TransactionEventBuilder();
- builder3.addSpan(
- new MockSpan({
- startTimestamp: 200,
- endTimestamp: 300,
- }),
- 5
- );
- rerender(
- <TraceView
- organization={data.organization}
- waterfallModel={new WaterfallModel(builder3.getEventFixture())}
- />
- );
- expect(await screen.findByTestId('span-row-2')).toHaveTextContent(
- /Autogrouped — siblings/
- );
- });
- it('should automatically expand a sibling span group and select a span if it is anchored', async () => {
- data = initializeData({});
- const builder = new TransactionEventBuilder();
- builder.addSpan(
- new MockSpan({
- startTimestamp: 100,
- endTimestamp: 200,
- op: 'http',
- description: 'group me',
- }),
- 5
- );
- // Manually set the hash here, the AnchorLinkManager is expected to automatically expand the group and scroll to the span with this id
- location.hash = spanTargetHash('0000000000000003');
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <AnchorLinkManager.Provider>
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- </AnchorLinkManager.Provider>
- );
- expect(await screen.findByText(/0000000000000003/i)).toBeInTheDocument();
- location.hash = '';
- });
- it('should automatically expand a descendant span group and select a span if it is anchored', async () => {
- data = initializeData({});
- const builder = new TransactionEventBuilder();
- const span = new MockSpan({
- startTimestamp: 50,
- endTimestamp: 100,
- op: 'http',
- description: 'group me',
- }).addDuplicateNestedChildren(5);
- builder.addSpan(span);
- location.hash = spanTargetHash('0000000000000003');
- const waterfallModel = new WaterfallModel(builder.getEventFixture());
- render(
- <AnchorLinkManager.Provider>
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- </AnchorLinkManager.Provider>
- );
- expect(await screen.findByText(/0000000000000003/i)).toBeInTheDocument();
- location.hash = '';
- });
- });
- it('should merge web vitals labels if they are too close together', () => {
- data = initializeData({});
- const event = generateSampleEvent();
- generateSampleSpan('browser', 'test1', 'b000000000000000', 'a000000000000000', event);
- generateSampleSpan('browser', 'test2', 'c000000000000000', 'a000000000000000', event);
- generateSampleSpan('browser', 'test3', 'd000000000000000', 'a000000000000000', event);
- generateSampleSpan('browser', 'test4', 'e000000000000000', 'a000000000000000', event);
- generateSampleSpan('browser', 'test5', 'f000000000000000', 'a000000000000000', event);
- event.measurements = {
- fcp: {value: 1000},
- fp: {value: 1050},
- lcp: {value: 1100},
- };
- const waterfallModel = new WaterfallModel(event);
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- const labelContainer = screen.getByText(/fcp/i).parentElement?.parentElement;
- expect(labelContainer).toBeInTheDocument();
- expect(within(labelContainer!).getByText(/fcp/i)).toBeInTheDocument();
- expect(within(labelContainer!).getByText(/fp/i)).toBeInTheDocument();
- expect(within(labelContainer!).getByText(/lcp/i)).toBeInTheDocument();
- });
- it('should not merge web vitals labels if they are spaced away from each other', () => {
- data = initializeData({});
- const event = generateSampleEvent();
- generateSampleSpan('browser', 'test1', 'b000000000000000', 'a000000000000000', event);
- event.startTimestamp = 1;
- event.endTimestamp = 100;
- event.measurements = {
- fcp: {value: 858.3002090454102, unit: 'millisecond'},
- lcp: {value: 1000363.800048828125, unit: 'millisecond'},
- };
- const waterfallModel = new WaterfallModel(event);
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- const fcpLabelContainer = screen.getByText(/fcp/i).parentElement?.parentElement;
- expect(fcpLabelContainer).toBeInTheDocument();
- // LCP should not be merged along with FCP. We expect it to be in a separate element
- expect(within(fcpLabelContainer!).queryByText(/lcp/i)).not.toBeInTheDocument();
- const lcpLabelContainer = screen.getByText(/lcp/i).parentElement?.parentElement;
- expect(lcpLabelContainer).toBeInTheDocument();
- });
- it('should have all focused spans visible', async () => {
- data = initializeData({});
- const event = generateSampleEvent();
- for (let i = 0; i < 10; i++) {
- generateSampleSpan(`desc${i}`, 'db', `id${i}`, 'c000000000000000', event);
- }
- const waterfallModel = new WaterfallModel(event, ['id3'], ['id3', 'id4']);
- render(
- <TraceView organization={data.organization} waterfallModel={waterfallModel} />
- );
- expect(await screen.findByTestId('span-row-1')).toHaveTextContent('desc3');
- expect(await screen.findByTestId('span-row-2')).toHaveTextContent('desc4');
- expect(screen.queryByTestId('span-row-3')).not.toBeInTheDocument();
- });
- });
|