Skip to content
53 changes: 53 additions & 0 deletions static/app/views/dashboards/datasetConfig/spans.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
isEquation,
isEquationAlias,
type Aggregation,
type AggregationOutputType,
type DataUnit,
type QueryFieldValue,
} from 'sentry/utils/discover/fields';
import {
Expand Down Expand Up @@ -238,6 +240,57 @@ export const SpansConfig: DatasetConfig<
}
return getFieldRenderer(field, meta, false, widget, dashboardFilters);
},
getSeriesResultUnit: (data, widgetQuery) => {
const resultUnits: Record<string, DataUnit> = {};
widgetQuery.units?.forEach((unit, index) => {
if (unit && widgetQuery.fields) {
resultUnits[widgetQuery.fields[index]!] = unit.valueUnit;
}
});
const isMultiSeriesStats = isMultiSeriesEventsStats(data);

// if there's only one aggregate and more then one group by the series names are the name of the group, not the aggregate name
// But we can just assume the units is for all the series
// TODO: This doesn't work with multiple aggregates
const firstUnit = widgetQuery.units?.find(unit => unit !== null);
if (
isMultiSeriesStats &&
firstUnit &&
widgetQuery.aggregates?.length === 1 &&
widgetQuery.columns?.length > 0
) {
Object.keys(data).forEach(seriesName => {
resultUnits[seriesName] = firstUnit.valueUnit;
});
}
return resultUnits;
},
getSeriesResultType: (data, widgetQuery) => {
const resultTypes: Record<string, AggregationOutputType> = {};
widgetQuery.units?.forEach((unit, index) => {
if (unit && widgetQuery.fields) {
resultTypes[widgetQuery.fields[index]!] = unit.valueType;
}
});

const isMultiSeriesStats = isMultiSeriesEventsStats(data);

// if there's only one aggregate and more then one group by the series names are the name of the group, not the aggregate name
// But we can just assume the units is for all the series
// TODO: This doesn't work with multiple aggregates
const firstUnit = widgetQuery.units?.find(unit => unit !== null);
if (
isMultiSeriesStats &&
firstUnit &&
widgetQuery.aggregates?.length === 1 &&
widgetQuery.columns?.length > 0
) {
Object.keys(data).forEach(seriesName => {
resultTypes[seriesName] = firstUnit.valueType;
});
}
return resultTypes;
},
};

function getPrimaryFieldOptions(
Expand Down
8 changes: 8 additions & 0 deletions static/app/views/dashboards/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type {Layout} from 'react-grid-layout';
import {t} from 'sentry/locale';
import type {Tag} from 'sentry/types/group';
import type {User} from 'sentry/types/user';
import type {AggregationOutputType, DataUnit} from 'sentry/utils/discover/fields';
import {SavedQueryDatasets, type DatasetSource} from 'sentry/utils/discover/types';
import type {PrebuiltDashboardId} from 'sentry/views/dashboards/utils/prebuiltConfigs';

Expand Down Expand Up @@ -82,6 +83,11 @@ export type LinkedDashboard = {
staticDashboardId?: PrebuiltDashboardId;
};

type Unit = {
valueType: AggregationOutputType;
valueUnit: DataUnit;
};

/**
* A widget query is one or more aggregates and a single filter string (conditions.)
* Widgets can have multiple widget queries, and they all combine into a unified timeseries view (for example)
Expand Down Expand Up @@ -109,6 +115,8 @@ export type WidgetQuery = {
// TODO: currently not stored in the backend, only used
// by prebuilt dashboards in the frontend.
slideOutId?: SlideoutId;
// Used to define the units of the fields in the widget queries, currently not saved
units?: Array<Unit | null>;
};

type WidgetChangedReason = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ const FIRST_ROW_WIDGETS: Widget[] = spaceWidgetsEquallyOnRow(
`count(${SpanFields.SPAN_DURATION})`,
`equation|count_if(${SpanFields.TRACE_STATUS},equals,internal_error) / count(${SpanFields.SPAN_DURATION})`,
],
units: [null, {valueType: 'percentage', valueUnit: null}],
columns: [],
fieldAliases: [],
conditions: `${SpanFields.SPAN_OP}:http.server`,
Expand Down Expand Up @@ -129,6 +130,7 @@ const SECOND_ROW_WIDGETS: Widget[] = spaceWidgetsEquallyOnRow(
`equation|count_if(${SpanFields.TRACE_STATUS},equals,internal_error) / count(${SpanFields.SPAN_DURATION})`,
],
columns: [],
units: [null, {valueType: 'percentage', valueUnit: null}],
fieldAliases: [],
conditions: `${SpanFields.SPAN_OP}:queue.process`,
orderby: `count(${SpanFields.SPAN_DURATION})`,
Expand Down Expand Up @@ -171,13 +173,13 @@ const SECOND_ROW_WIDGETS: Widget[] = spaceWidgetsEquallyOnRow(
{
name: '',
fields: [
SpanFields.TRANSACTION,
`equation|count_if(${SpanFields.CACHE_HIT},equals,false) / count(${SpanFields.SPAN_DURATION})`,
],
aggregates: [
`equation|count_if(${SpanFields.CACHE_HIT},equals,false) / count(${SpanFields.SPAN_DURATION})`,
],
columns: [SpanFields.TRANSACTION],
units: [{valueType: 'percentage', valueUnit: null}],
fieldAliases: [''],
conditions: `${SpanFields.SPAN_OP}:[cache.get,cache.get_item]`,
orderby: `-equation|count_if(${SpanFields.CACHE_HIT},equals,false) / count(${SpanFields.SPAN_DURATION})`,
Expand Down Expand Up @@ -211,6 +213,7 @@ const THIRD_ROW_WIDGETS: Widget[] = spaceWidgetsEquallyOnRow(
`equation|count_if(${SpanFields.TRACE_STATUS},equals,internal_error) / count(${SpanFields.SPAN_DURATION})`,
],
columns: [],
units: [null, {valueType: 'percentage', valueUnit: null}],
fieldAliases: ['Jobs', 'Error Rate'],
conditions: `${SpanFields.SPAN_OP}:queue.process`,
orderby: `-count(${SpanFields.SPAN_DURATION})`,
Expand Down Expand Up @@ -271,6 +274,7 @@ const THIRD_ROW_WIDGETS: Widget[] = spaceWidgetsEquallyOnRow(
`equation|count_if(${SpanFields.CACHE_HIT},equals,false) / count(${SpanFields.SPAN_DURATION})`,
],
columns: [SpanFields.TRANSACTION],
units: [null, null, null, {valueType: 'percentage', valueUnit: null}],
fieldAliases: ['', 'Cache Misses', 'Cache Calls', 'Cache Miss Rate'],
conditions: `${SpanFields.SPAN_OP}:[cache.get,cache.get_item]`,
orderby: '-equation[1]',
Expand Down Expand Up @@ -329,6 +333,16 @@ const TRANSACTIONS_TABLE: Widget = {
'Users',
'Time Spent',
],
units: [
null,
null,
null,
null,
null,
null,
null,
{valueType: 'percentage', valueUnit: null},
],
conditions: TABLE_QUERY.formatString(),
orderby: '-sum(span.duration)',
linkedDashboards: [],
Expand Down
12 changes: 12 additions & 0 deletions static/app/views/dashboards/widgetCard/genericWidgetQueries.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,18 @@ class GenericWidgetQueries<SeriesResponse, TableResponse> extends Component<
) as TableDataWithTitle;
transformedData.title = widget.queries[i]?.name ?? '';

const meta = transformedData.meta;
const widgetUnits = widget?.queries?.[i]?.units;
if (widgetUnits && meta) {
widgetUnits.forEach((unit, index) => {
const field = widget.queries?.[i]?.fields?.[index];
if (unit && field) {
meta.units![field] = unit.valueUnit ?? '';
meta.fields![field] = unit.valueType;
}
});
}

// Overwrite the local var to work around state being stale in tests.
transformedTableResults = [...transformedTableResults, transformedData];

Expand Down
4 changes: 2 additions & 2 deletions static/app/views/dashboards/widgets/common/types.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type {Confidence} from 'sentry/types/organization';
import type {DataUnit} from 'sentry/utils/discover/fields';
import type {ThresholdsConfig} from 'sentry/views/dashboards/widgetBuilder/buildSteps/thresholdsStep/thresholds';

type AttributeValueType =
export type AttributeValueType =
| 'number'
| 'integer'
| 'date'
Expand All @@ -16,7 +16,7 @@ type AttributeValueType =
| 'score'
| 'currency';

type AttributeValueUnit = DataUnit | null;
export type AttributeValueUnit = DataUnit | null;

type TimeSeriesValueType = AttributeValueType;
export type TimeSeriesValueUnit = AttributeValueUnit;
Expand Down
Loading