Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import {t} from 'sentry/locale';
import {DisplayType, WidgetType, type Widget} from 'sentry/views/dashboards/types';
import {SpanFields} from 'sentry/views/insights/types';

export const QUEUE_CHARTS: Widget[] = [
{
id: 'average-duration-widget',
title: t('Average Duration'),
displayType: DisplayType.AREA,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
aggregates: [
`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
Comment on lines +6 to +16
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The new AREA and LINE widgets in queueCharts.ts are missing the fields property. Other similar widgets in the codebase include fields that mirror aggregates.
Severity: MEDIUM

Suggested Fix

Add a fields property to both the average-duration-widget and throughput-widget in queueCharts.ts. The value of the fields array should be identical to the aggregates array to maintain consistency with other prebuilt chart widgets.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: static/app/views/dashboards/utils/prebuiltConfigs/queues/queueCharts.ts#L6-L16

Potential issue: The `average-duration-widget` and `throughput-widget` in
`queueCharts.ts` are defined with an `aggregates` property but lack a corresponding
`fields` property. While the `WidgetQuery` type defines `fields` as optional, a
consistent pattern across all other prebuilt AREA and LINE chart widgets in the codebase
is to include a `fields` array that mirrors the `aggregates` array. This inconsistency
could lead to runtime errors or incorrect rendering if the dashboard rendering logic
implicitly relies on the `fields` array, as the established pattern suggests it might.

`avg(${SpanFields.SPAN_DURATION})`,
],
columns: [],
conditions: `${SpanFields.SPAN_OP}:queue.process`,
orderby: `avg(${SpanFields.SPAN_DURATION})`,
},
],
},
{
id: 'throughput-widget',
title: t('Published vs Processed'),
displayType: DisplayType.LINE,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
aggregates: ['epm()'],
columns: [SpanFields.SPAN_OP],
conditions: `${SpanFields.SPAN_OP}:[queue.publish, queue.process]`,
orderby: 'epm()',
},
],
},
];
Original file line number Diff line number Diff line change
@@ -1,10 +1,198 @@
import {t} from 'sentry/locale';
import {DisplayType, WidgetType, type Widget} from 'sentry/views/dashboards/types';
import type {PrebuiltDashboard} from 'sentry/views/dashboards/utils/prebuiltConfigs';
import {QUEUE_CHARTS} from 'sentry/views/dashboards/utils/prebuiltConfigs/queues/queueCharts';
import {SUMMARY_DASHBOARD_TITLE} from 'sentry/views/dashboards/utils/prebuiltConfigs/queues/settings';
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields} from 'sentry/views/insights/types';

const SPAN_OP_FILTER = `${SpanFields.SPAN_OP}:[queue.process,queue.publish]`;

const FIRST_ROW_WIDGTS = spaceWidgetsEquallyOnRow(
[
{
id: 'avg-time-in-queue-widget',
title: t('Avg Time in Queue'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`],
aggregates: [`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`],
columns: [],
conditions: SPAN_OP_FILTER,
orderby: `avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
},
],
},
{
id: 'avg-processing-time-widget',
title: t('Avg Processing Time'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [
`equation|avg_if(${SpanFields.SPAN_DURATION},${SpanFields.SPAN_OP},equals,queue.process)`,
],
aggregates: [
`equation|avg_if(${SpanFields.SPAN_DURATION},${SpanFields.SPAN_OP},equals,queue.process)`,
],
columns: [],
conditions: SPAN_OP_FILTER,
orderby: `equation|avg_if(${SpanFields.SPAN_DURATION},${SpanFields.SPAN_OP},equals,queue.process)`,
},
],
},
{
id: 'error-rate-widget',
title: t('Error Rate'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [
`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
],
aggregates: [
`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
],
columns: [],
conditions: SPAN_OP_FILTER,
fieldMeta: [{valueType: 'percentage', valueUnit: null}],
orderby: `equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
},
],
},
{
id: 'published-widget',
title: t('Published'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`],
aggregates: [`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`],
columns: [],
conditions: SPAN_OP_FILTER,
orderby: `count_if(${SpanFields.SPAN_OP},equals,queue.publish)`,
},
],
},
{
id: 'processed-widget',
title: t('Processed'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [`count_if(${SpanFields.SPAN_OP},equals,queue.process)`],
aggregates: [`count_if(${SpanFields.SPAN_OP},equals,queue.process)`],
columns: [],
conditions: SPAN_OP_FILTER,
orderby: `count_if(${SpanFields.SPAN_OP},equals,queue.process)`,
},
],
},
{
id: 'time-spent-widget',
title: t('Time Spent'),
displayType: DisplayType.BIG_NUMBER,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [`sum(${SpanFields.SPAN_DURATION})`],
aggregates: [`sum(${SpanFields.SPAN_DURATION})`],
columns: [],
conditions: SPAN_OP_FILTER,
orderby: `sum(${SpanFields.SPAN_DURATION})`,
},
],
},
],
0,
{h: 1, minH: 1}
);

const SECOND_ROW_WIDGETS = spaceWidgetsEquallyOnRow([...QUEUE_CHARTS], 1);

const TRANSACTIONS_TABLE: Widget = {
id: 'transactions-table',
title: t('Transactions Interacting with Destination'),
displayType: DisplayType.TABLE,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [
SpanFields.TRANSACTION,
SpanFields.SPAN_OP,
`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
`avg_if(${SpanFields.SPAN_DURATION},${SpanFields.SPAN_OP},equals,queue.process)`,
`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`,
`count_if(${SpanFields.SPAN_OP},equals,queue.process)`,
`sum(${SpanFields.SPAN_DURATION})`,
],
aggregates: [
`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
`avg_if(${SpanFields.SPAN_DURATION},${SpanFields.SPAN_OP},equals,queue.process)`,
`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`,
`count_if(${SpanFields.SPAN_OP},equals,queue.process)`,
`sum(${SpanFields.SPAN_DURATION})`,
],
columns: [SpanFields.TRANSACTION, SpanFields.SPAN_OP],
fieldAliases: [
t('Transaction'),
t('Type'),
t('Avg time in queue'),
t('Avg processing time'),
t('Error rate'),
t('Published'),
t('Processed'),
t('Time spent'),
],
fieldMeta: [
null,
null,
null,
null,
{valueType: 'percentage', valueUnit: null},
null,
null,
null,
],
conditions: SPAN_OP_FILTER,
orderby: `-sum(${SpanFields.SPAN_DURATION})`,
},
],
layout: {
x: 0,
y: 4,
w: 6,
h: 6,
minH: 6,
},
};

export const QUEUE_SUMMARY_PREBUILT_CONFIG: PrebuiltDashboard = {
dateCreated: '',
projects: [],
title: SUMMARY_DASHBOARD_TITLE,
filters: {},
widgets: [],
widgets: [...FIRST_ROW_WIDGTS, ...SECOND_ROW_WIDGETS, TRANSACTIONS_TABLE],
};
Original file line number Diff line number Diff line change
@@ -1,10 +1,95 @@
import {t} from 'sentry/locale';
import {FieldKind} from 'sentry/utils/fields';
import {DisplayType, WidgetType, type Widget} from 'sentry/views/dashboards/types';
import type {PrebuiltDashboard} from 'sentry/views/dashboards/utils/prebuiltConfigs';
import {QUEUE_CHARTS} from 'sentry/views/dashboards/utils/prebuiltConfigs/queues/queueCharts';
import {DASHBOARD_TITLE} from 'sentry/views/dashboards/utils/prebuiltConfigs/queues/settings';
import {spaceWidgetsEquallyOnRow} from 'sentry/views/dashboards/utils/prebuiltConfigs/utils/spaceWidgetsEquallyOnRow';
import {SpanFields} from 'sentry/views/insights/types';

const FIRST_ROW_WIDGETS = spaceWidgetsEquallyOnRow([...QUEUE_CHARTS], 0);

const DESTINATION_TABLE: Widget = {
id: 'destination-table',
title: t('Destinations'),
displayType: DisplayType.TABLE,
widgetType: WidgetType.SPANS,
interval: '5m',
queries: [
{
name: '',
fields: [
SpanFields.MESSAGING_MESSAGE_DESTINATION_NAME,
`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
`avg(${SpanFields.SPAN_DURATION})`,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Destination table uses wrong aggregate for processing time

High Severity

The DESTINATION_TABLE uses avg(span.duration) for the column labeled "Avg processing time," but since the query condition includes both queue.publish and queue.process spans, this averages across both operation types. The original non-platformized table and the summary page's TRANSACTIONS_TABLE both correctly use avg_if(span.duration,span.op,equals,queue.process) to restrict the average to only processing spans. This will display incorrect, inflated or deflated values for "Avg processing time" in the destination table.

Additional Locations (1)

Fix in Cursor Fix in Web

`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`,
`count_if(${SpanFields.SPAN_OP},equals,queue.process)`,
`sum(${SpanFields.SPAN_DURATION})`,
],
columns: [SpanFields.MESSAGING_MESSAGE_DESTINATION_NAME],
aggregates: [
`avg(${SpanFields.MESSAGING_MESSAGE_RECEIVE_LATENCY})`,
`avg(${SpanFields.SPAN_DURATION})`,
`equation|1 - (count_if(${SpanFields.TRACE_STATUS},equals,ok) / count(${SpanFields.SPAN_DURATION}))`,
`count_if(${SpanFields.SPAN_OP},equals,queue.publish)`,
`count_if(${SpanFields.SPAN_OP},equals,queue.process)`,
`sum(${SpanFields.SPAN_DURATION})`,
],
fieldAliases: [
t('Destination'),
t('Avg time in queue'),
t('Avg processing time'),
t('Error rate'),
t('Published'),
t('Processed'),
t('Time spent'),
],
fieldMeta: [
null,
null,
null,
{valueType: 'percentage', valueUnit: null},
null,
null,
null,
],
linkedDashboards: [
{
dashboardId: '-1',
field: SpanFields.MESSAGING_MESSAGE_DESTINATION_NAME,
staticDashboardId: 27,
},
],
conditions: `${SpanFields.SPAN_OP}:[queue.publish, queue.process]`,
orderby: `-sum(${SpanFields.SPAN_DURATION})`,
},
],
layout: {
x: 0,
y: 3,
w: 6,
h: 6,
minH: 6,
},
};

export const QUEUES_PREBUILT_CONFIG: PrebuiltDashboard = {
dateCreated: '',
projects: [],
title: DASHBOARD_TITLE,
filters: {},
widgets: [],
filters: {
globalFilter: [
{
dataset: WidgetType.SPANS,
tag: {
key: SpanFields.MESSAGING_MESSAGE_DESTINATION_NAME,
name: SpanFields.MESSAGING_MESSAGE_DESTINATION_NAME,
kind: FieldKind.TAG,
},
value: '',
},
],
},
widgets: [...FIRST_ROW_WIDGETS, DESTINATION_TABLE],
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import useOrganization from 'sentry/utils/useOrganization';

export function useHasPlatformizedQueues() {
const organization = useOrganization();

return organization.features.includes('insights-queue-dashboard-migration');
}
20 changes: 20 additions & 0 deletions static/app/views/insights/queues/views/platformizedOverview.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {DataCategory} from 'sentry/types/core';
import {useMaxPickableDays} from 'sentry/utils/useMaxPickableDays';
import {PrebuiltDashboardRenderer} from 'sentry/views/dashboards/prebuiltDashboardRenderer';
import {PrebuiltDashboardId} from 'sentry/views/dashboards/utils/prebuiltConfigs';
import {ModulePageProviders} from 'sentry/views/insights/common/components/modulePageProviders';

export function PlatformizedQueuesOverview() {
const maxPickableDays = useMaxPickableDays({
dataCategories: [DataCategory.SPANS],
});

return (
<ModulePageProviders
moduleName="queue"
maxPickableDays={maxPickableDays.maxPickableDays}
>
<PrebuiltDashboardRenderer prebuiltId={PrebuiltDashboardId.BACKEND_QUEUES} />
</ModulePageProviders>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing analytics event in platformized queues overview

Medium Severity

PlatformizedQueuesOverview does not pass analyticEventName to ModulePageProviders, so the "insight.page_loads.queue" analytics event won't fire when the migration flag is enabled. The non-platformized path passes analyticEventName="insight.page_loads.queue", and the analogous PlatformizedAssetsOverview correctly includes analyticEventName="insight.page_loads.assets". This silently drops page-load analytics tracking for all users with the flag on.

Additional Locations (1)

Fix in Cursor Fix in Web

);
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ import {
isAValidSort,
QueuesTable,
} from 'sentry/views/insights/queues/components/tables/queuesTable';
import {useHasPlatformizedQueues} from 'sentry/views/insights/queues/utils/useHasPlatformizedQueues';
import {PlatformizedQueuesOverview} from 'sentry/views/insights/queues/views/platformizedOverview';
import {ModuleName} from 'sentry/views/insights/types';

const DEFAULT_SORT = {
Expand Down Expand Up @@ -107,10 +109,15 @@ function QueuesLandingPage() {
}

function PageWithProviders() {
const hasPlatformizedQueues = useHasPlatformizedQueues();
const maxPickableDays = useMaxPickableDays({
dataCategories: [DataCategory.SPANS],
});

if (hasPlatformizedQueues) {
return <PlatformizedQueuesOverview />;
}

return (
<ModulePageProviders
moduleName="queue"
Expand Down
Loading