mirror of
https://github.com/hanzoai/sentinel.git
synced 2026-09-29 22:34:33 +00:00
683 lines
18 KiB
TypeScript
683 lines
18 KiB
TypeScript
import {Fragment} from 'react';
|
|
import moment from 'moment';
|
|
|
|
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}
|
|
/>,
|
|
{
|
|
context: data.routerContext,
|
|
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();
|
|
});
|
|
});
|
|
});
|