Skip to content

Conversation

@k-fish
Copy link
Member

@k-fish k-fish commented Nov 4, 2025

Summary

Splits out tables and cleans up some code before infinite table + trace view changes.

Splits out tables and cleans up some code before infinite table + trace
view changes.
@k-fish k-fish requested a review from a team as a code owner November 4, 2025 23:07
@github-actions github-actions bot added the Scope: Frontend Automatically applied to PRs that change frontend components label Nov 4, 2025
embedded={embedded}
>
{isValueColumn ? (
<Tooltip showOnlyOnOverflow title={row.value}>
Copy link

Choose a reason for hiding this comment

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

Bug: row.value is incorrectly used for tooltip title; it should be row[TraceMetricKnownFieldKey.METRIC_VALUE].
Severity: HIGH | Confidence: 1.00

🔍 Detailed Analysis

In the SampleTableRow component, line 293 attempts to access a tooltip title using row.value. However, the TraceMetricEventsResponseItem type definition specifies the metric value as row[TraceMetricKnownFieldKey.METRIC_VALUE]. This incorrect property access pattern is inconsistent with other parts of the codebase, such as line 88 in the same file, and will result in the tooltip displaying undefined instead of the actual numeric metric value.

💡 Suggested Fix

Change title={row.value} to title={row[TraceMetricKnownFieldKey.METRIC_VALUE]} on line 293 in metricsSamplesTableRow.tsx.

🤖 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/explore/metrics/metricInfoTabs/metricsSamplesTableRow.tsx#L293

Potential issue: In the `SampleTableRow` component, line 293 attempts to access a
tooltip title using `row.value`. However, the `TraceMetricEventsResponseItem` type
definition specifies the metric value as `row[TraceMetricKnownFieldKey.METRIC_VALUE]`.
This incorrect property access pattern is inconsistent with other parts of the codebase,
such as line 88 in the same file, and will result in the tooltip displaying `undefined`
instead of the actual numeric metric value.

Did we get this right? 👍 / 👎 to inform future reviews.

Copy link
Member

Choose a reason for hiding this comment

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

This sounds reasonable, we could either do row[TraceMetricKnownFieldKey.METRIC_VALUE] or alternatively row[field] to avoid hard coding things here.

@codecov
Copy link

codecov bot commented Nov 4, 2025

❌ 3 Tests Failed:

Tests completed Failed Passed Skipped
12315 3 12312 10
View the top 3 failed test(s) by shortest run time
useMetricOptions sorts metrics alphabetically by name
Stack Traces | 0.013s run time
Error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

Expected: "....../organizations/org-slug/events/", ObjectContaining {"query": ObjectContaining {"orderby": "metric.name"}}
Received: "....../organizations/org-slug/events/", {"data": undefined, "error": [Function error], "headers": undefined, "host": undefined, "method": "GET", "query": {"dataset": "tracemetrics", "field": ["metric.name", "metric.type", "metric.unit", "count(metric.name)"], "project": ["2"], "query": "", "referrer": "api.explore.metric-options", "statsPeriod": "1h"}, "success": [Function success]}

Number of calls: 1
    at Object.toHaveBeenCalledWith (.../explore/hooks/useMetricOptions.spec.tsx:105:25)
    at Promise.finally.completed (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:1559:28)
    at new Promise (<anonymous>)
    at callAsyncCircusFn (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:1499:10)
    at _callCircusTest (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:1009:40)
    at _runTest (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:949:3)
    at _runTestsForDescribeBlock (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:839:13)
    at _runTestsForDescribeBlock (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:829:11)
    at run (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:757:3)
    at runAndTransformResultsToJestFormat (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/jestAdapterInit.js:1920:21)
    at jestAdapter (.../sentry/node_modules/.pnpm/jest-circus@30.0.4_babel-plugin-macros@3.1..../jest-circus/build/runner.js:101:19)
    at runTestInternal (.../sentry/node_modules/.pnpm/jest-runner@30.0..../jest-runner/build/testWorker.js:272:16)
    at runTest (.../sentry/node_modules/.pnpm/jest-runner@30.0..../jest-runner/build/testWorker.js:340:7)
    at Object.worker (.../sentry/node_modules/.pnpm/jest-runner@30.0..../jest-runner/build/testWorker.js:494:12)
useMetricOptions fetches metric options from tracemetrics dataset
Stack Traces | 0.077s run time
Error: expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 0
    at Object.toHaveBeenCalledTimes (.../explore/hooks/useMetricOptions.spec.tsx:79:25)
useMetricSamplesTable triggers the high accuracy request when there is no data and a partial scan
Stack Traces | 1.05s run time
Error: expect(jest.fn()).toHaveBeenCalledTimes(expected)

Expected number of calls: 1
Received number of calls: 2

Ignored nodes: comments, script, style
...
    at toHaveBeenCalledTimes (.../metrics/hooks/useMetricSamplesTable.spec.tsx:72:39)
    at runWithExpensiveErrorDiagnosticsDisabled (.../sentry/node_modules/.pnpm/@testing-library+dom@10.4.0/node_modules/@.../dom/dist/config.js:47:12)
    at checkCallback (.../sentry/node_modules/.pnpm/@testing-library+dom@10.4.0/node_modules/@.../dom/dist/wait-for.js:124:77)
    at checkRealTimersCallback (.../sentry/node_modules/.pnpm/@testing-library+dom@10.4.0/node_modules/@.../dom/dist/wait-for.js:118:16)
    at Timeout.task [as _onTimeout] (.../sentry/node_modules/.pnpm/jsdom@26.1..../jsdom/browser/Window.js:579:19)
    at listOnTimeout (node:internal/timers:588:17)
    at processTimers (node:internal/timers:523:7)

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.


if (result.data?.data) {
result.data.data = sortedResult;
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Mutating Cached Query Data During Sorting Issue

The code mutates the cached query result by directly assigning to result.data.data. The sortedResult is computed using result.data?.data?.sort() which mutates the original array, and then it's assigned back to result.data.data. This violates React Query's immutability principle and can cause unexpected behavior with caching and stale data. The array should be copied before sorting (e.g., using [...result.data?.data].sort() or result.data?.data?.slice().sort()) to avoid mutating the cached response.

Fix in Cursor Fix in Web

- Update useMetricOptions tests to match new API (no orderby, includes metric.unit field, client-side sorting)
- Fix useMetricSamplesTable hook to properly pass enabled parameter to useProgressiveQuery
- Remove disableExtrapolation option that was causing double API calls
name: metricOptions[0]?.value ?? '',
type: metricOptions[0]?.type ?? '',
name: metricOptions[0].metricName,
type: metricOptions[0].metricType,
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Metric Selection Resets on Options Update

The useEffect condition traceMetric.name !== metricOptions[0].metricName will override the user's metric selection whenever the first option in the list changes, even if the user has manually selected a different metric. The old code only set the metric when !traceMetric.name (i.e., when no metric was selected). This change can cause the metric selection to be unexpectedly reset when metricOptions updates, potentially creating an infinite loop if the user selects a non-first option and the options list updates.

Fix in Cursor Fix in Web

}
});
} else {
query.statsPeriod = '1h'; // Default to a much smaller time window if not searching.
Copy link
Member

Choose a reason for hiding this comment

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

I'm inclined to say we should raise this to say 24 hours to capture metrics that are emitted hourly

Comment on lines +94 to +99
if (parts.length === 0) {
return '';
}
if (parts.length === 1) {
return parts[0];
}
Copy link
Member

Choose a reason for hiding this comment

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

These checks look redundant?

Copy link
Member Author

Choose a reason for hiding this comment

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

prob

attributes: row,
attributeTypes: meta.fields ?? {},
highlightTerms: [],
logColors: getLogColors(SeverityLevel.INFO, theme),
Copy link
Member

Choose a reason for hiding this comment

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

Isn't this the metric's timestamp? I guess you're reusing the timestamp renderer from logs? Not necessary in this PR but it'd be good to pull this component into a shared one.

Copy link
Member Author

Choose a reason for hiding this comment

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

We could pull it out, sure, but we should also make a fieldRenderers probably for metrics instead of these inline renders cells

embedded={embedded}
>
{isValueColumn ? (
<Tooltip showOnlyOnOverflow title={row.value}>
Copy link
Member

Choose a reason for hiding this comment

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

This sounds reasonable, we could either do row[TraceMetricKnownFieldKey.METRIC_VALUE] or alternatively row[field] to avoid hard coding things here.

Comment on lines +8 to +10
export function SamplesTab({traceMetric}: SamplesTabProps) {
return <MetricsSamplesTable traceMetric={traceMetric} />;
}
Copy link
Member

Choose a reason for hiding this comment

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

What is the purpose of this component? Just using the MetricsSamplesTable component is the same right?

Copy link
Member Author

Choose a reason for hiding this comment

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

I was just keeping it for parity with aggregates for now, we -can- remove

Comment on lines +149 to +151
for (const key in ReadableQueryParams.prototype) {
delete target.query[key];
}
Copy link
Member

Choose a reason for hiding this comment

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

Does this do anything? We encode the the query params into a json object and put it under location.query.metric.

name: typedOption.value,
type: typedOption.type,
name: typedOption.metricName,
type: typedOption.metricType,
Copy link
Member

Choose a reason for hiding this comment

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

This change isn't fully handling the metric unit correctly. It fetches the unit for the metric and renders it in the option but doesn't propagate it anywhere. I'm almost inclined to say we should leave it for a separate PR because this gives the impression units should work when it doesn't.

Copy link
Member Author

Choose a reason for hiding this comment

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

We could, but I'd rather leave it in and just update it in these spots since we'll have to shortly anyway

const target: Location = {...location, query: {...location.query}};
for (const key in ReadableQueryParams.prototype) {
delete target.query[key];
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Bug: Prototype loop fails to strip query parameters

The for (const key in ReadableQueryParams.prototype) loop attempts to iterate over prototype properties to delete query parameters. However, ReadableQueryParams is a class with instance properties defined in the constructor, not on the prototype. The prototype only contains the replace method. This means the loop will likely only delete the replace key (if it exists in the query object), not the actual query parameters like query, fields, sortBys, aggregateFields, etc. The function won't properly strip the metric-related query parameters as intended.

Fix in Cursor Fix in Web

@k-fish k-fish enabled auto-merge (squash) November 5, 2025 18:11
@k-fish k-fish merged commit e405e7e into master Nov 5, 2025
47 checks passed
@k-fish k-fish deleted the feat/tracemetrics/update-table-and-selector branch November 5, 2025 18:22
priscilawebdev pushed a commit that referenced this pull request Nov 6, 2025
…102737)

### Summary
Splits out tables and cleans up some code before infinite table + trace
view changes.
Jesse-Box pushed a commit that referenced this pull request Nov 12, 2025
…102737)

### Summary
Splits out tables and cleans up some code before infinite table + trace
view changes.
andrewshie-sentry pushed a commit that referenced this pull request Nov 13, 2025
…102737)

### Summary
Splits out tables and cleans up some code before infinite table + trace
view changes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Scope: Frontend Automatically applied to PRs that change frontend components

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants