Skip to content

Commit 2f0f04a

Browse files
authored
feat(dashboards): Add support for "score" value type (#88329)
A new value type! A "score" is a special kind of integer. It only ranges from 0 to 100, and it has no units. Soon, the backend will return this type for functions like `performance_score`, but for now just frontend support is helpful. The "score" type: - always rounded to nearest integer in the tooltip formatter - always 0-100 on the Y axis Contributes to [DAIN-111: Replace Web Vitals "Score Breakdown" chart with `TimeSeriesWidgetVisualization`](https://linear.app/getsentry/issue/DAIN-111/replace-web-vitals-score-breakdown-chart-with)
1 parent 45b8ec1 commit 2f0f04a

File tree

8 files changed

+272
-0
lines changed

8 files changed

+272
-0
lines changed

static/app/utils/fields/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,7 @@ export enum FieldValueType {
145145
SIZE = 'size',
146146
RATE = 'rate',
147147
PERCENT_CHANGE = 'percent_change',
148+
SCORE = 'score',
148149
}
149150

150151
export enum WebVital {

static/app/views/dashboards/widgets/common/settings.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ export const PLOTTABLE_TIME_SERIES_VALUE_TYPES = [
1111
'percentage',
1212
'size',
1313
'rate',
14+
'score',
1415
] as const;
1516

1617
export const MIN_WIDTH = 110;

static/app/views/dashboards/widgets/common/types.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type AttributeValueType =
1414
| 'string'
1515
| 'size'
1616
| 'rate'
17+
| 'score'
1718
| null;
1819

1920
type AttributeValueUnit = DataUnit | null;
Lines changed: 207 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,207 @@
1+
import {TimeSeries} from '../../common/types';
2+
3+
export const sampleScoreTimeSeries: TimeSeries = {
4+
field: 'performance_score(measurements.score.lcp)',
5+
meta: {
6+
type: 'score',
7+
unit: null,
8+
},
9+
data: [
10+
{
11+
value: 16,
12+
timestamp: '2024-10-24T15:00:00-04:00',
13+
},
14+
{
15+
value: 16,
16+
timestamp: '2024-10-24T15:30:00-04:00',
17+
},
18+
{
19+
value: 16,
20+
timestamp: '2024-10-24T16:00:00-04:00',
21+
},
22+
{
23+
value: 17,
24+
timestamp: '2024-10-24T16:30:00-04:00',
25+
},
26+
{
27+
value: 17,
28+
timestamp: '2024-10-24T17:00:00-04:00',
29+
},
30+
{
31+
value: 17,
32+
timestamp: '2024-10-24T17:30:00-04:00',
33+
},
34+
{
35+
value: 15,
36+
timestamp: '2024-10-24T18:00:00-04:00',
37+
},
38+
{
39+
value: 17,
40+
timestamp: '2024-10-24T18:30:00-04:00',
41+
},
42+
{
43+
value: 16,
44+
timestamp: '2024-10-24T19:00:00-04:00',
45+
},
46+
{
47+
value: 16,
48+
timestamp: '2024-10-24T19:30:00-04:00',
49+
},
50+
{
51+
value: 15,
52+
timestamp: '2024-10-24T20:00:00-04:00',
53+
},
54+
{
55+
value: 16,
56+
timestamp: '2024-10-24T20:30:00-04:00',
57+
},
58+
{
59+
value: 15,
60+
timestamp: '2024-10-24T21:00:00-04:00',
61+
},
62+
{
63+
value: 18,
64+
timestamp: '2024-10-24T21:30:00-04:00',
65+
},
66+
{
67+
value: 17,
68+
timestamp: '2024-10-24T22:00:00-04:00',
69+
},
70+
{
71+
value: 18,
72+
timestamp: '2024-10-24T22:30:00-04:00',
73+
},
74+
{
75+
value: 16,
76+
timestamp: '2024-10-24T23:00:00-04:00',
77+
},
78+
{
79+
value: 18,
80+
timestamp: '2024-10-24T23:30:00-04:00',
81+
},
82+
{
83+
value: 18,
84+
timestamp: '2024-10-25T00:00:00-04:00',
85+
},
86+
{
87+
value: 19,
88+
timestamp: '2024-10-25T00:30:00-04:00',
89+
},
90+
{
91+
value: 17,
92+
timestamp: '2024-10-25T01:00:00-04:00',
93+
},
94+
{
95+
value: 20,
96+
timestamp: '2024-10-25T01:30:00-04:00',
97+
},
98+
{
99+
value: 19,
100+
timestamp: '2024-10-25T02:00:00-04:00',
101+
},
102+
{
103+
value: 19,
104+
timestamp: '2024-10-25T02:30:00-04:00',
105+
},
106+
{
107+
value: 18,
108+
timestamp: '2024-10-25T03:00:00-04:00',
109+
},
110+
{
111+
value: 19,
112+
timestamp: '2024-10-25T03:30:00-04:00',
113+
},
114+
{
115+
value: 19,
116+
timestamp: '2024-10-25T04:00:00-04:00',
117+
},
118+
{
119+
value: 19,
120+
timestamp: '2024-10-25T04:30:00-04:00',
121+
},
122+
{
123+
value: 17,
124+
timestamp: '2024-10-25T05:00:00-04:00',
125+
},
126+
{
127+
value: 19,
128+
timestamp: '2024-10-25T05:30:00-04:00',
129+
},
130+
{
131+
value: 18,
132+
timestamp: '2024-10-25T06:00:00-04:00',
133+
},
134+
{
135+
value: 18,
136+
timestamp: '2024-10-25T06:30:00-04:00',
137+
},
138+
{
139+
value: 19,
140+
timestamp: '2024-10-25T07:00:00-04:00',
141+
},
142+
{
143+
value: 19,
144+
timestamp: '2024-10-25T07:30:00-04:00',
145+
},
146+
{
147+
value: 18,
148+
timestamp: '2024-10-25T08:00:00-04:00',
149+
},
150+
{
151+
value: 18,
152+
timestamp: '2024-10-25T08:30:00-04:00',
153+
},
154+
{
155+
value: 19,
156+
timestamp: '2024-10-25T09:00:00-04:00',
157+
},
158+
{
159+
value: 19,
160+
timestamp: '2024-10-25T09:30:00-04:00',
161+
},
162+
{
163+
value: 18,
164+
timestamp: '2024-10-25T10:00:00-04:00',
165+
},
166+
{
167+
value: 18,
168+
timestamp: '2024-10-25T10:30:00-04:00',
169+
},
170+
{
171+
value: 19,
172+
timestamp: '2024-10-25T11:00:00-04:00',
173+
},
174+
{
175+
value: 17,
176+
timestamp: '2024-10-25T11:30:00-04:00',
177+
},
178+
{
179+
value: 17,
180+
timestamp: '2024-10-25T12:00:00-04:00',
181+
},
182+
{
183+
value: 18,
184+
timestamp: '2024-10-25T12:30:00-04:00',
185+
},
186+
{
187+
value: 17,
188+
timestamp: '2024-10-25T13:00:00-04:00',
189+
},
190+
{
191+
value: 17,
192+
timestamp: '2024-10-25T13:30:00-04:00',
193+
},
194+
{
195+
value: 16,
196+
timestamp: '2024-10-25T14:00:00-04:00',
197+
},
198+
{
199+
value: 17,
200+
timestamp: '2024-10-25T14:30:00-04:00',
201+
},
202+
{
203+
value: 18,
204+
timestamp: '2024-10-25T15:00:00-04:00',
205+
},
206+
],
207+
};

static/app/views/dashboards/widgets/timeSeriesWidget/formatters/formatTooltipValue.spec.tsx

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,4 +64,13 @@ describe('formatTooltipValue', () => {
6464
expect(formatTooltipValue(value, 'rate', unit)).toEqual(formattedValue);
6565
});
6666
});
67+
68+
describe('score', () => {
69+
it.each([
70+
[0, undefined, '0'],
71+
[17.231, undefined, '17'],
72+
])('Formats %s as %s', (value, unit, formattedValue) => {
73+
expect(formatTooltipValue(value, 'score', unit)).toEqual(formattedValue);
74+
});
75+
});
6776
});

static/app/views/dashboards/widgets/timeSeriesWidget/formatters/formatTooltipValue.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export function formatTooltipValue(
4848
// This way, named rate functions like `epm()` will be shows in per minute
4949
// units
5050
return formatRate(value, unit as RateUnit);
51+
case 'score':
52+
// Scores are always integers, no half-marks.
53+
return value.toFixed(0);
5154
default:
5255
return value.toString();
5356
}

static/app/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetVisualization.stories.tsx

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import useLocationQuery from 'sentry/utils/url/useLocationQuery';
1717
import type {LegendSelection, Release, TimeSeries, TimeSeriesMeta} from '../common/types';
1818

1919
import {sampleDurationTimeSeries} from './fixtures/sampleDurationTimeSeries';
20+
import {sampleScoreTimeSeries} from './fixtures/sampleScoreTimeSeries';
2021
import {sampleThroughputTimeSeries} from './fixtures/sampleThroughputTimeSeries';
2122
import {spanSamplesWithDurations} from './fixtures/spanSamplesWithDurations';
2223
import {Area} from './plottables/area';
@@ -218,6 +219,49 @@ export default storyBook('TimeSeriesWidgetVisualization', (story, APIReference)
218219
);
219220
});
220221

222+
story('Data Types', () => {
223+
return (
224+
<Fragment>
225+
<p>
226+
<JSXNode name="TimeSeriesWidgetVisualization" /> can plot most, but not all data
227+
types that come back from our time series endpoints. The supported data types
228+
are:
229+
<ul>
230+
<li>
231+
<code>number</code>
232+
</li>
233+
<li>
234+
<code>integer</code>
235+
</li>
236+
<li>
237+
<code>duration</code>
238+
</li>
239+
<li>
240+
<code>percentage</code>
241+
</li>
242+
<li>
243+
<code>size</code>
244+
</li>
245+
<li>
246+
<code>rate</code>
247+
</li>
248+
<li>
249+
<code>score</code>
250+
</li>
251+
</ul>
252+
</p>
253+
<p>
254+
Each of those types has specific behavior in its axes range, axis value
255+
formatting, tooltip formatting, unit scaling, and so on. For example, the{' '}
256+
<code>score</code> type always uses the 0-100 Y axis range.
257+
</p>
258+
<MediumWidget>
259+
<TimeSeriesWidgetVisualization plottables={[new Area(sampleScoreTimeSeries)]} />
260+
</MediumWidget>
261+
</Fragment>
262+
);
263+
});
264+
221265
story('Y Axes', () => {
222266
return (
223267
<Fragment>

static/app/views/dashboards/widgets/timeSeriesWidget/timeSeriesWidgetYAxis.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,12 @@ export function TimeSeriesWidgetYAxis(
5050
return 0.001;
5151
}
5252

53+
// "Score" axes are _always_ from 0 to 100. Otherwise it's unclear how much
54+
// opportunity there is to improve them.
55+
if (yAxisFieldType === 'score') {
56+
return 100;
57+
}
58+
5359
return null;
5460
},
5561
},

0 commit comments

Comments
 (0)