-
-
Notifications
You must be signed in to change notification settings - Fork 4.6k
fix(tracemetrics): Parse units off response in events request for alerts #109608
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
a855152
f5b9424
3918de2
86f174c
a1063fa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -22,7 +22,7 @@ import type { | |
| } from 'sentry/types/organization'; | ||
| import {defined} from 'sentry/utils'; | ||
| import {DURATION_UNITS, SIZE_UNITS} from 'sentry/utils/discover/fieldRenderers'; | ||
| import type {AggregationOutputType} from 'sentry/utils/discover/fields'; | ||
| import type {AggregationOutputType, DataUnit} from 'sentry/utils/discover/fields'; | ||
| import {getAggregateAlias, stripEquationPrefix} from 'sentry/utils/discover/fields'; | ||
| import type {DiscoverDatasets} from 'sentry/utils/discover/types'; | ||
| import type {SamplingMode} from 'sentry/views/explore/hooks/useProgressiveQuery'; | ||
|
|
@@ -38,6 +38,7 @@ type TimeSeriesData = { | |
| // timeseries data | ||
| timeseriesData?: Series[]; | ||
| timeseriesResultsTypes?: Record<string, AggregationOutputType>; | ||
| timeseriesResultsUnits?: Record<string, DataUnit>; | ||
| timeseriesTotals?: {count: number}; | ||
| yAxis?: string | string[]; | ||
| }; | ||
|
|
@@ -548,11 +549,19 @@ class EventsRequest extends PureComponent<EventsRequestProps, EventsRequestState | |
| ) | ||
| .sort((a, b) => a[0] - b[0]); | ||
| const timeseriesResultsTypes: Record<string, AggregationOutputType> = {}; | ||
| const timeseriesResultsUnits: Record<string, DataUnit> = {}; | ||
| Object.keys(timeseriesData).forEach(key => { | ||
| const fieldsMeta = timeseriesData[key]!.meta?.fields[getAggregateAlias(key)]; | ||
| const alias = getAggregateAlias(key); | ||
| const fieldsMeta = | ||
| timeseriesData[key]!.meta?.fields?.[key] ?? | ||
| timeseriesData[key]!.meta?.fields?.[alias]; | ||
| if (fieldsMeta) { | ||
| timeseriesResultsTypes[key] = fieldsMeta; | ||
| } | ||
| const unitsMeta = timeseriesData[key]!.meta?.units?.[alias]; | ||
| if (unitsMeta) { | ||
| timeseriesResultsUnits[key] = unitsMeta as DataUnit; | ||
| } | ||
| }); | ||
| const results: Series[] = sortedTimeseriesData.map(item => { | ||
| return item[1]; | ||
|
|
@@ -573,17 +582,27 @@ class EventsRequest extends PureComponent<EventsRequestProps, EventsRequestState | |
| previousTimeseriesData, | ||
| seriesAdditionalInfo, | ||
| timeseriesResultsTypes, | ||
| timeseriesResultsUnits, | ||
| // sometimes we want to reference props that were given to EventsRequest | ||
| ...props, | ||
| }); | ||
| } | ||
| if (timeseriesData) { | ||
| const yAxisKey = yAxis && (typeof yAxis === 'string' ? yAxis : yAxis[0]); | ||
| const yAxisFieldType = | ||
| yAxisKey && timeseriesData.meta?.fields[getAggregateAlias(yAxisKey)]; | ||
| yAxisKey && | ||
| (timeseriesData.meta?.fields[getAggregateAlias(yAxisKey)] || | ||
| timeseriesData.meta?.fields[yAxisKey]); | ||
| const timeseriesResultsTypes = yAxisFieldType | ||
| ? {[yAxisKey]: yAxisFieldType} | ||
| : undefined; | ||
| const yAxisAlias = yAxisKey && getAggregateAlias(yAxisKey); | ||
| const yAxisUnit = | ||
| yAxisAlias && | ||
| (timeseriesData.meta?.units?.[yAxisAlias] ?? | ||
| timeseriesData.meta?.units?.[yAxisKey]); | ||
|
Comment on lines
+602
to
+603
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Decided to check for both here because the backend no longer requires us to send the aggregate alias, so this would allow both the alias and the raw series name to match. |
||
| const timeseriesResultsUnits = | ||
| yAxisKey && yAxisUnit ? {[yAxisKey]: yAxisUnit as DataUnit} : undefined; | ||
| const { | ||
| data: transformedTimeseriesData, | ||
| comparisonData: transformedComparisonTimeseriesData, | ||
|
|
@@ -625,6 +644,7 @@ class EventsRequest extends PureComponent<EventsRequestProps, EventsRequestState | |
| timeAggregatedData, | ||
| timeframe, | ||
| timeseriesResultsTypes, | ||
| timeseriesResultsUnits, | ||
| // sometimes we want to reference props that were given to EventsRequest | ||
| ...props, | ||
| }); | ||
|
|
@@ -650,7 +670,8 @@ export function transformTimeseriesData( | |
| ): Series[] { | ||
| let scale = 1; | ||
| if (seriesName) { | ||
| const unit = meta?.units?.[getAggregateAlias(seriesName)]; | ||
| const unit = | ||
| meta?.units?.[seriesName] ?? meta?.units?.[getAggregateAlias(seriesName)]; | ||
| // Scale series values to milliseconds or bytes depending on units from meta | ||
| scale = | ||
| ((unit && | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,14 +15,22 @@ import {DEFAULT_STATS_PERIOD} from 'sentry/constants'; | |
| import {space} from 'sentry/styles/space'; | ||
| import type {PageFilters} from 'sentry/types/core'; | ||
| import type {Series} from 'sentry/types/echarts'; | ||
| import {defined} from 'sentry/utils'; | ||
| import { | ||
| axisLabelFormatterUsingAggregateOutputType, | ||
| getDurationUnit, | ||
| tooltipFormatter, | ||
| } from 'sentry/utils/discover/charts'; | ||
| import type {AggregationOutputType, DataUnit} from 'sentry/utils/discover/fields'; | ||
| import {aggregateOutputType, RateUnit} from 'sentry/utils/discover/fields'; | ||
| import type {MetricRule, Trigger} from 'sentry/views/alerts/rules/metric/types'; | ||
| import { | ||
| AlertRuleThresholdType, | ||
| AlertRuleTriggerType, | ||
| } from 'sentry/views/alerts/rules/metric/types'; | ||
| import {getAnomalyMarkerSeries} from 'sentry/views/alerts/rules/metric/utils/anomalyChart'; | ||
| import type {Anomaly} from 'sentry/views/alerts/types'; | ||
| import {alertAxisFormatter, alertTooltipValueFormatter} from 'sentry/views/alerts/utils'; | ||
| import {alertAxisFormatter, isSessionAggregate} from 'sentry/views/alerts/utils'; | ||
| import {getChangeStatus} from 'sentry/views/alerts/utils/getChangeStatus'; | ||
|
|
||
| type DefaultProps = { | ||
|
|
@@ -45,6 +53,8 @@ type Props = DefaultProps & { | |
| maxValue?: number; | ||
| minValue?: number; | ||
| minutesThresholdToDisplaySeconds?: number; | ||
| timeseriesResultsTypes?: Record<string, AggregationOutputType>; | ||
| timeseriesResultsUnits?: Record<string, DataUnit>; | ||
| } & Partial<PageFilters['datetime']>; | ||
|
|
||
| const CHART_GRID = { | ||
|
|
@@ -206,6 +216,8 @@ export default class ThresholdsChart extends PureComponent<Props> { | |
| resolveThreshold, | ||
| anomalies = [], | ||
| theme, | ||
| timeseriesResultsTypes, | ||
| timeseriesResultsUnits, | ||
| } = this.props; | ||
|
|
||
| const dataWithoutRecentBucket = data?.map(({data: eventData, ...restOfData}) => { | ||
|
|
@@ -230,12 +242,33 @@ export default class ThresholdsChart extends PureComponent<Props> { | |
| }) | ||
| ); | ||
|
|
||
| // Resolve output type and unit from server-provided metadata, falling back | ||
| // to inferring from the aggregate string when not available. | ||
| const outputType = | ||
| timeseriesResultsTypes?.[aggregate] ?? aggregateOutputType(aggregate); | ||
| const isDurationChart = outputType === 'duration'; | ||
| const durationUnit = isDurationChart | ||
| ? getDurationUnit(dataWithoutRecentBucket) | ||
| : undefined; | ||
| const rateUnit = Object.values(RateUnit).includes( | ||
| timeseriesResultsUnits?.[aggregate] as RateUnit | ||
| ) | ||
| ? (timeseriesResultsUnits?.[aggregate] as RateUnit) | ||
| : undefined; | ||
cursor[bot] marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| const chartOptions = { | ||
| tooltip: { | ||
| // use the main aggregate for all series (main, min, max, avg, comparison) | ||
| // to format all values similarly | ||
| valueFormatter: (value: number) => | ||
| alertTooltipValueFormatter(value, aggregate, aggregate), | ||
| valueFormatter: (value: number) => { | ||
| if (isSessionAggregate(aggregate)) { | ||
| return defined(value) ? `${value}%` : '\u2015'; | ||
| } | ||
| const type = | ||
| timeseriesResultsTypes?.[aggregate] ?? aggregateOutputType(aggregate); | ||
| const unit = timeseriesResultsUnits?.[aggregate]; | ||
| return tooltipFormatter(value, type, unit); | ||
| }, | ||
|
|
||
| formatAxisLabel: ( | ||
| value: number, | ||
|
|
@@ -305,8 +338,18 @@ export default class ThresholdsChart extends PureComponent<Props> { | |
| resolveThreshold === '' ? null : resolveThreshold | ||
| ), | ||
| axisLabel: { | ||
| formatter: (value: number) => | ||
| alertAxisFormatter(value, data[0]!.seriesName, aggregate), | ||
| formatter: (value: number) => { | ||
| if (timeseriesResultsTypes && !isSessionAggregate(aggregate)) { | ||
| return axisLabelFormatterUsingAggregateOutputType( | ||
| value, | ||
| outputType, | ||
| true, | ||
| durationUnit, | ||
| rateUnit | ||
| ); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Axis label formatter missing sizeUnit parameter for size metricsMedium Severity The call to
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is okay. I agree this is weird but in the existing code we already normalized to bytes, so passing in the unit here would have doubled the normalization. I suppose we could get rid of the normalization but I wanted to avoid touching any of the existing behaviour to ensure this only applied for when |
||
| } | ||
| return alertAxisFormatter(value, data[0]!.seriesName, aggregate); | ||
| }, | ||
| }, | ||
| splitLine: { | ||
| show: false, | ||
|
|
||


Uh oh!
There was an error while loading. Please reload this page.