import {LocationFixture} from 'sentry-fixture/locationFixture';
import {OrganizationFixture} from 'sentry-fixture/organization';
import {initializeOrg} from 'sentry-test/initializeOrg';
import {generateSuspectSpansResponse} from 'sentry-test/performance/initializePerformanceData';
import {
act,
render,
screen,
waitForElementToBeRemoved,
within,
} from 'sentry-test/reactTestingLibrary';
import ProjectsStore from 'sentry/stores/projectsStore';
import {useLocation} from 'sentry/utils/useLocation';
import TransactionSpans from 'sentry/views/performance/transactionSummary/transactionSpans';
import {
SpanSortOthers,
SpanSortPercentiles,
} from 'sentry/views/performance/transactionSummary/transactionSpans/types';
jest.mock('sentry/utils/useLocation');
const mockUseLocation = jest.mocked(useLocation);
function initializeData(options: {query: {}; additionalFeatures?: string[]}) {
const {query, additionalFeatures} = options;
const defaultFeatures = ['performance-view'];
const organization = OrganizationFixture({
features: [...defaultFeatures, ...(additionalFeatures ? additionalFeatures : [])],
});
const initialData = initializeOrg({
organization,
router: {
location: {
query: {
transaction: 'Test Transaction',
project: '1',
...query,
},
},
},
});
act(() => void ProjectsStore.loadInitialData(initialData.projects));
return initialData;
}
describe('Performance > Transaction Spans', function () {
let eventsMock: jest.Mock;
let eventsSpanOpsMock: jest.Mock;
let eventsSpansPerformanceMock: jest.Mock;
beforeEach(function () {
mockUseLocation.mockReturnValue(
LocationFixture({pathname: '/organizations/org-slug/performance/summary'})
);
MockApiClient.addMockResponse({
url: '/organizations/org-slug/projects/',
body: [],
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/prompts-activity/',
body: {},
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/sdk-updates/',
body: [],
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/events-has-measurements/',
body: {measurements: false},
});
eventsMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/events/',
body: [{'count()': 100}],
});
eventsSpanOpsMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/events-span-ops/',
body: [],
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/replay-count/',
body: {},
});
MockApiClient.addMockResponse({
url: '/organizations/org-slug/spans/fields/',
body: [],
});
});
afterEach(function () {
MockApiClient.clearMockResponses();
ProjectsStore.reset();
});
describe('Without Span Data', function () {
beforeEach(function () {
eventsSpansPerformanceMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/events-spans-performance/',
body: [],
});
});
it('renders empty state', async function () {
const initialData = initializeData({
query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
});
render(, {
router: initialData.router,
organization: initialData.organization,
});
expect(
await screen.findByText('No results found for your query')
).toBeInTheDocument();
});
});
describe('With Span Data', function () {
beforeEach(function () {
eventsSpansPerformanceMock = MockApiClient.addMockResponse({
url: '/organizations/org-slug/events-spans-performance/',
body: generateSuspectSpansResponse({examples: 0}),
});
});
it('renders basic UI elements', async function () {
const initialData = initializeData({
query: {sort: SpanSortOthers.SUM_EXCLUSIVE_TIME},
});
render(, {
router: initialData.router,
organization: initialData.organization,
});
// default visible columns
const grid = await screen.findByTestId('grid-editable');
expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
expect(await within(grid).findByText('P75 Self Time')).toBeInTheDocument();
expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
// there should be a row for each of the spans
expect(await within(grid).findByText('op1')).toBeInTheDocument();
expect(await within(grid).findByText('op2')).toBeInTheDocument();
expect(eventsMock).toHaveBeenCalledTimes(1);
expect(eventsSpanOpsMock).toHaveBeenCalledTimes(1);
expect(eventsSpansPerformanceMock).toHaveBeenCalledTimes(1);
});
[
{sort: SpanSortPercentiles.P50_EXCLUSIVE_TIME, label: 'P50 Self Time'},
{sort: SpanSortPercentiles.P75_EXCLUSIVE_TIME, label: 'P75 Self Time'},
{sort: SpanSortPercentiles.P95_EXCLUSIVE_TIME, label: 'P95 Self Time'},
{sort: SpanSortPercentiles.P99_EXCLUSIVE_TIME, label: 'P99 Self Time'},
].forEach(({sort, label}) => {
it('renders the right percentile header', async function () {
const initialData = initializeData({query: {sort}});
render(, {
router: initialData.router,
organization: initialData.organization,
});
const grid = await screen.findByTestId('grid-editable');
expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
expect(await within(grid).findByText(label)).toBeInTheDocument();
expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
});
});
it('renders the right avg occurrence header', async function () {
const initialData = initializeData({query: {sort: SpanSortOthers.AVG_OCCURRENCE}});
render(, {
router: initialData.router,
organization: initialData.organization,
});
const grid = await screen.findByTestId('grid-editable');
expect(await within(grid).findByText('Span Operation')).toBeInTheDocument();
expect(await within(grid).findByText('Span Name')).toBeInTheDocument();
expect(await within(grid).findByText('Average Occurrences')).toBeInTheDocument();
expect(await within(grid).findByText('Frequency')).toBeInTheDocument();
expect(await within(grid).findByText('P75 Self Time')).toBeInTheDocument();
expect(await within(grid).findByText('Total Self Time')).toBeInTheDocument();
});
});
describe('Spans Tab V2', function () {
it('does not propagate transaction search query and properly tokenizes span query', async function () {
const initialData = initializeData({
query: {query: 'http.method:POST', spansQuery: 'span.op:db span.action:SELECT'},
additionalFeatures: [
'performance-view',
'performance-spans-new-ui',
'insights-initial-modules',
],
});
render(, {
router: initialData.router,
organization: initialData.organization,
});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
const searchTokens = await screen.findAllByTestId('filter-token');
expect(searchTokens).toHaveLength(2);
expect(searchTokens[0]).toHaveTextContent('span.op:db');
expect(searchTokens[1]).toHaveTextContent('span.action:SELECT');
expect(await screen.findByTestId('smart-search-bar')).not.toHaveTextContent(
'http.method:POST'
);
});
});
});