| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  | import _ from 'lodash'; | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  | import { QueryCtrl } from 'app/plugins/sdk'; | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  | import appEvents from 'app/core/app_events'; | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  | export interface LabelType { | 
					
						
							|  |  |  |   key: string; | 
					
						
							|  |  |  |   value: string; | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  | export interface QueryMeta { | 
					
						
							|  |  |  |   rawQuery: string; | 
					
						
							|  |  |  |   rawQueryString: string; | 
					
						
							| 
									
										
										
										
											2018-09-14 05:51:45 +08:00
										 |  |  |   metricLabels: { [key: string]: string[] }; | 
					
						
							|  |  |  |   resourceLabels: { [key: string]: string[] }; | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  | export class StackdriverQueryCtrl extends QueryCtrl { | 
					
						
							|  |  |  |   static templateUrl = 'partials/query.editor.html'; | 
					
						
							| 
									
										
										
										
											2018-09-10 04:53:35 +08:00
										 |  |  |   target: { | 
					
						
							|  |  |  |     project: { | 
					
						
							|  |  |  |       id: string; | 
					
						
							|  |  |  |       name: string; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     metricType: string; | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  |     refId: string; | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |     aggregation: { | 
					
						
							|  |  |  |       crossSeriesReducer: string; | 
					
						
							|  |  |  |       alignmentPeriod: string; | 
					
						
							|  |  |  |       perSeriesAligner: string; | 
					
						
							|  |  |  |       groupBys: string[]; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  |   }; | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  |   defaultDropdownValue = 'Select metric'; | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-11 01:08:39 +08:00
										 |  |  |   defaults = { | 
					
						
							|  |  |  |     project: { | 
					
						
							|  |  |  |       id: 'default', | 
					
						
							|  |  |  |       name: 'loading project...', | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2018-09-12 06:24:59 +08:00
										 |  |  |     metricType: this.defaultDropdownValue, | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |     aggregation: { | 
					
						
							|  |  |  |       crossSeriesReducer: 'REDUCE_MEAN', | 
					
						
							|  |  |  |       alignmentPeriod: '', | 
					
						
							|  |  |  |       perSeriesAligner: '', | 
					
						
							|  |  |  |       groupBys: [], | 
					
						
							|  |  |  |     }, | 
					
						
							| 
									
										
										
										
											2018-09-11 01:08:39 +08:00
										 |  |  |   }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |   groupBySegments: any[]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 06:24:59 +08:00
										 |  |  |   aggOptions = [ | 
					
						
							|  |  |  |     { text: 'none', value: 'REDUCE_NONE' }, | 
					
						
							|  |  |  |     { text: 'mean', value: 'REDUCE_MEAN' }, | 
					
						
							|  |  |  |     { text: 'min', value: 'REDUCE_MIN' }, | 
					
						
							|  |  |  |     { text: 'max', value: 'REDUCE_MAX' }, | 
					
						
							|  |  |  |     { text: 'sum', value: 'REDUCE_SUM' }, | 
					
						
							|  |  |  |     { text: 'std. dev.', value: 'REDUCE_STDDEV' }, | 
					
						
							|  |  |  |     { text: 'count', value: 'REDUCE_COUNT' }, | 
					
						
							|  |  |  |     { text: '99th percentile', value: 'REDUCE_PERCENTILE_99' }, | 
					
						
							|  |  |  |     { text: '95th percentile', value: 'REDUCE_PERCENTILE_95' }, | 
					
						
							|  |  |  |     { text: '50th percentile', value: 'REDUCE_PERCENTILE_50' }, | 
					
						
							|  |  |  |     { text: '5th percentile', value: 'REDUCE_PERCENTILE_05' }, | 
					
						
							|  |  |  |   ]; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  |   showHelp: boolean; | 
					
						
							|  |  |  |   showLastQuery: boolean; | 
					
						
							|  |  |  |   lastQueryMeta: QueryMeta; | 
					
						
							|  |  |  |   lastQueryError?: string; | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |   metricLabels: LabelType[]; | 
					
						
							|  |  |  |   resourceLabels: LabelType[]; | 
					
						
							| 
									
										
										
										
											2018-09-14 06:13:23 +08:00
										 |  |  |   removeSegment: any; | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  |   /** @ngInject */ | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |   constructor($scope, $injector, private uiSegmentSrv, private timeSrv) { | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  |     super($scope, $injector); | 
					
						
							| 
									
										
										
										
											2018-09-11 01:08:39 +08:00
										 |  |  |     _.defaultsDeep(this.target, this.defaults); | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     this.panelCtrl.events.on('data-received', this.onDataReceived.bind(this), $scope); | 
					
						
							|  |  |  |     this.panelCtrl.events.on('data-error', this.onDataError.bind(this), $scope); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |     this.getCurrentProject() | 
					
						
							|  |  |  |       .then(this.getMetricTypes.bind(this)) | 
					
						
							|  |  |  |       .then(this.getLabels.bind(this)); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |     this.groupBySegments = this.target.aggregation.groupBys.map(groupBy => { | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |       return uiSegmentSrv.getSegmentForValue(groupBy); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2018-09-14 06:13:23 +08:00
										 |  |  |     this.removeSegment = uiSegmentSrv.newSegment({ fake: true, value: '-- remove group by --' }); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |     this.ensurePlusButton(this.groupBySegments); | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getCurrentProject() { | 
					
						
							|  |  |  |     try { | 
					
						
							|  |  |  |       const projects = await this.datasource.getProjects(); | 
					
						
							|  |  |  |       if (projects && projects.length > 0) { | 
					
						
							| 
									
										
										
										
											2018-09-11 01:16:19 +08:00
										 |  |  |         this.target.project = projects[0]; | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  |       } else { | 
					
						
							|  |  |  |         throw new Error('No projects found'); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } catch (error) { | 
					
						
							|  |  |  |       let message = 'Projects cannot be fetched: '; | 
					
						
							|  |  |  |       message += error.statusText ? error.statusText + ': ' : ''; | 
					
						
							|  |  |  |       if (error && error.data && error.data.error && error.data.error.message) { | 
					
						
							|  |  |  |         if (error.data.error.code === 403) { | 
					
						
							|  |  |  |           message += `
 | 
					
						
							|  |  |  |             A list of projects could not be fetched from the Google Cloud Resource Manager API. | 
					
						
							|  |  |  |             You might need to enable it first: | 
					
						
							|  |  |  |             https://console.developers.google.com/apis/library/cloudresourcemanager.googleapis.com`;
 | 
					
						
							|  |  |  |         } else { | 
					
						
							|  |  |  |           message += error.data.error.code + '. ' + error.data.error.message; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  |       } else { | 
					
						
							|  |  |  |         message += 'Cannot connect to Stackdriver API'; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       appEvents.emit('ds-request-error', message); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async getMetricTypes() { | 
					
						
							|  |  |  |     //projects/raintank-production/metricDescriptors/agent.googleapis.com/agent/api_request_count
 | 
					
						
							| 
									
										
										
										
											2018-09-10 04:53:35 +08:00
										 |  |  |     if (this.target.project.id !== 'default') { | 
					
						
							|  |  |  |       const metricTypes = await this.datasource.getMetricTypes(this.target.project.id); | 
					
						
							|  |  |  |       if (this.target.metricType === this.defaultDropdownValue && metricTypes.length > 0) { | 
					
						
							|  |  |  |         this.$scope.$apply(() => (this.target.metricType = metricTypes[0].name)); | 
					
						
							| 
									
										
										
										
											2018-09-07 23:18:15 +08:00
										 |  |  |       } | 
					
						
							|  |  |  |       return metricTypes.map(mt => ({ value: mt.id, text: mt.id })); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       return []; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |   async getLabels() { | 
					
						
							|  |  |  |     const data = await this.datasource.getTimeSeries({ | 
					
						
							|  |  |  |       targets: [ | 
					
						
							|  |  |  |         { | 
					
						
							|  |  |  |           refId: this.target.refId, | 
					
						
							|  |  |  |           datasourceId: this.datasource.id, | 
					
						
							|  |  |  |           metricType: this.target.metricType, | 
					
						
							|  |  |  |           aggregation: { | 
					
						
							|  |  |  |             crossSeriesReducer: 'REDUCE_NONE', | 
					
						
							|  |  |  |           }, | 
					
						
							|  |  |  |           view: 'HEADERS', | 
					
						
							|  |  |  |         }, | 
					
						
							|  |  |  |       ], | 
					
						
							|  |  |  |       range: this.timeSrv.timeRange(), | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.metricLabels = data.results[this.target.refId].meta.metricLabels; | 
					
						
							|  |  |  |     this.resourceLabels = data.results[this.target.refId].meta.resourceLabels; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   async onMetricTypeChange() { | 
					
						
							|  |  |  |     this.refresh(); | 
					
						
							|  |  |  |     this.getLabels(); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |   getGroupBys() { | 
					
						
							| 
									
										
										
										
											2018-09-14 05:51:45 +08:00
										 |  |  |     const metricLabels = Object.keys(this.metricLabels) | 
					
						
							|  |  |  |       .filter(ml => { | 
					
						
							|  |  |  |         return this.target.aggregation.groupBys.indexOf('metric.label.' + ml) === -1; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .map(l => { | 
					
						
							|  |  |  |         return this.uiSegmentSrv.newSegment({ | 
					
						
							|  |  |  |           value: `metric.label.${l}`, | 
					
						
							|  |  |  |           expandable: false, | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |       }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 05:51:45 +08:00
										 |  |  |     const resourceLabels = Object.keys(this.resourceLabels) | 
					
						
							|  |  |  |       .filter(ml => { | 
					
						
							|  |  |  |         return this.target.aggregation.groupBys.indexOf('resource.label.' + ml) === -1; | 
					
						
							|  |  |  |       }) | 
					
						
							|  |  |  |       .map(l => { | 
					
						
							|  |  |  |         return this.uiSegmentSrv.newSegment({ | 
					
						
							|  |  |  |           value: `resource.label.${l}`, | 
					
						
							|  |  |  |           expandable: false, | 
					
						
							|  |  |  |         }); | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |       }); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 06:13:23 +08:00
										 |  |  |     return Promise.resolve([...metricLabels, ...resourceLabels, this.removeSegment]); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-14 06:13:23 +08:00
										 |  |  |   groupByChanged(segment, index) { | 
					
						
							|  |  |  |     if (segment.value === this.removeSegment.value) { | 
					
						
							|  |  |  |       this.groupBySegments.splice(index, 1); | 
					
						
							|  |  |  |     } else { | 
					
						
							|  |  |  |       segment.type = 'value'; | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-09-14 05:51:45 +08:00
										 |  |  | 
 | 
					
						
							|  |  |  |     const reducer = (memo, seg) => { | 
					
						
							|  |  |  |       if (!seg.fake) { | 
					
						
							|  |  |  |         memo.push(seg.value); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |       return memo; | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     this.target.aggregation.groupBys = this.groupBySegments.reduce(reducer, []); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |     this.ensurePlusButton(this.groupBySegments); | 
					
						
							| 
									
										
										
										
											2018-09-14 00:22:48 +08:00
										 |  |  |     this.refresh(); | 
					
						
							| 
									
										
										
										
											2018-09-13 17:02:31 +08:00
										 |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   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()); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-09-12 04:41:24 +08:00
										 |  |  |   onDataReceived(dataList) { | 
					
						
							|  |  |  |     this.lastQueryError = null; | 
					
						
							|  |  |  |     this.lastQueryMeta = null; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId }); | 
					
						
							|  |  |  |     if (anySeriesFromQuery) { | 
					
						
							|  |  |  |       this.lastQueryMeta = anySeriesFromQuery.meta; | 
					
						
							|  |  |  |       this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   onDataError(err) { | 
					
						
							|  |  |  |     if (err.data && err.data.results) { | 
					
						
							|  |  |  |       const queryRes = err.data.results[this.target.refId]; | 
					
						
							|  |  |  |       if (queryRes) { | 
					
						
							|  |  |  |         this.lastQueryMeta = queryRes.meta; | 
					
						
							|  |  |  |         this.lastQueryMeta.rawQueryString = decodeURIComponent(this.lastQueryMeta.rawQuery); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         let jsonBody; | 
					
						
							|  |  |  |         try { | 
					
						
							|  |  |  |           jsonBody = JSON.parse(queryRes.error); | 
					
						
							|  |  |  |         } catch { | 
					
						
							|  |  |  |           this.lastQueryError = queryRes.error; | 
					
						
							|  |  |  |         } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         this.lastQueryError = jsonBody.error.message; | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |   } | 
					
						
							| 
									
										
										
										
											2018-09-04 19:21:02 +08:00
										 |  |  | } |