import {OrganizationFixture} from 'sentry-fixture/organization';
import {render, screen, waitForElementToBeRemoved} from 'sentry-test/reactTestingLibrary';
import {useLocation} from 'sentry/utils/useLocation';
import usePageFilters from 'sentry/utils/usePageFilters';
import {MessageSpanSamplesPanel} from 'sentry/views/performance/queues/destinationSummary/messageSpanSamplesPanel';
jest.mock('sentry/utils/useLocation');
jest.mock('sentry/utils/usePageFilters');
describe('messageSpanSamplesPanel', () => {
const organization = OrganizationFixture();
let eventsRequestMock, eventsStatsRequestMock, samplesRequestMock, spanFieldTagsMock;
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: {transaction: 'sentry.tasks.store.save_event', destination: 'event-queue'},
hash: '',
state: undefined,
action: 'PUSH',
key: '',
});
beforeEach(() => {
eventsStatsRequestMock = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events-stats/`,
method: 'GET',
body: {
data: [[1699907700, [{count: 7810}]]],
meta: {},
},
});
eventsRequestMock = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/events/`,
method: 'GET',
body: {
data: [
{
'sum(span.duration)': 10.0,
'trace_status_rate(ok)': 0.8,
'count_op(queue.publish)': 222,
'count_op(queue.process)': 333,
'avg_if(span.duration,span.op,queue.publish)': 3.0,
'avg_if(span.duration,span.op,queue.process)': 4.0,
'count()': 555,
'avg(messaging.message.receive.latency)': 2.0,
'avg(span.duration)': 3.5,
},
],
meta: {
fields: {
'sum(span.duration)': 'duration',
'trace_status_rate(ok)': 'percentage',
'count_op(queue.publish)': 'integer',
'count_op(queue.process)': 'integer',
'avg_if(span.duration,span.op,queue.publish)': 'duration',
'avg_if(span.duration,span.op,queue.process)': 'duration',
'count()': 'integer',
'avg(messaging.message.receive.latency)': 'number',
'avg(span.duration)': 'duration',
},
units: {
'sum(span.duration)': 'millisecond',
'trace_status_rate(ok)': null,
'count_op(queue.publish)': null,
'count_op(queue.process)': null,
'avg_if(span.duration,span.op,queue.publish)': 'millisecond',
'avg_if(span.duration,span.op,queue.process)': 'millisecond',
'count()': null,
'avg(messaging.message.receive.latency)': null,
'avg(span.duration)': 'millisecond',
},
},
},
});
samplesRequestMock = MockApiClient.addMockResponse({
url: `/api/0/organizations/${organization.slug}/spans-samples/`,
method: 'GET',
body: {
data: [
{
span_id: '123',
trace: 'abc',
project: 'project',
timestamp: '2024-03-25T20:31:36+00:00',
'span.duration': 320.300102,
},
],
},
});
spanFieldTagsMock = MockApiClient.addMockResponse({
url: `/organizations/${organization.slug}/spans/fields/`,
method: 'GET',
body: [
{
key: 'api_key',
name: 'Api Key',
},
{
key: 'bytes.size',
name: 'Bytes.Size',
},
],
});
});
afterAll(() => {
jest.resetAllMocks();
});
it('renders consumer panel', async () => {
jest.mocked(useLocation).mockReturnValue({
pathname: '',
search: '',
query: {
transaction: 'sentry.tasks.store.save_event',
destination: 'event-queue',
'span.op': 'queue.process',
},
hash: '',
state: undefined,
action: 'PUSH',
key: '',
});
render(, {organization});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
expect(eventsStatsRequestMock).toHaveBeenCalled();
expect(eventsRequestMock).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events/`,
expect.objectContaining({
method: 'GET',
query: expect.objectContaining({
dataset: 'spansMetrics',
environment: [],
field: [
'count()',
'count_op(queue.publish)',
'count_op(queue.process)',
'sum(span.duration)',
'avg(span.duration)',
'avg_if(span.duration,span.op,queue.publish)',
'avg_if(span.duration,span.op,queue.process)',
'avg(messaging.message.receive.latency)',
'trace_status_rate(ok)',
'time_spent_percentage(app,span.duration)',
],
per_page: 10,
project: [],
query:
'span.op:[queue.process,queue.publish] messaging.destination.name:event-queue transaction:sentry.tasks.store.save_event',
statsPeriod: '10d',
}),
})
);
expect(samplesRequestMock).toHaveBeenCalledWith(
`/api/0/organizations/${organization.slug}/spans-samples/`,
expect.objectContaining({
query: expect.objectContaining({
additionalFields: [
'trace',
'transaction.id',
'span.description',
'measurements.messaging.message.body.size',
'measurements.messaging.message.receive.latency',
'measurements.messaging.message.retry.count',
'messaging.message.id',
'trace.status',
'span.duration',
],
firstBound: 2666.6666666666665,
lowerBound: 0,
project: [],
query:
'span.op:queue.process transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
referrer: undefined,
secondBound: 5333.333333333333,
statsPeriod: '10d',
upperBound: 8000,
}),
})
);
expect(spanFieldTagsMock).toHaveBeenNthCalledWith(
1,
`/organizations/${organization.slug}/spans/fields/`,
expect.objectContaining({
method: 'GET',
query: {
project: [],
environment: [],
statsPeriod: '1h',
},
})
);
expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
expect(screen.getByText('Consumer')).toBeInTheDocument();
// Metrics Ribbon
expect(screen.getByText('Processed')).toBeInTheDocument();
expect(screen.getByText('Error Rate')).toBeInTheDocument();
expect(screen.getByText('Avg Time In Queue')).toBeInTheDocument();
expect(screen.getByText('Avg Processing Time')).toBeInTheDocument();
expect(screen.getByText('333')).toBeInTheDocument();
expect(screen.getByText('20%')).toBeInTheDocument();
expect(screen.getByText('2.00ms')).toBeInTheDocument();
expect(screen.getByText('4.00ms')).toBeInTheDocument();
});
it('renders producer panel', async () => {
jest.mocked(useLocation).mockReturnValue({
pathname: '',
search: '',
query: {
transaction: 'sentry.tasks.store.save_event',
destination: 'event-queue',
'span.op': 'queue.publish',
},
hash: '',
state: undefined,
action: 'PUSH',
key: '',
});
render(, {organization});
await waitForElementToBeRemoved(() => screen.queryAllByTestId('loading-indicator'));
expect(eventsStatsRequestMock).toHaveBeenCalled();
expect(eventsRequestMock).toHaveBeenCalledWith(
`/organizations/${organization.slug}/events/`,
expect.objectContaining({
method: 'GET',
query: expect.objectContaining({
dataset: 'spansMetrics',
environment: [],
field: [
'count()',
'count_op(queue.publish)',
'count_op(queue.process)',
'sum(span.duration)',
'avg(span.duration)',
'avg_if(span.duration,span.op,queue.publish)',
'avg_if(span.duration,span.op,queue.process)',
'avg(messaging.message.receive.latency)',
'trace_status_rate(ok)',
'time_spent_percentage(app,span.duration)',
],
per_page: 10,
project: [],
query:
'span.op:[queue.process,queue.publish] messaging.destination.name:event-queue transaction:sentry.tasks.store.save_event',
statsPeriod: '10d',
}),
})
);
expect(samplesRequestMock).toHaveBeenCalledWith(
`/api/0/organizations/${organization.slug}/spans-samples/`,
expect.objectContaining({
query: expect.objectContaining({
additionalFields: [
'trace',
'transaction.id',
'span.description',
'measurements.messaging.message.body.size',
'measurements.messaging.message.receive.latency',
'measurements.messaging.message.retry.count',
'messaging.message.id',
'trace.status',
'span.duration',
],
firstBound: 2666.6666666666665,
lowerBound: 0,
project: [],
query:
'span.op:queue.publish transaction:sentry.tasks.store.save_event messaging.destination.name:event-queue',
referrer: undefined,
secondBound: 5333.333333333333,
statsPeriod: '10d',
upperBound: 8000,
}),
})
);
expect(spanFieldTagsMock).toHaveBeenCalledWith(
`/organizations/${organization.slug}/spans/fields/`,
expect.objectContaining({
method: 'GET',
query: {
project: [],
environment: [],
statsPeriod: '1h',
},
})
);
expect(screen.getByRole('table', {name: 'Span Samples'})).toBeInTheDocument();
expect(screen.getByText('Producer')).toBeInTheDocument();
// Metrics Ribbon
expect(screen.getByText('Published')).toBeInTheDocument();
expect(screen.getByText('Error Rate')).toBeInTheDocument();
expect(screen.getByText('222')).toBeInTheDocument();
expect(screen.getByText('20%')).toBeInTheDocument();
});
});