diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go index 123ab49c61e..788627598d2 100644 --- a/pkg/tsdb/stackdriver/stackdriver.go +++ b/pkg/tsdb/stackdriver/stackdriver.go @@ -30,9 +30,15 @@ import ( ) var ( - slog log.Logger - legendKeyFormat *regexp.Regexp - metricNameFormat *regexp.Regexp + slog log.Logger +) + +var ( + matchAllCap = regexp.MustCompile("(.)([A-Z][a-z]*)") + legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) + metricNameFormat = regexp.MustCompile(`([\w\d_]+)\.(googleapis\.com|io)/(.+)`) + wildcardRegexRe = regexp.MustCompile(`[-\/^$+?.()|[\]{}]`) + alignmentPeriodRe = regexp.MustCompile("[0-9]+") ) const ( @@ -62,8 +68,6 @@ func NewStackdriverExecutor(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, func init() { slog = log.New("tsdb.stackdriver") tsdb.RegisterTsdbQueryEndpoint("stackdriver", NewStackdriverExecutor) - legendKeyFormat = regexp.MustCompile(`\{\{\s*(.+?)\s*\}\}`) - metricNameFormat = regexp.MustCompile(`([\w\d_]+)\.googleapis\.com/(.+)`) } // Query takes in the frontend queries, parses them into the Stackdriver query format @@ -197,8 +201,7 @@ func interpolateFilterWildcards(value string) string { value = reverse(strings.Replace(reverse(value), "*", "", 1)) value = fmt.Sprintf(`starts_with("%s")`, value) } else if matches != 0 { - re := regexp.MustCompile(`[-\/^$+?.()|[\]{}]`) - value = string(re.ReplaceAllFunc([]byte(value), func(in []byte) []byte { + value = string(wildcardRegexRe.ReplaceAllFunc([]byte(value), func(in []byte) []byte { return []byte(strings.Replace(string(in), string(in), `\\`+string(in), 1)) })) value = strings.Replace(value, "*", ".*", -1) @@ -287,8 +290,7 @@ func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *Stackdriv alignmentPeriod, ok := req.URL.Query()["aggregation.alignmentPeriod"] if ok { - re := regexp.MustCompile("[0-9]+") - seconds, err := strconv.ParseInt(re.FindString(alignmentPeriod[0]), 10, 64) + seconds, err := strconv.ParseInt(alignmentPeriodRe.FindString(alignmentPeriod[0]), 10, 64) if err == nil { queryResult.Meta.Set("alignmentPeriod", seconds) } @@ -333,8 +335,6 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver return StackdriverResponse{}, err } - // slog.Info("stackdriver", "response", string(body)) - if res.StatusCode/100 != 2 { slog.Error("Request failed", "status", res.Status, "body", string(body)) return StackdriverResponse{}, fmt.Errorf(string(body)) @@ -351,42 +351,66 @@ func (e *StackdriverExecutor) unmarshalResponse(res *http.Response) (Stackdriver } func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data StackdriverResponse, query *StackdriverQuery) error { - metricLabels := make(map[string][]string) - resourceLabels := make(map[string][]string) - var resourceTypes []string - - for _, series := range data.TimeSeries { - if !containsLabel(resourceTypes, series.Resource.Type) { - resourceTypes = append(resourceTypes, series.Resource.Type) - } - } + labels := make(map[string]map[string]bool) for _, series := range data.TimeSeries { points := make([]tsdb.TimePoint, 0) - + seriesLabels := make(map[string]string) defaultMetricName := series.Metric.Type - if len(resourceTypes) > 1 { - defaultMetricName += " " + series.Resource.Type - } + labels["resource.type"] = map[string]bool{series.Resource.Type: true} for key, value := range series.Metric.Labels { - if !containsLabel(metricLabels[key], value) { - metricLabels[key] = append(metricLabels[key], value) + if _, ok := labels["metric.label."+key]; !ok { + labels["metric.label."+key] = map[string]bool{} } + labels["metric.label."+key][value] = true + seriesLabels["metric.label."+key] = value + if len(query.GroupBys) == 0 || containsLabel(query.GroupBys, "metric.label."+key) { defaultMetricName += " " + value } } for key, value := range series.Resource.Labels { - if !containsLabel(resourceLabels[key], value) { - resourceLabels[key] = append(resourceLabels[key], value) + if _, ok := labels["resource.label."+key]; !ok { + labels["resource.label."+key] = map[string]bool{} } + labels["resource.label."+key][value] = true + seriesLabels["resource.label."+key] = value + if containsLabel(query.GroupBys, "resource.label."+key) { defaultMetricName += " " + value } } + for labelType, labelTypeValues := range series.MetaData { + for labelKey, labelValue := range labelTypeValues { + key := toSnakeCase(fmt.Sprintf("metadata.%s.%s", labelType, labelKey)) + if _, ok := labels[key]; !ok { + labels[key] = map[string]bool{} + } + + switch v := labelValue.(type) { + case string: + labels[key][v] = true + seriesLabels[key] = v + case bool: + strVal := strconv.FormatBool(v) + labels[key][strVal] = true + seriesLabels[key] = strVal + case []interface{}: + for _, v := range v { + strVal := v.(string) + labels[key][strVal] = true + if len(seriesLabels[key]) > 0 { + strVal = fmt.Sprintf("%s, %s", seriesLabels[key], strVal) + } + seriesLabels[key] = strVal + } + } + } + } + // reverse the order to be ascending if series.ValueType != "DISTRIBUTION" { for i := len(series.Points) - 1; i >= 0; i-- { @@ -411,7 +435,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta points = append(points, tsdb.NewTimePoint(null.FloatFrom(value), float64((point.Interval.EndTime).Unix())*1000)) } - metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, make(map[string]string), query) + metricName := formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, nil, query) queryRes.Series = append(queryRes.Series, &tsdb.TimeSeries{ Name: metricName, @@ -437,7 +461,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i) additionalLabels := map[string]string{"bucket": bucketBound} buckets[i] = &tsdb.TimeSeries{ - Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, additionalLabels, query), + Name: formatLegendKeys(series.Metric.Type, defaultMetricName, nil, additionalLabels, query), Points: make([]tsdb.TimePoint, 0), } if maxKey < i { @@ -453,7 +477,7 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta bucketBound := calcBucketBound(point.Value.DistributionValue.BucketOptions, i) additionalLabels := map[string]string{"bucket": bucketBound} buckets[i] = &tsdb.TimeSeries{ - Name: formatLegendKeys(series.Metric.Type, defaultMetricName, series.Resource.Type, series.Metric.Labels, series.Resource.Labels, additionalLabels, query), + Name: formatLegendKeys(series.Metric.Type, defaultMetricName, seriesLabels, additionalLabels, query), Points: make([]tsdb.TimePoint, 0), } } @@ -465,14 +489,23 @@ func (e *StackdriverExecutor) parseResponse(queryRes *tsdb.QueryResult, data Sta } } - queryRes.Meta.Set("resourceLabels", resourceLabels) - queryRes.Meta.Set("metricLabels", metricLabels) + labelsByKey := make(map[string][]string) + for key, values := range labels { + for value := range values { + labelsByKey[key] = append(labelsByKey[key], value) + } + } + + queryRes.Meta.Set("labels", labelsByKey) queryRes.Meta.Set("groupBys", query.GroupBys) - queryRes.Meta.Set("resourceTypes", resourceTypes) return nil } +func toSnakeCase(str string) string { + return strings.ToLower(matchAllCap.ReplaceAllString(str, "${1}_${2}")) +} + func containsLabel(labels []string, newLabel string) bool { for _, val := range labels { if val == newLabel { @@ -482,7 +515,7 @@ func containsLabel(labels []string, newLabel string) bool { return false } -func formatLegendKeys(metricType string, defaultMetricName string, resourceType string, metricLabels map[string]string, resourceLabels map[string]string, additionalLabels map[string]string, query *StackdriverQuery) string { +func formatLegendKeys(metricType string, defaultMetricName string, labels map[string]string, additionalLabels map[string]string, query *StackdriverQuery) string { if query.AliasBy == "" { return defaultMetricName } @@ -496,25 +529,13 @@ func formatLegendKeys(metricType string, defaultMetricName string, resourceType return []byte(metricType) } - if metaPartName == "resource.type" && resourceType != "" { - return []byte(resourceType) - } - metricPart := replaceWithMetricPart(metaPartName, metricType) if metricPart != nil { return metricPart } - metaPartName = strings.Replace(metaPartName, "metric.label.", "", 1) - - if val, exists := metricLabels[metaPartName]; exists { - return []byte(val) - } - - metaPartName = strings.Replace(metaPartName, "resource.label.", "", 1) - - if val, exists := resourceLabels[metaPartName]; exists { + if val, exists := labels[metaPartName]; exists { return []byte(val) } @@ -533,8 +554,8 @@ func replaceWithMetricPart(metaPartName string, metricType string) []byte { shortMatches := metricNameFormat.FindStringSubmatch(metricType) if metaPartName == "metric.name" { - if len(shortMatches) > 0 { - return []byte(shortMatches[2]) + if len(shortMatches) > 2 { + return []byte(shortMatches[3]) } } diff --git a/pkg/tsdb/stackdriver/stackdriver_test.go b/pkg/tsdb/stackdriver/stackdriver_test.go index 9d72162cf86..7ee52db2de9 100644 --- a/pkg/tsdb/stackdriver/stackdriver_test.go +++ b/pkg/tsdb/stackdriver/stackdriver_test.go @@ -260,22 +260,20 @@ func TestStackdriver(t *testing.T) { }) Convey("Should add meta for labels to the response", func() { - metricLabels := res.Meta.Get("metricLabels").Interface().(map[string][]string) - So(metricLabels, ShouldNotBeNil) - So(len(metricLabels["instance_name"]), ShouldEqual, 3) - So(metricLabels["instance_name"][0], ShouldEqual, "collector-asia-east-1") - So(metricLabels["instance_name"][1], ShouldEqual, "collector-europe-west-1") - So(metricLabels["instance_name"][2], ShouldEqual, "collector-us-east-1") + labels := res.Meta.Get("labels").Interface().(map[string][]string) + So(labels, ShouldNotBeNil) + So(len(labels["metric.label.instance_name"]), ShouldEqual, 3) + So(labels["metric.label.instance_name"], ShouldContain, "collector-asia-east-1") + So(labels["metric.label.instance_name"], ShouldContain, "collector-europe-west-1") + So(labels["metric.label.instance_name"], ShouldContain, "collector-us-east-1") - resourceLabels := res.Meta.Get("resourceLabels").Interface().(map[string][]string) - So(resourceLabels, ShouldNotBeNil) - So(len(resourceLabels["zone"]), ShouldEqual, 3) - So(resourceLabels["zone"][0], ShouldEqual, "asia-east1-a") - So(resourceLabels["zone"][1], ShouldEqual, "europe-west1-b") - So(resourceLabels["zone"][2], ShouldEqual, "us-east1-b") + So(len(labels["resource.label.zone"]), ShouldEqual, 3) + So(labels["resource.label.zone"], ShouldContain, "asia-east1-a") + So(labels["resource.label.zone"], ShouldContain, "europe-west1-b") + So(labels["resource.label.zone"], ShouldContain, "us-east1-b") - So(len(resourceLabels["project_id"]), ShouldEqual, 1) - So(resourceLabels["project_id"][0], ShouldEqual, "grafana-prod") + So(len(labels["resource.label.project_id"]), ShouldEqual, 1) + So(labels["resource.label.project_id"][0], ShouldEqual, "grafana-prod") }) }) @@ -419,6 +417,72 @@ func TestStackdriver(t *testing.T) { So(res.Series[10].Points[1][0].Float64, ShouldEqual, 56) }) }) + + Convey("when data from query returns metadata system labels", func() { + data, err := loadTestFile("./test-data/5-series-response-meta-data.json") + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 3) + + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &StackdriverQuery{AliasBy: "{{bucket}}"} + err = executor.parseResponse(res, data, query) + labels := res.Meta.Get("labels").Interface().(map[string][]string) + So(err, ShouldBeNil) + + So(len(res.Series), ShouldEqual, 3) + + Convey("and systemlabel contains key with array of string", func() { + So(len(labels["metadata.system_labels.test"]), ShouldEqual, 5) + So(labels["metadata.system_labels.test"], ShouldContain, "value1") + So(labels["metadata.system_labels.test"], ShouldContain, "value2") + So(labels["metadata.system_labels.test"], ShouldContain, "value3") + So(labels["metadata.system_labels.test"], ShouldContain, "value4") + So(labels["metadata.system_labels.test"], ShouldContain, "value5") + }) + + Convey("and systemlabel contains key with primitive strings", func() { + So(len(labels["metadata.system_labels.region"]), ShouldEqual, 2) + So(labels["metadata.system_labels.region"], ShouldContain, "us-central1") + So(labels["metadata.system_labels.region"], ShouldContain, "us-west1") + }) + + Convey("and userLabel contains key with primitive strings", func() { + So(len(labels["metadata.user_labels.region"]), ShouldEqual, 2) + So(labels["metadata.user_labels.region"], ShouldContain, "region1") + So(labels["metadata.user_labels.region"], ShouldContain, "region3") + + So(len(labels["metadata.user_labels.name"]), ShouldEqual, 2) + So(labels["metadata.user_labels.name"], ShouldContain, "name1") + So(labels["metadata.user_labels.name"], ShouldContain, "name3") + }) + }) + Convey("when data from query returns metadata system labels and alias by is defined", func() { + data, err := loadTestFile("./test-data/5-series-response-meta-data.json") + So(err, ShouldBeNil) + So(len(data.TimeSeries), ShouldEqual, 3) + + Convey("and systemlabel contains key with array of string", func() { + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &StackdriverQuery{AliasBy: "{{metadata.system_labels.test}}"} + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + So(len(res.Series), ShouldEqual, 3) + fmt.Println(res.Series[0].Name) + So(res.Series[0].Name, ShouldEqual, "value1, value2") + So(res.Series[1].Name, ShouldEqual, "value1, value2, value3") + So(res.Series[2].Name, ShouldEqual, "value1, value2, value4, value5") + }) + + Convey("and systemlabel contains key with array of string2", func() { + res := &tsdb.QueryResult{Meta: simplejson.New(), RefId: "A"} + query := &StackdriverQuery{AliasBy: "{{metadata.system_labels.test2}}"} + err = executor.parseResponse(res, data, query) + So(err, ShouldBeNil) + So(len(res.Series), ShouldEqual, 3) + fmt.Println(res.Series[0].Name) + So(res.Series[2].Name, ShouldEqual, "testvalue") + }) + }) }) Convey("when interpolating filter wildcards", func() { diff --git a/pkg/tsdb/stackdriver/test-data/5-series-response-meta-data.json b/pkg/tsdb/stackdriver/test-data/5-series-response-meta-data.json new file mode 100644 index 00000000000..8066d4589f2 --- /dev/null +++ b/pkg/tsdb/stackdriver/test-data/5-series-response-meta-data.json @@ -0,0 +1,78 @@ +{ + "timeSeries": [{ + "metric": { + "type": "compute.googleapis.com/firewall/dropped_bytes_count" + }, + "resource": { + "type": "gce_instance", + "labels": { + "instance_id": "114250375703598695", + "project_id": "raintank-dev" + } + }, + "metricKind": "DELTA", + "valueType": "INT64", + "metadata": { + "systemLabels": { + "region": "us-west1", + "name": "diana-debian9", + "test": ["value1", "value2"], + "spot_instance": false + }, + "userLabels": { + "name": "name1", + "region": "region1" + } + } + }, + { + "metric": { + "type": "compute.googleapis.com/firewall/dropped_bytes_count" + }, + "resource": { + "type": "gce_instance", + "labels": { + "instance_id": "2268399396335228490", + "project_id": "raintank-dev" + } + }, + "metricKind": "DELTA", + "valueType": "INT64", + "metadata": { + "systemLabels": { + "region": "us-west1", + "name": "diana-ubuntu1910", + "test": ["value1", "value2","value3"], + "spot_instance": false + } + } + }, + { + "metric": { + "type": "compute.googleapis.com/firewall/dropped_bytes_count" + }, + "resource": { + "type": "gce_instance", + "labels": { + "instance_id": "2390486324245305300", + "project_id": "raintank-dev" + } + }, + "metricKind": "DELTA", + "valueType": "INT64", + "metadata": { + "systemLabels": { + "name": "premium-plugin-staging", + "region": "us-central1", + "test": ["value1", "value2","value4", "value5"], + "test2": ["testvalue"], + "spot_instance": true + }, + "userLabels": { + "name": "name3", + "region": "region3" + } + } + } + ] +} diff --git a/pkg/tsdb/stackdriver/types.go b/pkg/tsdb/stackdriver/types.go index e4ede41d269..a8a4661b597 100644 --- a/pkg/tsdb/stackdriver/types.go +++ b/pkg/tsdb/stackdriver/types.go @@ -41,8 +41,9 @@ type StackdriverResponse struct { Type string `json:"type"` Labels map[string]string `json:"labels"` } `json:"resource"` - MetricKind string `json:"metricKind"` - ValueType string `json:"valueType"` + MetaData map[string]map[string]interface{} `json:"metadata"` + MetricKind string `json:"metricKind"` + ValueType string `json:"valueType"` Points []struct { Interval struct { StartTime time.Time `json:"startTime"` diff --git a/public/app/core/angular_wrappers.ts b/public/app/core/angular_wrappers.ts index 92657fd80e7..36bf0cd9aeb 100644 --- a/public/app/core/angular_wrappers.ts +++ b/public/app/core/angular_wrappers.ts @@ -90,7 +90,6 @@ export function registerAngularDirectives() { react2AngularDirective('stackdriverAnnotationQueryEditor', StackdriverAnnotationQueryEditor, [ 'target', 'onQueryChange', - 'onExecuteQuery', ['datasource', { watchDepth: 'reference' }], ['templateSrv', { watchDepth: 'reference' }], ]); diff --git a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts index 3d37a952068..311be8634c9 100644 --- a/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts +++ b/public/app/plugins/datasource/stackdriver/StackdriverMetricFindQuery.ts @@ -77,16 +77,9 @@ export default class StackdriverMetricFindQuery { return []; } const refId = 'handleLabelValuesQuery'; - const response = await this.datasource.getLabels(selectedMetricType, refId); + const labels = await this.datasource.getLabels(selectedMetricType, refId, [labelKey]); const interpolatedKey = this.datasource.templateSrv.replace(labelKey); - const [name] = interpolatedKey.split('.').reverse(); - let values = []; - if (response.meta && response.meta.metricLabels && response.meta.metricLabels.hasOwnProperty(name)) { - values = response.meta.metricLabels[name]; - } else if (response.meta && response.meta.resourceLabels && response.meta.resourceLabels.hasOwnProperty(name)) { - values = response.meta.resourceLabels[name]; - } - + const values = labels.hasOwnProperty(interpolatedKey) ? labels[interpolatedKey] : []; return values.map(this.toFindQueryResult); } @@ -95,8 +88,8 @@ export default class StackdriverMetricFindQuery { return []; } const refId = 'handleResourceTypeQueryQueryType'; - const response = await this.datasource.getLabels(selectedMetricType, refId); - return response.meta.resourceTypes ? response.meta.resourceTypes.map(this.toFindQueryResult) : []; + const labels = await this.datasource.getLabels(selectedMetricType, refId); + return labels['resource.type'].map(this.toFindQueryResult); } async handleAlignersQuery({ selectedMetricType }: any) { diff --git a/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts b/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts index 1b37dfefe98..3968e1b1ec5 100644 --- a/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts @@ -6,8 +6,7 @@ export class StackdriverAnnotationsQueryCtrl { templateSrv: TemplateSrv; /** @ngInject */ - constructor(templateSrv: TemplateSrv) { - this.templateSrv = templateSrv; + constructor() { this.annotation.target = this.annotation.target || {}; this.onQueryChange = this.onQueryChange.bind(this); } diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx index 4c81e54ab4a..ad1880e9806 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.test.tsx @@ -16,6 +16,7 @@ const props: Props = { crossSeriesReducer: '', groupBys: [], children: renderProps =>
, + templateVariableOptions: [], }; describe('Aggregations', () => { @@ -32,7 +33,7 @@ describe('Aggregations', () => { wrapper = shallow(); }); it('', () => { - const options = wrapper.state().aggOptions[0].options; + const options = wrapper.state().aggOptions; expect(options.length).toEqual(11); expect(options.map((o: any) => o.value)).toEqual( expect.not.arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE']) @@ -49,8 +50,7 @@ describe('Aggregations', () => { wrapper = shallow(); }); it('', () => { - const options = wrapper.state().aggOptions[0].options; - + const options = wrapper.state().aggOptions; expect(options.length).toEqual(10); expect(options.map((o: any) => o.value)).toEqual(expect.arrayContaining(['REDUCE_NONE'])); }); diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx index 0ab20eb22ce..f3299bc9959 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx @@ -1,14 +1,13 @@ import React from 'react'; import _ from 'lodash'; -import { MetricSelect } from 'app/core/components/Select/MetricSelect'; +import { SelectableValue } from '@grafana/data'; +import { Segment } from '@grafana/ui'; import { getAggregationOptionsByMetric } from '../functions'; -import { TemplateSrv } from 'app/features/templating/template_srv'; import { ValueTypes, MetricKind } from '../constants'; export interface Props { onChange: (metricDescriptor: any) => void; - templateSrv: TemplateSrv; metricDescriptor: { valueType: string; metricKind: string; @@ -16,6 +15,7 @@ export interface Props { crossSeriesReducer: string; groupBys: string[]; children?: (renderProps: any) => JSX.Element; + templateVariableOptions: Array>; } export interface State { @@ -40,19 +40,13 @@ export class Aggregations extends React.Component { setAggOptions({ metricDescriptor }: Props) { let aggOptions: any[] = []; if (metricDescriptor) { - aggOptions = [ - { - label: 'Aggregations', - expanded: true, - options: getAggregationOptionsByMetric( - metricDescriptor.valueType as ValueTypes, - metricDescriptor.metricKind as MetricKind - ).map(a => ({ - ...a, - label: a.text, - })), - }, - ]; + aggOptions = getAggregationOptionsByMetric( + metricDescriptor.valueType as ValueTypes, + metricDescriptor.metricKind as MetricKind + ).map(a => ({ + ...a, + label: a.text, + })); } this.setState({ aggOptions }); } @@ -65,22 +59,28 @@ export class Aggregations extends React.Component { render() { const { displayAdvancedOptions, aggOptions } = this.state; - const { templateSrv, onChange, crossSeriesReducer } = this.props; + const { templateVariableOptions, onChange, crossSeriesReducer } = this.props; return ( <>
-
- - -
+ + onChange(value)} + value={[...aggOptions, ...templateVariableOptions].find(s => s.value === crossSeriesReducer)} + options={[ + { + label: 'Template Variables', + options: templateVariableOptions, + }, + { + label: 'Aggregations', + expanded: true, + options: aggOptions, + }, + ]} + placeholder="Select Reducer" + >