Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
18 commits
Select commit Hold shift + click to select a range
d399f8b
perf(metrics): Stop re-rendering unchanged sample rows on URL changes
JoshuaKGoldberg Oct 8, 2026
d1e0954
ref(metrics): Read location in a trace link cell instead of the sampl…
JoshuaKGoldberg Oct 13, 2026
f412c68
perf(explore): Keep field cell actions from re-rendering on URL changes
JoshuaKGoldberg Oct 13, 2026
bb90f54
perf(navigation): Stop re-rendering buttons and starred queries on UR…
JoshuaKGoldberg Oct 13, 2026
5117c01
revert: Keep field cell actions from re-rendering on URL changes
JoshuaKGoldberg Oct 13, 2026
261cdfa
revert: Drop the memoized starred queries list
JoshuaKGoldberg Oct 13, 2026
7f89782
perf(metrics): Keep unchanged metric panels stable when another panel…
JoshuaKGoldberg Oct 13, 2026
b14862b
perf(explore): Keep field cell actions from re-rendering on URL changes
JoshuaKGoldberg Oct 13, 2026
f8c88bf
Merge remote-tracking branch 'origin/master' into joshgoldberg/logs-1…
JoshuaKGoldberg Oct 13, 2026
59f5d00
lil comment removals
JoshuaKGoldberg Oct 13, 2026
0ba1559
fix(metrics): Give row mutators the latest queries in descendant layo…
JoshuaKGoldberg Oct 14, 2026
bc81230
fix(metrics): Stop sharing decoded queries between identical metric p…
JoshuaKGoldberg Oct 14, 2026
55ecb2c
ref(metrics): Remove new explanatory comments
JoshuaKGoldberg Oct 14, 2026
7bcd07d
ref(analytics): Key button tracking on the route path instead of a la…
JoshuaKGoldberg Oct 21, 2026
9526755
revert(metrics): Move metric panel stability to a follow-up
JoshuaKGoldberg Oct 21, 2026
736a69e
Merge remote-tracking branch 'origin/master' into joshgoldberg/logs-1…
JoshuaKGoldberg Oct 21, 2026
8b1b033
perf(metrics): Keep the samples table meta stable when the query is d…
JoshuaKGoldberg Oct 21, 2026
59d7274
perf(metrics): keep unchanged metric panels stable when another panel…
JoshuaKGoldberg Oct 22, 2026
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
34 changes: 18 additions & 16 deletions static/app/scrapsProviders/tracking.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,23 +5,25 @@ import {

import {getOverride} from 'sentry/overrideRegistry';

function logButtonClick(props: TrackingProps) {
const hasAnalyticsDebug = window.localStorage?.getItem('DEBUG_ANALYTICS') === '1';
const hasCustomAnalytics =
props.analyticsEventName || props.analyticsEventKey || props.analyticsParams;
if (hasCustomAnalytics && hasAnalyticsDebug) {
// eslint-disable-next-line no-console
console.log('buttonAnalyticsEvent', {
clickType: props.clickType,
eventKey: props.analyticsEventKey,
eventName: props.analyticsEventName,
variant: props.variant,
href: props.href,
...props.analyticsParams,
});
}
}

function useDefaultButtonTracking() {
return (props: TrackingProps) => {
const hasAnalyticsDebug = window.localStorage?.getItem('DEBUG_ANALYTICS') === '1';
const hasCustomAnalytics =
props.analyticsEventName || props.analyticsEventKey || props.analyticsParams;
if (hasCustomAnalytics && hasAnalyticsDebug) {
// eslint-disable-next-line no-console
console.log('buttonAnalyticsEvent', {
clickType: props.clickType,
eventKey: props.analyticsEventKey,
eventName: props.analyticsEventName,
variant: props.variant,
href: props.href,
...props.analyticsParams,
});
}
};
return logButtonClick;
}

export function SentryTrackingProvider({children}: {children: React.ReactNode}) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import {useCallback, useMemo, useState} from 'react';
import type {DragEndEvent} from '@dnd-kit/core';
import {KeyboardSensor, PointerSensor, useSensor, useSensors} from '@dnd-kit/core';
import {arrayMove, sortableKeyboardCoordinates} from '@dnd-kit/sortable';
import {sortableKeyboardCoordinates} from '@dnd-kit/sortable';

import type {MetricQuery} from 'sentry/views/explore/metrics/metricQuery';
import {
Expand Down Expand Up @@ -57,14 +57,10 @@ export function useSortableMetricQueries({
return;
}

reorderMetricQueries(
arrayMove([...metricQueries], oldIndex, newIndex),
oldIndex,
newIndex
);
reorderMetricQueries(oldIndex, newIndex);
}
},
[sortableItems, metricQueries, reorderMetricQueries]
[sortableItems, reorderMetricQueries]
);

const onDragCancel = useCallback(() => {
Expand Down
33 changes: 26 additions & 7 deletions static/app/views/explore/metrics/hooks/useStableLabels.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,23 +77,35 @@ function parseEquationIndex(label: string): number {
*/
export function useStableLabels(queries: BaseMetricQuery[]) {
const labelsRef = useRef<string[]>([]);
const rowIdsRef = useRef<string[]>([]);

// oxlint-disable-next-line react/refs
if (labelsRef.current.length !== queries.length) {
// oxlint-disable-next-line react/refs
labelsRef.current = assignSequentialLabels(queries);
// oxlint-disable-next-line react/refs
rowIdsRef.current = createRowIds(queries.length);
}

return useMemo(
() => ({
getLabel(i: number): string {
return labelsRef.current[i] ?? getFunctionLabel(i);
},
/**
* Unique per row and moved along with its label, unlike labels, which
* can repeat after an external navigation that keeps the row count.
*/
getRowIds(): readonly string[] {
return rowIdsRef.current;
},
insert(position: number, label: string) {
labelsRef.current = labelsRef.current.toSpliced(position, 0, label);
rowIdsRef.current = rowIdsRef.current.toSpliced(position, 0, ...createRowIds(1));
},
remove(position: number) {
labelsRef.current = labelsRef.current.filter((_, j) => j !== position);
rowIdsRef.current = rowIdsRef.current.filter((_, j) => j !== position);
},
move(from: number, to: number) {
if (
Expand All @@ -106,15 +118,22 @@ export function useStableLabels(queries: BaseMetricQuery[]) {
return;
}

const next = [...labelsRef.current];
const [label] = next.splice(from, 1);
if (!label) {
return;
}
next.splice(to, 0, label);
labelsRef.current = next;
labelsRef.current = moveItem(labelsRef.current, from, to);
rowIdsRef.current = moveItem(rowIdsRef.current, from, to);
},
}),
[]
);
}

export function moveItem<T>(items: readonly T[], from: number, to: number): T[] {
const next = [...items];
next.splice(to, 0, ...next.splice(from, 1));
return next;
}

let nextRowId = 0;

function createRowIds(count: number): string[] {
return Array.from({length: count}, () => String(nextRowId++));
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import {GenericWidgetEmptyStateWarning} from 'sentry/views/performance/landing/w

const RESULT_LIMIT = 50;
const EMBEDDED_RESULT_LIMIT = 100;
const EMPTY_META: EventsMetaType = {fields: {}, units: {}};

interface MetricsSamplesTableProps {
isMetricOptionsEmpty?: boolean;
Expand All @@ -59,7 +60,7 @@ export function MetricsSamplesTable({

const {
result: {data},
meta = {fields: {}, units: {}},
meta = EMPTY_META,
error,
isFetching,
} = useMetricSamplesTable({
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import {Fragment, useRef, useState, type ReactNode, type RefObject} from 'react';
import {Fragment, memo, useRef, useState, type ReactNode, type RefObject} from 'react';
import {IconChevron} from '@sentry/icons/chevron';

import {Button} from '@sentry/scraps/button';
Expand Down Expand Up @@ -273,7 +273,43 @@ function MetricDefaultCell({
);
}

export function SampleTableRow({
function TraceLinkCell({
organization,
row,
selection,
}: {
organization: Organization;
row: TraceMetricEventsResponseItem;
selection: PageFilters;
}) {
const location = useLocation();
const traceId = row[TraceMetricKnownFieldKey.TRACE];
const timestamp = row[TraceMetricKnownFieldKey.TIMESTAMP];
const spanId = row[TraceMetricKnownFieldKey.SPAN_ID];
const oldSpanId = row[TraceMetricKnownFieldKey.OLD_SPAN_ID] as string;
const spanIdToUse = oldSpanId || spanId;
const target = getTraceDetailsUrl({
organization,
traceSlug: traceId,
dateSelection: normalizeDateTimeParams(selection.datetime),
timestamp,
location: stripMetricParamsFromLocation(location),
source: TraceViewSources.TRACE_METRICS,
spanId: spanIdToUse || undefined,
// tab: spanIdToUse ? TraceLayoutTabKeys.WATERFALL : TraceLayoutTabKeys.METRICS, // TODO: Can use this if want to go to the waterfall view if we add metrics to span details.
tab: TraceLayoutTabKeys.METRICS,
});

return (
<WrappingText>
<Link to={target} style={{minWidth: '66px'}}>
{getShortEventId(traceId)}
</Link>
</WrappingText>
);
}

export const SampleTableRow = memo(function SampleTableRow({
routingHint,
row,
columns,
Expand All @@ -283,7 +319,6 @@ export function SampleTableRow({
}: SampleTableRowProps) {
const organization = useOrganization();
const {selection} = usePageFilters();
const location = useLocation();
const [isExpanded, setIsExpanded] = useState(false);
const measureRef = useRef<HTMLTableRowElement>(null);
const projects = useProjects();
Expand All @@ -292,8 +327,6 @@ export function SampleTableRow({
const projectSlug = project?.slug ?? '';
const isEmbedded = isEmbeddedMetricsSamplesTableSource(source);

const traceId = row[TraceMetricKnownFieldKey.TRACE];

const renderExpandRowCell = () => {
return (
<Button
Expand All @@ -307,33 +340,9 @@ export function SampleTableRow({
);
};

const renderTraceCell = () => {
const timestamp = row[TraceMetricKnownFieldKey.TIMESTAMP];
const spanId = row[TraceMetricKnownFieldKey.SPAN_ID];
const oldSpanId = row[TraceMetricKnownFieldKey.OLD_SPAN_ID] as string;
const spanIdToUse = oldSpanId || spanId;
const strippedLocation = stripMetricParamsFromLocation(location);

const target = getTraceDetailsUrl({
organization,
traceSlug: traceId,
dateSelection: normalizeDateTimeParams(selection.datetime),
timestamp,
location: strippedLocation,
source: TraceViewSources.TRACE_METRICS,
spanId: spanIdToUse || undefined,
// tab: spanIdToUse ? TraceLayoutTabKeys.WATERFALL : TraceLayoutTabKeys.METRICS, // TODO: Can use this if want to go to the waterfall view if we add metrics to span details.
tab: TraceLayoutTabKeys.METRICS,
});

return (
<WrappingText>
<Link to={target} style={{minWidth: '66px'}}>
{getShortEventId(traceId)}
</Link>
</WrappingText>
);
};
const renderTraceCell = () => (
<TraceLinkCell organization={organization} row={row} selection={selection} />
);

const renderMetricTypeCell = () => {
return <MetricTypeBadge metricType={row[TraceMetricKnownFieldKey.METRIC_TYPE]} />;
Expand Down Expand Up @@ -402,4 +411,4 @@ export function SampleTableRow({
)}
</Fragment>
);
}
});
Loading
Loading