123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682 |
- import {Fragment} from 'react';
- import moment from 'moment-timezone';
- import {initializeData} from 'sentry-test/performance/initializePerformanceData';
- import {act, render, screen, waitFor} from 'sentry-test/reactTestingLibrary';
- import ProjectsStore from 'sentry/stores/projectsStore';
- import type {SuspectSpans} from 'sentry/utils/performance/suspectSpans/types';
- import type {EventsResultsDataRow} from 'sentry/utils/profiling/hooks/types';
- import {PerformanceChangeExplorer} from 'sentry/views/performance/trends/changeExplorer';
- import type {FunctionsField} from 'sentry/views/performance/trends/changeExplorerUtils/functionsList';
- import {FunctionsList} from 'sentry/views/performance/trends/changeExplorerUtils/functionsList';
- import {
- COLUMNS,
- MetricsTable,
- renderBodyCell,
- } from 'sentry/views/performance/trends/changeExplorerUtils/metricsTable';
- import {SpansList} from 'sentry/views/performance/trends/changeExplorerUtils/spansList';
- import type {NormalizedTrendsTransaction} from 'sentry/views/performance/trends/types';
- import {TrendChangeType, TrendFunctionField} from 'sentry/views/performance/trends/types';
- import {TRENDS_PARAMETERS} from 'sentry/views/performance/trends/utils';
- async function waitForMockCall(mock: jest.Mock) {
- await waitFor(() => {
- expect(mock).toHaveBeenCalled();
- });
- }
- const transaction: NormalizedTrendsTransaction = {
- aggregate_range_1: 78.2757131147541,
- aggregate_range_2: 110.50465131578949,
- breakpoint: 1687262400,
- project: 'sentry',
- transaction: 'sentry.tasks.store.save_event',
- trend_difference: 32.22893820103539,
- trend_percentage: 1.411736117354651,
- count: 3459,
- received_at: moment(1601251200000),
- };
- const spanResults: SuspectSpans = [
- {
- op: 'db',
- group: '1',
- description: 'span1',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '2',
- description: 'span2',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '3',
- description: 'span3',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '4',
- description: 'span4',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '5',
- description: 'span5',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '6',
- description: 'span6',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '7',
- description: 'span7',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '8',
- description: 'span8',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '9',
- description: 'span9',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '10',
- description: 'span10',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- {
- op: 'db',
- group: '11',
- description: 'span11',
- frequency: 4,
- count: 4,
- avgOccurrences: undefined,
- sumExclusiveTime: 345345,
- p50ExclusiveTime: undefined,
- p75ExclusiveTime: 25,
- p95ExclusiveTime: undefined,
- p99ExclusiveTime: undefined,
- examples: [],
- },
- ];
- const functionResults: EventsResultsDataRow<FunctionsField>[] = [
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f1',
- 'p75()': 4239847,
- package: 'p1',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f2',
- 'p75()': 4239847,
- package: 'p2',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f3',
- 'p75()': 4239847,
- package: 'p3',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f4',
- 'p75()': 4239847,
- package: 'p4',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f5',
- 'p75()': 4239847,
- package: 'p5',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f5',
- 'p75()': 4239847,
- package: 'p5',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f7',
- 'p75()': 4239847,
- package: 'p7',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f8',
- 'p75()': 4239847,
- package: 'p8',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f9',
- 'p75()': 4239847,
- package: 'p9',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f10',
- 'p75()': 4239847,
- package: 'p10',
- 'sum()': 2304823908,
- },
- {
- 'count()': 234,
- 'examples()': ['serw8r9s', 'aeo4i2u38'],
- function: 'f11',
- 'p75()': 4239847,
- package: 'p11',
- 'sum()': 2304823908,
- },
- ];
- describe('Performance > Trends > Performance Change Explorer', function () {
- let eventsMockBefore;
- let spansMock;
- beforeEach(function () {
- eventsMockBefore = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- body: {
- data: [
- {
- 'p95()': 1010.9232499999998,
- 'p50()': 47.34580982348902,
- 'tps()': 3.7226926286168966,
- 'count()': 345,
- 'failure_rate()': 0.23498234,
- 'examples()': ['dkwj4w8sdjk', 'asdi389a8'],
- },
- ],
- meta: {
- fields: {
- 'p95()': 'duration',
- '950()': 'duration',
- 'tps()': 'number',
- 'count()': 'number',
- 'failure_rate()': 'number',
- 'examples()': 'Array',
- },
- units: {
- 'p95()': 'millisecond',
- 'p50()': 'millisecond',
- 'tps()': null,
- 'count()': null,
- 'failure_rate()': null,
- 'examples()': null,
- },
- isMetricsData: true,
- tips: {},
- dataset: 'metrics',
- },
- },
- });
- spansMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-spans-performance/',
- body: [],
- });
- });
- afterEach(function () {
- MockApiClient.clearMockResponses();
- act(() => ProjectsStore.reset());
- });
- it('renders basic UI elements', async function () {
- const data = initializeData();
- const statsData = {
- ['/organizations/:orgId/performance/']: {
- data: [],
- order: 0,
- },
- };
- render(
- <PerformanceChangeExplorer
- collapsed={false}
- transaction={transaction}
- onClose={() => {}}
- trendChangeType={TrendChangeType.REGRESSION}
- trendFunction={TrendFunctionField.P50}
- trendParameter={TRENDS_PARAMETERS[0]}
- trendView={data.eventView}
- statsData={statsData}
- isLoading={false}
- organization={data.organization}
- projects={data.projects}
- location={data.location}
- />,
- {
- router: data.router,
- organization: data.organization,
- }
- );
- await waitForMockCall(eventsMockBefore);
- await waitForMockCall(spansMock);
- await waitFor(() => {
- expect(screen.getByTestId('pce-header')).toBeInTheDocument();
- expect(screen.getByTestId('pce-graph')).toBeInTheDocument();
- expect(screen.getByTestId('grid-editable')).toBeInTheDocument();
- expect(screen.getAllByTestId('pce-metrics-chart-row-metric')).toHaveLength(4);
- expect(screen.getAllByTestId('pce-metrics-chart-row-before')).toHaveLength(4);
- expect(screen.getAllByTestId('pce-metrics-chart-row-after')).toHaveLength(4);
- expect(screen.getAllByTestId('pce-metrics-chart-row-change')).toHaveLength(4);
- expect(screen.getByTestId('list-item')).toBeInTheDocument();
- });
- });
- it('shows correct change notation for no change', async () => {
- const data = initializeData();
- render(
- <MetricsTable
- isLoading={false}
- location={data.location}
- trendFunction={TrendFunctionField.P50}
- transaction={transaction}
- trendView={data.eventView}
- organization={data.organization}
- />
- );
- await waitForMockCall(eventsMockBefore);
- await waitFor(() => {
- expect(screen.getAllByText('3.7 ps')).toHaveLength(2);
- expect(screen.getAllByTestId('pce-metrics-text-change')[0]).toHaveTextContent('-');
- });
- });
- it('shows correct change notation for positive change', async () => {
- const data = initializeData();
- render(
- <MetricsTable
- isLoading={false}
- location={data.location}
- trendFunction={TrendFunctionField.P50}
- transaction={transaction}
- trendView={data.eventView}
- organization={data.organization}
- />
- );
- await waitForMockCall(eventsMockBefore);
- await waitFor(() => {
- expect(screen.getAllByTestId('pce-metrics-text-before')[1]).toHaveTextContent(
- '78.3 ms'
- );
- expect(screen.getAllByTestId('pce-metrics-text-after')[1]).toHaveTextContent(
- '110.5 ms'
- );
- expect(screen.getAllByTestId('pce-metrics-text-change')[1]).toHaveTextContent(
- '+41.2%'
- );
- });
- });
- it('shows correct change notation for negative change', async () => {
- const data = initializeData();
- const negativeTransaction = {
- ...transaction,
- aggregate_range_1: 110.50465131578949,
- aggregate_range_2: 78.2757131147541,
- trend_percentage: 0.588263882645349,
- };
- render(
- <MetricsTable
- isLoading={false}
- location={data.location}
- trendFunction={TrendFunctionField.P50}
- transaction={negativeTransaction}
- trendView={data.eventView}
- organization={data.organization}
- />
- );
- await waitForMockCall(eventsMockBefore);
- await waitFor(() => {
- expect(screen.getAllByTestId('pce-metrics-text-after')[1]).toHaveTextContent(
- '78.3 ms'
- );
- expect(screen.getAllByTestId('pce-metrics-text-before')[1]).toHaveTextContent(
- '110.5 ms'
- );
- expect(screen.getAllByTestId('pce-metrics-text-change')[1]).toHaveTextContent(
- '-41.2%'
- );
- });
- });
- it('shows correct change notation for no results', async () => {
- const data = initializeData();
- const nullEventsMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- body: {
- data: [
- {
- 'p95()': 1010.9232499999998,
- 'p50()': 47.34580982348902,
- 'count()': 345,
- },
- ],
- meta: {
- fields: {
- 'p95()': 'duration',
- '950()': 'duration',
- 'count()': 'number',
- },
- units: {
- 'p95()': 'millisecond',
- 'p50()': 'millisecond',
- 'count()': null,
- },
- isMetricsData: true,
- tips: {},
- dataset: 'metrics',
- },
- },
- });
- render(
- <MetricsTable
- isLoading={false}
- location={data.location}
- trendFunction={TrendFunctionField.P50}
- transaction={transaction}
- trendView={data.eventView}
- organization={data.organization}
- />
- );
- await waitForMockCall(nullEventsMock);
- await waitFor(() => {
- expect(screen.getAllByTestId('pce-metrics-text-after')[0]).toHaveTextContent('-');
- expect(screen.getAllByTestId('pce-metrics-text-before')[0]).toHaveTextContent('-');
- expect(screen.getAllByTestId('pce-metrics-text-change')[0]).toHaveTextContent('-');
- });
- });
- it('returns correct null formatting for change column', () => {
- render(
- <Fragment>
- {renderBodyCell(COLUMNS.change, {
- metric: null,
- before: null,
- after: null,
- change: '0%',
- })}
- {renderBodyCell(COLUMNS.change, {
- metric: null,
- before: null,
- after: null,
- change: '+NaN%',
- })}
- {renderBodyCell(COLUMNS.change, {
- metric: null,
- before: null,
- after: null,
- change: '-',
- })}
- </Fragment>
- );
- expect(screen.getAllByTestId('pce-metrics-text-change')[0]).toHaveTextContent('-');
- expect(screen.getAllByTestId('pce-metrics-text-change')[1]).toHaveTextContent('-');
- expect(screen.getAllByTestId('pce-metrics-text-change')[2]).toHaveTextContent('-');
- });
- it('returns correct positive formatting for change column', () => {
- render(
- renderBodyCell(COLUMNS.change, {
- metric: null,
- before: null,
- after: null,
- change: '40.3%',
- })
- );
- expect(screen.getByText('+40.3%')).toBeInTheDocument();
- });
- it('renders spans list with no results', async () => {
- const data = initializeData();
- const emptyEventsMock = MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- body: {},
- });
- render(
- <div>
- <SpansList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- <FunctionsList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- </div>
- );
- await waitForMockCall(spansMock);
- await waitForMockCall(emptyEventsMock);
- await waitFor(() => {
- expect(screen.getAllByTestId('empty-state')).toHaveLength(2);
- expect(screen.getByTestId('spans-no-results')).toBeInTheDocument();
- expect(screen.getByTestId('functions-no-results')).toBeInTheDocument();
- });
- });
- it('renders spans list with error message', async () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-spans-performance/',
- statusCode: 504,
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- statusCode: 504,
- });
- const data = initializeData();
- render(
- <div>
- <SpansList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- <FunctionsList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- </div>
- );
- await waitFor(() => {
- expect(screen.getByTestId('error-indicator-spans')).toBeInTheDocument();
- expect(screen.getByTestId('error-indicator-functions')).toBeInTheDocument();
- });
- });
- it('renders spans list with no changes message', async () => {
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events-spans-performance/',
- body: spanResults,
- });
- MockApiClient.addMockResponse({
- url: '/organizations/org-slug/events/',
- body: {
- data: functionResults,
- meta: {},
- },
- });
- const data = initializeData();
- render(
- <div>
- <SpansList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- <FunctionsList
- location={data.location}
- organization={data.organization}
- trendView={data.eventView}
- breakpoint={transaction.breakpoint!}
- transaction={transaction}
- trendChangeType={TrendChangeType.REGRESSION}
- />
- </div>
- );
- await waitFor(() => {
- expect(screen.getAllByTestId('empty-state')).toHaveLength(2);
- expect(screen.getByTestId('spans-no-changes')).toBeInTheDocument();
- expect(screen.getByTestId('functions-no-changes')).toBeInTheDocument();
- });
- });
- });
|