Skip to content

Commit b7bbb04

Browse files
authored
ref(dam): Use cols and aggregates (#32441)
Read from columns and aggregates instead of fields to build widgets. Fields are still used to maintain order on table widgets.
1 parent 72edd6e commit b7bbb04

37 files changed

+618
-194
lines changed

static/app/components/dashboards/issueWidgetQueriesForm.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,9 @@ class IssueWidgetQueriesForm extends React.Component<Props, State> {
7777

7878
render() {
7979
const {organization, error, query, tags, fieldOptions, onChange} = this.props;
80-
const explodedFields = query.fields.map(field => explodeField({field}));
80+
const explodedFields = (query.fields ?? [...query.columns, ...query.aggregates]).map(
81+
field => explodeField({field})
82+
);
8183
const {blurTimeout} = this.state;
8284

8385
return (

static/app/components/dashboards/widgetQueriesForm.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,12 @@ import {IconAdd, IconDelete} from 'sentry/icons';
1212
import {t} from 'sentry/locale';
1313
import space from 'sentry/styles/space';
1414
import {Organization, PageFilters, SelectValue} from 'sentry/types';
15+
import {defined} from 'sentry/utils';
1516
import {
1617
explodeField,
1718
generateFieldAsString,
1819
getAggregateAlias,
19-
getColumnsAndAggregates,
20+
getColumnsAndAggregatesAsStrings,
2021
isEquation,
2122
stripEquationPrefix,
2223
} from 'sentry/utils/discover/fields';
@@ -181,7 +182,10 @@ class WidgetQueriesForm extends React.Component<Props> {
181182
const isMetrics = widgetType === WidgetType.METRICS;
182183

183184
const hideLegendAlias = ['table', 'world_map', 'big_number'].includes(displayType);
184-
const explodedFields = queries[0].fields.map(field => explodeField({field}));
185+
const query = queries[0];
186+
const explodedFields = defined(query.fields)
187+
? query.fields.map(field => explodeField({field}))
188+
: [...query.columns, ...query.aggregates].map(field => explodeField({field}));
185189

186190
return (
187191
<QueryWrapper>
@@ -247,19 +251,22 @@ class WidgetQueriesForm extends React.Component<Props> {
247251
fields={explodedFields}
248252
organization={organization}
249253
onChange={fields => {
254+
const {aggregates, columns} = getColumnsAndAggregatesAsStrings(fields);
250255
const fieldStrings = fields.map(field => generateFieldAsString(field));
251256
const aggregateAliasFieldStrings = isMetrics
252257
? fieldStrings
253258
: fieldStrings.map(field => getAggregateAlias(field));
254259
queries.forEach((widgetQuery, queryIndex) => {
255260
const descending = widgetQuery.orderby.startsWith('-');
256261
const orderbyAggregateAliasField = widgetQuery.orderby.replace('-', '');
257-
const prevAggregateAliasFieldStrings = widgetQuery.fields.map(field =>
262+
const prevAggregateAliasFields = defined(widgetQuery.fields)
263+
? widgetQuery.fields
264+
: [...widgetQuery.columns, ...widgetQuery.aggregates];
265+
const prevAggregateAliasFieldStrings = prevAggregateAliasFields.map(field =>
258266
isMetrics ? field : getAggregateAlias(field)
259267
);
260268
const newQuery = cloneDeep(widgetQuery);
261269
newQuery.fields = fieldStrings;
262-
const {columns, aggregates} = getColumnsAndAggregates(fieldStrings);
263270
newQuery.aggregates = aggregates;
264271
newQuery.columns = columns;
265272
if (
@@ -295,7 +302,8 @@ class WidgetQueriesForm extends React.Component<Props> {
295302
name="orderby"
296303
options={generateOrderOptions({
297304
widgetType,
298-
...getColumnsAndAggregates(queries[0].fields),
305+
columns: queries[0].columns,
306+
aggregates: queries[0].aggregates,
299307
})}
300308
onChange={(option: SelectValue<string>) =>
301309
this.handleFieldChange(0, 'orderby')(option.value)

static/app/components/modals/addDashboardWidgetModal.tsx

Lines changed: 12 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import {
3333
SelectValue,
3434
TagCollection,
3535
} from 'sentry/types';
36+
import {defined} from 'sentry/utils';
3637
import trackAdvancedAnalyticsEvent from 'sentry/utils/analytics/trackAdvancedAnalyticsEvent';
3738
import {getColumnsAndAggregates} from 'sentry/utils/discover/fields';
3839
import Measurements from 'sentry/utils/measurements/measurements';
@@ -159,17 +160,13 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
159160
constructor(props: Props) {
160161
super(props);
161162

162-
const {widget, defaultTitle, displayType} = props;
163+
const {widget, defaultTitle, displayType, defaultWidgetQuery} = props;
163164
if (!widget) {
164165
this.state = {
165166
title: defaultTitle ?? '',
166167
displayType: displayType ?? DisplayType.TABLE,
167168
interval: '5m',
168-
queries: [
169-
this.defaultWidgetQueries
170-
? {...this.defaultWidgetQueries}
171-
: {...newDiscoverQuery},
172-
],
169+
queries: [defaultWidgetQuery ? {...defaultWidgetQuery} : {...newDiscoverQuery}],
173170
errors: undefined,
174171
loading: !!this.omitDashboardProp,
175172
dashboards: [],
@@ -201,16 +198,6 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
201198
}
202199
}
203200

204-
get defaultWidgetQueries() {
205-
const {defaultWidgetQuery} = this.props;
206-
if (defaultWidgetQuery) {
207-
const {columns, aggregates} = getColumnsAndAggregates(defaultWidgetQuery.fields);
208-
defaultWidgetQuery.aggregates = aggregates;
209-
defaultWidgetQuery.columns = columns;
210-
}
211-
return defaultWidgetQuery;
212-
}
213-
214201
get omitDashboardProp() {
215202
// when opening from discover or issues page, the user selects the dashboard in the widget UI
216203
return [
@@ -315,7 +302,10 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
315302
} = {
316303
queryNames: [],
317304
queryConditions: [],
318-
queryFields: widgetData.queries[0].fields,
305+
queryFields: [
306+
...widgetData.queries[0].columns,
307+
...widgetData.queries[0].aggregates,
308+
],
319309
queryOrderby: widgetData.queries[0].orderby,
320310
};
321311
widgetData.queries.forEach(query => {
@@ -428,12 +418,11 @@ class AddDashboardWidgetModal extends React.Component<Props, State> {
428418
} else if (newDisplayType === displayType) {
429419
// When switching back to original display type, default fields back to the fields provided from the discover query
430420
normalized.forEach(query => {
431-
query.fields = [...defaultWidgetQuery.fields];
432-
const {columns, aggregates} = getColumnsAndAggregates([
433-
...defaultWidgetQuery.fields,
434-
]);
435-
query.aggregates = aggregates;
436-
query.columns = columns;
421+
query.aggregates = [...defaultWidgetQuery.aggregates];
422+
query.columns = [...defaultWidgetQuery.columns];
423+
query.fields = defined(defaultWidgetQuery.fields)
424+
? [...defaultWidgetQuery.fields]
425+
: [...defaultWidgetQuery.columns, ...defaultWidgetQuery.aggregates];
437426
query.orderby = defaultWidgetQuery.orderby;
438427
});
439428
}

static/app/components/modals/dashboardWidgetQuerySelectorModal.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,9 @@ class DashboardWidgetQuerySelectorModal extends React.Component<Props> {
4545
// Pull a max of 3 valid Y-Axis from the widget
4646
const yAxisOptions = eventView.getYAxisOptions().map(({value}) => value);
4747
discoverLocation.query.yAxis = [
48-
...new Set(query.fields.filter(field => yAxisOptions.includes(field))),
48+
...new Set(
49+
query.aggregates.filter(aggregate => yAxisOptions.includes(aggregate))
50+
),
4951
].slice(0, 3);
5052
switch (widget.displayType) {
5153
case DisplayType.BAR:

static/app/components/modals/widgetViewerModal.tsx

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,9 @@ import Tooltip from 'sentry/components/tooltip';
2020
import {t} from 'sentry/locale';
2121
import space from 'sentry/styles/space';
2222
import {Organization, PageFilters, SelectValue} from 'sentry/types';
23+
import {defined} from 'sentry/utils';
2324
import {getUtcDateString} from 'sentry/utils/dates';
25+
import {isAggregateField} from 'sentry/utils/discover/fields';
2426
import parseLinkHeader from 'sentry/utils/parseLinkHeader';
2527
import {decodeInteger, decodeList, decodeScalar} from 'sentry/utils/queryString';
2628
import useApi from 'sentry/utils/useApi';
@@ -122,16 +124,20 @@ function WidgetViewerModal(props: Props) {
122124
...cloneDeep({...widget, queries: [sortedQueries[selectedQueryIndex]]}),
123125
displayType: DisplayType.TABLE,
124126
};
125-
const fields = tableWidget.queries[0].fields;
127+
const {aggregates, columns} = tableWidget.queries[0];
128+
129+
const fields = defined(tableWidget.queries[0].fields)
130+
? tableWidget.queries[0].fields
131+
: [...columns, ...aggregates];
126132

127133
// World Map view should always have geo.country in the table chart
128134
if (
129135
widget.displayType === DisplayType.WORLD_MAP &&
130-
!fields.includes(GEO_COUNTRY_CODE)
136+
!columns.includes(GEO_COUNTRY_CODE)
131137
) {
132138
fields.unshift(GEO_COUNTRY_CODE);
139+
columns.unshift(GEO_COUNTRY_CODE);
133140
}
134-
135141
// Default table columns for visualizations that don't have a column setting
136142
const shouldReplaceTableColumns = [
137143
DisplayType.AREA,
@@ -147,6 +153,11 @@ function WidgetViewerModal(props: Props) {
147153
fields.length,
148154
...['title', 'event.type', 'project', 'user.display', 'timestamp']
149155
);
156+
columns.splice(
157+
0,
158+
fields.length,
159+
...['title', 'event.type', 'project', 'user.display', 'timestamp']
160+
);
150161
}
151162

152163
const prependColumnWidths = shouldReplaceTableColumns
@@ -160,8 +171,15 @@ function WidgetViewerModal(props: Props) {
160171
if (Array.isArray(fields) && !fields.includes(term)) {
161172
fields.unshift(term);
162173
}
174+
if (isAggregateField(term) && !aggregates.includes(term)) {
175+
aggregates.unshift(term);
176+
}
177+
if (!isAggregateField(term) && !columns.includes(term)) {
178+
columns.unshift(term);
179+
}
163180
});
164181
}
182+
165183
const eventView = eventViewFromWidget(
166184
tableWidget.title,
167185
tableWidget.queries[0],

static/app/utils/discover/fields.tsx

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1009,6 +1009,30 @@ export function getColumnsAndAggregates(fields: string[]): {
10091009
return {columns, aggregates};
10101010
}
10111011

1012+
export function getColumnsAndAggregatesAsStrings(fields: QueryFieldValue[]): {
1013+
aggregates: string[];
1014+
columns: string[];
1015+
} {
1016+
const aggregateFields: string[] = [];
1017+
const nonAggregateFields: string[] = [];
1018+
1019+
for (const field of fields) {
1020+
const fieldString = generateFieldAsString(field);
1021+
if (field.kind === 'function') {
1022+
aggregateFields.push(fieldString);
1023+
} else if (field.kind === 'equation') {
1024+
if (isAggregateEquation(fieldString)) {
1025+
aggregateFields.push(fieldString);
1026+
} else {
1027+
nonAggregateFields.push(fieldString);
1028+
}
1029+
} else {
1030+
nonAggregateFields.push(fieldString);
1031+
}
1032+
}
1033+
return {aggregates: aggregateFields, columns: nonAggregateFields};
1034+
}
1035+
10121036
/**
10131037
* Convert a function string into type it will output.
10141038
* This is useful when you need to format values in tooltips,

static/app/views/dashboardsV2/types.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,15 @@ export enum WidgetType {
2828
}
2929

3030
export type WidgetQuery = {
31+
aggregates: string[];
32+
columns: string[];
3133
conditions: string;
32-
fields: string[];
3334
name: string;
3435
orderby: string;
35-
aggregates?: string[];
36-
columns?: string[];
36+
// Fields is replaced with aggregates + columns. It
37+
// is currently used to track column order on table
38+
// widgets.
39+
fields?: string[];
3740
};
3841

3942
export type Widget = {

static/app/views/dashboardsV2/utils.tsx

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import WidgetWorldMap from 'sentry-images/dashboard/widget-world-map.svg';
1313
import {parseArithmetic} from 'sentry/components/arithmeticInput/parser';
1414
import {getDiffInMinutes, getInterval} from 'sentry/components/charts/utils';
1515
import {Organization, PageFilters} from 'sentry/types';
16+
import {defined} from 'sentry/utils';
1617
import {getUtcDateString, parsePeriodToHours} from 'sentry/utils/dates';
1718
import EventView from 'sentry/utils/discover/eventView';
1819
import {
@@ -45,9 +46,9 @@ export function eventViewFromWidget(
4546
// World Map requires an additional column (geo.country_code) to display in discover when navigating from the widget
4647
const fields =
4748
widgetDisplayType === DisplayType.WORLD_MAP &&
48-
!query.fields.includes('geo.country_code')
49-
? ['geo.country_code', ...query.fields]
50-
: query.fields;
49+
!query.columns.includes('geo.country_code')
50+
? ['geo.country_code', ...query.columns, ...query.aggregates]
51+
: [...query.columns, ...query.aggregates];
5152
const conditions =
5253
widgetDisplayType === DisplayType.WORLD_MAP &&
5354
!query.conditions.includes('has:geo.country_code')
@@ -189,7 +190,9 @@ export function getWidgetDiscoverUrl(
189190
// Pull a max of 3 valid Y-Axis from the widget
190191
const yAxisOptions = eventView.getYAxisOptions().map(({value}) => value);
191192
discoverLocation.query.yAxis = [
192-
...new Set(widget.queries[0].fields.filter(field => yAxisOptions.includes(field))),
193+
...new Set(
194+
widget.queries[0].aggregates.filter(aggregate => yAxisOptions.includes(aggregate))
195+
),
193196
].slice(0, 3);
194197

195198
// Visualization specific transforms
@@ -203,10 +206,10 @@ export function getWidgetDiscoverUrl(
203206
case DisplayType.TOP_N:
204207
discoverLocation.query.display = DisplayModes.TOP5;
205208
// Last field is used as the yAxis
206-
const fields = widget.queries[0].fields;
207-
discoverLocation.query.yAxis = fields[fields.length - 1];
208-
if (fields.slice(0, -1).includes(fields[fields.length - 1])) {
209-
discoverLocation.query.field = fields.slice(0, -1);
209+
const aggregates = widget.queries[0].aggregates;
210+
discoverLocation.query.yAxis = aggregates[aggregates.length - 1];
211+
if (aggregates.slice(0, -1).includes(aggregates[aggregates.length - 1])) {
212+
discoverLocation.query.field = aggregates.slice(0, -1);
210213
}
211214
break;
212215
default:
@@ -215,7 +218,11 @@ export function getWidgetDiscoverUrl(
215218

216219
// Equation fields need to have their terms explicitly selected as columns in the discover table
217220
const fields = discoverLocation.query.field;
218-
const equationFields = getFieldsFromEquations(widget.queries[0].fields);
221+
const query = widget.queries[0];
222+
const queryFields = defined(query.fields)
223+
? query.fields
224+
: [...query.columns, ...query.aggregates];
225+
const equationFields = getFieldsFromEquations(queryFields);
219226
// Updates fields by adding any individual terms from equation fields as a column
220227
equationFields.forEach(term => {
221228
if (Array.isArray(fields) && !fields.includes(term)) {

static/app/views/dashboardsV2/widgetBuilder/columnFields.tsx

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,20 +12,24 @@ import {WidgetType} from '../types';
1212
import {DisplayType} from './utils';
1313

1414
interface Props {
15+
aggregates: QueryFieldValue[];
1516
columns: QueryFieldValue[];
1617
displayType: DisplayType;
1718
fieldOptions: ReturnType<typeof generateFieldOptions>;
19+
fields: QueryFieldValue[];
1820
onChange: (newColumns: QueryFieldValue[]) => void;
1921
organization: Organization;
2022
widgetType: WidgetType;
2123
errors?: Record<string, string>[];
2224
}
2325

2426
export function ColumnFields({
27+
aggregates,
28+
columns,
2529
displayType,
2630
fieldOptions,
2731
widgetType,
28-
columns,
32+
fields,
2933
organization,
3034
errors,
3135
onChange,
@@ -39,17 +43,17 @@ export function ColumnFields({
3943
>
4044
{displayType === DisplayType.TABLE ? (
4145
<ColumnCollectionEdit
42-
columns={columns}
46+
columns={fields}
4347
onChange={onChange}
4448
fieldOptions={fieldOptions}
4549
organization={organization}
4650
source={widgetType}
4751
/>
4852
) : (
4953
<ColumnCollectionEdit
50-
columns={columns.slice(0, columns.length - 1)}
54+
columns={[...columns, ...aggregates.slice(0, aggregates.length - 1)]}
5155
onChange={newColumns => {
52-
onChange([...newColumns, columns[columns.length - 1]]);
56+
onChange([...newColumns, fields[fields.length - 1]]);
5357
}}
5458
fieldOptions={fieldOptions}
5559
organization={organization}

0 commit comments

Comments
 (0)