import {OrganizationFixture} from 'sentry-fixture/organization';
import {ProjectFixture} from 'sentry-fixture/project';
import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
import type {Organization} from 'sentry/types/organization';
import {useLocation} from 'sentry/utils/useLocation';
import usePageFilters from 'sentry/utils/usePageFilters';
import useProjects from 'sentry/utils/useProjects';
import {CacheLandingPage} from 'sentry/views/performance/cache/cacheLandingPage';
jest.mock('sentry/utils/useLocation');
jest.mock('sentry/utils/usePageFilters');
jest.mock('sentry/utils/useProjects');
const requestMocks = {
onboardingDataCheck: jest.fn(),
missRateChart: jest.fn(),
cacheSamplesMissRateChart: jest.fn(),
throughputChart: jest.fn(),
spanTransactionList: jest.fn(),
transactionDurations: jest.fn(),
spanFields: jest.fn(),
};
describe('CacheLandingPage', function () {
const organization = OrganizationFixture();
jest.mocked(usePageFilters).mockReturnValue({
isReady: true,
desyncedFilters: new Set(),
pinnedFilters: new Set(),
shouldPersist: true,
selection: {
datetime: {
period: '10d',
start: null,
end: null,
utc: false,
},
environments: [],
projects: [],
},
});
jest.mocked(useLocation).mockReturnValue({
pathname: '',
search: '',
query: {statsPeriod: '10d', project: '1'},
hash: '',
state: undefined,
action: 'PUSH',
key: '',
});
jest.mocked(useProjects).mockReturnValue({
projects: [
ProjectFixture({
id: '1',
name: 'Backend',
slug: 'backend',
firstTransactionEvent: true,
platform: 'javascript',
}),
],
onSearch: jest.fn(),
placeholders: [],
fetching: false,
hasMore: null,
fetchError: null,
initiallyLoaded: false,
});
beforeEach(function () {
jest.clearAllMocks();
setRequestMocks(organization);
});
afterAll(function () {
jest.resetAllMocks();
});
it('fetches module data', async function () {
render(, {organization});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
expect(requestMocks.missRateChart).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events-stats/`,
expect.objectContaining({
method: 'GET',
query: {
cursor: undefined,
dataset: 'spansMetrics',
environment: [],
excludeOther: 0,
field: [],
interval: '30m',
orderby: undefined,
partial: 1,
per_page: 50,
project: [],
query: 'span.op:[cache.get_item,cache.get] project.id:1',
referrer: 'api.performance.cache.samples-cache-hit-miss-chart',
statsPeriod: '10d',
topEvents: undefined,
yAxis: 'cache_miss_rate()',
},
})
);
expect(requestMocks.throughputChart).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events-stats/`,
expect.objectContaining({
method: 'GET',
query: {
cursor: undefined,
dataset: 'spansMetrics',
environment: [],
excludeOther: 0,
field: [],
interval: '30m',
orderby: undefined,
partial: 1,
per_page: 50,
project: [],
query: 'span.op:[cache.get_item,cache.get]',
referrer: 'api.performance.cache.landing-cache-throughput-chart',
statsPeriod: '10d',
topEvents: undefined,
yAxis: 'spm()',
},
})
);
expect(requestMocks.spanTransactionList).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events/`,
expect.objectContaining({
method: 'GET',
query: {
dataset: 'spansMetrics',
environment: [],
field: [
'project',
'project.id',
'transaction',
'spm()',
'cache_miss_rate()',
'sum(span.self_time)',
'time_spent_percentage()',
'avg(cache.item_size)',
],
per_page: 20,
project: [],
query: 'span.op:[cache.get_item,cache.get]',
referrer: 'api.performance.cache.landing-cache-transaction-list',
sort: '-time_spent_percentage()',
statsPeriod: '10d',
},
})
);
expect(requestMocks.transactionDurations).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events/`,
expect.objectContaining({
method: 'GET',
query: {
dataset: 'metrics',
environment: [],
field: ['avg(transaction.duration)', 'transaction'],
per_page: 50,
project: [],
query: 'transaction:["my-transaction"]',
referrer: 'api.performance.cache.landing-cache-transaction-duration',
statsPeriod: '10d',
},
})
);
});
it('renders a list of transactions', async function () {
render(, {organization});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
expect(screen.getByRole('columnheader', {name: 'Transaction'})).toBeInTheDocument();
expect(screen.getByRole('cell', {name: 'my-transaction'})).toBeInTheDocument();
expect(screen.getByRole('link', {name: 'my-transaction'})).toHaveAttribute(
'href',
'/organizations/org-slug/insights/caches/?project=123&statsPeriod=10d&transaction=my-transaction'
);
expect(screen.getByRole('columnheader', {name: 'Project'})).toBeInTheDocument();
expect(screen.getByRole('cell', {name: 'backend'})).toBeInTheDocument();
expect(screen.getByRole('link', {name: 'backend'})).toHaveAttribute(
'href',
'/organizations/org-slug/projects/backend/?project=1'
);
expect(
screen.getByRole('columnheader', {name: 'Avg Value Size'})
).toBeInTheDocument();
expect(screen.getByRole('cell', {name: '123.0 B'})).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Requests Per Minute'})
).toBeInTheDocument();
expect(screen.getByRole('cell', {name: '123/s'})).toBeInTheDocument();
expect(
screen.getByRole('columnheader', {name: 'Avg Transaction Duration'})
).toBeInTheDocument();
const avgTxnCell = screen
.getAllByRole('cell')
.find(cell => cell?.textContent?.includes('456.00ms'));
expect(avgTxnCell).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Miss Rate'})).toBeInTheDocument();
expect(screen.getByRole('cell', {name: '12.3%'})).toBeInTheDocument();
expect(screen.getByRole('columnheader', {name: 'Time Spent'})).toBeInTheDocument();
const timeSpentCell = screen
.getAllByRole('cell')
.find(cell => cell?.textContent?.includes('123.00ms'));
expect(timeSpentCell).toBeInTheDocument();
});
it('shows module onboarding', async function () {
requestMocks.onboardingDataCheck = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-onboarding',
}),
],
body: {
data: [{'count()': 0}],
meta: {fields: {'count()': 'integer'}},
},
});
render(, {organization});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
expect(
screen.getByText('Start collecting Insights about your Caches!')
).toBeInTheDocument();
});
});
const setRequestMocks = (organization: Organization) => {
MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/projects/`,
body: [ProjectFixture({name: 'backend'})],
});
requestMocks.onboardingDataCheck = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-onboarding',
}),
],
body: {
data: [{'count()': 43374}],
meta: {
fields: {'count()': 'integer'},
},
},
});
requestMocks.missRateChart = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events-stats/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-hit-miss-chart',
}),
],
body: {
data: [
[1716379200, [{count: 0.5}]],
[1716393600, [{count: 0.75}]],
],
meta: {
fields: {
time: 'date',
cache_miss_rate: 'percentage',
},
},
},
});
requestMocks.missRateChart = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events-stats/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.samples-cache-hit-miss-chart',
}),
],
body: {
data: [
[1716379200, [{count: 0.5}]],
[1716393600, [{count: 0.75}]],
],
meta: {
fields: {
time: 'date',
cache_miss_rate: 'percentage',
},
},
},
});
requestMocks.throughputChart = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events-stats/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-throughput-chart',
}),
],
body: {
data: [
[1716379200, [{count: 100}]],
[1716393600, [{count: 200}]],
],
meta: {
fields: {
time: 'date',
spm_14400: 'rate',
},
},
},
});
requestMocks.spanTransactionList = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-transaction-list',
}),
],
body: {
data: [
{
transaction: 'my-transaction',
project: 'backend',
'project.id': 123,
'avg(cache.item_size)': 123,
'spm()': 123,
'sum(span.self_time)': 123,
'cache_miss_rate()': 0.123,
'time_spent_percentage()': 0.123,
},
],
meta: {
fields: {
transaction: 'string',
project: 'string',
'project.id': 'integer',
'avg(cache.item_size)': 'number',
'spm()': 'rate',
'sum(span.self_time)': 'duration',
'cache_miss_rate()': 'percentage',
'time_spent_percentage()': 'percentage',
},
units: {},
},
},
});
requestMocks.transactionDurations = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events/`,
method: 'GET',
match: [
MockApiClient.matchQuery({
referrer: 'api.performance.cache.landing-cache-transaction-duration',
}),
],
body: {
data: [
{
transaction: 'my-transaction',
'avg(transaction.duration)': 456,
},
],
meta: {
fields: {
transaction: 'string',
'avg(transaction.duration)': 'duration',
},
units: {},
},
},
});
requestMocks.spanFields = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/spans/fields/`,
method: 'GET',
body: [],
});
};