From 847f5491bf906604f84c26020cb6c840d45c7840 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 13:14:55 +0100 Subject: [PATCH 001/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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/352] 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 - +
-
-
Panel has no alert rule defined
- -
-
+
+
Panel has no alert rule defined
+ +
From f95359fb344ca94545d9d234cf7c43c289448358 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 19 Dec 2018 12:58:10 +0100 Subject: [PATCH 040/352] Notify user on query error --- .../features/dashboard/dashgrid/DataPanel.tsx | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/DataPanel.tsx b/public/app/features/dashboard/dashgrid/DataPanel.tsx index 9926410f40d..72f405dcbe7 100644 --- a/public/app/features/dashboard/dashgrid/DataPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DataPanel.tsx @@ -1,5 +1,6 @@ // Library import React, { Component } from 'react'; +import Tooltip from 'app/core/components/Tooltip/Tooltip'; // Services import { getDatasourceSrv, DatasourceSrv } from 'app/features/plugins/datasource_srv'; @@ -138,7 +139,7 @@ export class DataPanel extends Component { const timeSeries = response.data; if (isFirstLoad && loading === LoadingState.Loading) { - return this.renderLoadingSpinner(); + return this.renderLoadingState(); } if (!queries.length) { @@ -151,7 +152,7 @@ export class DataPanel extends Component { return ( <> - {this.renderLoadingSpinner()} + {this.renderLoadingState()} {this.props.children({ timeSeries, loading, @@ -160,15 +161,26 @@ export class DataPanel extends Component { ); } - private renderLoadingSpinner(): JSX.Element { + private renderLoadingState(): JSX.Element { const { loading } = this.state; - if (loading === LoadingState.Loading) { return (
); + } else if (loading === LoadingState.Error) { + return ( + + + + + ); } return null; From f3ba3b4df0e4a71e32b0e70024a11ba9204665c5 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Wed, 19 Dec 2018 13:09:53 +0100 Subject: [PATCH 041/352] render editor toolbar buttons --- .../features/dashboard/dashgrid/AlertTab.tsx | 22 ++++++++++++++++++- .../dashboard/dashgrid/PanelEditor.tsx | 2 +- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 7df7864c758..f9f565222d8 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -3,9 +3,11 @@ import React, { PureComponent } from 'react'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import { EditorTabBody } from './EditorTabBody'; import 'app/features/alerting/AlertTabCtrl'; +import { PanelModel } from '../panel_model'; interface Props { angularPanel?: AngularComponent; + panel: PanelModel; } export class AlertTab extends PureComponent { @@ -63,8 +65,26 @@ export class AlertTab extends PureComponent { } render() { + const { alert } = this.props.panel; + + const stateHistory = { + title: 'State history', + render: () => { + return
State history
; + }, + }; + + const deleteAlert = { + title: 'Delete button', + render: () => { + return
Hello
; + }, + }; + + const toolbarItems = alert ? [deleteAlert, stateHistory] : []; + return ( - +
(this.element = element)} /> ); diff --git a/public/app/features/dashboard/dashgrid/PanelEditor.tsx b/public/app/features/dashboard/dashgrid/PanelEditor.tsx index a746d6c4b91..b3d94f43487 100644 --- a/public/app/features/dashboard/dashgrid/PanelEditor.tsx +++ b/public/app/features/dashboard/dashgrid/PanelEditor.tsx @@ -54,7 +54,7 @@ export class PanelEditor extends PureComponent { case 'queries': return ; case 'alert': - return ; + return ; case 'visualization': return ( Date: Wed, 19 Dec 2018 14:19:27 +0100 Subject: [PATCH 042/352] cleanup --- .../stackdriver/angular_wrappers.ts | 3 +- .../components/AggregationPicker.tsx | 54 ++-- .../stackdriver/components/Filter.tsx | 239 +++--------------- .../stackdriver/components/QueryEditor.tsx | 73 ++++-- .../datasource/stackdriver/filter_segments.ts | 13 +- .../stackdriver/partials/query.editor.html | 2 +- .../stackdriver/partials/query.filter.html | 56 ++-- .../datasource/stackdriver/query_ctrl.ts | 38 +-- .../stackdriver/query_filter_ctrl.ts | 152 ++--------- 9 files changed, 162 insertions(+), 468 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/angular_wrappers.ts b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts index 683eceac904..6549135790c 100644 --- a/public/app/plugins/datasource/stackdriver/angular_wrappers.ts +++ b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts @@ -4,7 +4,8 @@ import { QueryEditor } from './components/QueryEditor'; export function registerAngularDirectives() { react2AngularDirective('queryEditor', QueryEditor, [ 'target', - 'onChange', + 'onQueryChange', + 'onExecuteQuery', ['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 index e357fd0ae2e..0a41a093d8f 100644 --- a/public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx +++ b/public/app/plugins/datasource/stackdriver/components/AggregationPicker.tsx @@ -39,7 +39,7 @@ export class AggregationPicker extends React.Component { } componentDidMount() { - this.setAggOptions(); + this.setAggOptions(this.props); } componentWillReceiveProps(nextProps: Props) { @@ -49,17 +49,21 @@ export class AggregationPicker extends React.Component { nextProps.metricKind !== metricKind || nextProps.aggregation.groupBys !== aggregation.groupBys ) { - this.setAggOptions(); + this.setAggOptions(nextProps); } } - setAggOptions() { - const { valueType, metricKind, aggregation, templateSrv } = this.props; + setAggOptions({ valueType, metricKind, aggregation }) { + const { templateSrv } = this.props; let aggregations = getAggregationOptionsByMetric(valueType, metricKind).map(a => ({ ...a, label: a.text, })); - if (!aggregations.find(o => o.value === templateSrv.replace(aggregation.crossSeriesReducer))) { + + if ( + aggregations.length > 0 && + !aggregations.find(o => o.value === templateSrv.replace(aggregation.crossSeriesReducer)) + ) { this.deselectAggregationOption('REDUCE_NONE'); } @@ -67,15 +71,7 @@ export class AggregationPicker extends React.Component { aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } - this.setState({ - aggOptions: [ - this.getTemplateVariablesGroup(), - { - label: 'Aggregations', - options: aggregations, - }, - ], - }); + this.setState({ aggOptions: aggregations }); } deselectAggregationOption(notValidOptionValue: string) { @@ -86,17 +82,6 @@ export class AggregationPicker extends React.Component { 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() { @@ -108,16 +93,15 @@ export class AggregationPicker extends React.Component {
-
- this.handleAggregationChange(value)} - selected={aggregation.crossSeriesReducer} - options={aggOptions} - searchable={true} - placeholder="Select Aggregation" - className="width-15" - /> -
+ this.handleAggregationChange(value)} + selected={aggregation.crossSeriesReducer} + options={aggOptions} + searchable={true} + placeholder="Select Aggregation" + className="width-15" + groupName="Aggregations" + />
diff --git a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts index 7213b02550d..d08bf8d94d9 100644 --- a/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_aggregation_ctrl.ts @@ -54,11 +54,11 @@ export class StackdriverAggregationCtrl { ...a, label: a.text, })); - if (!aggregations.find(o => o.value === this.templateSrv.replace(this.target.aggregation.crossSeriesReducer))) { + if (!aggregations.find(o => o.value === this.templateSrv.replace(this.target.crossSeriesReducer))) { this.deselectAggregationOption('REDUCE_NONE'); } - if (this.target.aggregation.groupBys.length > 0) { + if (this.target.groupBys.length > 0) { aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); this.deselectAggregationOption('REDUCE_NONE'); } @@ -72,24 +72,24 @@ export class StackdriverAggregationCtrl { } handleAlignmentChange(value) { - this.target.aggregation.perSeriesAligner = value; + this.target.perSeriesAligner = value; this.$scope.refresh(); } handleAggregationChange(value) { - this.target.aggregation.crossSeriesReducer = value; + this.target.crossSeriesReducer = value; this.$scope.refresh(); } handleAlignmentPeriodChange(value) { - this.target.aggregation.alignmentPeriod = value; + this.target.alignmentPeriod = value; this.$scope.refresh(); } formatAlignmentText() { const alignments = getAlignmentOptionsByMetric(this.target.valueType, this.target.metricKind); const selectedAlignment = alignments.find( - ap => ap.value === this.templateSrv.replace(this.target.aggregation.perSeriesAligner) + ap => ap.value === this.templateSrv.replace(this.target.perSeriesAligner) ); return `${kbn.secondsToHms(this.$scope.alignmentPeriod)} interval (${ selectedAlignment ? selectedAlignment.text : '' @@ -99,7 +99,7 @@ export class StackdriverAggregationCtrl { deselectAggregationOption(notValidOptionValue: string) { const aggregations = getAggregationOptionsByMetric(this.target.valueType, this.target.metricKind); const newValue = aggregations.find(o => o.value !== notValidOptionValue); - this.target.aggregation.crossSeriesReducer = newValue ? newValue.value : ''; + this.target.crossSeriesReducer = newValue ? newValue.value : ''; } getTemplateVariablesGroup() { diff --git a/public/app/plugins/datasource/stackdriver/query_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_ctrl.ts index 8dbfc7038dc..d7b08522711 100644 --- a/public/app/plugins/datasource/stackdriver/query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_ctrl.ts @@ -13,12 +13,10 @@ export const DefaultTarget = { service: '', metric: '', unit: '', - aggregation: { - crossSeriesReducer: 'REDUCE_MEAN', - alignmentPeriod: 'stackdriver-auto', - perSeriesAligner: 'ALIGN_MEAN', - groupBys: [], - }, + crossSeriesReducer: 'REDUCE_MEAN', + alignmentPeriod: 'stackdriver-auto', + perSeriesAligner: 'ALIGN_MEAN', + groupBys: [], filters: [], showAggregationOptions: false, aliasBy: '', @@ -36,12 +34,10 @@ export class StackdriverQueryCtrl extends QueryCtrl { service: '', metric: '', unit: '', - aggregation: { - crossSeriesReducer: 'REDUCE_MEAN', - alignmentPeriod: 'stackdriver-auto', - perSeriesAligner: 'ALIGN_MEAN', - groupBys: [], - }, + crossSeriesReducer: 'REDUCE_MEAN', + alignmentPeriod: 'stackdriver-auto', + perSeriesAligner: 'ALIGN_MEAN', + groupBys: [], filters: [], showAggregationOptions: false, aliasBy: '', diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index d02ed91b53d..767c80c4721 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -40,7 +40,7 @@ export class StackdriverFilterCtrl { initSegments(hideGroupBys: boolean) { if (!hideGroupBys) { - this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => { + this.groupBySegments = this.target.groupBys.map(groupBy => { return this.uiSegmentSrv.getSegmentForValue(groupBy); }); this.ensurePlusButton(this.groupBySegments); @@ -111,7 +111,7 @@ export class StackdriverFilterCtrl { async getGroupBys(segment) { let elements = await this.createLabelKeyElements(); - elements = elements.filter(e => this.target.aggregation.groupBys.indexOf(e.value) === -1); + elements = elements.filter(e => this.target.groupBys.indexOf(e.value) === -1); const noValueOrPlusButton = !segment || segment.type === 'plus-button'; if (noValueOrPlusButton && elements.length === 0) { return []; diff --git a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts index d0d8462e506..46cdd77b7a9 100644 --- a/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts +++ b/public/app/plugins/datasource/stackdriver/specs/datasource.test.ts @@ -82,7 +82,6 @@ describe('StackdriverDataSource', () => { targets: [ { refId: 'A', - aggregation: {}, }, ], }; 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 6e83824d504..c2273526844 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 @@ -12,7 +12,8 @@ describe('StackdriverAggregationCtrl', () => { target: { valueType: 'DOUBLE', metricKind: 'GAUGE', - aggregation: { crossSeriesReducer: '', groupBys: [] }, + crossSeriesReducer: '', + groupBys: [], }, }, { @@ -52,7 +53,8 @@ describe('StackdriverAggregationCtrl', () => { target: { valueType: 'DOUBLE', metricKind: 'DELTA', - aggregation: { crossSeriesReducer: '', groupBys: [] }, + crossSeriesReducer: '', + groupBys: [], }, }, { @@ -87,7 +89,8 @@ describe('StackdriverAggregationCtrl', () => { target: { valueType: 'DOUBLE', metricKind: 'GAUGE', - aggregation: { crossSeriesReducer: 'REDUCE_NONE', groupBys: ['resource.label.projectid'] }, + crossSeriesReducer: 'REDUCE_NONE', + groupBys: ['resource.label.projectid'], }, }, { @@ -110,8 +113,8 @@ describe('StackdriverAggregationCtrl', () => { it('should select some other reducer than REDUCE_NONE', () => { ctrl.setAggOptions(); - expect(ctrl.target.aggregation.crossSeriesReducer).not.toBe(''); - expect(ctrl.target.aggregation.crossSeriesReducer).not.toBe('REDUCE_NONE'); + expect(ctrl.target.crossSeriesReducer).not.toBe(''); + expect(ctrl.target.crossSeriesReducer).not.toBe('REDUCE_NONE'); }); }); }); 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 5e8d7c0aea0..539ac5c71da 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 @@ -56,7 +56,7 @@ describe('StackdriverQueryFilterCtrl', () => { 'resource-key-1': ['resource-value-1'], 'resource-key-2': ['resource-value-2'], }; - ctrl.target.aggregation.groupBys = ['metric.label.metric-key-1', 'resource.label.resource-key-1']; + ctrl.target.groupBys = ['metric.label.metric-key-1', 'resource.label.resource-key-1']; result = await ctrl.getGroupBys(); }); @@ -78,7 +78,7 @@ describe('StackdriverQueryFilterCtrl', () => { }); it('should be added to group bys list', () => { - expect(ctrl.target.aggregation.groupBys.length).toBe(1); + expect(ctrl.target.groupBys.length).toBe(1); }); }); @@ -91,7 +91,7 @@ describe('StackdriverQueryFilterCtrl', () => { }); it('should be added to group bys list', () => { - expect(ctrl.target.aggregation.groupBys.length).toBe(0); + expect(ctrl.target.groupBys.length).toBe(0); }); }); }); @@ -425,12 +425,10 @@ function createTarget(existingFilters?: string[]) { metricType: 'ametric', service: '', refId: 'A', - aggregation: { - crossSeriesReducer: '', - alignmentPeriod: '', - perSeriesAligner: '', - groupBys: [], - }, + crossSeriesReducer: '', + alignmentPeriod: '', + perSeriesAligner: '', + groupBys: [], filters: existingFilters || [], aliasBy: '', metricService: '', diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index fa46800ce21..ffd4f28cad1 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -26,12 +26,10 @@ export interface Target { metricType: string; service: string; refId: string; - aggregation: { - crossSeriesReducer: string; - alignmentPeriod: string; - perSeriesAligner: string; - groupBys: string[]; - }; + crossSeriesReducer: string; + alignmentPeriod: string; + perSeriesAligner: string; + groupBys: string[]; filters: string[]; aliasBy: string; metricKind: any; From 1452bc2e8a6573bad6f4a6dba2eaf7cd73330593 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 20 Dec 2018 11:26:05 +0100 Subject: [PATCH 050/352] move alignment population code to parent component. make alignment a stateless component instead. --- .../stackdriver/components/Alignments.tsx | 90 +++++-------------- .../stackdriver/components/QueryEditor.tsx | 47 +++++++--- .../datasource/stackdriver/functions.ts | 11 +++ 3 files changed, 66 insertions(+), 82 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/Alignments.tsx b/public/app/plugins/datasource/stackdriver/components/Alignments.tsx index b871984a1b3..bf26d3347d0 100644 --- a/public/app/plugins/datasource/stackdriver/components/Alignments.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Alignments.tsx @@ -1,16 +1,12 @@ -import React from 'react'; +import React, { SFC } from 'react'; import _ from 'lodash'; -// import { OptionPicker } from './OptionPicker'; -// import { alignmentPeriods } from '../constants'; -// import { getAlignmentOptionsByMetric, getAggregationOptionsByMetric } from '../functions'; -import { getAlignmentOptionsByMetric } from '../functions'; import { StackdriverPicker } from './StackdriverPicker'; -// import kbn from 'app/core/utils/kbn'; export interface Props { onChange: (metricDescriptor) => void; templateSrv: any; + alignOptions: any[]; metricDescriptor: { valueType: string; metricKind: string; @@ -18,66 +14,24 @@ export interface Props { perSeriesAligner: string; } -interface State { - alignOptions: any[]; -} - -export class Alignments extends React.Component { - state: State = { - alignOptions: [], - }; - - constructor(props) { - super(props); - } - - componentDidMount() { - if (this.props.metricDescriptor !== null) { - this.setAlignOptions(this.props); - } - } - - componentWillReceiveProps(nextProps: Props) { - if (nextProps.metricDescriptor !== null) { - this.setAlignOptions(nextProps); - } - } - - setAlignOptions({ metricDescriptor, perSeriesAligner, templateSrv, onChange }) { - const alignOptions = getAlignmentOptionsByMetric(metricDescriptor.valueType, metricDescriptor.metricKind).map( - option => ({ - ...option, - label: option.text, - }) - ); - if (!alignOptions.some(o => o.value === templateSrv.replace(perSeriesAligner))) { - onChange(alignOptions.length > 0 ? alignOptions[0].value : ''); - } - this.setState({ alignOptions }); - } - - render() { - const { alignOptions } = this.state; - const { perSeriesAligner, templateSrv, onChange } = this.props; - - return ( - -
-
- - onChange(value)} - selected={perSeriesAligner} - templateVariables={templateSrv.variables} - options={alignOptions} - searchable={true} - placeholder="Select Alignment" - className="width-15" - groupName="Alignment Options" - /> -
+export const Alignments: SFC = ({ perSeriesAligner, templateSrv, onChange, alignOptions }) => { + return ( + +
+
+ + onChange(value)} + selected={perSeriesAligner} + templateVariables={templateSrv.variables} + options={alignOptions} + searchable={true} + placeholder="Select Alignment" + className="width-15" + groupName="Alignment Options" + />
- - ); - } -} +
+
+ ); +}; diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index 24dec2e90d5..b661166e2a2 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -6,6 +6,7 @@ import { Filter } from './Filter'; import { Aggregations } from './Aggregations'; import { Alignments } from './Alignments'; import { Target } from '../types'; +import { getAlignmentPickerData } from '../functions'; export interface Props { onQueryChange: (target: Target) => void; @@ -16,7 +17,11 @@ export interface Props { uiSegmentSrv: any; } -const DefaultTarget: Target = { +interface State extends Target { + alignOptions: any[]; +} + +const DefaultTarget: State = { defaultProject: 'loading project...', metricType: '', metricKind: '', @@ -30,18 +35,30 @@ const DefaultTarget: Target = { groupBys: [], filters: [], aliasBy: '', + alignOptions: [], }; -export class QueryEditor extends React.Component { - state: Target = DefaultTarget; +export class QueryEditor extends React.Component { + state: State = DefaultTarget; componentDidMount() { - this.setState(this.props.target); + const { perSeriesAligner, alignOptions } = getAlignmentPickerData(this.props.target, this.props.templateSrv); + this.setState({ + ...this.props.target, + alignOptions, + perSeriesAligner, + }); } handleMetricTypeChange({ valueType, metricKind, type, unit }) { + const { perSeriesAligner, alignOptions } = getAlignmentPickerData( + { valueType, metricKind, perSeriesAligner: this.state.perSeriesAligner }, + this.props.templateSrv + ); this.setState( { + alignOptions, + perSeriesAligner, metricType: type, unit, valueType, @@ -97,7 +114,7 @@ export class QueryEditor extends React.Component { } render() { - const { defaultProject, metricType, crossSeriesReducer, groupBys, perSeriesAligner } = this.state; + const { defaultProject, metricType, crossSeriesReducer, groupBys, perSeriesAligner, alignOptions } = this.state; const { templateSrv, datasource, uiSegmentSrv } = this.props; return ( @@ -127,15 +144,17 @@ export class QueryEditor extends React.Component { groupBys={groupBys} onChange={value => this.handleAggregationChange(value)} > - {displayAdvancedOptions => ( - this.handleAlignmentChange(value)} - /> - )} + {displayAdvancedOptions => + displayAdvancedOptions && ( + this.handleAlignmentChange(value)} + /> + ) + } )} diff --git a/public/app/plugins/datasource/stackdriver/functions.ts b/public/app/plugins/datasource/stackdriver/functions.ts index e39a7d42508..81074b07707 100644 --- a/public/app/plugins/datasource/stackdriver/functions.ts +++ b/public/app/plugins/datasource/stackdriver/functions.ts @@ -46,3 +46,14 @@ export const getLabelKeys = async (datasource, selectedMetricType) => { : []; return labelKeys; }; + +export const getAlignmentPickerData = ({ valueType, metricKind, perSeriesAligner }, templateSrv) => { + const alignOptions = getAlignmentOptionsByMetric(valueType, metricKind).map(option => ({ + ...option, + label: option.text, + })); + if (!alignOptions.some(o => o.value === templateSrv.replace(perSeriesAligner))) { + perSeriesAligner = alignOptions.length > 0 ? alignOptions[0].value : ''; + } + return { alignOptions, perSeriesAligner }; +}; From 7a31076f0932edae267b4744f77f88174b89f9f9 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 20 Dec 2018 13:27:47 +0100 Subject: [PATCH 051/352] cleanup aggregation picker --- .../stackdriver/components/Aggregations.tsx | 45 ++++--------------- .../stackdriver/components/QueryEditor.tsx | 17 +++---- 2 files changed, 18 insertions(+), 44 deletions(-) diff --git a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx index 2fa3b9b9f7d..22bd2d268cb 100644 --- a/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Aggregations.tsx @@ -27,50 +27,23 @@ export class Aggregations extends React.Component { displayAdvancedOptions: false, }; - constructor(props) { - super(props); - } - componentDidMount() { - if (this.props.metricDescriptor !== null) { - this.setAggOptions(this.props); - } + this.setAggOptions(this.props); } componentWillReceiveProps(nextProps: Props) { - if (nextProps.metricDescriptor !== null) { - this.setAggOptions(nextProps); - } + this.setAggOptions(nextProps); } - setAggOptions({ metricDescriptor, crossSeriesReducer, groupBys, templateSrv }) { - let aggregations = getAggregationOptionsByMetric(metricDescriptor.valueType, metricDescriptor.metricKind).map( - a => ({ + setAggOptions({ metricDescriptor }: Props) { + let aggOptions = []; + if (metricDescriptor !== null) { + aggOptions = getAggregationOptionsByMetric(metricDescriptor.valueType, metricDescriptor.metricKind).map(a => ({ ...a, label: a.text, - }) - ); - - if (aggregations.length > 0 && !aggregations.find(o => o.value === templateSrv.replace(crossSeriesReducer))) { - this.deselectAggregationOption('REDUCE_NONE'); + })); } - - if (groupBys.length > 0) { - aggregations = aggregations.filter(o => o.value !== 'REDUCE_NONE'); - if (crossSeriesReducer === 'REDUCE_NONE') { - this.deselectAggregationOption('REDUCE_NONE'); - } - } - this.setState({ aggOptions: aggregations }); - } - - deselectAggregationOption(notValidOptionValue: string) { - const aggregations = getAggregationOptionsByMetric( - this.props.metricDescriptor.valueType, - this.props.metricDescriptor.metricKind - ); - const newValue = aggregations.find(o => o.value !== notValidOptionValue); - this.props.onChange(newValue ? newValue.value : ''); + this.setState({ aggOptions }); } handleToggleDisplayAdvanced() { @@ -80,7 +53,7 @@ export class Aggregations extends React.Component { } render() { - const { aggOptions, displayAdvancedOptions } = this.state; + const { displayAdvancedOptions, aggOptions } = this.state; const { templateSrv, onChange, crossSeriesReducer } = this.props; return ( diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index b661166e2a2..b08ecd55fe9 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -65,7 +65,7 @@ export class QueryEditor extends React.Component { metricKind, }, () => { - // this.props.onQueryChange(this.state); + this.props.onQueryChange(this.state); this.props.onExecuteQuery(); } ); @@ -77,7 +77,7 @@ export class QueryEditor extends React.Component { filters: value, }, () => { - // this.props.onQueryChange(this.state); + this.props.onQueryChange(this.state); this.props.onExecuteQuery(); } ); @@ -89,7 +89,7 @@ export class QueryEditor extends React.Component { groupBys: value, }, () => { - // this.props.onQueryChange(this.state); + this.props.onQueryChange(this.state); this.props.onExecuteQuery(); } ); @@ -97,21 +97,22 @@ export class QueryEditor extends React.Component { handleAggregationChange(value) { this.setState({ crossSeriesReducer: value }, () => { - // this.props.onQueryChange(this.state); + this.props.onQueryChange(this.state); this.props.onExecuteQuery(); }); } handleAlignmentChange(value) { this.setState({ perSeriesAligner: value }, () => { - // this.props.onQueryChange(this.state); + this.props.onQueryChange(this.state); this.props.onExecuteQuery(); }); } - componentDidUpdate(prevProps: Props, prevState: Target) { - this.props.onQueryChange(this.state); - } + // componentDidUpdate(prevProps: Props, prevState: Target) { + // this.props.onQueryChange(this.state); + + // } render() { const { defaultProject, metricType, crossSeriesReducer, groupBys, perSeriesAligner, alignOptions } = this.state; From 861f911cdaac4d8be7751bc55531aab0e2c048ba Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 20 Dec 2018 13:36:10 +0100 Subject: [PATCH 052/352] add alignment periods component --- .../components/AlignmentPeriods.tsx | 36 +++++++++++++++++++ .../stackdriver/components/QueryEditor.tsx | 26 +++++++++++--- 2 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/AlignmentPeriods.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/AlignmentPeriods.tsx b/public/app/plugins/datasource/stackdriver/components/AlignmentPeriods.tsx new file mode 100644 index 00000000000..5bedb7ec040 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/AlignmentPeriods.tsx @@ -0,0 +1,36 @@ +import React, { SFC } from 'react'; +import _ from 'lodash'; + +import { StackdriverPicker } from './StackdriverPicker'; +import { alignmentPeriods } from '../constants'; + +export interface Props { + onChange: (alignmentPeriod) => void; + templateSrv: any; + alignmentPeriod: string; +} + +export const AlignmentPeriods: SFC = ({ alignmentPeriod, templateSrv, onChange }) => { + return ( + +
+
+ + onChange(value)} + selected={alignmentPeriod} + templateVariables={templateSrv.variables} + options={alignmentPeriods.map(ap => ({ + ...ap, + label: ap.text, + }))} + searchable={true} + placeholder="Select Alignment" + className="width-15" + groupName="Alignment Options" + /> +
+
+
+ ); +}; diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index b08ecd55fe9..fd985a353c9 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -5,6 +5,7 @@ import { Metrics } from './Metrics'; import { Filter } from './Filter'; import { Aggregations } from './Aggregations'; import { Alignments } from './Alignments'; +import { AlignmentPeriods } from './AlignmentPeriods'; import { Target } from '../types'; import { getAlignmentPickerData } from '../functions'; @@ -109,13 +110,23 @@ export class QueryEditor extends React.Component { }); } - // componentDidUpdate(prevProps: Props, prevState: Target) { - // this.props.onQueryChange(this.state); - - // } + handleAlignmentPeriodChange(value) { + this.setState({ alignmentPeriod: value }, () => { + this.props.onQueryChange(this.state); + this.props.onExecuteQuery(); + }); + } render() { - const { defaultProject, metricType, crossSeriesReducer, groupBys, perSeriesAligner, alignOptions } = this.state; + const { + defaultProject, + metricType, + crossSeriesReducer, + groupBys, + perSeriesAligner, + alignOptions, + alignmentPeriod, + } = this.state; const { templateSrv, datasource, uiSegmentSrv } = this.props; return ( @@ -157,6 +168,11 @@ export class QueryEditor extends React.Component { ) } + this.handleAlignmentPeriodChange(value)} + /> )} From 06f771afd39cf02d28303063503b0a936d7ac99c Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 20 Dec 2018 15:59:21 +0100 Subject: [PATCH 053/352] add alias by component --- .../stackdriver/components/AliasBy.tsx | 54 +++++++++++++++++++ .../stackdriver/components/QueryEditor.tsx | 10 ++++ .../stackdriver/partials/query.editor.html | 4 +- 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/AliasBy.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/AliasBy.tsx b/public/app/plugins/datasource/stackdriver/components/AliasBy.tsx new file mode 100644 index 00000000000..ae397a2c4d8 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/AliasBy.tsx @@ -0,0 +1,54 @@ +import React, { Component } from 'react'; +import { debounce } from 'lodash'; + +export interface Props { + onChange: (alignmentPeriod) => void; + value: string; +} + +export interface State { + value: string; +} + +export class AliasBy extends Component { + constructor(props) { + super(props); + this.handleChange = this.handleChange.bind(this); + this.onChange = debounce(this.onChange.bind(this), 500); + this.state = { value: '' }; + } + + componentDidMount() { + this.setState({ value: this.props.value }); + } + + handleChange(e) { + this.setState({ value: e.target.value }); + this.onChange(e.target.value); + } + + onChange(value) { + this.props.onChange(value); + } + + render() { + return ( + +
+
+ + +
+
+
+
+
+ + ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index fd985a353c9..a5a85760054 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -6,6 +6,7 @@ import { Filter } from './Filter'; import { Aggregations } from './Aggregations'; import { Alignments } from './Alignments'; import { AlignmentPeriods } from './AlignmentPeriods'; +import { AliasBy } from './AliasBy'; import { Target } from '../types'; import { getAlignmentPickerData } from '../functions'; @@ -117,6 +118,13 @@ export class QueryEditor extends React.Component { }); } + handleAliasByChange(value) { + this.setState({ aliasBy: value }, () => { + this.props.onQueryChange(this.state); + this.props.onExecuteQuery(); + }); + } + render() { const { defaultProject, @@ -126,6 +134,7 @@ export class QueryEditor extends React.Component { perSeriesAligner, alignOptions, alignmentPeriod, + aliasBy, } = this.state; const { templateSrv, datasource, uiSegmentSrv } = this.props; @@ -173,6 +182,7 @@ export class QueryEditor extends React.Component { alignmentPeriod={alignmentPeriod} onChange={value => this.handleAlignmentPeriodChange(value)} /> + this.handleAliasByChange(value)} /> )} diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index f87dda0f044..20b3a6634a5 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -9,7 +9,7 @@ -
+
Project From e2ed1637791e63b280c0312f02d359f46a3174df Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Thu, 20 Dec 2018 16:55:02 +0100 Subject: [PATCH 054/352] add project and help component --- .../stackdriver/components/Help.tsx | 59 +++++++++++++++++ .../stackdriver/components/Project.tsx | 30 +++++++++ .../stackdriver/components/QueryEditor.tsx | 63 +++++++++++-------- .../stackdriver/partials/query.editor.html | 4 +- 4 files changed, 129 insertions(+), 27 deletions(-) create mode 100644 public/app/plugins/datasource/stackdriver/components/Help.tsx create mode 100644 public/app/plugins/datasource/stackdriver/components/Project.tsx diff --git a/public/app/plugins/datasource/stackdriver/components/Help.tsx b/public/app/plugins/datasource/stackdriver/components/Help.tsx new file mode 100644 index 00000000000..9cc02acffa0 --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/Help.tsx @@ -0,0 +1,59 @@ +import React from 'react'; +import { Project } from './Project'; + +export interface Props { + datasource: any; +} + +interface State { + displayHelp: boolean; + displaRawQuery: boolean; +} + +export class Help extends React.Component { + state: State = { + displayHelp: false, + displaRawQuery: false, + }; + + handleHelpClicked() { + this.setState({ displayHelp: !this.state.displayHelp }); + } + + handleRawQueryClicked() { + this.setState({ displayHelp: !this.state.displayHelp }); + } + + render() { + const { displayHelp, displaRawQuery } = this.state; + const { datasource } = this.props; + + return ( +
+ + {/* {displayHelp && ( */} +
+ +
+ {/* )} */} + + {displaRawQuery && ( +
+ +
+ )} +
+
+
+
+ ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/Project.tsx b/public/app/plugins/datasource/stackdriver/components/Project.tsx new file mode 100644 index 00000000000..65fd76884dc --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/Project.tsx @@ -0,0 +1,30 @@ +import React from 'react'; + +export interface Props { + datasource: any; +} + +interface State { + projectName: string; +} + +export class Project extends React.Component { + state: State = { + projectName: 'Loading project...', + }; + + async componentDidMount() { + const projectName = await this.props.datasource.getDefaultProject(); + this.setState({ projectName }); + } + + render() { + const { projectName } = this.state; + return ( +
+ Project + +
+ ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index a5a85760054..373e2ccddb0 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -7,6 +7,7 @@ import { Aggregations } from './Aggregations'; import { Alignments } from './Alignments'; import { AlignmentPeriods } from './AlignmentPeriods'; import { AliasBy } from './AliasBy'; +import { Help } from './Help'; import { Target } from '../types'; import { getAlignmentPickerData } from '../functions'; @@ -73,28 +74,18 @@ export class QueryEditor extends React.Component { ); } - handleFilterChange(value) { - this.setState( - { - filters: value, - }, - () => { - this.props.onQueryChange(this.state); - this.props.onExecuteQuery(); - } - ); + handleFilterChange(filters) { + this.setState({ filters }, () => { + this.props.onQueryChange(this.state); + this.props.onExecuteQuery(); + }); } - handleGroupBysChange(value) { - this.setState( - { - groupBys: value, - }, - () => { - this.props.onQueryChange(this.state); - this.props.onExecuteQuery(); - } - ); + handleGroupBysChange(groupBys) { + this.setState({ groupBys }, () => { + this.props.onQueryChange(this.state); + this.props.onExecuteQuery(); + }); } handleAggregationChange(value) { @@ -177,15 +168,37 @@ export class QueryEditor extends React.Component { ) } - this.handleAlignmentPeriodChange(value)} - /> this.handleAliasByChange(value)} /> )} + this.handleAlignmentPeriodChange(value)} + /> + + + {/*
+ +
+ +
+
+ +
+
+
+
+
*/} ); } diff --git a/public/app/plugins/datasource/stackdriver/partials/query.editor.html b/public/app/plugins/datasource/stackdriver/partials/query.editor.html index 20b3a6634a5..0e531694eff 100755 --- a/public/app/plugins/datasource/stackdriver/partials/query.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/query.editor.html @@ -19,7 +19,7 @@
--> -
+
{{ctrl.lastQueryMeta.rawQueryString}}
From 6aacd0734b6c4ec99913c0772bd9329dee9a12da Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 21 Dec 2018 11:57:21 +0100 Subject: [PATCH 056/352] typings and renamings --- .../features/dashboard/dashgrid/AlertTab.tsx | 8 +++--- .../dashboard/dashgrid/EditorTabBody.tsx | 26 +++++++++++++------ .../dashboard/dashgrid/QueriesTab.tsx | 19 +++++++------- .../dashboard/dashgrid/VisualizationTab.tsx | 10 +++---- 4 files changed, 36 insertions(+), 27 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index f9f565222d8..ee7d320a85b 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; -import { EditorTabBody } from './EditorTabBody'; +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import 'app/features/alerting/AlertTabCtrl'; import { PanelModel } from '../panel_model'; @@ -67,11 +67,12 @@ export class AlertTab extends PureComponent { render() { const { alert } = this.props.panel; - const stateHistory = { + const stateHistory: EditorToolbarView = { title: 'State history', render: () => { return
State history
; }, + buttonType: ToolbarButtonType.View, }; const deleteAlert = { @@ -79,6 +80,7 @@ export class AlertTab extends PureComponent { render: () => { return
Hello
; }, + buttonType: ToolbarButtonType.Action, }; const toolbarItems = alert ? [deleteAlert, stateHistory] : []; diff --git a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx index 7606d327405..2cd247ed704 100644 --- a/public/app/features/dashboard/dashgrid/EditorTabBody.tsx +++ b/public/app/features/dashboard/dashgrid/EditorTabBody.tsx @@ -10,21 +10,28 @@ interface Props { children: JSX.Element; heading: string; renderToolbar?: () => JSX.Element; - toolbarItems?: EditorToolBarView[]; + toolbarItems?: EditorToolbarView[]; } -export interface EditorToolBarView { +export enum ToolbarButtonType { + Action = 'action', + View = 'view', +} + +export interface EditorToolbarView { title?: string; heading?: string; imgSrc?: string; icon?: string; disabled?: boolean; onClick?: () => void; - render: (closeFunction?: any) => JSX.Element | JSX.Element[]; + render?: (closeFunction?: any) => JSX.Element | JSX.Element[]; + action?: () => void; + buttonType: ToolbarButtonType; } interface State { - openView?: EditorToolBarView; + openView?: EditorToolbarView; isOpen: boolean; fadeIn: boolean; } @@ -48,7 +55,7 @@ export class EditorTabBody extends PureComponent { this.setState({ fadeIn: true }); } - onToggleToolBarView = (item: EditorToolBarView) => { + onToggleToolBarView = (item: EditorToolbarView) => { this.setState({ openView: item, isOpen: !this.state.isOpen, @@ -74,12 +81,15 @@ export class EditorTabBody extends PureComponent { return state; } - renderButton(view: EditorToolBarView) { + renderButton(view: EditorToolbarView) { const onClick = () => { if (view.onClick) { view.onClick(); } - this.onToggleToolBarView(view); + + if (view.buttonType !== ToolbarButtonType.Action) { + this.onToggleToolBarView(view); + } }; return ( @@ -91,7 +101,7 @@ export class EditorTabBody extends PureComponent { ); } - renderOpenView(view: EditorToolBarView) { + renderOpenView(view: EditorToolbarView) { return ( {view.render()} diff --git a/public/app/features/dashboard/dashgrid/QueriesTab.tsx b/public/app/features/dashboard/dashgrid/QueriesTab.tsx index 9ad0bb3cadd..98287e4888e 100644 --- a/public/app/features/dashboard/dashgrid/QueriesTab.tsx +++ b/public/app/features/dashboard/dashgrid/QueriesTab.tsx @@ -1,26 +1,23 @@ // Libraries -import React, { SFC, PureComponent } from 'react'; +import React, { PureComponent, SFC } from 'react'; import _ from 'lodash'; - // Components import './../../panel/metrics_tab'; -import { EditorTabBody } from './EditorTabBody'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import { DataSourcePicker } from 'app/core/components/Select/DataSourcePicker'; import { QueryInspector } from './QueryInspector'; import { QueryOptions } from './QueryOptions'; import { AngularQueryComponentScope } from 'app/features/panel/metrics_tab'; import { PanelOptionSection } from './PanelOptionSection'; - // Services import { getDatasourceSrv } from 'app/features/plugins/datasource_srv'; -import { getBackendSrv, BackendSrv } from 'app/core/services/backend_srv'; -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { BackendSrv, getBackendSrv } from 'app/core/services/backend_srv'; +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import config from 'app/core/config'; - // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; -import { DataSourceSelectItem, DataQuery } from 'app/types'; +import { DataQuery, DataSourceSelectItem } from 'app/types'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; interface Props { @@ -204,15 +201,17 @@ export class QueriesTab extends PureComponent { const { panel } = this.props; const { currentDS, isAddingMixed } = this.state; - const queryInspector = { + const queryInspector: EditorToolbarView = { title: 'Query Inspector', render: this.renderQueryInspector, + buttonType: ToolbarButtonType.View, }; - const dsHelp = { + const dsHelp: EditorToolbarView = { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, + buttonType: ToolbarButtonType.View, }; return ( diff --git a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx index 42d9bf6a6eb..8a57dd17190 100644 --- a/public/app/features/dashboard/dashgrid/VisualizationTab.tsx +++ b/public/app/features/dashboard/dashgrid/VisualizationTab.tsx @@ -1,16 +1,13 @@ // Libraries import React, { PureComponent } from 'react'; - // Utils & Services -import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; - +import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; // Components -import { EditorTabBody } from './EditorTabBody'; +import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; import { VizTypePicker } from './VizTypePicker'; import { PluginHelp } from 'app/core/components/PluginHelp/PluginHelp'; import { FadeIn } from 'app/core/components/Animations/FadeIn'; import { PanelOptionSection } from './PanelOptionSection'; - // Types import { PanelModel } from '../panel_model'; import { DashboardModel } from '../dashboard_model'; @@ -206,10 +203,11 @@ export class VisualizationTab extends PureComponent { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; - const pluginHelp = { + const pluginHelp: EditorToolbarView = { heading: 'Help', icon: 'fa fa-question', render: this.renderHelp, + buttonType: ToolbarButtonType.View, }; return ( From 4002f80ab880e34b60a855b03820f4b21d1f9814 Mon Sep 17 00:00:00 2001 From: Peter Holmberg Date: Fri, 21 Dec 2018 14:56:49 +0100 Subject: [PATCH 057/352] delete works --- .../features/alerting/partials/alert_tab.html | 4 +- .../features/dashboard/dashgrid/AlertTab.tsx | 44 ++++++++++++++----- 2 files changed, 34 insertions(+), 14 deletions(-) diff --git a/public/app/features/alerting/partials/alert_tab.html b/public/app/features/alerting/partials/alert_tab.html index 5fcc7ae5e44..cbddb93207a 100644 --- a/public/app/features/alerting/partials/alert_tab.html +++ b/public/app/features/alerting/partials/alert_tab.html @@ -1,4 +1,4 @@ -
+
{{ctrl.error}}
@@ -154,7 +154,7 @@
-
+
Panel has no alert rule defined
-
-
diff --git a/public/app/features/dashboard/dashgrid/AlertTab.tsx b/public/app/features/dashboard/dashgrid/AlertTab.tsx index 2a715536ca8..5ccfa791328 100644 --- a/public/app/features/dashboard/dashgrid/AlertTab.tsx +++ b/public/app/features/dashboard/dashgrid/AlertTab.tsx @@ -1,7 +1,7 @@ import React, { PureComponent } from 'react'; - import { AngularComponent, getAngularLoader } from 'app/core/services/AngularLoader'; import { EditorTabBody, EditorToolbarView, ToolbarButtonType } from './EditorTabBody'; +import EmptyListCTA from 'app/core/components/EmptyListCTA/EmptyListCTA'; import appEvents from 'app/core/app_events'; import { PanelModel } from '../panel_model'; import 'app/features/alerting/AlertTabCtrl'; @@ -33,7 +33,7 @@ export class AlertTab extends PureComponent { } shouldLoadAlertTab() { - return this.props.angularPanel && this.element; + return this.props.angularPanel && this.element && !this.component; } componentWillUnmount() { @@ -93,6 +93,7 @@ export class AlertTab extends PureComponent { panel.thresholds = []; this.panelCtrl.alertState = null; this.panelCtrl.render(); + this.forceUpdate(); }, }); }, @@ -100,15 +101,31 @@ export class AlertTab extends PureComponent { }; }; + onAddAlert = () => { + this.panelCtrl._enableAlert(); + this.component.digest(); + this.forceUpdate(); + }; + render() { const { alert } = this.props.panel; const toolbarItems = alert ? [this.stateHistory(), this.deleteAlert()] : []; + const model = { + title: 'Panel has no alert rule defined', + icon: 'icon-gf icon-gf-alert', + onClick: this.onAddAlert, + buttonTitle: 'Create Alert', + }; + //TODO move add button react from angular and add condition to render angular view return ( -
(this.element = element)} /> + <> +
(this.element = element)} /> + {!alert && } + ); } From 70c2efd7117c0cff1397bf1301672d437baa108f Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 13:14:55 +0100 Subject: [PATCH 069/352] 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 488e92e7f5bb026c569d0154443811c07c02e871 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:11:18 +0100 Subject: [PATCH 070/352] 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 404a212241b..3acb76e960d 100644 --- a/public/sass/_grafana.scss +++ b/public/sass/_grafana.scss @@ -105,6 +105,7 @@ @import 'components/page_loader'; @import 'components/thresholds'; @import 'components/toggle_button_group'; +@import 'components/group-heading'; @import 'components/value-mappings'; @import 'components/popover-box'; 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 b5d6cd1cc86be4691d54d40f279d2e6d0fc32e27 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 11 Dec 2018 20:12:33 +0100 Subject: [PATCH 071/352] 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 406c0d15a5ba2ea733bc2fdf27bff21b37d274f5 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:14:06 +0100 Subject: [PATCH 072/352] 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 4d19ac5c02f09770fd0e831c73632d5725434f58 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 09:57:30 +0100 Subject: [PATCH 073/352] 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 615fa6fc7d7a57ab820287cd10e6d8ef59279983 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:55:23 +0100 Subject: [PATCH 074/352] 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 b6c6cc1ff692dafca9f6214f58b69fbbc6e9efbf Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 12:57:57 +0100 Subject: [PATCH 075/352] 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 f68db4eded59eb043cf352e1274d52794251cf5e Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:57:32 +0100 Subject: [PATCH 076/352] 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 85a434568f85ef124c78318b98633fe375afc1d0 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:58:25 +0100 Subject: [PATCH 077/352] 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 014457c48645b38bc840ff990c7539c00126de88 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 13:59:13 +0100 Subject: [PATCH 078/352] 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 478cfc195afe702aefaf55212a8a40d533693554 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Wed, 12 Dec 2018 14:08:47 +0100 Subject: [PATCH 079/352] 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 4734788db47c6f31394da69b96f681ca9ccf7a8a Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Tue, 18 Dec 2018 11:25:13 +0100 Subject: [PATCH 080/352] 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 - +