Skip to content

Commit 90b7cba

Browse files
Attribute expressions (#1367)
* feat: add support for attribute expressions * test: update tests
1 parent c3baf8f commit 90b7cba

File tree

48 files changed

+308
-273
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

48 files changed

+308
-273
lines changed

projects/observability/src/pages/explorer/explorer.component.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -360,7 +360,7 @@ describe('Explorer component', () => {
360360
spectator.query(ExploreQueryEditorComponent)!.setInterval(new TimeDuration(30, TimeUnit.Second));
361361
spectator.query(ExploreQueryEditorComponent)!.updateGroupByKey(
362362
{
363-
keys: ['apiName'],
363+
keyExpressions: [{ key: 'apiName' }],
364364
limit: 6,
365365
includeRest: true
366366
},
@@ -370,7 +370,7 @@ describe('Explorer component', () => {
370370
expect(queryParamChangeSpy).toHaveBeenLastCalledWith({
371371
scope: 'spans',
372372
series: ['column:avg(second)'],
373-
group: 'apiName',
373+
group: ['apiName'],
374374
limit: 6,
375375
other: true,
376376
interval: '30s'

projects/observability/src/pages/explorer/explorer.component.ts

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import {
99
TimeDurationService
1010
} from '@hypertrace/common';
1111
import { Filter, ToggleItem } from '@hypertrace/components';
12-
import { isNil } from 'lodash-es';
12+
import { isEmpty, isNil } from 'lodash-es';
1313
import { concat, EMPTY, Observable, Subject } from 'rxjs';
1414
import { map, take } from 'rxjs/operators';
1515
import { CartesianSeriesVisualizationType } from '../../shared/components/cartesian/chart';
@@ -19,6 +19,7 @@ import {
1919
ExploreVisualizationRequest
2020
} from '../../shared/components/explore-query-editor/explore-visualization-builder';
2121
import { IntervalValue } from '../../shared/components/interval-select/interval-select.component';
22+
import { AttributeExpression } from '../../shared/graphql/model/attribute/attribute-expression';
2223
import { AttributeMetadata } from '../../shared/graphql/model/metadata/attribute-metadata';
2324
import { MetricAggregationType } from '../../shared/graphql/model/metrics/metric-aggregation';
2425
import { GraphQlGroupBy } from '../../shared/graphql/model/schema/groupby/graphql-group-by';
@@ -216,8 +217,8 @@ export class ExplorerComponent {
216217
}
217218

218219
private getGroupByQueryParams(groupBy?: GraphQlGroupBy): QueryParamObject {
219-
const key = groupBy?.keys[0];
220-
if (key === undefined) {
220+
const keyExpressions = groupBy?.keyExpressions ?? [];
221+
if (keyExpressions.length === 0) {
221222
return {
222223
// Clear existing selection
223224
[ExplorerQueryParam.Group]: undefined,
@@ -227,7 +228,7 @@ export class ExplorerComponent {
227228
}
228229

229230
return {
230-
[ExplorerQueryParam.Group]: key,
231+
[ExplorerQueryParam.Group]: keyExpressions.map(expression => this.encodeAttributeExpression(expression)),
231232
[ExplorerQueryParam.OtherGroup]: groupBy?.includeRest || undefined, // No param needed for false
232233
[ExplorerQueryParam.GroupLimit]: groupBy?.limit
233234
};
@@ -238,7 +239,9 @@ export class ExplorerComponent {
238239
contextToggle: this.getOrDefaultContextItemFromQueryParam(param.get(ExplorerQueryParam.Scope) as ScopeQueryParam),
239240
groupBy: param.has(ExplorerQueryParam.Group)
240241
? {
241-
keys: param.getAll(ExplorerQueryParam.Group),
242+
keyExpressions: param
243+
.getAll(ExplorerQueryParam.Group)
244+
.flatMap(expressionString => this.tryDecodeAttributeExpression(expressionString)),
242245
includeRest: param.get(ExplorerQueryParam.OtherGroup) === 'true',
243246
// tslint:disable-next-line: strict-boolean-expressions
244247
limit: parseInt(param.get(ExplorerQueryParam.GroupLimit)!) || 5
@@ -294,6 +297,19 @@ export class ExplorerComponent {
294297
}
295298
];
296299
}
300+
301+
private encodeAttributeExpression(attributeExpression: AttributeExpression): string {
302+
if (isEmpty(attributeExpression.subpath)) {
303+
return attributeExpression.key;
304+
}
305+
306+
return `${attributeExpression.key}__${attributeExpression.subpath}`;
307+
}
308+
private tryDecodeAttributeExpression(expressionString: string): [AttributeExpression] | [] {
309+
const [key, subpath] = expressionString.split('__');
310+
311+
return [{ key: key, ...(isEmpty(subpath) ? { subpath: subpath } : {}) }];
312+
}
297313
}
298314
interface ContextToggleItem extends ToggleItem<ExplorerContextScope> {
299315
value: ExplorerContextScope;

projects/observability/src/public-api.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ export * from './shared/services/metadata/metadata.service';
6464
export * from './shared/services/metadata/metadata.service.module';
6565
export * from './shared/graphql/model/metadata/attribute-metadata';
6666

67+
export * from './shared/graphql/model/attribute/attribute-expression';
6768
export * from './shared/graphql/model/metrics/metric-aggregation';
6869
export * from './shared/graphql/model/metrics/metric-health';
6970

projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,7 +176,7 @@ describe('Explore query editor', () => {
176176
expect.objectContaining({
177177
series: [defaultSeries],
178178
groupBy: {
179-
keys: ['first groupable'],
179+
keyExpressions: [{ key: 'first groupable' }],
180180
limit: 5 // Default group by limit
181181
}
182182
})
@@ -215,7 +215,7 @@ describe('Explore query editor', () => {
215215
expect.objectContaining({
216216
series: [defaultSeries],
217217
groupBy: {
218-
keys: ['first groupable'],
218+
keyExpressions: [{ key: 'first groupable' }],
219219
limit: 6
220220
}
221221
})

projects/observability/src/shared/components/explore-query-editor/explore-query-editor.component.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ import {
3939
<ht-explore-query-group-by-editor
4040
class="group-by"
4141
[context]="currentVisualization.context"
42-
[groupByKey]="(currentVisualization.groupBy?.keys)[0]"
42+
[groupByKey]="(currentVisualization.groupBy?.keyExpressions)?.[0]?.key"
4343
(groupByKeyChange)="this.updateGroupByKey(currentVisualization.groupBy, $event)"
4444
></ht-explore-query-group-by-editor>
4545
@@ -103,8 +103,8 @@ export class ExploreQueryEditorComponent implements OnChanges, OnInit {
103103
this.setInterval(this.interval);
104104
}
105105

106-
if (changeObject.groupBy && this.groupBy?.keys.length) {
107-
this.updateGroupByKey(this.groupBy, this.groupBy.keys[0]);
106+
if (changeObject.groupBy && this.groupBy?.keyExpressions.length) {
107+
this.updateGroupByKey(this.groupBy, this.groupBy.keyExpressions[0]?.key);
108108
}
109109
}
110110

@@ -117,7 +117,9 @@ export class ExploreQueryEditorComponent implements OnChanges, OnInit {
117117
this.visualizationBuilder.groupBy();
118118
} else {
119119
this.visualizationBuilder.groupBy(
120-
groupBy ? { ...groupBy, keys: [key] } : { keys: [key], limit: ExploreQueryEditorComponent.DEFAULT_GROUP_LIMIT }
120+
groupBy
121+
? { ...groupBy, keyExpressions: [{ key: key }] }
122+
: { keyExpressions: [{ key: key }], limit: ExploreQueryEditorComponent.DEFAULT_GROUP_LIMIT }
121123
);
122124
}
123125
}

projects/observability/src/shared/components/explore-query-editor/explore-visualization-builder.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,15 +84,15 @@ describe('Explore visualization builder', () => {
8484
spectator.service
8585
.setSeries([buildSeries('test1')])
8686
.groupBy({
87-
keys: ['testGroupBy'],
87+
keyExpressions: [{ key: 'testGroupBy' }],
8888
limit: 15
8989
})
9090
.setSeries([buildSeries('test2')]);
9191

9292
expectObservable(recordedRequests).toBe('10ms x', {
9393
x: expectedQuery({
9494
series: [matchSeriesWithName('test2')],
95-
groupBy: { keys: ['testGroupBy'], limit: 15 }
95+
groupBy: { keyExpressions: [{ key: 'testGroupBy' }], limit: 15 }
9696
})
9797
});
9898
});

projects/observability/src/shared/dashboard/dashboard-wrapper/navigable-dashboard.component.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ describe('Navigable dashboard component', () => {
144144
};
145145
spectator.query(FilterBarComponent)?.filtersChange.next([explicitFilter]);
146146
expect(mockDataSource.addFilters).toHaveBeenCalledWith(
147-
expect.objectContaining({ key: 'foo', operator: GraphQlOperatorType.Equals, value: 'bar' })
147+
expect.objectContaining({ keyOrExpression: 'foo', operator: GraphQlOperatorType.Equals, value: 'bar' })
148148
);
149149
});
150150

projects/observability/src/shared/dashboard/data/graphql/explore/explore-cartesian-data-source.model.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ describe('Explore cartesian data source model', () => {
155155
];
156156

157157
model.groupBy = {
158-
keys: ['baz'],
158+
keyExpressions: [{ key: 'baz' }],
159159
includeRest: true,
160160
limit: 5
161161
};
@@ -220,7 +220,7 @@ describe('Explore cartesian data source model', () => {
220220
];
221221

222222
model.groupBy = {
223-
keys: ['baz'],
223+
keyExpressions: [{ key: 'baz' }],
224224
limit: 5
225225
};
226226

projects/observability/src/shared/dashboard/data/graphql/explore/explore-cartesian-data-source.model.ts

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { NEVER, Observable, of } from 'rxjs';
55
import { map, mergeMap } from 'rxjs/operators';
66
import { Series } from '../../../../components/cartesian/chart';
77
import { ExploreRequestState } from '../../../../components/explore-query-editor/explore-visualization-builder';
8+
import { AttributeExpression } from '../../../../graphql/model/attribute/attribute-expression';
89
import { MetricTimeseriesInterval } from '../../../../graphql/model/metric/metric-timeseries';
910
import { GraphQlFilter } from '../../../../graphql/model/schema/filter/graphql-filter';
1011
import { ExploreSpecification } from '../../../../graphql/model/schema/specifications/explore-specification';
@@ -107,19 +108,19 @@ export abstract class ExploreCartesianDataSourceModel extends GraphQlDataSourceM
107108
(selection): selection is RequireBy<ExploreSpecification, 'aggregation'> => selection.aggregation !== undefined
108109
);
109110

110-
const groupByKeys = request.groupBy?.keys ?? [];
111-
const isGroupBy = groupByKeys.length > 0;
111+
const groupByExpressions = request.groupBy?.keyExpressions ?? [];
112+
const isGroupBy = groupByExpressions.length > 0;
112113

113114
if (!isGroupBy && request.interval) {
114115
return aggregatableSpecs.map(spec => this.buildTimeseriesData(spec, result));
115116
}
116117

117118
if (isGroupBy && !request.interval) {
118-
return aggregatableSpecs.map(spec => this.buildGroupedSeriesData(spec, groupByKeys, result));
119+
return aggregatableSpecs.map(spec => this.buildGroupedSeriesData(spec, groupByExpressions, result));
119120
}
120121

121122
if (isGroupBy && request.interval) {
122-
return aggregatableSpecs.map(spec => this.buildGroupedTimeseriesData(spec, groupByKeys, result)).flat();
123+
return aggregatableSpecs.map(spec => this.buildGroupedTimeseriesData(spec, groupByExpressions, result)).flat();
123124
}
124125

125126
return [];
@@ -149,21 +150,25 @@ export abstract class ExploreCartesianDataSourceModel extends GraphQlDataSourceM
149150
};
150151
}
151152

152-
public buildGroupedSeriesData(spec: AggregatableSpec, groupByKeys: string[], result: ExploreResult): SeriesData {
153+
public buildGroupedSeriesData(
154+
spec: AggregatableSpec,
155+
groupByExpressions: AttributeExpression[],
156+
result: ExploreResult
157+
): SeriesData {
153158
return {
154159
data: result
155-
.getGroupedSeriesData(groupByKeys, spec.name, spec.aggregation)
160+
.getGroupedSeriesData(groupByExpressions, spec.name, spec.aggregation)
156161
.map(({ keys, value }) => [this.buildGroupedSeriesName(keys), value]),
157162
spec: spec
158163
};
159164
}
160165

161166
public buildGroupedTimeseriesData(
162167
spec: AggregatableSpec,
163-
groupByKeys: string[],
168+
groupByExpressions: AttributeExpression[],
164169
result: ExploreResult
165170
): SeriesData[] {
166-
return Array.from(result.getGroupedTimeSeriesData(groupByKeys, spec.name, spec.aggregation).entries()).map(
171+
return Array.from(result.getGroupedTimeSeriesData(groupByExpressions, spec.name, spec.aggregation).entries()).map(
167172
([groupNames, data]) => ({
168173
data: data,
169174
groupName: this.buildGroupedSeriesName(groupNames),

projects/observability/src/shared/dashboard/data/graphql/explore/explore-result.test.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ describe('Explore result', () => {
8383
]
8484
});
8585

86-
expect(result.getGroupedSeriesData(['group'], 'foo', MetricAggregationType.Sum)).toEqual([
86+
expect(result.getGroupedSeriesData([{ key: 'group' }], 'foo', MetricAggregationType.Sum)).toEqual([
8787
{ keys: ['first'], value: 10 },
8888
{ keys: ['second'], value: 15 },
8989
{ keys: ['third'], value: 20 }
@@ -116,7 +116,7 @@ describe('Explore result', () => {
116116
]
117117
});
118118

119-
expect(result.getGroupedSeriesData(['group'], 'foo', MetricAggregationType.Sum)).toEqual([
119+
expect(result.getGroupedSeriesData([{ key: 'group' }], 'foo', MetricAggregationType.Sum)).toEqual([
120120
{ keys: ['first'], value: 10 },
121121
{ keys: ['Others'], value: 15 }
122122
]);
@@ -172,7 +172,7 @@ describe('Explore result', () => {
172172
]
173173
});
174174

175-
expect(result.getGroupedTimeSeriesData(['group'], 'foo', MetricAggregationType.Sum)).toEqual(
175+
expect(result.getGroupedTimeSeriesData([{ key: 'group' }], 'foo', MetricAggregationType.Sum)).toEqual(
176176
new Map([
177177
[
178178
['first'],

0 commit comments

Comments
 (0)