From 847f5491bf906604f84c26020cb6c840d45c7840 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 13:14:55 +0100 Subject: [PATCH 001/166] Wrap react select component in angular directive --- .../stackdriver/components/OptionPicker.tsx | 48 +++++++++++++++++++ .../stackdriver/partials/query.filter.html | 34 ++++++------- .../datasource/stackdriver/query_ctrl.ts | 9 ++++ .../stackdriver/query_filter_ctrl.ts | 16 +++++-- 4 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx new file mode 100644 index 00000000000..f83777e5f79 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +// import SimplePicker from 'app/core/components/Picker/SimplePicker'; +import Select from 'react-select'; +// import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; +import DescriptionOption from 'app/core/components/Picker/DescriptionOption'; +import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; +import ResetStyles from 'app/core/components/Picker/ResetStyles'; +import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; +import _ from 'lodash'; + +export interface Props { + onChange: (value: string) => void; + options: any[]; + selected: string; + placeholder?: string; + className?: string; +} + +export class OptionPicker extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { onChange, options, selected, placeholder, className } = this.props; + const selectedOption = options.find(metric => metric.value === selected); + return ( + + +
+ +
Metric - +
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index c2607964456..3186a5e2012 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -2,6 +2,8 @@ import _ from 'lodash'; import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; +import { OptionPicker } from './components/OptionPicker'; +import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface QueryMeta { alignmentPeriod: string; @@ -64,6 +66,13 @@ export class StackdriverQueryCtrl extends QueryCtrl { _.defaultsDeep(this.target, this.defaults); this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); + react2AngularDirective('optionPicker', OptionPicker, [ + 'options', + 'onChange', + 'selected', + 'className', + 'placeholder', + ]); } onDataReceived(dataList) { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 0f5dce559fd..7926da9aa1c 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -59,6 +59,13 @@ export class StackdriverFilterCtrl { .then(this.getLabels.bind(this)); this.initSegments($scope.hideGroupBys); + this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); + this.handleServiceChange = this.handleServiceChange.bind(this); + } + + handleMetricTypeChange(value) { + this.metricType = value; + this.onMetricTypeChange(); } initSegments(hideGroupBys: boolean) { @@ -110,7 +117,7 @@ export class StackdriverFilterCtrl { const services = this.metricDescriptors.map(m => { return { value: m.service, - text: m.serviceShortName, + label: m.serviceShortName, }; }); @@ -128,7 +135,8 @@ export class StackdriverFilterCtrl { value: m.type, serviceShortName: m.serviceShortName, text: m.displayName, - title: m.description, + label: m.displayName, + description: m.description, }; }); @@ -167,8 +175,8 @@ export class StackdriverFilterCtrl { }); } - onServiceChange() { - this.target.service = this.service; + handleServiceChange(service) { + this.target.service = this.service = service; this.metrics = this.getMetricsList(); this.setMetricType(); this.getLabels(); From b18f06481719b2322e45777575ba3c17f9452feb Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:11:18 +0100 Subject: [PATCH 002/166] wip: add basic option header --- public/app/core/components/Picker/GroupHeading.tsx | 12 ++++++++++++ public/sass/_grafana.scss | 1 + public/sass/components/_group-heading.scss | 5 +++++ 3 files changed, 18 insertions(+) create mode 100644 public/app/core/components/Picker/GroupHeading.tsx create mode 100644 public/sass/components/_group-heading.scss diff --git a/public/app/core/components/Picker/GroupHeading.tsx b/public/app/core/components/Picker/GroupHeading.tsx new file mode 100644 index 00000000000..76012ed3e7f --- /dev/null +++ b/public/app/core/components/Picker/GroupHeading.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { components } from 'react-select'; + +export const GroupHeading = props => { + return ( + + ); +}; + +export default GroupHeading; diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index dac8efcccf0..eb62c5ab612 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -103,6 +103,7 @@ @import 'components/add_data_source.scss'; @import 'components/page_loader'; @import 'components/toggle_button_group'; +@import 'components/group-heading'; // PAGES @import 'pages/login'; diff --git a/public/sass/components/_group-heading.scss b/public/sass/components/_group-heading.scss new file mode 100644 index 00000000000..9656277d123 --- /dev/null +++ b/public/sass/components/_group-heading.scss @@ -0,0 +1,5 @@ +.picker-option-group { + cursor: default; + font-weight: $lead-font-weight; + color: $btn-primary-bg-hl; +} From ab5e5de8147b82d49b11dc50ef0727c219231722 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:12:33 +0100 Subject: [PATCH 003/166] wip: add option group component --- .../components/OptionGroupPicker.tsx | 53 +++++++++++++++++++ .../stackdriver/components/OptionPicker.tsx | 15 +++--- .../stackdriver/partials/query.filter.html | 14 ++--- .../datasource/stackdriver/query_ctrl.ts | 10 ++++ .../stackdriver/query_filter_ctrl.ts | 39 +++++++++++++- 5 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx new file mode 100644 index 00000000000..7401b226413 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import Select from 'react-select'; +import _ from 'lodash'; + +import GroupHeading from 'app/core/components/Picker/GroupHeading'; +import DescriptionOption from 'app/core/components/Picker/DescriptionOption'; +import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; +import ResetStyles from 'app/core/components/Picker/ResetStyles'; +import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; + +export interface Props { + onChange: (value: string) => void; + groups: any[]; + searchable: boolean; + selected: string; + placeholder?: string; + className?: string; +} + +export class OptionGroupPicker extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { onChange, groups, selected, placeholder, className, searchable } = this.props; + const options = _.flatten(groups.map(o => o.options)); + const selectedOption = options.find(option => option.value === selected); + + return ( + { NoOptionsMessage, }} styles={ResetStyles} - isDisabled={false} + isSearchable={searchable} + maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index 1d87c8a3005..df3821ac463 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -5,8 +5,9 @@ onChange="ctrl.handleServiceChange" selected="ctrl.service" options="ctrl.services" - placeholder="ctrl.defaultDropdownValue" - className="width-12" + searchable="false" + placeholder="ctrl.defaultServiceValue" + className=""width-15"" >
@@ -14,13 +15,14 @@
Metric - + className=""width-15"" + >
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 3186a5e2012..8c00d6caddb 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -3,6 +3,7 @@ import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; import { OptionPicker } from './components/OptionPicker'; +import { OptionGroupPicker } from './components/OptionGroupPicker'; import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface QueryMeta { @@ -70,6 +71,15 @@ export class StackdriverQueryCtrl extends QueryCtrl { 'options', 'onChange', 'selected', + 'searchable', + 'className', + 'placeholder', + ]); + react2AngularDirective('optionGroupPicker', OptionGroupPicker, [ + 'groups', + 'onChange', + 'selected', + 'searchable', 'className', 'placeholder', ]); diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 7926da9aa1c..dd7178ecca0 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -36,6 +36,7 @@ export class StackdriverFilterCtrl { metricType: string; metricDescriptors: any[]; metrics: any[]; + metricGroups: any[]; services: any[]; groupBySegments: any[]; filterSegments: FilterSegments; @@ -52,6 +53,7 @@ export class StackdriverFilterCtrl { this.metricDescriptors = []; this.metrics = []; + this.metricGroups = []; this.services = []; this.getCurrentProject() @@ -106,6 +108,7 @@ export class StackdriverFilterCtrl { this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject); this.services = this.getServicesList(); this.metrics = this.getMetricsList(); + this.metricGroups = this.getMetricGroups(); return this.metricDescriptors; } else { return []; @@ -113,11 +116,11 @@ export class StackdriverFilterCtrl { } getServicesList() { - const defaultValue = { value: this.$scope.defaultServiceValue, text: this.$scope.defaultServiceValue }; + const defaultValue = { value: this.$scope.defaultServiceValue, label: this.$scope.defaultServiceValue }; const services = this.metricDescriptors.map(m => { return { value: m.service, - label: m.serviceShortName, + label: _.startCase(m.serviceShortName), }; }); @@ -128,6 +131,37 @@ export class StackdriverFilterCtrl { return services.length > 0 ? [defaultValue, ..._.uniqBy(services, 'value')] : []; } + getMetricGroups() { + return this.metrics.reduce((acc, curr) => { + const group = acc.find(group => group.service === curr.service); + if (group) { + group.options = [...group.options, { value: curr.value, label: curr.label }]; + } else { + acc = [ + ...acc, + { + label: _.startCase(curr.serviceShortName), + service: curr.service, + options: [{ value: curr.value, label: curr.label }], + }, + ]; + } + return acc; + }, []); + } + + insertTemplateVariables(options) { + const templateVariables = { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + description: `$${v.definition}`, + })), + }; + return [templateVariables, { label: 'Metrics', options }]; + } + getMetricsList() { const metrics = this.metricDescriptors.map(m => { return { @@ -178,6 +212,7 @@ export class StackdriverFilterCtrl { handleServiceChange(service) { this.target.service = this.service = service; this.metrics = this.getMetricsList(); + this.metricGroups = this.getMetricGroups(); this.setMetricType(); this.getLabels(); if (!this.metrics.find(m => m.value === this.target.metricType)) { From 33146b248ec0c94a9d61b4d3df1cc7e03c0986aa Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:14:06 +0100 Subject: [PATCH 004/166] remove redundant default value --- .../datasource/stackdriver/datasource.ts | 1 + .../stackdriver/partials/query.editor.html | 3 +- .../stackdriver/partials/query.filter.html | 12 ++-- .../datasource/stackdriver/query_ctrl.ts | 7 +- .../stackdriver/query_filter_ctrl.ts | 72 +++++++++++-------- .../specs/query_filter_ctrl.test.ts | 2 - 6 files changed, 53 insertions(+), 44 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index d1545655652..7b1afbae5ac 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -274,6 +274,7 @@ export default class StackdriverDatasource { m.service = service; m.serviceShortName = serviceShortName; m.displayName = m.displayName || m.type; + return m; }); } diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 5c7bc8935b1..66961d106aa 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,6 +1,5 @@ - +
diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index df3821ac463..583744d0e67 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -6,8 +6,8 @@ selected="ctrl.service" options="ctrl.services" searchable="false" - placeholder="ctrl.defaultServiceValue" - className=""width-15"" + placeholder="'Select Services'" + className="'width-15'" >
@@ -17,11 +17,11 @@ Metric
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 8c00d6caddb..cf22802ae76 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -34,13 +34,10 @@ export class StackdriverQueryCtrl extends QueryCtrl { valueType: any; }; - defaultDropdownValue = 'Select Metric'; - defaultServiceValue = 'All Services'; - defaults = { defaultProject: 'loading project...', - metricType: this.defaultDropdownValue, - service: this.defaultServiceValue, + metricType: '', + service: '', metric: '', unit: '', aggregation: { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index dd7178ecca0..ba96cfc5a60 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -15,8 +15,6 @@ export class StackdriverFilter { target: '=', datasource: '=', refresh: '&', - defaultDropdownValue: '<', - defaultServiceValue: '<', hideGroupBys: '<', }, }; @@ -48,9 +46,6 @@ export class StackdriverFilterCtrl { constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) { this.datasource = $scope.datasource; this.target = $scope.target; - this.metricType = $scope.defaultDropdownValue; - this.service = $scope.defaultServiceValue; - this.metricDescriptors = []; this.metrics = []; this.metricGroups = []; @@ -66,7 +61,7 @@ export class StackdriverFilterCtrl { } handleMetricTypeChange(value) { - this.metricType = value; + this.target.metricType = value; this.onMetricTypeChange(); } @@ -116,7 +111,6 @@ export class StackdriverFilterCtrl { } getServicesList() { - const defaultValue = { value: this.$scope.defaultServiceValue, label: this.$scope.defaultServiceValue }; const services = this.metricDescriptors.map(m => { return { value: m.service, @@ -128,10 +122,10 @@ export class StackdriverFilterCtrl { this.service = this.target.service; } - return services.length > 0 ? [defaultValue, ..._.uniqBy(services, 'value')] : []; + return services.length > 0 ? _.uniqBy(services, 'value') : []; } - getMetricGroups() { + getMetricGroupsOld() { return this.metrics.reduce((acc, curr) => { const group = acc.find(group => group.service === curr.service); if (group) { @@ -150,6 +144,27 @@ export class StackdriverFilterCtrl { }, []); } + getMetricGroups() { + return [ + this.getTemplateVariablesGroup(), + { + label: 'Metrics', + options: this.metrics, + }, + ]; + } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + description: `$${v.definition}`, + })), + }; + } + insertTemplateVariables(options) { const templateVariables = { label: 'Template Variables', @@ -174,29 +189,28 @@ export class StackdriverFilterCtrl { }; }); - let result; - if (this.target.service === this.$scope.defaultServiceValue) { - result = metrics.map(m => ({ ...m, text: `${m.service} - ${m.text}` })); - } else { - result = metrics.filter(m => m.service === this.target.service); + const metricsByService = metrics.filter(m => m.service === this.target.service); + if ( + metricsByService.length > 0 && + !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) + ) { + this.target.metricType = metricsByService[0].value; } - - if (result.find(m => m.value === this.templateSrv.replace(this.target.metricType))) { - this.metricType = this.target.metricType; - } else if (result.length > 0) { - this.metricType = this.target.metricType = result[0].value; - } - return result; + return metricsByService; } async getLabels() { this.loadLabelsPromise = new Promise(async resolve => { try { - const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); - this.metricLabels = meta.metricLabels; - this.resourceLabels = meta.resourceLabels; - this.resourceTypes = meta.resourceTypes; - resolve(); + if (this.target.metricType) { + const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); + this.metricLabels = meta.metricLabels; + this.resourceLabels = meta.resourceLabels; + this.resourceTypes = meta.resourceTypes; + resolve(); + } else { + resolve(); + } } catch (error) { if (error.data && error.data.message) { console.log(error.data.message); @@ -216,7 +230,7 @@ export class StackdriverFilterCtrl { this.setMetricType(); this.getLabels(); if (!this.metrics.find(m => m.value === this.target.metricType)) { - this.target.metricType = this.$scope.defaultDropdownValue; + this.target.metricType = ''; } else { this.$scope.refresh(); } @@ -229,9 +243,9 @@ export class StackdriverFilterCtrl { } setMetricType() { - this.target.metricType = this.metricType; + // this.target.metricType = this.metricType; const { valueType, metricKind, unit } = this.metricDescriptors.find( - m => m.type === this.templateSrv.replace(this.metricType) + m => m.type === this.templateSrv.replace(this.target.metricType) ); this.target.unit = unit; this.target.valueType = valueType; diff --git a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts index 020db584508..e8d04357cb8 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts @@ -409,8 +409,6 @@ function createCtrlWithFakes(existingFilters?: string[]) { return 'project'; }, }, - defaultDropdownValue: 'Select Metric', - defaultServiceValue: 'All Services', refresh: () => {}, }; From ae922747dbefc069a86448cc5b56c8e091e42645 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:57:30 +0100 Subject: [PATCH 005/166] use new option group in aggregation directive --- .../partials/query.aggregation.html | 43 ++++++---- .../stackdriver/partials/query.editor.html | 3 +- .../stackdriver/query_aggregation_ctrl.ts | 79 ++++++++++++++++--- 3 files changed, 97 insertions(+), 28 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html b/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html index 5f16d3fc61d..7396c6eb546 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html @@ -2,16 +2,21 @@
- +
@@ -20,27 +25,35 @@
- +
-
-
-
+
- +
- +
-
\ No newline at end of file + diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 66961d106aa..8760aaf1bb3 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,5 +1,6 @@ - +
diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 628cc494242..f5ff3ad3143 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -1,6 +1,6 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; -import * as options from './constants'; +import { alignmentPeriods } from './constants'; import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from './functions'; import kbn from 'app/core/utils/kbn'; @@ -29,9 +29,16 @@ export class StackdriverAggregationCtrl { constructor(private $scope, private templateSrv) { this.$scope.ctrl = this; this.target = $scope.target; - this.alignmentPeriods = options.alignmentPeriods; - this.aggOptions = options.aggOptions; - this.alignOptions = options.alignOptions; + this.alignmentPeriods = [ + this.getTemplateVariablesGroup(), + { + label: 'Alignment Periods', + options: alignmentPeriods.map(ap => ({ + ...ap, + label: ap.text, + })), + }, + ]; this.setAggOptions(); this.setAlignOptions(); const self = this; @@ -39,30 +46,68 @@ export class StackdriverAggregationCtrl { self.setAggOptions(); self.setAlignOptions(); }); + this.handleAlignmentChange = this.handleAlignmentChange.bind(this); + this.handleAggregationChange = this.handleAggregationChange.bind(this); + this.handleAlignmentPeriodChange = this.handleAlignmentPeriodChange.bind(this); } setAlignOptions() { - this.alignOptions = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); - if (!this.alignOptions.find(o => o.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner))) { - this.target.aggregation.perSeriesAligner = this.alignOptions.length > 0 ? this.alignOptions[0].value : ''; + const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ + ...a, + label: a.text, + })); + this.alignOptions = [ + this.getTemplateVariablesGroup(), + { + label: 'Alignment Options', + options: alignments, + }, + ]; + if (!alignments.find(o => o.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner))) { + this.target.aggregation.perSeriesAligner = alignments.length > 0 ? alignments[0].value : ''; } } setAggOptions() { - this.aggOptions = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); - - if (!this.aggOptions.find(o => o.value === this.templateSrv.replace(this.target.aggregation.crossSeriesReducer))) { + let aggregations = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ + ...a, + label: a.text, + })); + if (!aggregations.find(o => o.value === this.templateSrv.replace(this.target.aggregation.crossSeriesReducer))) { this.deselectAggregationOption('REDUCE_NONE'); } if (this.target.aggregation.groupBys.length > 0) { - this.aggOptions = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); + aggregations = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } + this.aggOptions = [ + this.getTemplateVariablesGroup(), + { + label: 'Aggregations', + options: aggregations, + }, + ]; + } + + handleAlignmentChange(value) { + this.target.aggregation.perSeriesAligner = value; + this.$scope.refresh(); + } + + handleAggregationChange(value) { + this.target.aggregation.crossSeriesReducer = value; + this.$scope.refresh(); + } + + handleAlignmentPeriodChange(value) { + this.target.aggregation.alignmentPeriod = value; + this.$scope.refresh(); } formatAlignmentText() { - const selectedAlignment = this.alignOptions.find( + const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); + const selectedAlignment = alignments.find( ap => ap.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner) ); return `${kbn.secondsToHms(this.$scope.alignmentPeriod)} interval (${ @@ -74,6 +119,16 @@ export class StackdriverAggregationCtrl { const newValue = this.aggOptions.find(o => o.value !== notValidOptionValue); this.target.aggregation.crossSeriesReducer = newValue ? newValue.value : ''; } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + })), + }; + } } coreModule.directive('stackdriverAggregation', StackdriverAggregation); From bcb218f57c136678706447a5abaaf9c654e1c6e2 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:55:23 +0100 Subject: [PATCH 006/166] remove redundant default value --- .../datasource/stackdriver/components/OptionGroupPicker.tsx | 1 - .../plugins/datasource/stackdriver/components/OptionPicker.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx index 7401b226413..f7863dd2a16 100644 --- a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx @@ -41,7 +41,6 @@ export class OptionGroupPicker extends React.Component { }} styles={ResetStyles} isSearchable={searchable} - maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} diff --git a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx index 3d99ed3bafc..64d68f1bd70 100644 --- a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx @@ -39,7 +39,6 @@ export class OptionPicker extends React.Component { }} styles={ResetStyles} isSearchable={searchable} - maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} From ad9a35198d7f936a4d40deca5a99524a4407bda0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:57:57 +0100 Subject: [PATCH 007/166] update failing tests --- .../stackdriver/query_aggregation_ctrl.ts | 6 +- .../stackdriver/query_filter_ctrl.ts | 14 +---- .../specs/query_aggregation_ctrl.test.ts | 57 +++++++++++++++++-- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index f5ff3ad3143..d4478c6214c 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -52,6 +52,7 @@ export class StackdriverAggregationCtrl { } setAlignOptions() { + console.log('this.target.metricKind', this.target.metricKind); const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ ...a, label: a.text, @@ -78,7 +79,7 @@ export class StackdriverAggregationCtrl { } if (this.target.aggregation.groupBys.length > 0) { - aggregations = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); + aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } this.aggOptions = [ @@ -116,7 +117,8 @@ export class StackdriverAggregationCtrl { } deselectAggregationOption(notValidOptionValue: string) { - const newValue = this.aggOptions.find(o => o.value !== notValidOptionValue); + const aggregations = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); + const newValue = aggregations.find(o => o.value !== notValidOptionValue); this.target.aggregation.crossSeriesReducer = newValue ? newValue.value : ''; } diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index ba96cfc5a60..74ca2c9b067 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -160,23 +160,11 @@ export class StackdriverFilterCtrl { options: this.templateSrv.variables.map(v => ({ label: `$${v.name}`, value: `$${v.name}`, - description: `$${v.definition}`, + // description: `$${v.definition}`, })), }; } - insertTemplateVariables(options) { - const templateVariables = { - label: 'Template Variables', - options: this.templateSrv.variables.map(v => ({ - label: `$${v.name}`, - value: `$${v.name}`, - description: `$${v.definition}`, - })), - }; - return [templateVariables, { label: 'Metrics', options }]; - } - getMetricsList() { const metrics = this.metricDescriptors.map(m => { return { diff --git a/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts index 81011f5dfe0..6e83824d504 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts @@ -17,27 +17,68 @@ describe('StackdriverAggregationCtrl', () => { }, { replace: s => s, + variables: [{ name: 'someVariable1' }, { name: 'someVariable2' }], } ); }); it('should populate all aggregate options except two', () => { ctrl.setAggOptions(); - expect(ctrl.aggOptions.length).toBe(11); - expect(ctrl.aggOptions.map(o => o.value)).toEqual( + expect(ctrl.aggOptions.length).toBe(2); + const [templateVariableGroup, aggOptionsGroup] = ctrl.aggOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(aggOptionsGroup.options.length).toBe(11); + expect(aggOptionsGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE']) ); }); it('should populate all alignment options except two', () => { ctrl.setAlignOptions(); - expect(ctrl.alignOptions.length).toBe(9); - expect(ctrl.alignOptions.map(o => o.value)).toEqual( + const [templateVariableGroup, alignOptionGroup] = ctrl.aggOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(alignOptionGroup.options.length).toBe(11); + expect(alignOptionGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE']) ); }); }); + describe('and result is double and delta and no group by is used', () => { + beforeEach(async () => { + ctrl = new StackdriverAggregationCtrl( + { + $on: () => {}, + target: { + valueType: 'DOUBLE', + metricKind: 'DELTA', + aggregation: { crossSeriesReducer: '', groupBys: [] }, + }, + }, + { + replace: s => s, + variables: [{ name: 'someVariable1' }, { name: 'someVariable2' }], + } + ); + }); + + it('should populate all alignment options except four', () => { + ctrl.setAlignOptions(); + const [templateVariableGroup, alignOptionGroup] = ctrl.alignOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(alignOptionGroup.options.length).toBe(9); + expect(alignOptionGroup.options.map(o => o.value)).toEqual( + expect['not'].arrayContaining([ + 'ALIGN_NEXT_OLDER', + 'ALIGN_INTERPOLATE', + 'ALIGN_COUNT_TRUE', + 'ALIGN_COUNT_FALSE', + 'ALIGN_FRACTION_TRUE', + ]) + ); + }); + }); + describe('and result is double and gauge and a group by is used', () => { beforeEach(async () => { ctrl = new StackdriverAggregationCtrl( @@ -51,14 +92,18 @@ describe('StackdriverAggregationCtrl', () => { }, { replace: s => s, + variables: [{ name: 'someVariable1' }], } ); }); it('should populate all aggregate options except three', () => { ctrl.setAggOptions(); - expect(ctrl.aggOptions.length).toBe(10); - expect(ctrl.aggOptions.map(o => o.value)).toEqual( + const [templateVariableGroup, aggOptionsGroup] = ctrl.aggOptions; + expect(ctrl.aggOptions.length).toBe(2); + expect(templateVariableGroup.options.length).toBe(1); + expect(aggOptionsGroup.options.length).toBe(10); + expect(aggOptionsGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE', 'REDUCE_NONE']) ); }); From ac416ba88052fa7a46d1d2cf0d5a93be6f0c846a Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:57:32 +0100 Subject: [PATCH 008/166] cleanup query filter --- .../stackdriver/partials/query.filter.html | 2 +- .../stackdriver/query_filter_ctrl.ts | 62 +++++-------------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index 583744d0e67..632f89b8248 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -3,7 +3,7 @@ Service ; - service: string; - metricType: string; metricDescriptors: any[]; metrics: any[]; metricGroups: any[]; @@ -111,37 +109,12 @@ export class StackdriverFilterCtrl { } getServicesList() { - const services = this.metricDescriptors.map(m => { - return { - value: m.service, - label: _.startCase(m.serviceShortName), - }; - }); + const services = this.metricDescriptors.map(m => ({ + value: m.service, + label: _.startCase(m.serviceShortName), + })); - if (services.find(m => m.value === this.target.service)) { - this.service = this.target.service; - } - - return services.length > 0 ? _.uniqBy(services, 'value') : []; - } - - getMetricGroupsOld() { - return this.metrics.reduce((acc, curr) => { - const group = acc.find(group => group.service === curr.service); - if (group) { - group.options = [...group.options, { value: curr.value, label: curr.label }]; - } else { - acc = [ - ...acc, - { - label: _.startCase(curr.serviceShortName), - service: curr.service, - options: [{ value: curr.value, label: curr.label }], - }, - ]; - } - return acc; - }, []); + return services.length > 0 ? _.uniqBy(services, s => s.value) : []; } getMetricGroups() { @@ -149,7 +122,7 @@ export class StackdriverFilterCtrl { this.getTemplateVariablesGroup(), { label: 'Metrics', - options: this.metrics, + options: this.getMetricsList(), }, ]; } @@ -160,24 +133,18 @@ export class StackdriverFilterCtrl { options: this.templateSrv.variables.map(v => ({ label: `$${v.name}`, value: `$${v.name}`, - // description: `$${v.definition}`, })), }; } getMetricsList() { - const metrics = this.metricDescriptors.map(m => { - return { - service: m.service, - value: m.type, - serviceShortName: m.serviceShortName, - text: m.displayName, - label: m.displayName, - description: m.description, - }; - }); + const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({ + service: m.service, + value: m.type, + label: m.displayName, + description: m.description, + })); - const metricsByService = metrics.filter(m => m.service === this.target.service); if ( metricsByService.length > 0 && !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) @@ -212,12 +179,12 @@ export class StackdriverFilterCtrl { } handleServiceChange(service) { - this.target.service = this.service = service; + this.target.service = service; this.metrics = this.getMetricsList(); this.metricGroups = this.getMetricGroups(); this.setMetricType(); this.getLabels(); - if (!this.metrics.find(m => m.value === this.target.metricType)) { + if (!this.metrics.some(m => m.value === this.target.metricType)) { this.target.metricType = ''; } else { this.$scope.refresh(); @@ -231,7 +198,6 @@ export class StackdriverFilterCtrl { } setMetricType() { - // this.target.metricType = this.metricType; const { valueType, metricKind, unit } = this.metricDescriptors.find( m => m.type === this.templateSrv.replace(this.target.metricType) ); From d154d160613edb5daf6945e8b0832acd10a3f6a3 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:58:25 +0100 Subject: [PATCH 009/166] use same color for label as in explore dropdown --- public/sass/components/_group-heading.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_group-heading.scss b/public/sass/components/_group-heading.scss index 9656277d123..8fc39c0dc2a 100644 --- a/public/sass/components/_group-heading.scss +++ b/public/sass/components/_group-heading.scss @@ -1,5 +1,6 @@ .picker-option-group { cursor: default; + color: $text-color-weak; + font-size: $font-size-sm; font-weight: $lead-font-weight; - color: $btn-primary-bg-hl; } From f6109d21d269159c67462b4e762b9b588648f72f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:59:13 +0100 Subject: [PATCH 010/166] fix remove filter bug --- .../app/plugins/datasource/stackdriver/query_filter_ctrl.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index d270290abb2..8ce2dfde554 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -252,8 +252,10 @@ export class StackdriverFilterCtrl { return []; } - this.removeSegment.value = removeText; - return [...elements, this.removeSegment]; + return [ + ...elements, + this.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.defaultRemoveGroupByValue }), + ]; } async getGroupBys(segment) { From 7cc42d7c130eab2667958b56f32e9351a38edba0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 14:08:47 +0100 Subject: [PATCH 011/166] remove on metric type change --- .../datasource/stackdriver/query_filter_ctrl.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 8ce2dfde554..e617dba5945 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -60,7 +60,9 @@ export class StackdriverFilterCtrl { handleMetricTypeChange(value) { this.target.metricType = value; - this.onMetricTypeChange(); + this.setMetricType(); + this.$scope.refresh(); + this.getLabels(); } initSegments(hideGroupBys: boolean) { @@ -191,12 +193,6 @@ export class StackdriverFilterCtrl { } } - async onMetricTypeChange() { - this.setMetricType(); - this.$scope.refresh(); - this.getLabels(); - } - setMetricType() { const { valueType, metricKind, unit } = this.metricDescriptors.find( m => m.type === this.templateSrv.replace(this.target.metricType) From ad55be9865b4a62f0b45e63ca8e8c960f8b12726 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 13 Dec 2018 00:16:48 +0100 Subject: [PATCH 012/166] break out metric picker and filter --- .../stackdriver/components/MetricPicker.tsx | 181 ++++++++++ .../stackdriver/partials/query.editor.html | 6 +- .../stackdriver/partials/query.filter.html | 54 +-- .../stackdriver/query_aggregation_ctrl.ts | 1 - .../datasource/stackdriver/query_ctrl.ts | 47 ++- .../stackdriver/query_filter_ctrl.ts | 195 +++-------- .../stackdriver/query_filter_ctrl2.ts | 330 ++++++++++++++++++ .../specs/query_filter_ctrl.test.ts | 12 +- 8 files changed, 642 insertions(+), 184 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/MetricPicker.tsx create mode 100644 public/app/plugins/datasource/stackdriver/query_filter_ctrl2.ts diff --git a/public/app/plugins/datasource/stackdriver/components/MetricPicker.tsx b/public/app/plugins/datasource/stackdriver/components/MetricPicker.tsx new file mode 100644 index 00000000000..c3d7d6fda22 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/MetricPicker.tsx @@ -0,0 +1,181 @@ +import React from 'react'; +import _ from 'lodash'; + +import { OptionPicker } from './OptionPicker'; +import { OptionGroupPicker } from './OptionGroupPicker'; + +export interface Props { + onChange: (metricDescriptor) => void; + templateSrv: any; + datasource: any; + defaultProject: string; + metricType: string; +} + +interface State { + metricDescriptors: any[]; + metrics: any[]; + services: any[]; + service: string; + metric: string; +} + +export class MetricPicker extends React.Component { + state: State = { + metricDescriptors: [], + metrics: [], + services: [], + service: '', + metric: '', + }; + + constructor(props) { + super(props); + } + + componentDidMount() { + this.getCurrentProject() + .then(this.loadMetricDescriptors.bind(this)) + .then(this.initializeServiceAndMetrics.bind(this)); + // .then(this.getLabels.bind(this)); + } + + async getCurrentProject() { + return new Promise(async (resolve, reject) => { + try { + if (!this.props.defaultProject || this.props.defaultProject === 'loading project...') { + // this.props.defaultProject = await this.props.datasource.getDefaultProject(); + await this.props.datasource.getDefaultProject(); + } + resolve(this.props.defaultProject); + } catch (error) { + // appEvents.emit('ds-request-error', error); + reject(); + } + }); + } + + async loadMetricDescriptors() { + if (this.props.defaultProject !== 'loading project...') { + const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.defaultProject); + this.setState({ metricDescriptors }); + return metricDescriptors; + } else { + return []; + } + } + + async initializeServiceAndMetrics() { + const { metricDescriptors } = this.state; + const services = this.getServicesList(metricDescriptors); + const metrics = this.getMetricsList(metricDescriptors); + const service = metrics.length > 0 ? metrics[0].service : ''; + this.setState({ metricDescriptors, services, metrics, service: service }); + } + + getMetricsList(metricDescriptors) { + const selectedMetricDescriptor = metricDescriptors.find(md => md.type === this.props.metricType); + const metricsByService = metricDescriptors.filter(m => m.service === selectedMetricDescriptor.service).map(m => ({ + service: m.service, + value: m.type, + label: m.displayName, + description: m.description, + })); + return metricsByService; + } + + handleServiceChange(service) { + console.log('handleServiceChange', service); + const { metricDescriptors } = this.state; + const { templateSrv, metricType } = this.props; + + const metrics = metricDescriptors.filter(m => m.service === templateSrv.replace(service)).map(m => ({ + service: m.service, + value: m.type, + label: m.displayName, + description: m.description, + })); + + this.setState({ service, metrics }); + + if (metrics.length > 0 && !metrics.some(m => m.value === templateSrv.replace(metricType))) { + this.handleMetricTypeChange(metrics[0].value); + } + } + + handleMetricTypeChange(value) { + const selectedMetricDescriptor = this.state.metricDescriptors.find(md => md.type === value); + this.props.onChange(selectedMetricDescriptor); + } + + getServicesList(metricDescriptors) { + const services = metricDescriptors.map(m => ({ + value: m.service, + label: _.startCase(m.serviceShortName), + })); + + return services.length > 0 ? _.uniqBy(services, s => s.value) : []; + } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.props.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + })), + }; + } + + getMetricGroups() { + return [ + this.getTemplateVariablesGroup(), + { + label: 'Metrics', + options: this.state.metrics, + }, + ]; + } + + render() { + const { services, service } = this.state; + const { metricType } = this.props; + + return ( + +
+
+ Service + this.handleServiceChange(value)} + selected={service} + options={services} + searchable={false} + placeholder="Select Services" + className="width-15" + /> +
+
+
+
+
+
+
+ Metric + this.handleMetricTypeChange(value)} + selected={metricType} + groups={this.getMetricGroups()} + searchable={true} + placeholder="Select Metric" + className="width-15" + /> +
+
+
+
+
+ + ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 8760aaf1bb3..a0a37a5ab83 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,7 +1,11 @@ - + +
Alias By diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index 632f89b8248..34eaee0ee3a 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -1,31 +1,33 @@ -
-
- Service - +
Filter diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index d4478c6214c..0077c74a46e 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -52,7 +52,6 @@ export class StackdriverAggregationCtrl { } setAlignOptions() { - console.log('this.target.metricKind', this.target.metricKind); const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ ...a, label: a.text, diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index cf22802ae76..8feb2f7dec1 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -1,7 +1,9 @@ import _ from 'lodash'; +import appEvents from 'app/core/app_events'; import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; +import { MetricPicker } from './components/MetricPicker'; import { OptionPicker } from './components/OptionPicker'; import { OptionGroupPicker } from './components/OptionGroupPicker'; import { react2AngularDirective } from 'app/core/utils/react2angular'; @@ -57,13 +59,19 @@ export class StackdriverQueryCtrl extends QueryCtrl { showLastQuery: boolean; lastQueryMeta: QueryMeta; lastQueryError?: string; + labelData: QueryMeta; + + loadLabelsPromise: Promise; + templateSrv: any; /** @ngInject */ - constructor($scope, $injector) { + constructor($scope, $injector, templateSrv, private $rootScope) { super($scope, $injector); + this.templateSrv = templateSrv; _.defaultsDeep(this.target, this.defaults); this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); + this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); react2AngularDirective('optionPicker', OptionPicker, [ 'options', 'onChange', @@ -80,6 +88,43 @@ export class StackdriverQueryCtrl extends QueryCtrl { 'className', 'placeholder', ]); + react2AngularDirective('metricPicker', MetricPicker, [ + 'target', + ['onChange', { watchDepth: 'reference' }], + 'defaultProject', + 'metricType', + ['templateSrv', { watchDepth: 'reference' }], + ['datasource', { watchDepth: 'reference' }], + ]); + this.getLabels(); + } + + handleMetricTypeChange({ valueType, metricKind, type, unit }) { + this.target.metricType = type; + this.target.unit = unit; + this.target.valueType = valueType; + this.target.metricKind = metricKind; + this.$rootScope.$broadcast('metricTypeChanged'); + this.getLabels(); + this.refresh(); + } + + async getLabels() { + this.loadLabelsPromise = new Promise(async resolve => { + try { + const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); + this.labelData = meta; + resolve(); + } catch (error) { + if (error.data && error.data.message) { + console.log(error.data.message); + } else { + console.log(error); + } + appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]); + resolve(); + } + }); } onDataReceived(dataList) { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index e617dba5945..99f5386b608 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -1,7 +1,8 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; import { FilterSegments } from './filter_segments'; -import appEvents from 'app/core/app_events'; +import { QueryMeta } from './query_ctrl'; +// import appEvents from 'app/core/app_events'; export class StackdriverFilter { /** @ngInject */ @@ -12,8 +13,9 @@ export class StackdriverFilter { controllerAs: 'ctrl', restrict: 'E', scope: { + labelData: '<', + loading: '<', target: '=', - datasource: '=', refresh: '&', hideGroupBys: '<', }, @@ -28,41 +30,26 @@ export class StackdriverFilterCtrl { defaultRemoveGroupByValue = '-- remove group by --'; resourceTypeValue = 'resource.type'; - loadLabelsPromise: Promise; metricDescriptors: any[]; metrics: any[]; - metricGroups: any[]; services: any[]; groupBySegments: any[]; filterSegments: FilterSegments; removeSegment: any; target: any; - datasource: any; + + labelData: QueryMeta; + loading: Promise; /** @ngInject */ constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) { - this.datasource = $scope.datasource; this.target = $scope.target; this.metricDescriptors = []; this.metrics = []; - this.metricGroups = []; this.services = []; - this.getCurrentProject() - .then(this.loadMetricDescriptors.bind(this)) - .then(this.getLabels.bind(this)); - this.initSegments($scope.hideGroupBys); - this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); - this.handleServiceChange = this.handleServiceChange.bind(this); - } - - handleMetricTypeChange(value) { - this.target.metricType = value; - this.setMetricType(); - this.$scope.refresh(); - this.getLabels(); } initSegments(hideGroupBys: boolean) { @@ -84,129 +71,34 @@ export class StackdriverFilterCtrl { this.filterSegments.buildSegmentModel(); } - async getCurrentProject() { - return new Promise(async (resolve, reject) => { - try { - if (!this.target.defaultProject || this.target.defaultProject === 'loading project...') { - this.target.defaultProject = await this.datasource.getDefaultProject(); - } - resolve(this.target.defaultProject); - } catch (error) { - appEvents.emit('ds-request-error', error); - reject(); - } - }); - } - - async loadMetricDescriptors() { - if (this.target.defaultProject !== 'loading project...') { - this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject); - this.services = this.getServicesList(); - this.metrics = this.getMetricsList(); - this.metricGroups = this.getMetricGroups(); - return this.metricDescriptors; - } else { - return []; - } - } - - getServicesList() { - const services = this.metricDescriptors.map(m => ({ - value: m.service, - label: _.startCase(m.serviceShortName), - })); - - return services.length > 0 ? _.uniqBy(services, s => s.value) : []; - } - - getMetricGroups() { - return [ - this.getTemplateVariablesGroup(), - { - label: 'Metrics', - options: this.getMetricsList(), - }, - ]; - } - - getTemplateVariablesGroup() { - return { - label: 'Template Variables', - options: this.templateSrv.variables.map(v => ({ - label: `$${v.name}`, - value: `$${v.name}`, - })), - }; - } - - getMetricsList() { - const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({ - service: m.service, - value: m.type, - label: m.displayName, - description: m.description, - })); - - if ( - metricsByService.length > 0 && - !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) - ) { - this.target.metricType = metricsByService[0].value; - } - return metricsByService; - } - - async getLabels() { - this.loadLabelsPromise = new Promise(async resolve => { - try { - if (this.target.metricType) { - const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); - this.metricLabels = meta.metricLabels; - this.resourceLabels = meta.resourceLabels; - this.resourceTypes = meta.resourceTypes; - resolve(); - } else { - resolve(); - } - } catch (error) { - if (error.data && error.data.message) { - console.log(error.data.message); - } else { - console.log(error); - } - appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]); - resolve(); - } - }); - } - - handleServiceChange(service) { - this.target.service = service; - this.metrics = this.getMetricsList(); - this.metricGroups = this.getMetricGroups(); - this.setMetricType(); - this.getLabels(); - if (!this.metrics.some(m => m.value === this.target.metricType)) { - this.target.metricType = ''; - } else { - this.$scope.refresh(); - } - } - - setMetricType() { - const { valueType, metricKind, unit } = this.metricDescriptors.find( - m => m.type === this.templateSrv.replace(this.target.metricType) - ); - this.target.unit = unit; - this.target.valueType = valueType; - this.target.metricKind = metricKind; - this.$rootScope.$broadcast('metricTypeChanged'); - } + // async getLabels() { + // this.loadLabelsPromise = new Promise(async resolve => { + // try { + // if (this.target.metricType) { + // const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); + // this.$scope.labelData.metricLabels = meta.metricLabels; + // this.$scope.labelData.resourceLabels = meta.resourceLabels; + // this.$scope.labelData.resourceTypes = meta.resourceTypes; + // resolve(); + // } else { + // resolve(); + // } + // } catch (error) { + // if (error.data && error.data.message) { + // console.log(error.data.message); + // } else { + // console.log(error); + // } + // appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]); + // resolve(); + // } + // }); + // } async createLabelKeyElements() { - await this.loadLabelsPromise; + await this.$scope.loading; - let elements = Object.keys(this.metricLabels || {}).map(l => { + let elements = Object.keys(this.$scope.labelData.metricLabels || {}).map(l => { return this.uiSegmentSrv.newSegment({ value: `metric.label.${l}`, expandable: false, @@ -215,7 +107,7 @@ export class StackdriverFilterCtrl { elements = [ ...elements, - ...Object.keys(this.resourceLabels || {}).map(l => { + ...Object.keys(this.$scope.labelData.resourceLabels || {}).map(l => { return this.uiSegmentSrv.newSegment({ value: `resource.label.${l}`, expandable: false, @@ -223,7 +115,7 @@ export class StackdriverFilterCtrl { }), ]; - if (this.resourceTypes && this.resourceTypes.length > 0) { + if (this.$scope.labelData.resourceTypes && this.$scope.labelData.resourceTypes.length > 0) { elements = [ ...elements, this.uiSegmentSrv.newSegment({ @@ -288,28 +180,33 @@ export class StackdriverFilterCtrl { } async getFilters(segment, index) { - const hasNoFilterKeys = this.metricLabels && Object.keys(this.metricLabels).length === 0; + const hasNoFilterKeys = + this.$scope.labelData.metricLabels && Object.keys(this.$scope.labelData.metricLabels).length === 0; return this.filterSegments.getFilters(segment, index, hasNoFilterKeys); } getFilterValues(index) { const filterKey = this.templateSrv.replace(this.filterSegments.filterSegments[index - 2].value); - if (!filterKey || !this.metricLabels || Object.keys(this.metricLabels).length === 0) { + if ( + !filterKey || + !this.$scope.labelData.metricLabels || + Object.keys(this.$scope.labelData.metricLabels).length === 0 + ) { return []; } const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7); - if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) { - return this.metricLabels[shortKey]; + if (filterKey.startsWith('metric.label.') && this.$scope.labelData.metricLabels.hasOwnProperty(shortKey)) { + return this.$scope.labelData.metricLabels[shortKey]; } - if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) { - return this.resourceLabels[shortKey]; + if (filterKey.startsWith('resource.label.') && this.$scope.labelData.resourceLabels.hasOwnProperty(shortKey)) { + return this.$scope.labelData.resourceLabels[shortKey]; } if (filterKey === this.resourceTypeValue) { - return this.resourceTypes; + return this.$scope.labelData.resourceTypes; } return []; diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl2.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl2.ts new file mode 100644 index 00000000000..1b70b86d59a --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl2.ts @@ -0,0 +1,330 @@ +// import coreModule from 'app/core/core_module'; +// import _ from 'lodash'; +// import { FilterSegments } from './filter_segments'; +// import appEvents from 'app/core/app_events'; + +// export class StackdriverFilter { +// /** @ngInject */ +// constructor() { +// return { +// templateUrl: 'public/app/plugins/datasource/stackdriver/partials/query.filter.html', +// controller: 'StackdriverFilterCtrl', +// controllerAs: 'ctrl', +// restrict: 'E', +// scope: { +// target: '=', +// datasource: '=', +// refresh: '&', +// hideGroupBys: '<', +// }, +// }; +// } +// } + +// export class StackdriverFilterCtrl { +// metricLabels: { [key: string]: string[] }; +// resourceLabels: { [key: string]: string[] }; +// resourceTypes: string[]; + +// defaultRemoveGroupByValue = '-- remove group by --'; +// resourceTypeValue = 'resource.type'; +// loadLabelsPromise: Promise; + +// metricDescriptors: any[]; +// metrics: any[]; +// services: any[]; +// groupBySegments: any[]; +// filterSegments: FilterSegments; +// removeSegment: any; +// target: any; +// datasource: any; + +// /** @ngInject */ +// constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) { +// this.datasource = $scope.datasource; +// this.target = $scope.target; +// this.metricDescriptors = []; +// this.metrics = []; +// this.services = []; + +// this.getCurrentProject() +// .then(this.loadMetricDescriptors.bind(this)) +// .then(this.getLabels.bind(this)); + +// this.initSegments($scope.hideGroupBys); +// this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); +// this.handleServiceChange = this.handleServiceChange.bind(this); +// } + +// handleMetricTypeChange(value) { +// this.target.metricType = value; +// this.setMetricType(); +// this.$scope.refresh(); +// this.getLabels(); +// } + +// initSegments(hideGroupBys: boolean) { +// if (!hideGroupBys) { +// this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => { +// return this.uiSegmentSrv.getSegmentForValue(groupBy); +// }); +// this.ensurePlusButton(this.groupBySegments); +// } + +// this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' }); + +// this.filterSegments = new FilterSegments( +// this.uiSegmentSrv, +// this.target, +// this.getFilterKeys.bind(this), +// this.getFilterValues.bind(this) +// ); +// this.filterSegments.buildSegmentModel(); +// } + +// async getCurrentProject() { +// return new Promise(async (resolve, reject) => { +// try { +// if (!this.target.defaultProject || this.target.defaultProject === 'loading project...') { +// this.target.defaultProject = await this.datasource.getDefaultProject(); +// } +// resolve(this.target.defaultProject); +// } catch (error) { +// appEvents.emit('ds-request-error', error); +// reject(); +// } +// }); +// } + +// async loadMetricDescriptors() { +// if (this.target.defaultProject !== 'loading project...') { +// this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject); +// this.services = this.getServicesList(); +// this.metrics = this.getMetricsList(); +// return this.metricDescriptors; +// } else { +// return []; +// } +// } + +// getServicesList() { +// const services = this.metricDescriptors.map(m => ({ +// value: m.service, +// label: _.startCase(m.serviceShortName), +// })); + +// return services.length > 0 ? _.uniqBy(services, s => s.value) : []; +// } + +// getMetricGroups() { +// return [ +// this.getTemplateVariablesGroup(), +// { +// label: 'Metrics', +// options: this.getMetricsList(), +// }, +// ]; +// } + +// getTemplateVariablesGroup() { +// return { +// label: 'Template Variables', +// options: this.templateSrv.variables.map(v => ({ +// label: `$${v.name}`, +// value: `$${v.name}`, +// })), +// }; +// } + +// getMetricsList() { +// const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({ +// service: m.service, +// value: m.type, +// label: m.displayName, +// description: m.description, +// })); + +// if ( +// metricsByService.length > 0 && +// !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) +// ) { +// // this.target.metricType = metricsByService[0].value; +// } +// return metricsByService; +// } + +// async getLabels() { +// this.loadLabelsPromise = new Promise(async resolve => { +// try { +// if (this.target.metricType) { +// const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); +// this.metricLabels = meta.metricLabels; +// this.resourceLabels = meta.resourceLabels; +// this.resourceTypes = meta.resourceTypes; +// resolve(); +// } else { +// resolve(); +// } +// } catch (error) { +// if (error.data && error.data.message) { +// console.log(error.data.message); +// } else { +// console.log(error); +// } +// appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]); +// resolve(); +// } +// }); +// } + +// handleServiceChange(service) { +// this.target.service = service; +// this.metrics = this.getMetricsList(); +// this.setMetricType(); +// this.getLabels(); +// if (!this.metrics.some(m => m.value === this.target.metricType)) { +// this.target.metricType = ''; +// } else { +// this.$scope.refresh(); +// } +// } + +// setMetricType() { +// const { valueType, metricKind, unit } = this.metricDescriptors.find( +// m => m.type === this.templateSrv.replace(this.target.metricType) +// ); +// this.target.unit = unit; +// this.target.valueType = valueType; +// this.target.metricKind = metricKind; +// this.$rootScope.$broadcast('metricTypeChanged'); +// } + +// async createLabelKeyElements() { +// await this.loadLabelsPromise; + +// let elements = Object.keys(this.metricLabels || {}).map(l => { +// return this.uiSegmentSrv.newSegment({ +// value: `metric.label.${l}`, +// expandable: false, +// }); +// }); + +// elements = [ +// ...elements, +// ...Object.keys(this.resourceLabels || {}).map(l => { +// return this.uiSegmentSrv.newSegment({ +// value: `resource.label.${l}`, +// expandable: false, +// }); +// }), +// ]; + +// if (this.resourceTypes && this.resourceTypes.length > 0) { +// elements = [ +// ...elements, +// this.uiSegmentSrv.newSegment({ +// value: this.resourceTypeValue, +// expandable: false, +// }), +// ]; +// } + +// return elements; +// } + +// async getFilterKeys(segment, removeText?: string) { +// let elements = await this.createLabelKeyElements(); + +// if (this.target.filters.indexOf(this.resourceTypeValue) !== -1) { +// elements = elements.filter(e => e.value !== this.resourceTypeValue); +// } + +// const noValueOrPlusButton = !segment || segment.type === 'plus-button'; +// if (noValueOrPlusButton && elements.length === 0) { +// return []; +// } + +// return [ +// ...elements, +// this.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.defaultRemoveGroupByValue }), +// ]; +// } + +// async getGroupBys(segment) { +// let elements = await this.createLabelKeyElements(); + +// elements = elements.filter(e => this.target.aggregation.groupBys.indexOf(e.value) === -1); +// const noValueOrPlusButton = !segment || segment.type === 'plus-button'; +// if (noValueOrPlusButton && elements.length === 0) { +// return []; +// } + +// this.removeSegment.value = this.defaultRemoveGroupByValue; +// return [...elements, this.removeSegment]; +// } + +// groupByChanged(segment, index) { +// if (segment.value === this.removeSegment.value) { +// this.groupBySegments.splice(index, 1); +// } else { +// segment.type = 'value'; +// } + +// const reducer = (memo, seg) => { +// if (!seg.fake) { +// memo.push(seg.value); +// } +// return memo; +// }; + +// this.target.aggregation.groupBys = this.groupBySegments.reduce(reducer, []); +// this.ensurePlusButton(this.groupBySegments); +// this.$rootScope.$broadcast('metricTypeChanged'); +// this.$scope.refresh(); +// } + +// async getFilters(segment, index) { +// const hasNoFilterKeys = this.metricLabels && Object.keys(this.metricLabels).length === 0; +// return this.filterSegments.getFilters(segment, index, hasNoFilterKeys); +// } + +// getFilterValues(index) { +// const filterKey = this.templateSrv.replace(this.filterSegments.filterSegments[index - 2].value); +// if (!filterKey || !this.metricLabels || Object.keys(this.metricLabels).length === 0) { +// return []; +// } + +// const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7); + +// if (filterKey.startsWith('metric.label.') && this.metricLabels.hasOwnProperty(shortKey)) { +// return this.metricLabels[shortKey]; +// } + +// if (filterKey.startsWith('resource.label.') && this.resourceLabels.hasOwnProperty(shortKey)) { +// return this.resourceLabels[shortKey]; +// } + +// if (filterKey === this.resourceTypeValue) { +// return this.resourceTypes; +// } + +// return []; +// } + +// filterSegmentUpdated(segment, index) { +// this.target.filters = this.filterSegments.filterSegmentUpdated(segment, index); +// this.$scope.refresh(); +// } + +// ensurePlusButton(segments) { +// const count = segments.length; +// const lastSegment = segments[Math.max(count - 1, 0)]; + +// if (!lastSegment || lastSegment.type !== 'plus-button') { +// segments.push(this.uiSegmentSrv.newPlusButton()); +// } +// } +// } + +// coreModule.directive('stackdriverFilter', StackdriverFilter); +// coreModule.controller('StackdriverFilterCtrl', StackdriverFilterCtrl); diff --git a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts index e8d04357cb8..5e8d7c0aea0 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts @@ -367,12 +367,12 @@ describe('StackdriverQueryFilterCtrl', () => { }); function createCtrlWithFakes(existingFilters?: string[]) { - StackdriverFilterCtrl.prototype.loadMetricDescriptors = () => { - return Promise.resolve([]); - }; - StackdriverFilterCtrl.prototype.getLabels = () => { - return Promise.resolve(); - }; + // StackdriverFilterCtrl.prototype.loadMetricDescriptors = () => { + // return Promise.resolve([]); + // }; + // StackdriverFilterCtrl.prototype.getLabels = () => { + // return Promise.resolve(); + // }; const fakeSegmentServer = { newKey: val => { From 146aa7abab4f36ea003b9c0ebcc95fc142be2983 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 13 Dec 2018 16:40:14 +0100 Subject: [PATCH 013/166] wip: convert angular directives to react components --- .../stackdriver/angular_wrappers.ts | 48 ++++ .../components/AggregationPicker.tsx | 134 ++++++++++ .../stackdriver/components/Filter.tsx | 239 ++++++++++++++++++ .../stackdriver/components/QueryEditor.tsx | 141 +++++++++++ .../stackdriver/components/Segment.tsx | 44 ++++ .../datasource/stackdriver/filter_segments.ts | 13 +- .../stackdriver/partials/query.editor.html | 7 +- .../datasource/stackdriver/query_ctrl.ts | 134 ++++------ .../stackdriver/query_filter_ctrl.ts | 16 +- .../plugins/datasource/stackdriver/types.ts | 27 ++ 10 files changed, 704 insertions(+), 99 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/angular_wrappers.ts create mode 100644 public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/Filter.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/Segment.tsx diff --git a/public/app/plugins/datasource/stackdriver/angular_wrappers.ts b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts new file mode 100644 index 00000000000..e4451821b44 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts @@ -0,0 +1,48 @@ +import { react2AngularDirective } from 'app/core/utils/react2angular'; +import { QueryEditor } from './components/QueryEditor'; +// import { MetricPicker } from './components/MetricPicker'; +// import { OptionPicker } from './components/OptionPicker'; +// import { OptionGroupPicker } from './components/OptionGroupPicker'; +// import { AggregationPicker } from './components/AggregationPicker'; + +export function registerAngularDirectives() { + // react2AngularDirective('optionPicker', OptionPicker, [ + // 'options', + // 'onChange', + // 'selected', + // 'searchable', + // 'className', + // 'placeholder', + // ]); + // react2AngularDirective('optionGroupPicker', OptionGroupPicker, [ + // 'groups', + // 'onChange', + // 'selected', + // 'searchable', + // 'className', + // 'placeholder', + // ]); + // react2AngularDirective('metricPicker', MetricPicker, [ + // 'target', + // ['onChange', { watchDepth: 'reference' }], + // 'defaultProject', + // 'metricType', + // ['templateSrv', { watchDepth: 'reference' }], + // ['datasource', { watchDepth: 'reference' }], + // ]); + // react2AngularDirective('aggregationPicker', AggregationPicker, [ + // 'valueType', + // 'metricKind', + // 'onChange', + // 'aggregation', + // ['templateSrv', { watchDepth: 'reference' }], + // ]); + + react2AngularDirective('queryEditor', QueryEditor, [ + 'target', + 'onChange', + ['uiSegmentSrv', { watchDepth: 'reference' }], + ['datasource', { watchDepth: 'reference' }], + ['templateSrv', { watchDepth: 'reference' }], + ]); +} diff --git a/public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx b/public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx new file mode 100644 index 00000000000..4f733e3e984 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx @@ -0,0 +1,134 @@ +import React from 'react'; +import _ from 'lodash'; + +// import { OptionPicker } from './OptionPicker'; +import { OptionGroupPicker } from './OptionGroupPicker'; +// import { alignmentPeriods } from '../constants'; +// import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from '../functions'; +import { getAggregationOptionsByMetric } from '../functions'; +// import kbn from 'app/core/utils/kbn'; + +export interface Props { + onChange: (metricDescriptor) => void; + templateSrv: any; + valueType: string; + metricKind: string; + aggregation: { + crossSeriesReducer: string; + alignmentPeriod: string; + perSeriesAligner: string; + groupBys: string[]; + }; +} + +interface State { + alignmentPeriods: any[]; + alignOptions: any[]; + aggOptions: any[]; +} + +export class AggregationPicker extends React.Component { + state: State = { + alignmentPeriods: [], + alignOptions: [], + aggOptions: [], + }; + + constructor(props) { + super(props); + } + + componentDidMount() { + this.setAggOptions(); + } + + componentWillReceiveProps(nextProps: Props) { + const { valueType, metricKind, aggregation } = this.props; + if ( + nextProps.valueType !== valueType || + nextProps.metricKind !== metricKind || + nextProps.aggregation.groupBys !== aggregation.groupBys + ) { + this.setAggOptions(); + } + } + + setAggOptions() { + const { valueType, metricKind, aggregation, templateSrv } = this.props; + let aggregations = getAggregationOptionsByMetric(valueType, metricKind).map(a => ({ + ...a, + label: a.text, + })); + if (!aggregations.find(o => o.value === templateSrv.replace(aggregation.crossSeriesReducer))) { + this.deselectAggregationOption('REDUCE_NONE'); + } + + if (aggregation.groupBys.length > 0) { + aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); + this.deselectAggregationOption('REDUCE_NONE'); + } + this.setState({ + aggOptions: [ + this.getTemplateVariablesGroup(), + { + label: 'Aggregations', + options: aggregations, + }, + ], + }); + } + + deselectAggregationOption(notValidOptionValue: string) { + const aggregations = getAggregationOptionsByMetric(this.props.valueType, this.props.metricKind); + const newValue = aggregations.find(o => o.value !== notValidOptionValue); + this.handleAggregationChange(newValue ? newValue.value : ''); + } + + handleAggregationChange(value) { + this.props.onChange(value); + // this.$scope.refresh(); + } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.props.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + })), + }; + } + + render() { + const { aggOptions } = this.state; + const { aggregation } = this.props; + + return ( + +
+
+ +
+ this.handleAggregationChange(value)} + selected={aggregation.crossSeriesReducer} + groups={aggOptions} + searchable={true} + placeholder="Select Aggregation" + className="width-15" + /> +
+
+ +
+
+ ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/Filter.tsx b/public/app/plugins/datasource/stackdriver/components/Filter.tsx new file mode 100644 index 00000000000..59967b998c1 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/Filter.tsx @@ -0,0 +1,239 @@ +import React from 'react'; +import _ from 'lodash'; + +import Segment from './Segment'; +import { QueryMeta, Target } from '../types'; +import { FilterSegments } from '../filter_segments'; + +export interface Props { + onChange: (metricDescriptor) => void; + templateSrv: any; + labelData: QueryMeta; + loading: Promise; + target: Target; + uiSegmentSrv: any; +} + +interface State { + defaultRemoveGroupByValue: string; + resourceTypeValue: string; + groupBySegments: any[]; + // filterSegments: FilterSegments; + filterSegments: any; + removeSegment?: any; +} + +export class Filter extends React.Component { + state: State = { + defaultRemoveGroupByValue: '-- remove group by --', + resourceTypeValue: 'resource.type', + groupBySegments: [], + filterSegments: new FilterSegments(this.getFilterKeys.bind(this), this.getFilterValues.bind(this)), + }; + + constructor(props) { + super(props); + } + + componentDidMount() { + this.initSegments(false); + } + + shouldComponentUpdate(nextProps) { + return this.state.filterSegments.filterSegments.length > 0; + } + + initSegments(hideGroupBys: boolean) { + this.state.filterSegments.init(this.props.uiSegmentSrv); + if (!hideGroupBys) { + this.setState({ + groupBySegments: this.props.target.aggregation.groupBys.map(groupBy => { + return this.props.uiSegmentSrv.getSegmentForValue(groupBy); + }), + }); + + this.ensurePlusButton(this.state.groupBySegments); + } + + this.setState({ + removeSegment: this.props.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' }), + }); + + this.state.filterSegments.buildSegmentModel(this.props.target.filters); + } + + async createLabelKeyElements() { + await this.props.loading; + + let elements = Object.keys(this.props.labelData.metricLabels || {}).map(l => { + return this.props.uiSegmentSrv.newSegment({ + value: `metric.label.${l}`, + expandable: false, + }); + }); + + elements = [ + ...elements, + ...Object.keys(this.props.labelData.resourceLabels || {}).map(l => { + return this.props.uiSegmentSrv.newSegment({ + value: `resource.label.${l}`, + expandable: false, + }); + }), + ]; + + if (this.props.labelData.resourceTypes && this.props.labelData.resourceTypes.length > 0) { + elements = [ + ...elements, + this.props.uiSegmentSrv.newSegment({ + value: this.state.resourceTypeValue, + expandable: false, + }), + ]; + } + + return elements; + } + + async getFilterKeys(segment, removeText?: string) { + let elements = await this.createLabelKeyElements(); + + if (this.props.target.filters.indexOf(this.state.resourceTypeValue) !== -1) { + elements = elements.filter(e => e.value !== this.state.resourceTypeValue); + } + + const noValueOrPlusButton = !segment || segment.type === 'plus-button'; + if (noValueOrPlusButton && elements.length === 0) { + return []; + } + + return [ + ...elements, + this.props.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.state.defaultRemoveGroupByValue }), + ]; + } + + async getGroupBys(segment) { + let elements = await this.createLabelKeyElements(); + + elements = elements.filter(e => this.props.target.aggregation.groupBys.indexOf(e.value) === -1); + const noValueOrPlusButton = !segment || segment.type === 'plus-button'; + if (noValueOrPlusButton && elements.length === 0) { + return []; + } + + this.state.removeSegment.value = this.state.defaultRemoveGroupByValue; + return [...elements, this.state.removeSegment]; + } + + groupByChanged(segment, index) { + if (segment.value === this.state.removeSegment.value) { + // this.groupBySegments.splice(index, 1); + } else { + segment.type = 'value'; + } + + const reducer = (memo, seg) => { + if (!seg.fake) { + memo.push(seg.value); + } + return memo; + }; + + this.props.target.aggregation.groupBys = this.state.groupBySegments.reduce(reducer, []); + this.ensurePlusButton(this.state.groupBySegments); + // this.$rootScope.$broadcast('metricTypeChanged'); + // this.$scope.refresh(); + } + + async getFilters(segment, index) { + await this.props.loading; + const hasNoFilterKeys = + this.props.labelData.metricLabels && Object.keys(this.props.labelData.metricLabels).length === 0; + return this.state.filterSegments.getFilters(segment, index, hasNoFilterKeys); + } + + getFilterValues(index) { + const filterKey = this.props.templateSrv.replace(this.state.filterSegments.filterSegments[index - 2].value); + if ( + !filterKey || + !this.props.labelData.metricLabels || + Object.keys(this.props.labelData.metricLabels).length === 0 + ) { + return []; + } + + const shortKey = filterKey.substring(filterKey.indexOf('.label.') + 7); + + if (filterKey.startsWith('metric.label.') && this.props.labelData.metricLabels.hasOwnProperty(shortKey)) { + return this.props.labelData.metricLabels[shortKey]; + } + + if (filterKey.startsWith('resource.label.') && this.props.labelData.resourceLabels.hasOwnProperty(shortKey)) { + return this.props.labelData.resourceLabels[shortKey]; + } + + if (filterKey === this.state.resourceTypeValue) { + return this.props.labelData.resourceTypes; + } + + return []; + } + + filterSegmentUpdated(segment, index) { + this.props.target.filters = this.state.filterSegments.filterSegmentUpdated(segment, index); + // this.$scope.refresh(); + } + + ensurePlusButton(segments) { + const count = segments.length; + const lastSegment = segments[Math.max(count - 1, 0)]; + + if (!lastSegment || lastSegment.type !== 'plus-button') { + segments.push(this.props.uiSegmentSrv.newPlusButton()); + } + } + + render() { + const { filterSegments } = this.state; + // const { metrifilterSegmentscType } = this.props; + + return ( + +
+
+ Filter +
+ {filterSegments.filterSegments.map((segment, i) => ( + this.getFilters(segment, i)} + onChange={segment => this.filterSegmentUpdated(segment, i)} + /> + ))} +
+
+
+
+
+
+ {/*
+
+ Group By +
+ +
+
+
+
+
+
*/} + + ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx new file mode 100644 index 00000000000..5308853324c --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -0,0 +1,141 @@ +import React from 'react'; +import _ from 'lodash'; +import appEvents from 'app/core/app_events'; + +import { MetricPicker } from './MetricPicker'; +import { Filter } from './Filter'; +// import { AggregationPicker } from './AggregationPicker'; +import { Target, QueryMeta } from '../types'; + +export interface Props { + onChange: (target: Target) => void; + target: Target; + datasource: any; + templateSrv: any; + uiSegmentSrv: any; +} + +interface State { + target: Target; + labelData: QueryMeta; + loadLabelsPromise: Promise; +} + +const DefaultTarget: Target = { + defaultProject: 'loading project...', + metricType: '', + refId: '', + service: '', + unit: '', + aggregation: { + crossSeriesReducer: 'REDUCE_MEAN', + alignmentPeriod: 'stackdriver-auto', + perSeriesAligner: 'ALIGN_MEAN', + groupBys: [], + }, + filters: [], + aliasBy: '', + metricKind: '', + valueType: '', +}; + +export class QueryEditor extends React.Component { + state: State = { labelData: null, loadLabelsPromise: null, target: DefaultTarget }; + + constructor(props) { + super(props); + this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); + this.handleAggregationChange = this.handleAggregationChange.bind(this); + } + + componentDidMount() { + this.setState({ target: this.props.target }); + this.getLabels(); + } + + async getLabels() { + const loadLabelsPromise = new Promise(async resolve => { + try { + const { meta } = await this.props.datasource.getLabels(this.props.target.metricType, this.props.target.refId); + this.setState({ labelData: meta }); + resolve(); + } catch (error) { + appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.target.metricType]); + resolve(); + } + }); + this.setState({ loadLabelsPromise }); + } + + handleMetricTypeChange({ valueType, metricKind, type, unit }) { + this.setState({ + target: { + ...this.state.target, + ...{ + metricType: type, + unit, + valueType, + metricKind, + }, + }, + }); + + // this.$rootScope.$broadcast('metricTypeChanged'); + // this.getLabels(); + // this.refresh(); + } + + handleAggregationChange(crossSeriesReducer) { + // this.target.aggregation.crossSeriesReducer = crossSeriesReducer; + // this.refresh(); + } + + render() { + const { labelData, loadLabelsPromise, target } = this.state; + const { defaultProject, metricType } = target; + const { templateSrv, datasource, uiSegmentSrv } = this.props; + + return ( + + this.handleMetricTypeChange(value)} + /> + console.log('change filter')} + target={target} + uiSegmentSrv={uiSegmentSrv} + labelData={labelData} + templateSrv={templateSrv} + loading={loadLabelsPromise} + /> + {/* target="ctrl.target" refresh="ctrl.refresh()" loading="ctrl.loadLabelsPromise" label-data="ctrl.labelData" */} + {/* + + + */} + + ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/Segment.tsx b/public/app/plugins/datasource/stackdriver/components/Segment.tsx new file mode 100644 index 00000000000..e330b1ab312 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/Segment.tsx @@ -0,0 +1,44 @@ +import React, { PureComponent } from 'react'; +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import 'app/core/directives/metric_segment'; + +interface QueryEditorProps { + segment: any; + getOptions: () => Promise; + onChange: (segment, index) => void; + key: number; +} + +export default class Segment extends PureComponent { + element: any; + component: AngularComponent; + + async componentDidMount() { + if (!this.element) { + return; + } + + const { segment, getOptions, onChange } = this.props; + const loader = getAngularLoader(); + const template = ' '; + + const scopeProps = { + segment, + onChange, + getOptions, + debounce: false, + }; + + this.component = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.component) { + this.component.destroy(); + } + } + + render() { + return
(this.element = element)} style={{ width: '100%' }} />; + } +} diff --git a/public/app/plugins/datasource/stackdriver/filter_segments.ts b/public/app/plugins/datasource/stackdriver/filter_segments.ts index 5adb56e2fcf..7fe406ed765 100644 --- a/public/app/plugins/datasource/stackdriver/filter_segments.ts +++ b/public/app/plugins/datasource/stackdriver/filter_segments.ts @@ -4,14 +4,21 @@ export const DefaultFilterValue = 'select value'; export class FilterSegments { filterSegments: any[]; removeSegment: any; + uiSegmentSrv: any; - constructor(private uiSegmentSrv, private target, private getFilterKeysFunc, private getFilterValuesFunc) {} + constructor(private getFilterKeysFunc, private getFilterValuesFunc) { + this.filterSegments = []; + } - buildSegmentModel() { + init(uiSegmentSrv) { + this.uiSegmentSrv = uiSegmentSrv; + } + + buildSegmentModel(filters) { this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: DefaultRemoveFilterValue }); this.filterSegments = []; - this.target.filters.forEach((f, index) => { + filters.forEach((f, index) => { switch (index % 4) { case 0: this.filterSegments.push(this.uiSegmentSrv.newKey(f)); diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index a0a37a5ab83..9cb24815df0 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,8 +1,11 @@ - + + diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 8feb2f7dec1..45dabcdd93d 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -3,57 +3,33 @@ import appEvents from 'app/core/app_events'; import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; -import { MetricPicker } from './components/MetricPicker'; -import { OptionPicker } from './components/OptionPicker'; -import { OptionGroupPicker } from './components/OptionGroupPicker'; -import { react2AngularDirective } from 'app/core/utils/react2angular'; +import { registerAngularDirectives } from './angular_wrappers'; +import { Target, QueryMeta } from './types'; -export interface QueryMeta { - alignmentPeriod: string; - rawQuery: string; - rawQueryString: string; - metricLabels: { [key: string]: string[] }; - resourceLabels: { [key: string]: string[] }; -} +export const DefaultTarget = { + defaultProject: 'loading project...', + metricType: '', + service: '', + metric: '', + unit: '', + aggregation: { + crossSeriesReducer: 'REDUCE_MEAN', + alignmentPeriod: 'stackdriver-auto', + perSeriesAligner: 'ALIGN_MEAN', + groupBys: [], + }, + filters: [], + showAggregationOptions: false, + aliasBy: '', + metricKind: '', + valueType: '', +}; export class StackdriverQueryCtrl extends QueryCtrl { static templateUrl = 'partials/query.editor.html'; - target: { - defaultProject: string; - unit: string; - metricType: string; - service: string; - refId: string; - aggregation: { - crossSeriesReducer: string; - alignmentPeriod: string; - perSeriesAligner: string; - groupBys: string[]; - }; - filters: string[]; - aliasBy: string; - metricKind: any; - valueType: any; - }; + target: Target; - defaults = { - defaultProject: 'loading project...', - metricType: '', - service: '', - metric: '', - unit: '', - aggregation: { - crossSeriesReducer: 'REDUCE_MEAN', - alignmentPeriod: 'stackdriver-auto', - perSeriesAligner: 'ALIGN_MEAN', - groupBys: [], - }, - filters: [], - showAggregationOptions: false, - aliasBy: '', - metricKind: '', - valueType: '', - }; + defaults = DefaultTarget; showHelp: boolean; showLastQuery: boolean; @@ -63,50 +39,41 @@ export class StackdriverQueryCtrl extends QueryCtrl { loadLabelsPromise: Promise; templateSrv: any; + $rootScope: any; + uiSegmentSrv: any; /** @ngInject */ - constructor($scope, $injector, templateSrv, private $rootScope) { + constructor($scope, $injector, templateSrv, $rootScope, uiSegmentSrv) { super($scope, $injector); this.templateSrv = templateSrv; + this.$rootScope = $rootScope; + this.uiSegmentSrv = uiSegmentSrv; _.defaultsDeep(this.target, this.defaults); this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); - this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); - react2AngularDirective('optionPicker', OptionPicker, [ - 'options', - 'onChange', - 'selected', - 'searchable', - 'className', - 'placeholder', - ]); - react2AngularDirective('optionGroupPicker', OptionGroupPicker, [ - 'groups', - 'onChange', - 'selected', - 'searchable', - 'className', - 'placeholder', - ]); - react2AngularDirective('metricPicker', MetricPicker, [ - 'target', - ['onChange', { watchDepth: 'reference' }], - 'defaultProject', - 'metricType', - ['templateSrv', { watchDepth: 'reference' }], - ['datasource', { watchDepth: 'reference' }], - ]); - this.getLabels(); + // this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); + // this.handleAggregationChange = this.handleAggregationChange.bind(this); + this.handleTargetChange = this.handleTargetChange.bind(this); + registerAngularDirectives(); + // this.getLabels(); } - handleMetricTypeChange({ valueType, metricKind, type, unit }) { - this.target.metricType = type; - this.target.unit = unit; - this.target.valueType = valueType; - this.target.metricKind = metricKind; - this.$rootScope.$broadcast('metricTypeChanged'); - this.getLabels(); - this.refresh(); + // handleMetricTypeChange({ valueType, metricKind, type, unit }) { + // this.target.metricType = type; + // this.target.unit = unit; + // this.target.valueType = valueType; + // this.target.metricKind = metricKind; + // this.$rootScope.$broadcast('metricTypeChanged'); + // this.getLabels(); + // this.refresh(); + // } + + // handleAggregationChange(crossSeriesReducer) { + // this.target.aggregation.crossSeriesReducer = crossSeriesReducer; + // this.refresh(); + // } + handleTargetChange(target: Target) { + console.log(target); } async getLabels() { @@ -116,11 +83,6 @@ export class StackdriverQueryCtrl extends QueryCtrl { this.labelData = meta; resolve(); } catch (error) { - if (error.data && error.data.message) { - console.log(error.data.message); - } else { - console.log(error); - } appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.target.metricType]); resolve(); } diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 99f5386b608..4ab71839971 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -1,7 +1,7 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; import { FilterSegments } from './filter_segments'; -import { QueryMeta } from './query_ctrl'; +import { QueryMeta } from './types'; // import appEvents from 'app/core/app_events'; export class StackdriverFilter { @@ -62,13 +62,13 @@ export class StackdriverFilterCtrl { this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' }); - this.filterSegments = new FilterSegments( - this.uiSegmentSrv, - this.target, - this.getFilterKeys.bind(this), - this.getFilterValues.bind(this) - ); - this.filterSegments.buildSegmentModel(); + // this.filterSegments = new FilterSegments( + // this.uiSegmentSrv, + // this.target, + // this.getFilterKeys.bind(this), + // this.getFilterValues.bind(this) + // ); + // this.filterSegments.buildSegmentModel(); } // async getLabels() { diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index df4c2886522..fa46800ce21 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -19,3 +19,30 @@ export interface VariableQueryData { metricTypes: Array<{ value: string; name: string }>; services: Array<{ value: string; name: string }>; } + +export interface Target { + defaultProject: string; + unit: string; + metricType: string; + service: string; + refId: string; + aggregation: { + crossSeriesReducer: string; + alignmentPeriod: string; + perSeriesAligner: string; + groupBys: string[]; + }; + filters: string[]; + aliasBy: string; + metricKind: any; + valueType: any; +} + +export interface QueryMeta { + alignmentPeriod: string; + rawQuery: string; + rawQueryString: string; + metricLabels: { [key: string]: string[] }; + resourceLabels: { [key: string]: string[] }; + resourceTypes: string[]; +} From 73b5bc680fa6013ac62caf195360f156ee29b1f6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 13:14:55 +0100 Subject: [PATCH 014/166] Wrap react select component in angular directive --- .../stackdriver/components/OptionPicker.tsx | 48 +++++++++++++++++++ .../stackdriver/partials/query.filter.html | 34 ++++++------- .../datasource/stackdriver/query_ctrl.ts | 9 ++++ .../stackdriver/query_filter_ctrl.ts | 16 +++++-- 4 files changed, 86 insertions(+), 21 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx new file mode 100644 index 00000000000..f83777e5f79 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx @@ -0,0 +1,48 @@ +import React from 'react'; +// import SimplePicker from 'app/core/components/Picker/SimplePicker'; +import Select from 'react-select'; +// import DescriptionPicker from 'app/core/components/Picker/DescriptionPicker'; +import DescriptionOption from 'app/core/components/Picker/DescriptionOption'; +import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; +import ResetStyles from 'app/core/components/Picker/ResetStyles'; +import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; +import _ from 'lodash'; + +export interface Props { + onChange: (value: string) => void; + options: any[]; + selected: string; + placeholder?: string; + className?: string; +} + +export class OptionPicker extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { onChange, options, selected, placeholder, className } = this.props; + const selectedOption = options.find(metric => metric.value === selected); + return ( + +
+
+
+
Metric - +
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index c2607964456..3186a5e2012 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -2,6 +2,8 @@ import _ from 'lodash'; import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; +import { OptionPicker } from './components/OptionPicker'; +import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface QueryMeta { alignmentPeriod: string; @@ -64,6 +66,13 @@ export class StackdriverQueryCtrl extends QueryCtrl { _.defaultsDeep(this.target, this.defaults); this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); + react2AngularDirective('optionPicker', OptionPicker, [ + 'options', + 'onChange', + 'selected', + 'className', + 'placeholder', + ]); } onDataReceived(dataList) { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 0f5dce559fd..7926da9aa1c 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -59,6 +59,13 @@ export class StackdriverFilterCtrl { .then(this.getLabels.bind(this)); this.initSegments($scope.hideGroupBys); + this.handleMetricTypeChange = this.handleMetricTypeChange.bind(this); + this.handleServiceChange = this.handleServiceChange.bind(this); + } + + handleMetricTypeChange(value) { + this.metricType = value; + this.onMetricTypeChange(); } initSegments(hideGroupBys: boolean) { @@ -110,7 +117,7 @@ export class StackdriverFilterCtrl { const services = this.metricDescriptors.map(m => { return { value: m.service, - text: m.serviceShortName, + label: m.serviceShortName, }; }); @@ -128,7 +135,8 @@ export class StackdriverFilterCtrl { value: m.type, serviceShortName: m.serviceShortName, text: m.displayName, - title: m.description, + label: m.displayName, + description: m.description, }; }); @@ -167,8 +175,8 @@ export class StackdriverFilterCtrl { }); } - onServiceChange() { - this.target.service = this.service; + handleServiceChange(service) { + this.target.service = this.service = service; this.metrics = this.getMetricsList(); this.setMetricType(); this.getLabels(); From 5370f78ee708f3a6b0a53aa56a9cca8c10ce0a90 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:11:18 +0100 Subject: [PATCH 015/166] wip: add basic option header --- public/app/core/components/Picker/GroupHeading.tsx | 12 ++++++++++++ public/sass/_grafana.scss | 1 + public/sass/components/_group-heading.scss | 5 +++++ 3 files changed, 18 insertions(+) create mode 100644 public/app/core/components/Picker/GroupHeading.tsx create mode 100644 public/sass/components/_group-heading.scss diff --git a/public/app/core/components/Picker/GroupHeading.tsx b/public/app/core/components/Picker/GroupHeading.tsx new file mode 100644 index 00000000000..76012ed3e7f --- /dev/null +++ b/public/app/core/components/Picker/GroupHeading.tsx @@ -0,0 +1,12 @@ +import React from 'react'; +import { components } from 'react-select'; + +export const GroupHeading = props => { + return ( + + ); +}; + +export default GroupHeading; diff --git a/public/sass/_grafana.scss b/public/sass/_grafana.scss index bfb186c0b02..c29abb4fa4b 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -103,6 +103,7 @@ @import 'components/page_loader'; @import 'components/thresholds'; @import 'components/toggle_button_group'; +@import 'components/group-heading'; @import 'components/value-mappings'; // PAGES diff --git a/public/sass/components/_group-heading.scss b/public/sass/components/_group-heading.scss new file mode 100644 index 00000000000..9656277d123 --- /dev/null +++ b/public/sass/components/_group-heading.scss @@ -0,0 +1,5 @@ +.picker-option-group { + cursor: default; + font-weight: $lead-font-weight; + color: $btn-primary-bg-hl; +} From fe4c77a8a403886d9afc37160b3bc6de0db332a6 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:12:33 +0100 Subject: [PATCH 016/166] wip: add option group component --- .../components/OptionGroupPicker.tsx | 53 +++++++++++++++++++ .../stackdriver/components/OptionPicker.tsx | 15 +++--- .../stackdriver/partials/query.filter.html | 14 ++--- .../datasource/stackdriver/query_ctrl.ts | 10 ++++ .../stackdriver/query_filter_ctrl.ts | 39 +++++++++++++- 5 files changed, 117 insertions(+), 14 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx new file mode 100644 index 00000000000..7401b226413 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import Select from 'react-select'; +import _ from 'lodash'; + +import GroupHeading from 'app/core/components/Picker/GroupHeading'; +import DescriptionOption from 'app/core/components/Picker/DescriptionOption'; +import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; +import ResetStyles from 'app/core/components/Picker/ResetStyles'; +import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; + +export interface Props { + onChange: (value: string) => void; + groups: any[]; + searchable: boolean; + selected: string; + placeholder?: string; + className?: string; +} + +export class OptionGroupPicker extends React.Component { + constructor(props) { + super(props); + } + + render() { + const { onChange, groups, selected, placeholder, className, searchable } = this.props; + const options = _.flatten(groups.map(o => o.options)); + const selectedOption = options.find(option => option.value === selected); + + return ( + { NoOptionsMessage, }} styles={ResetStyles} - isDisabled={false} + isSearchable={searchable} + maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index 1d87c8a3005..df3821ac463 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -5,8 +5,9 @@ onChange="ctrl.handleServiceChange" selected="ctrl.service" options="ctrl.services" - placeholder="ctrl.defaultDropdownValue" - className="width-12" + searchable="false" + placeholder="ctrl.defaultServiceValue" + className=""width-15"" >
@@ -14,13 +15,14 @@
Metric - + className=""width-15"" + >
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 3186a5e2012..8c00d6caddb 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -3,6 +3,7 @@ import { QueryCtrl } from 'app/plugins/sdk'; import './query_aggregation_ctrl'; import './query_filter_ctrl'; import { OptionPicker } from './components/OptionPicker'; +import { OptionGroupPicker } from './components/OptionGroupPicker'; import { react2AngularDirective } from 'app/core/utils/react2angular'; export interface QueryMeta { @@ -70,6 +71,15 @@ export class StackdriverQueryCtrl extends QueryCtrl { 'options', 'onChange', 'selected', + 'searchable', + 'className', + 'placeholder', + ]); + react2AngularDirective('optionGroupPicker', OptionGroupPicker, [ + 'groups', + 'onChange', + 'selected', + 'searchable', 'className', 'placeholder', ]); diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 7926da9aa1c..dd7178ecca0 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -36,6 +36,7 @@ export class StackdriverFilterCtrl { metricType: string; metricDescriptors: any[]; metrics: any[]; + metricGroups: any[]; services: any[]; groupBySegments: any[]; filterSegments: FilterSegments; @@ -52,6 +53,7 @@ export class StackdriverFilterCtrl { this.metricDescriptors = []; this.metrics = []; + this.metricGroups = []; this.services = []; this.getCurrentProject() @@ -106,6 +108,7 @@ export class StackdriverFilterCtrl { this.metricDescriptors = await this.datasource.getMetricTypes(this.target.defaultProject); this.services = this.getServicesList(); this.metrics = this.getMetricsList(); + this.metricGroups = this.getMetricGroups(); return this.metricDescriptors; } else { return []; @@ -113,11 +116,11 @@ export class StackdriverFilterCtrl { } getServicesList() { - const defaultValue = { value: this.$scope.defaultServiceValue, text: this.$scope.defaultServiceValue }; + const defaultValue = { value: this.$scope.defaultServiceValue, label: this.$scope.defaultServiceValue }; const services = this.metricDescriptors.map(m => { return { value: m.service, - label: m.serviceShortName, + label: _.startCase(m.serviceShortName), }; }); @@ -128,6 +131,37 @@ export class StackdriverFilterCtrl { return services.length > 0 ? [defaultValue, ..._.uniqBy(services, 'value')] : []; } + getMetricGroups() { + return this.metrics.reduce((acc, curr) => { + const group = acc.find(group => group.service === curr.service); + if (group) { + group.options = [...group.options, { value: curr.value, label: curr.label }]; + } else { + acc = [ + ...acc, + { + label: _.startCase(curr.serviceShortName), + service: curr.service, + options: [{ value: curr.value, label: curr.label }], + }, + ]; + } + return acc; + }, []); + } + + insertTemplateVariables(options) { + const templateVariables = { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + description: `$${v.definition}`, + })), + }; + return [templateVariables, { label: 'Metrics', options }]; + } + getMetricsList() { const metrics = this.metricDescriptors.map(m => { return { @@ -178,6 +212,7 @@ export class StackdriverFilterCtrl { handleServiceChange(service) { this.target.service = this.service = service; this.metrics = this.getMetricsList(); + this.metricGroups = this.getMetricGroups(); this.setMetricType(); this.getLabels(); if (!this.metrics.find(m => m.value === this.target.metricType)) { From 2ac1fe2a17d34c947ad55e41458938db9d6b9a73 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:14:06 +0100 Subject: [PATCH 017/166] remove redundant default value --- .../datasource/stackdriver/datasource.ts | 1 + .../stackdriver/partials/query.editor.html | 3 +- .../stackdriver/partials/query.filter.html | 12 ++-- .../datasource/stackdriver/query_ctrl.ts | 7 +- .../stackdriver/query_filter_ctrl.ts | 72 +++++++++++-------- .../specs/query_filter_ctrl.test.ts | 2 - 6 files changed, 53 insertions(+), 44 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/datasource.ts b/public/app/plugins/datasource/stackdriver/datasource.ts index d1545655652..7b1afbae5ac 100644 --- a/public/app/plugins/datasource/stackdriver/datasource.ts +++ b/public/app/plugins/datasource/stackdriver/datasource.ts @@ -274,6 +274,7 @@ export default class StackdriverDatasource { m.service = service; m.serviceShortName = serviceShortName; m.displayName = m.displayName || m.type; + return m; }); } diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 5c7bc8935b1..66961d106aa 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,6 +1,5 @@ - +
diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index df3821ac463..583744d0e67 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -6,8 +6,8 @@ selected="ctrl.service" options="ctrl.services" searchable="false" - placeholder="ctrl.defaultServiceValue" - className=""width-15"" + placeholder="'Select Services'" + className="'width-15'" >
@@ -17,11 +17,11 @@ Metric
diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 8c00d6caddb..cf22802ae76 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -34,13 +34,10 @@ export class StackdriverQueryCtrl extends QueryCtrl { valueType: any; }; - defaultDropdownValue = 'Select Metric'; - defaultServiceValue = 'All Services'; - defaults = { defaultProject: 'loading project...', - metricType: this.defaultDropdownValue, - service: this.defaultServiceValue, + metricType: '', + service: '', metric: '', unit: '', aggregation: { diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index dd7178ecca0..ba96cfc5a60 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -15,8 +15,6 @@ export class StackdriverFilter { target: '=', datasource: '=', refresh: '&', - defaultDropdownValue: '<', - defaultServiceValue: '<', hideGroupBys: '<', }, }; @@ -48,9 +46,6 @@ export class StackdriverFilterCtrl { constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) { this.datasource = $scope.datasource; this.target = $scope.target; - this.metricType = $scope.defaultDropdownValue; - this.service = $scope.defaultServiceValue; - this.metricDescriptors = []; this.metrics = []; this.metricGroups = []; @@ -66,7 +61,7 @@ export class StackdriverFilterCtrl { } handleMetricTypeChange(value) { - this.metricType = value; + this.target.metricType = value; this.onMetricTypeChange(); } @@ -116,7 +111,6 @@ export class StackdriverFilterCtrl { } getServicesList() { - const defaultValue = { value: this.$scope.defaultServiceValue, label: this.$scope.defaultServiceValue }; const services = this.metricDescriptors.map(m => { return { value: m.service, @@ -128,10 +122,10 @@ export class StackdriverFilterCtrl { this.service = this.target.service; } - return services.length > 0 ? [defaultValue, ..._.uniqBy(services, 'value')] : []; + return services.length > 0 ? _.uniqBy(services, 'value') : []; } - getMetricGroups() { + getMetricGroupsOld() { return this.metrics.reduce((acc, curr) => { const group = acc.find(group => group.service === curr.service); if (group) { @@ -150,6 +144,27 @@ export class StackdriverFilterCtrl { }, []); } + getMetricGroups() { + return [ + this.getTemplateVariablesGroup(), + { + label: 'Metrics', + options: this.metrics, + }, + ]; + } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + description: `$${v.definition}`, + })), + }; + } + insertTemplateVariables(options) { const templateVariables = { label: 'Template Variables', @@ -174,29 +189,28 @@ export class StackdriverFilterCtrl { }; }); - let result; - if (this.target.service === this.$scope.defaultServiceValue) { - result = metrics.map(m => ({ ...m, text: `${m.service} - ${m.text}` })); - } else { - result = metrics.filter(m => m.service === this.target.service); + const metricsByService = metrics.filter(m => m.service === this.target.service); + if ( + metricsByService.length > 0 && + !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) + ) { + this.target.metricType = metricsByService[0].value; } - - if (result.find(m => m.value === this.templateSrv.replace(this.target.metricType))) { - this.metricType = this.target.metricType; - } else if (result.length > 0) { - this.metricType = this.target.metricType = result[0].value; - } - return result; + return metricsByService; } async getLabels() { this.loadLabelsPromise = new Promise(async resolve => { try { - const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); - this.metricLabels = meta.metricLabels; - this.resourceLabels = meta.resourceLabels; - this.resourceTypes = meta.resourceTypes; - resolve(); + if (this.target.metricType) { + const { meta } = await this.datasource.getLabels(this.target.metricType, this.target.refId); + this.metricLabels = meta.metricLabels; + this.resourceLabels = meta.resourceLabels; + this.resourceTypes = meta.resourceTypes; + resolve(); + } else { + resolve(); + } } catch (error) { if (error.data && error.data.message) { console.log(error.data.message); @@ -216,7 +230,7 @@ export class StackdriverFilterCtrl { this.setMetricType(); this.getLabels(); if (!this.metrics.find(m => m.value === this.target.metricType)) { - this.target.metricType = this.$scope.defaultDropdownValue; + this.target.metricType = ''; } else { this.$scope.refresh(); } @@ -229,9 +243,9 @@ export class StackdriverFilterCtrl { } setMetricType() { - this.target.metricType = this.metricType; + // this.target.metricType = this.metricType; const { valueType, metricKind, unit } = this.metricDescriptors.find( - m => m.type === this.templateSrv.replace(this.metricType) + m => m.type === this.templateSrv.replace(this.target.metricType) ); this.target.unit = unit; this.target.valueType = valueType; diff --git a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts index 020db584508..e8d04357cb8 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_filter_ctrl.test.ts @@ -409,8 +409,6 @@ function createCtrlWithFakes(existingFilters?: string[]) { return 'project'; }, }, - defaultDropdownValue: 'Select Metric', - defaultServiceValue: 'All Services', refresh: () => {}, }; From dcbe12989f180b9138ac9eb78898f5dc823e017e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:57:30 +0100 Subject: [PATCH 018/166] use new option group in aggregation directive --- .../partials/query.aggregation.html | 43 ++++++---- .../stackdriver/partials/query.editor.html | 3 +- .../stackdriver/query_aggregation_ctrl.ts | 79 ++++++++++++++++--- 3 files changed, 97 insertions(+), 28 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html b/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html index 5f16d3fc61d..7396c6eb546 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.aggregation.html @@ -2,16 +2,21 @@
- +
@@ -20,27 +25,35 @@
- +
-
-
-
+
- +
- +
-
\ No newline at end of file +
diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 66961d106aa..8760aaf1bb3 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -1,5 +1,6 @@ - +
diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 628cc494242..f5ff3ad3143 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -1,6 +1,6 @@ import coreModule from 'app/core/core_module'; import _ from 'lodash'; -import * as options from './constants'; +import { alignmentPeriods } from './constants'; import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from './functions'; import kbn from 'app/core/utils/kbn'; @@ -29,9 +29,16 @@ export class StackdriverAggregationCtrl { constructor(private $scope, private templateSrv) { this.$scope.ctrl = this; this.target = $scope.target; - this.alignmentPeriods = options.alignmentPeriods; - this.aggOptions = options.aggOptions; - this.alignOptions = options.alignOptions; + this.alignmentPeriods = [ + this.getTemplateVariablesGroup(), + { + label: 'Alignment Periods', + options: alignmentPeriods.map(ap => ({ + ...ap, + label: ap.text, + })), + }, + ]; this.setAggOptions(); this.setAlignOptions(); const self = this; @@ -39,30 +46,68 @@ export class StackdriverAggregationCtrl { self.setAggOptions(); self.setAlignOptions(); }); + this.handleAlignmentChange = this.handleAlignmentChange.bind(this); + this.handleAggregationChange = this.handleAggregationChange.bind(this); + this.handleAlignmentPeriodChange = this.handleAlignmentPeriodChange.bind(this); } setAlignOptions() { - this.alignOptions = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); - if (!this.alignOptions.find(o => o.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner))) { - this.target.aggregation.perSeriesAligner = this.alignOptions.length > 0 ? this.alignOptions[0].value : ''; + const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ + ...a, + label: a.text, + })); + this.alignOptions = [ + this.getTemplateVariablesGroup(), + { + label: 'Alignment Options', + options: alignments, + }, + ]; + if (!alignments.find(o => o.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner))) { + this.target.aggregation.perSeriesAligner = alignments.length > 0 ? alignments[0].value : ''; } } setAggOptions() { - this.aggOptions = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); - - if (!this.aggOptions.find(o => o.value === this.templateSrv.replace(this.target.aggregation.crossSeriesReducer))) { + let aggregations = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ + ...a, + label: a.text, + })); + if (!aggregations.find(o => o.value === this.templateSrv.replace(this.target.aggregation.crossSeriesReducer))) { this.deselectAggregationOption('REDUCE_NONE'); } if (this.target.aggregation.groupBys.length > 0) { - this.aggOptions = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); + aggregations = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } + this.aggOptions = [ + this.getTemplateVariablesGroup(), + { + label: 'Aggregations', + options: aggregations, + }, + ]; + } + + handleAlignmentChange(value) { + this.target.aggregation.perSeriesAligner = value; + this.$scope.refresh(); + } + + handleAggregationChange(value) { + this.target.aggregation.crossSeriesReducer = value; + this.$scope.refresh(); + } + + handleAlignmentPeriodChange(value) { + this.target.aggregation.alignmentPeriod = value; + this.$scope.refresh(); } formatAlignmentText() { - const selectedAlignment = this.alignOptions.find( + const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); + const selectedAlignment = alignments.find( ap => ap.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner) ); return `${kbn.secondsToHms(this.$scope.alignmentPeriod)} interval (${ @@ -74,6 +119,16 @@ export class StackdriverAggregationCtrl { const newValue = this.aggOptions.find(o => o.value !== notValidOptionValue); this.target.aggregation.crossSeriesReducer = newValue ? newValue.value : ''; } + + getTemplateVariablesGroup() { + return { + label: 'Template Variables', + options: this.templateSrv.variables.map(v => ({ + label: `$${v.name}`, + value: `$${v.name}`, + })), + }; + } } coreModule.directive('stackdriverAggregation', StackdriverAggregation); From 8afe48bb3e057a22cee779aad359602f7b428266 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:55:23 +0100 Subject: [PATCH 019/166] remove redundant default value --- .../datasource/stackdriver/components/OptionGroupPicker.tsx | 1 - .../plugins/datasource/stackdriver/components/OptionPicker.tsx | 1 - 2 files changed, 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx index 7401b226413..f7863dd2a16 100644 --- a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx @@ -41,7 +41,6 @@ export class OptionGroupPicker extends React.Component { }} styles={ResetStyles} isSearchable={searchable} - maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} diff --git a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx index 3d99ed3bafc..64d68f1bd70 100644 --- a/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx @@ -39,7 +39,6 @@ export class OptionPicker extends React.Component { }} styles={ResetStyles} isSearchable={searchable} - maxMenuHeight={50} onChange={option => onChange(option.value)} getOptionValue={i => i.value} getOptionLabel={i => i.label} From 5affc55756f389752d1adf69395b7929d615d595 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:57:57 +0100 Subject: [PATCH 020/166] update failing tests --- .../stackdriver/query_aggregation_ctrl.ts | 6 +- .../stackdriver/query_filter_ctrl.ts | 14 +---- .../specs/query_aggregation_ctrl.test.ts | 57 +++++++++++++++++-- 3 files changed, 56 insertions(+), 21 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index f5ff3ad3143..d4478c6214c 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -52,6 +52,7 @@ export class StackdriverAggregationCtrl { } setAlignOptions() { + console.log('this.target.metricKind', this.target.metricKind); const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind).map(a => ({ ...a, label: a.text, @@ -78,7 +79,7 @@ export class StackdriverAggregationCtrl { } if (this.target.aggregation.groupBys.length > 0) { - aggregations = this.aggOptions.filter(o => o.value !== 'REDUCE_NONE'); + aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } this.aggOptions = [ @@ -116,7 +117,8 @@ export class StackdriverAggregationCtrl { } deselectAggregationOption(notValidOptionValue: string) { - const newValue = this.aggOptions.find(o => o.value !== notValidOptionValue); + const aggregations = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); + const newValue = aggregations.find(o => o.value !== notValidOptionValue); this.target.aggregation.crossSeriesReducer = newValue ? newValue.value : ''; } diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index ba96cfc5a60..74ca2c9b067 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -160,23 +160,11 @@ export class StackdriverFilterCtrl { options: this.templateSrv.variables.map(v => ({ label: `$${v.name}`, value: `$${v.name}`, - description: `$${v.definition}`, + // description: `$${v.definition}`, })), }; } - insertTemplateVariables(options) { - const templateVariables = { - label: 'Template Variables', - options: this.templateSrv.variables.map(v => ({ - label: `$${v.name}`, - value: `$${v.name}`, - description: `$${v.definition}`, - })), - }; - return [templateVariables, { label: 'Metrics', options }]; - } - getMetricsList() { const metrics = this.metricDescriptors.map(m => { return { diff --git a/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts b/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts index 81011f5dfe0..6e83824d504 100644 --- a/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/query_aggregation_ctrl.test.ts @@ -17,27 +17,68 @@ describe('StackdriverAggregationCtrl', () => { }, { replace: s => s, + variables: [{ name: 'someVariable1' }, { name: 'someVariable2' }], } ); }); it('should populate all aggregate options except two', () => { ctrl.setAggOptions(); - expect(ctrl.aggOptions.length).toBe(11); - expect(ctrl.aggOptions.map(o => o.value)).toEqual( + expect(ctrl.aggOptions.length).toBe(2); + const [templateVariableGroup, aggOptionsGroup] = ctrl.aggOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(aggOptionsGroup.options.length).toBe(11); + expect(aggOptionsGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE']) ); }); it('should populate all alignment options except two', () => { ctrl.setAlignOptions(); - expect(ctrl.alignOptions.length).toBe(9); - expect(ctrl.alignOptions.map(o => o.value)).toEqual( + const [templateVariableGroup, alignOptionGroup] = ctrl.aggOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(alignOptionGroup.options.length).toBe(11); + expect(alignOptionGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE']) ); }); }); + describe('and result is double and delta and no group by is used', () => { + beforeEach(async () => { + ctrl = new StackdriverAggregationCtrl( + { + $on: () => {}, + target: { + valueType: 'DOUBLE', + metricKind: 'DELTA', + aggregation: { crossSeriesReducer: '', groupBys: [] }, + }, + }, + { + replace: s => s, + variables: [{ name: 'someVariable1' }, { name: 'someVariable2' }], + } + ); + }); + + it('should populate all alignment options except four', () => { + ctrl.setAlignOptions(); + const [templateVariableGroup, alignOptionGroup] = ctrl.alignOptions; + expect(templateVariableGroup.options.length).toBe(2); + expect(alignOptionGroup.options.length).toBe(9); + expect(alignOptionGroup.options.map(o => o.value)).toEqual( + expect['not'].arrayContaining([ + 'ALIGN_NEXT_OLDER', + 'ALIGN_INTERPOLATE', + 'ALIGN_COUNT_TRUE', + 'ALIGN_COUNT_FALSE', + 'ALIGN_FRACTION_TRUE', + ]) + ); + }); + }); + describe('and result is double and gauge and a group by is used', () => { beforeEach(async () => { ctrl = new StackdriverAggregationCtrl( @@ -51,14 +92,18 @@ describe('StackdriverAggregationCtrl', () => { }, { replace: s => s, + variables: [{ name: 'someVariable1' }], } ); }); it('should populate all aggregate options except three', () => { ctrl.setAggOptions(); - expect(ctrl.aggOptions.length).toBe(10); - expect(ctrl.aggOptions.map(o => o.value)).toEqual( + const [templateVariableGroup, aggOptionsGroup] = ctrl.aggOptions; + expect(ctrl.aggOptions.length).toBe(2); + expect(templateVariableGroup.options.length).toBe(1); + expect(aggOptionsGroup.options.length).toBe(10); + expect(aggOptionsGroup.options.map(o => o.value)).toEqual( expect['not'].arrayContaining(['REDUCE_COUNT_TRUE', 'REDUCE_COUNT_FALSE', 'REDUCE_NONE']) ); }); From 15ba05d7ee3f4afe12dacf8e7831fb2fdc9053fa Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:57:32 +0100 Subject: [PATCH 021/166] cleanup query filter --- .../stackdriver/partials/query.filter.html | 2 +- .../stackdriver/query_filter_ctrl.ts | 62 +++++-------------- 2 files changed, 15 insertions(+), 49 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/partials/query.filter.html b/public/app/plugins/datasource/stackdriver/partials/query.filter.html index 583744d0e67..632f89b8248 100644 --- a/public/app/plugins/datasource/stackdriver/partials/query.filter.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.filter.html @@ -3,7 +3,7 @@ Service ; - service: string; - metricType: string; metricDescriptors: any[]; metrics: any[]; metricGroups: any[]; @@ -111,37 +109,12 @@ export class StackdriverFilterCtrl { } getServicesList() { - const services = this.metricDescriptors.map(m => { - return { - value: m.service, - label: _.startCase(m.serviceShortName), - }; - }); + const services = this.metricDescriptors.map(m => ({ + value: m.service, + label: _.startCase(m.serviceShortName), + })); - if (services.find(m => m.value === this.target.service)) { - this.service = this.target.service; - } - - return services.length > 0 ? _.uniqBy(services, 'value') : []; - } - - getMetricGroupsOld() { - return this.metrics.reduce((acc, curr) => { - const group = acc.find(group => group.service === curr.service); - if (group) { - group.options = [...group.options, { value: curr.value, label: curr.label }]; - } else { - acc = [ - ...acc, - { - label: _.startCase(curr.serviceShortName), - service: curr.service, - options: [{ value: curr.value, label: curr.label }], - }, - ]; - } - return acc; - }, []); + return services.length > 0 ? _.uniqBy(services, s => s.value) : []; } getMetricGroups() { @@ -149,7 +122,7 @@ export class StackdriverFilterCtrl { this.getTemplateVariablesGroup(), { label: 'Metrics', - options: this.metrics, + options: this.getMetricsList(), }, ]; } @@ -160,24 +133,18 @@ export class StackdriverFilterCtrl { options: this.templateSrv.variables.map(v => ({ label: `$${v.name}`, value: `$${v.name}`, - // description: `$${v.definition}`, })), }; } getMetricsList() { - const metrics = this.metricDescriptors.map(m => { - return { - service: m.service, - value: m.type, - serviceShortName: m.serviceShortName, - text: m.displayName, - label: m.displayName, - description: m.description, - }; - }); + const metricsByService = this.metricDescriptors.filter(m => m.service === this.target.service).map(m => ({ + service: m.service, + value: m.type, + label: m.displayName, + description: m.description, + })); - const metricsByService = metrics.filter(m => m.service === this.target.service); if ( metricsByService.length > 0 && !metricsByService.some(m => m.value === this.templateSrv.replace(this.target.metricType)) @@ -212,12 +179,12 @@ export class StackdriverFilterCtrl { } handleServiceChange(service) { - this.target.service = this.service = service; + this.target.service = service; this.metrics = this.getMetricsList(); this.metricGroups = this.getMetricGroups(); this.setMetricType(); this.getLabels(); - if (!this.metrics.find(m => m.value === this.target.metricType)) { + if (!this.metrics.some(m => m.value === this.target.metricType)) { this.target.metricType = ''; } else { this.$scope.refresh(); @@ -231,7 +198,6 @@ export class StackdriverFilterCtrl { } setMetricType() { - // this.target.metricType = this.metricType; const { valueType, metricKind, unit } = this.metricDescriptors.find( m => m.type === this.templateSrv.replace(this.target.metricType) ); From a7bdd757ef0890ae92a21dfbc41843086c60b35d Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:58:25 +0100 Subject: [PATCH 022/166] use same color for label as in explore dropdown --- public/sass/components/_group-heading.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/public/sass/components/_group-heading.scss b/public/sass/components/_group-heading.scss index 9656277d123..8fc39c0dc2a 100644 --- a/public/sass/components/_group-heading.scss +++ b/public/sass/components/_group-heading.scss @@ -1,5 +1,6 @@ .picker-option-group { cursor: default; + color: $text-color-weak; + font-size: $font-size-sm; font-weight: $lead-font-weight; - color: $btn-primary-bg-hl; } From a5f64a5668ceae9eaccedf2556724762f62e083f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:59:13 +0100 Subject: [PATCH 023/166] fix remove filter bug --- .../app/plugins/datasource/stackdriver/query_filter_ctrl.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index d270290abb2..8ce2dfde554 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -252,8 +252,10 @@ export class StackdriverFilterCtrl { return []; } - this.removeSegment.value = removeText; - return [...elements, this.removeSegment]; + return [ + ...elements, + this.uiSegmentSrv.newSegment({ fake: true, value: removeText || this.defaultRemoveGroupByValue }), + ]; } async getGroupBys(segment) { From 47b3901e7d40bc29a3f9ce916152422aefb05fdb Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 14:08:47 +0100 Subject: [PATCH 024/166] remove on metric type change --- .../datasource/stackdriver/query_filter_ctrl.ts | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 8ce2dfde554..e617dba5945 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -60,7 +60,9 @@ export class StackdriverFilterCtrl { handleMetricTypeChange(value) { this.target.metricType = value; - this.onMetricTypeChange(); + this.setMetricType(); + this.$scope.refresh(); + this.getLabels(); } initSegments(hideGroupBys: boolean) { @@ -191,12 +193,6 @@ export class StackdriverFilterCtrl { } } - async onMetricTypeChange() { - this.setMetricType(); - this.$scope.refresh(); - this.getLabels(); - } - setMetricType() { const { valueType, metricKind, unit } = this.metricDescriptors.find( m => m.type === this.templateSrv.replace(this.target.metricType) From 8430c5a4915219ef2e67d62c74ac08b88f56559c Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 18 Dec 2018 11:25:13 +0100 Subject: [PATCH 025/166] use new generic picker --- .../components/OptionGroupPicker.tsx | 52 ------------------ .../stackdriver/components/OptionPicker.tsx | 50 ----------------- .../components/StackdriverPicker.tsx | 53 +++++++++++++++++++ .../partials/query.aggregation.html | 16 +++--- .../stackdriver/partials/query.filter.html | 6 +-- .../datasource/stackdriver/query_ctrl.ts | 13 +---- 6 files changed, 66 insertions(+), 124 deletions(-) delete mode 100644 public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx delete mode 100644 public/app/plugins/datasource/stackdriver/components/OptionPicker.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/StackdriverPicker.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx b/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx deleted file mode 100644 index f7863dd2a16..00000000000 --- a/public/app/plugins/datasource/stackdriver/components/OptionGroupPicker.tsx +++ /dev/null @@ -1,52 +0,0 @@ -import React from 'react'; -import Select from 'react-select'; -import _ from 'lodash'; - -import GroupHeading from 'app/core/components/Picker/GroupHeading'; -import DescriptionOption from 'app/core/components/Picker/DescriptionOption'; -import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer'; -import ResetStyles from 'app/core/components/Picker/ResetStyles'; -import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; - -export interface Props { - onChange: (value: string) => void; - groups: any[]; - searchable: boolean; - selected: string; - placeholder?: string; - className?: string; -} - -export class OptionGroupPicker extends React.Component { - constructor(props) { - super(props); - } - - render() { - const { onChange, groups, selected, placeholder, className, searchable } = this.props; - const options = _.flatten(groups.map(o => o.options)); - const selectedOption = options.find(option => option.value === selected); - - return ( - onChange(option.value)} - getOptionValue={i => i.value} - getOptionLabel={i => i.label} - value={selectedOption} - noOptionsMessage={() => 'No metrics found'} - /> - ); - } -} diff --git a/public/app/plugins/datasource/stackdriver/components/StackdriverPicker.tsx b/public/app/plugins/datasource/stackdriver/components/StackdriverPicker.tsx new file mode 100644 index 00000000000..b2c447feeff --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/StackdriverPicker.tsx @@ -0,0 +1,53 @@ +import React from 'react'; +import _ from 'lodash'; +import Select from 'app/core/components/Select/Select'; + +export interface Props { + onChange: (value: string) => void; + options: any[]; + searchable: boolean; + selected: string; + placeholder?: string; + className?: string; + groups?: boolean; +} + +export class StackdriverPicker extends React.Component { + constructor(props) { + super(props); + } + + extractOptions(options) { + return options.length > 0 && options.every(o => o.options) ? _.flatten(options.map(o => o.options)) : options; + } + + onChange = item => { + const extractedOptions = this.extractOptions(this.props.options); + const option = extractedOptions.find(option => option.value === item.value); + this.props.onChange(option.value); + }; + + render() { + const { options, selected, placeholder, className, searchable } = this.props; + const extractedOptions = this.extractOptions(options); + const selectedOption = extractedOptions.find(option => option.value === selected); + + return ( +
@@ -15,7 +15,7 @@
Project - +