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 = []; + } + + init(uiSegmentSrv) { + this.uiSegmentSrv = uiSegmentSrv; + } - buildSegmentModel() { + 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[]; +}