diff --git a/public/app/plugins/datasource/stackdriver/angular_wrappers.ts b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts index 6881632ecbf..513d7d3f623 100644 --- a/public/app/plugins/datasource/stackdriver/angular_wrappers.ts +++ b/public/app/plugins/datasource/stackdriver/angular_wrappers.ts @@ -1,5 +1,6 @@ import { react2AngularDirective } from 'app/core/utils/react2angular'; import { QueryEditor } from './components/QueryEditor'; +import { AnnotationQueryEditor } from './components/AnnotationQueryEditor'; export function registerAngularDirectives() { react2AngularDirective('queryEditor', QueryEditor, [ @@ -9,4 +10,10 @@ export function registerAngularDirectives() { ['events', { watchDepth: 'reference' }], ['datasource', { watchDepth: 'reference' }], ]); + react2AngularDirective('annotationQueryEditor', AnnotationQueryEditor, [ + 'target', + 'onQueryChange', + 'onExecuteQuery', + ['datasource', { watchDepth: 'reference' }], + ]); } diff --git a/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts b/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts index bccac02bf38..1975c75d5a6 100644 --- a/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/annotations_query_ctrl.ts @@ -1,5 +1,6 @@ import _ from 'lodash'; import './query_filter_ctrl'; +import { registerAngularDirectives } from './angular_wrappers'; export class StackdriverAnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; @@ -27,5 +28,11 @@ export class StackdriverAnnotationsQueryCtrl { this.annotation.target = this.annotation.target || {}; this.annotation.target.refId = 'annotationQuery'; _.defaultsDeep(this.annotation.target, this.defaults); + registerAngularDirectives(); + this.handleQueryChange = this.handleQueryChange.bind(this); + } + + handleQueryChange(target) { + Object.assign(this.annotation.target, target); } } diff --git a/public/app/plugins/datasource/stackdriver/components/AnnotationQueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/AnnotationQueryEditor.tsx new file mode 100644 index 00000000000..6c501d45eda --- /dev/null +++ b/public/app/plugins/datasource/stackdriver/components/AnnotationQueryEditor.tsx @@ -0,0 +1,108 @@ +import React from 'react'; +import _ from 'lodash'; + +import { Metrics } from './Metrics'; +import { Filter } from './Filter'; +import { AnnotationTarget } from '../types'; + +export interface Props { + onQueryChange: (target: AnnotationTarget) => void; + target: AnnotationTarget; + datasource: any; +} + +interface State extends AnnotationTarget { + [key: string]: any; +} + +const DefaultTarget: State = { + defaultProject: 'loading project...', + metricType: '', + filters: [], + metricKind: '', + valueType: '', + refId: 'annotationQuery', + title: '', + text: '', +}; + +export class AnnotationQueryEditor extends React.Component { + state: State = DefaultTarget; + + componentDidMount() { + this.setState({ + ...this.props.target, + }); + } + + handleMetricTypeChange({ valueType, metricKind, type, unit }) { + const { onQueryChange } = this.props; + this.setState( + { + metricType: type, + unit, + valueType, + metricKind, + }, + () => { + onQueryChange(this.state); + } + ); + } + + handleChange(prop, value) { + this.setState({ [prop]: value }, () => { + this.props.onQueryChange(this.state); + }); + } + + render() { + const { defaultProject, metricType, filters, refId, title } = this.state; + const { datasource } = this.props; + + return ( + + this.handleMetricTypeChange(value)} + > + {metric => ( + + this.handleChange('filters', value)} + filters={filters} + refId={refId} + hideGroupBys={true} + templateSrv={datasource.templateSrv} + datasource={datasource} + metricType={metric ? metric.type : ''} + /> + + )} + +
+
+ Title + this.handleChange('title', e.target.value)} + /> +
+
+ Text + +
+
+
+
+
+ {/* */} + + ); + } +} diff --git a/public/app/plugins/datasource/stackdriver/components/Filter.tsx b/public/app/plugins/datasource/stackdriver/components/Filter.tsx index 0bff47e809d..cfaa5480d0b 100644 --- a/public/app/plugins/datasource/stackdriver/components/Filter.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Filter.tsx @@ -2,17 +2,20 @@ import React from 'react'; import _ from 'lodash'; import appEvents from 'app/core/app_events'; -import { QueryMeta, Target } from '../types'; +import { QueryMeta } from '../types'; import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; import '../query_filter_ctrl'; export interface Props { filtersChanged: (filters) => void; - groupBysChanged: (groupBys) => void; + groupBysChanged?: (groupBys) => void; metricType: string; templateSrv: any; - target: Target; + groupBys?: string[]; + filters: string[]; datasource: any; + refId: string; + hideGroupBys: boolean; } interface State { @@ -20,6 +23,12 @@ interface State { loading: Promise; } +const defaultLabelData = { + metricLabels: {}, + resourceLabels: {}, + resourceTypes: [], +}; + export class Filter extends React.Component { element: any; component: AngularComponent; @@ -29,17 +38,20 @@ export class Filter extends React.Component { return; } - const { target, filtersChanged, groupBysChanged } = this.props; + const { groupBys, filters, filtersChanged, groupBysChanged, hideGroupBys } = this.props; const loader = getAngularLoader(); const template = ' '; const scopeProps = { - loading: this.loadLabels.bind(this), + loading: null, labelData: null, - target, + groupBys, + filters, filtersChanged, groupBysChanged, + hideGroupBys, }; + scopeProps.loading = this.loadLabels(scopeProps); this.component = loader.load(this.element, scopeProps, template); } @@ -60,11 +72,16 @@ export class Filter extends React.Component { async loadLabels(scope) { return new Promise(async resolve => { try { - const { meta } = await this.props.datasource.getLabels(this.props.target.metricType, this.props.target.refId); - scope.labelData = meta; + if (!this.props.metricType) { + scope.labelData = defaultLabelData; + } else { + const { meta } = await this.props.datasource.getLabels(this.props.metricType, this.props.refId); + scope.labelData = meta; + } resolve(); } catch (error) { - appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.target.metricType]); + appEvents.emit('alert-error', ['Error', 'Error loading metric labels for ' + this.props.metricType]); + scope.labelData = defaultLabelData; resolve(); } }); diff --git a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx index f2f1399593f..edb12456447 100644 --- a/public/app/plugins/datasource/stackdriver/components/Metrics.tsx +++ b/public/app/plugins/datasource/stackdriver/components/Metrics.tsx @@ -19,6 +19,7 @@ interface State { service: string; metric: string; metricDescriptor: any; + defaultProject: string; } export class Metrics extends React.Component { @@ -29,6 +30,7 @@ export class Metrics extends React.Component { service: '', metric: '', metricDescriptor: null, + defaultProject: '', }; constructor(props) { @@ -36,19 +38,21 @@ export class Metrics extends React.Component { } componentDidMount() { - this.getCurrentProject() - .then(this.loadMetricDescriptors.bind(this)) - .then(this.initializeServiceAndMetrics.bind(this)); + this.setState({ defaultProject: this.props.defaultProject }, () => { + this.getCurrentProject() + .then(this.loadMetricDescriptors.bind(this)) + .then(this.initializeServiceAndMetrics.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(); + if (!this.state.defaultProject || this.state.defaultProject === 'loading project...') { + const defaultProject = await this.props.datasource.getDefaultProject(); + this.setState({ defaultProject }); } - resolve(this.props.defaultProject); + resolve(this.state.defaultProject); } catch (error) { // appEvents.emit('ds-request-error', error); reject(); @@ -57,8 +61,8 @@ export class Metrics extends React.Component { } async loadMetricDescriptors() { - if (this.props.defaultProject !== 'loading project...') { - const metricDescriptors = await this.props.datasource.getMetricTypes(this.props.defaultProject); + if (this.state.defaultProject !== 'loading project...') { + const metricDescriptors = await this.props.datasource.getMetricTypes(this.state.defaultProject); this.setState({ metricDescriptors }); return metricDescriptors; } else { @@ -81,6 +85,9 @@ export class Metrics extends React.Component { getMetricsList(metricDescriptors) { const selectedMetricDescriptor = this.getSelectedMetricDescriptor(this.props.metricType); + if (!selectedMetricDescriptor) { + return []; + } const metricsByService = metricDescriptors.filter(m => m.service === selectedMetricDescriptor.service).map(m => ({ service: m.service, value: m.type, diff --git a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx index b99865f5070..c25134b4e70 100644 --- a/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx +++ b/public/app/plugins/datasource/stackdriver/components/QueryEditor.tsx @@ -60,6 +60,11 @@ export class QueryEditor extends React.Component { }); } + componentWillUnmount() { + this.props.events.off('data-received'); + this.props.events.off('data-error'); + } + onDataReceived(dataList) { const series = dataList.find(item => item.refId === this.props.target.refId); if (series) { @@ -121,12 +126,14 @@ export class QueryEditor extends React.Component { metricType, crossSeriesReducer, groupBys, + filters, perSeriesAligner, alignOptions, alignmentPeriod, aliasBy, lastQuery, lastQueryError, + refId, } = this.state; const { datasource } = this.props; @@ -144,7 +151,10 @@ export class QueryEditor extends React.Component { this.handleChange('filters', value)} groupBysChanged={value => this.handleChange('groupBys', value)} - target={this.state} + filters={filters} + groupBys={groupBys} + refId={refId} + hideGroupBys={false} templateSrv={datasource.templateSrv} datasource={datasource} metricType={metric ? metric.type : ''} @@ -168,13 +178,11 @@ export class QueryEditor extends React.Component { } this.handleChange('aliasBy', value)} /> - this.handleChange('alignmentPeriod', value)} /> - )} diff --git a/public/app/plugins/datasource/stackdriver/filter_segments.ts b/public/app/plugins/datasource/stackdriver/filter_segments.ts index 5adb56e2fcf..e9adcad6da6 100644 --- a/public/app/plugins/datasource/stackdriver/filter_segments.ts +++ b/public/app/plugins/datasource/stackdriver/filter_segments.ts @@ -5,13 +5,13 @@ export class FilterSegments { filterSegments: any[]; removeSegment: any; - constructor(private uiSegmentSrv, private target, private getFilterKeysFunc, private getFilterValuesFunc) {} + constructor(private uiSegmentSrv, private filters, private getFilterKeysFunc, private getFilterValuesFunc) {} buildSegmentModel() { this.removeSegment = this.uiSegmentSrv.newSegment({ fake: true, value: DefaultRemoveFilterValue }); this.filterSegments = []; - this.target.filters.forEach((f, index) => { + this.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/annotations.editor.html b/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html index 0c2ce32f894..41d4be493de 100644 --- a/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html +++ b/public/app/plugins/datasource/stackdriver/partials/annotations.editor.html @@ -1,7 +1,12 @@ - + + -
+
Annotation Query Format
diff --git a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts index 767c80c4721..c15560c30ab 100644 --- a/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts +++ b/public/app/plugins/datasource/stackdriver/query_filter_ctrl.ts @@ -13,7 +13,8 @@ export class StackdriverFilter { scope: { labelData: '<', loading: '<', - target: '=', + groupBys: '<', + filters: '<', filtersChanged: '&', groupBysChanged: '&', hideGroupBys: '<', @@ -28,19 +29,17 @@ export class StackdriverFilterCtrl { groupBySegments: any[]; filterSegments: FilterSegments; removeSegment: any; - target: any; /** @ngInject */ constructor(private $scope, private uiSegmentSrv, private templateSrv, private $rootScope) { this.$scope = $scope.$parent; - this.target = this.$scope.target; this.initSegments(this.$scope.hideGroupBys); } initSegments(hideGroupBys: boolean) { if (!hideGroupBys) { - this.groupBySegments = this.target.groupBys.map(groupBy => { + this.groupBySegments = this.$scope.groupBys.map(groupBy => { return this.uiSegmentSrv.getSegmentForValue(groupBy); }); this.ensurePlusButton(this.groupBySegments); @@ -50,7 +49,7 @@ export class StackdriverFilterCtrl { this.filterSegments = new FilterSegments( this.uiSegmentSrv, - this.target, + this.$scope.filters, this.getFilterKeys.bind(this), this.getFilterValues.bind(this) ); @@ -93,7 +92,7 @@ export class StackdriverFilterCtrl { async getFilterKeys(segment, removeText?: string) { let elements = await this.createLabelKeyElements(); - if (this.target.filters.indexOf(this.resourceTypeValue) !== -1) { + if (this.$scope.filters.indexOf(this.resourceTypeValue) !== -1) { elements = elements.filter(e => e.value !== this.resourceTypeValue); } @@ -111,7 +110,7 @@ export class StackdriverFilterCtrl { async getGroupBys(segment) { let elements = await this.createLabelKeyElements(); - elements = elements.filter(e => this.target.groupBys.indexOf(e.value) === -1); + elements = elements.filter(e => this.$scope.groupBys.indexOf(e.value) === -1); const noValueOrPlusButton = !segment || segment.type === 'plus-button'; if (noValueOrPlusButton && elements.length === 0) { return []; @@ -142,6 +141,7 @@ export class StackdriverFilterCtrl { } async getFilters(segment, index) { + await this.$scope.loading; const hasNoFilterKeys = this.$scope.labelData.metricLabels && Object.keys(this.$scope.labelData.metricLabels).length === 0; return this.filterSegments.getFilters(segment, index, hasNoFilterKeys); diff --git a/public/app/plugins/datasource/stackdriver/types.ts b/public/app/plugins/datasource/stackdriver/types.ts index ffd4f28cad1..c3259649d81 100644 --- a/public/app/plugins/datasource/stackdriver/types.ts +++ b/public/app/plugins/datasource/stackdriver/types.ts @@ -32,8 +32,19 @@ export interface Target { groupBys: string[]; filters: string[]; aliasBy: string; - metricKind: any; - valueType: any; + metricKind: string; + valueType: string; +} + +export interface AnnotationTarget { + defaultProject: string; + metricType: string; + refId: string; + filters: string[]; + metricKind: string; + valueType: string; + title: string; + text: string; } export interface QueryMeta {