reactify annotation query editor
This commit is contained in:
@@ -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' }],
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<Props, State> {
|
||||
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 (
|
||||
<React.Fragment>
|
||||
<Metrics
|
||||
defaultProject={defaultProject}
|
||||
metricType={metricType}
|
||||
templateSrv={datasource.templateSrv}
|
||||
datasource={datasource}
|
||||
onChange={value => this.handleMetricTypeChange(value)}
|
||||
>
|
||||
{metric => (
|
||||
<React.Fragment>
|
||||
<Filter
|
||||
filtersChanged={value => this.handleChange('filters', value)}
|
||||
filters={filters}
|
||||
refId={refId}
|
||||
hideGroupBys={true}
|
||||
templateSrv={datasource.templateSrv}
|
||||
datasource={datasource}
|
||||
metricType={metric ? metric.type : ''}
|
||||
/>
|
||||
</React.Fragment>
|
||||
)}
|
||||
</Metrics>
|
||||
<div className="gf-form gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label query-keyword width-9">Title</span>
|
||||
<input
|
||||
type="text"
|
||||
className="gf-form-input width-20"
|
||||
value={title}
|
||||
onChange={e => this.handleChange('title', e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div className="gf-form">
|
||||
<span className="gf-form-label query-keyword width-9">Text</span>
|
||||
<input type="text" className="gf-form-input width-20" ng-model="ctrl.annotation.target.text" />
|
||||
</div>
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label gf-form-label--grow" />
|
||||
</div>
|
||||
</div>
|
||||
{/* <Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} /> */}
|
||||
</React.Fragment>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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<any>;
|
||||
}
|
||||
|
||||
const defaultLabelData = {
|
||||
metricLabels: {},
|
||||
resourceLabels: {},
|
||||
resourceTypes: [],
|
||||
};
|
||||
|
||||
export class Filter extends React.Component<Props, State> {
|
||||
element: any;
|
||||
component: AngularComponent;
|
||||
@@ -29,17 +38,20 @@ export class Filter extends React.Component<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
const { target, filtersChanged, groupBysChanged } = this.props;
|
||||
const { groupBys, filters, filtersChanged, groupBysChanged, hideGroupBys } = this.props;
|
||||
const loader = getAngularLoader();
|
||||
const template = '<stackdriver-filter> </stackdriver-filter>';
|
||||
|
||||
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<Props, State> {
|
||||
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();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -19,6 +19,7 @@ interface State {
|
||||
service: string;
|
||||
metric: string;
|
||||
metricDescriptor: any;
|
||||
defaultProject: string;
|
||||
}
|
||||
|
||||
export class Metrics extends React.Component<Props, State> {
|
||||
@@ -29,6 +30,7 @@ export class Metrics extends React.Component<Props, State> {
|
||||
service: '',
|
||||
metric: '',
|
||||
metricDescriptor: null,
|
||||
defaultProject: '',
|
||||
};
|
||||
|
||||
constructor(props) {
|
||||
@@ -36,19 +38,21 @@ export class Metrics extends React.Component<Props, State> {
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
|
||||
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,
|
||||
|
||||
@@ -60,6 +60,11 @@ export class QueryEditor extends React.Component<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
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<Props, State> {
|
||||
<Filter
|
||||
filtersChanged={value => 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<Props, State> {
|
||||
}
|
||||
</Aggregations>
|
||||
<AliasBy value={aliasBy} onChange={value => this.handleChange('aliasBy', value)} />
|
||||
|
||||
<AlignmentPeriods
|
||||
templateSrv={datasource.templateSrv}
|
||||
alignmentPeriod={alignmentPeriod}
|
||||
onChange={value => this.handleChange('alignmentPeriod', value)}
|
||||
/>
|
||||
|
||||
<Help datasource={datasource} rawQuery={lastQuery} lastQueryError={lastQueryError} />
|
||||
</React.Fragment>
|
||||
)}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -1,7 +1,12 @@
|
||||
<stackdriver-filter target="ctrl.annotation.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
|
||||
default-dropdown-value="ctrl.defaultDropdownValue" default-service-value="ctrl.defaultServiceValue" hide-group-bys="true"></stackdriver-filter>
|
||||
<!-- <stackdriver-filter target="ctrl.annotation.target" refresh="ctrl.refresh()" datasource="ctrl.datasource"
|
||||
default-dropdown-value="ctrl.defaultDropdownValue" default-service-value="ctrl.defaultServiceValue" hide-group-bys="true"></stackdriver-filter> -->
|
||||
<annotation-query-editor
|
||||
target="ctrl.annotation.target"
|
||||
datasource="ctrl.datasource"
|
||||
on-query-change="(ctrl.handleQueryChange)"
|
||||
></annotation-query-editor>
|
||||
|
||||
<div class="gf-form gf-form-inline">
|
||||
<!-- <div class="gf-form gf-form-inline">
|
||||
<div class="gf-form">
|
||||
<span class="gf-form-label query-keyword width-9">Title</span>
|
||||
<input type="text" class="gf-form-input width-20" ng-model="ctrl.annotation.target.title" />
|
||||
@@ -10,10 +15,8 @@
|
||||
<span class="gf-form-label query-keyword width-9">Text</span>
|
||||
<input type="text" class="gf-form-input width-20" ng-model="ctrl.annotation.target.text" />
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
<div class="gf-form-label gf-form-label--grow"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow"><div class="gf-form-label gf-form-label--grow"></div></div>
|
||||
</div> -->
|
||||
|
||||
<div class="gf-form grafana-info-box" style="padding: 0">
|
||||
<pre class="gf-form-pre alert alert-info" style="margin-right: 0"><h5>Annotation Query Format</h5>
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user