mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
	
	
		
			242 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
		
		
			
		
	
	
			242 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
|  | import { isArray } from 'lodash'; | ||
|  | import { PanelCtrl } from 'app/angular/panel/panel_ctrl'; | ||
|  | import { applyPanelTimeOverrides } from 'app/features/dashboard/utils/panel'; | ||
|  | import { ContextSrv } from 'app/core/services/context_srv'; | ||
|  | import { | ||
|  |   DataFrame, | ||
|  |   DataQueryResponse, | ||
|  |   DataSourceApi, | ||
|  |   LegacyResponseData, | ||
|  |   LoadingState, | ||
|  |   PanelData, | ||
|  |   PanelEvents, | ||
|  |   TimeRange, | ||
|  |   toDataFrameDTO, | ||
|  |   toLegacyResponseData, | ||
|  | } from '@grafana/data'; | ||
|  | import { Unsubscribable } from 'rxjs'; | ||
|  | import { PanelModel } from 'app/features/dashboard/state'; | ||
|  | import { PanelQueryRunner } from '../../features/query/state/PanelQueryRunner'; | ||
|  | 
 | ||
|  | class MetricsPanelCtrl extends PanelCtrl { | ||
|  |   declare datasource: DataSourceApi; | ||
|  |   declare range: TimeRange; | ||
|  | 
 | ||
|  |   contextSrv: ContextSrv; | ||
|  |   datasourceSrv: any; | ||
|  |   timeSrv: any; | ||
|  |   templateSrv: any; | ||
|  |   interval: any; | ||
|  |   intervalMs: any; | ||
|  |   resolution: any; | ||
|  |   timeInfo?: string; | ||
|  |   skipDataOnInit = false; | ||
|  |   dataList: LegacyResponseData[] = []; | ||
|  |   querySubscription?: Unsubscribable | null; | ||
|  |   useDataFrames = false; | ||
|  |   panelData?: PanelData; | ||
|  | 
 | ||
|  |   constructor($scope: any, $injector: any) { | ||
|  |     super($scope, $injector); | ||
|  | 
 | ||
|  |     this.contextSrv = $injector.get('contextSrv'); | ||
|  |     this.datasourceSrv = $injector.get('datasourceSrv'); | ||
|  |     this.timeSrv = $injector.get('timeSrv'); | ||
|  |     this.templateSrv = $injector.get('templateSrv'); | ||
|  |     this.panel.datasource = this.panel.datasource || null; | ||
|  | 
 | ||
|  |     this.events.on(PanelEvents.refresh, this.onMetricsPanelRefresh.bind(this)); | ||
|  |     this.events.on(PanelEvents.panelTeardown, this.onPanelTearDown.bind(this)); | ||
|  |     this.events.on(PanelEvents.componentDidMount, this.onMetricsPanelMounted.bind(this)); | ||
|  |   } | ||
|  | 
 | ||
|  |   private onMetricsPanelMounted() { | ||
|  |     const queryRunner = this.panel.getQueryRunner() as PanelQueryRunner; | ||
|  |     this.querySubscription = queryRunner | ||
|  |       .getData({ withTransforms: true, withFieldConfig: true }) | ||
|  |       .subscribe(this.panelDataObserver); | ||
|  |   } | ||
|  | 
 | ||
|  |   private onPanelTearDown() { | ||
|  |     if (this.querySubscription) { | ||
|  |       this.querySubscription.unsubscribe(); | ||
|  |       this.querySubscription = null; | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   private onMetricsPanelRefresh() { | ||
|  |     // ignore fetching data if another panel is in fullscreen
 | ||
|  |     if (this.otherPanelInFullscreenMode()) { | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     // if we have snapshot data use that
 | ||
|  |     if (this.panel.snapshotData) { | ||
|  |       this.updateTimeRange(); | ||
|  |       let data = this.panel.snapshotData; | ||
|  |       // backward compatibility
 | ||
|  |       if (!isArray(data)) { | ||
|  |         data = data.data; | ||
|  |       } | ||
|  | 
 | ||
|  |       this.panelData = { | ||
|  |         state: LoadingState.Done, | ||
|  |         series: data, | ||
|  |         timeRange: this.range, | ||
|  |       }; | ||
|  | 
 | ||
|  |       // Defer panel rendering till the next digest cycle.
 | ||
|  |       // For some reason snapshot panels don't init at this time, so this helps to avoid rendering issues.
 | ||
|  |       return this.$timeout(() => { | ||
|  |         this.events.emit(PanelEvents.dataSnapshotLoad, data); | ||
|  |       }); | ||
|  |     } | ||
|  | 
 | ||
|  |     // clear loading/error state
 | ||
|  |     delete this.error; | ||
|  |     this.loading = true; | ||
|  | 
 | ||
|  |     // load datasource service
 | ||
|  |     return this.datasourceSrv | ||
|  |       .get(this.panel.datasource, this.panel.scopedVars) | ||
|  |       .then(this.issueQueries.bind(this)) | ||
|  |       .catch((err: any) => { | ||
|  |         this.processDataError(err); | ||
|  |       }); | ||
|  |   } | ||
|  | 
 | ||
|  |   processDataError(err: any) { | ||
|  |     // if canceled  keep loading set to true
 | ||
|  |     if (err.cancelled) { | ||
|  |       console.log('Panel request cancelled', err); | ||
|  |       return; | ||
|  |     } | ||
|  | 
 | ||
|  |     this.error = err.message || 'Request Error'; | ||
|  | 
 | ||
|  |     if (err.data) { | ||
|  |       if (err.data.message) { | ||
|  |         this.error = err.data.message; | ||
|  |       } else if (err.data.error) { | ||
|  |         this.error = err.data.error; | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     this.angularDirtyCheck(); | ||
|  |   } | ||
|  | 
 | ||
|  |   angularDirtyCheck() { | ||
|  |     if (!this.$scope.$root.$$phase) { | ||
|  |       this.$scope.$digest(); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   // Updates the response with information from the stream
 | ||
|  |   panelDataObserver = { | ||
|  |     next: (data: PanelData) => { | ||
|  |       this.panelData = data; | ||
|  | 
 | ||
|  |       if (data.state === LoadingState.Error) { | ||
|  |         this.loading = false; | ||
|  |         this.processDataError(data.error); | ||
|  |       } | ||
|  | 
 | ||
|  |       // Ignore data in loading state
 | ||
|  |       if (data.state === LoadingState.Loading) { | ||
|  |         this.loading = true; | ||
|  |         this.angularDirtyCheck(); | ||
|  |         return; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (data.request) { | ||
|  |         const { timeInfo } = data.request; | ||
|  |         if (timeInfo) { | ||
|  |           this.timeInfo = timeInfo; | ||
|  |         } | ||
|  |       } | ||
|  | 
 | ||
|  |       if (data.timeRange) { | ||
|  |         this.range = data.timeRange; | ||
|  |       } | ||
|  | 
 | ||
|  |       if (this.useDataFrames) { | ||
|  |         this.handleDataFrames(data.series); | ||
|  |       } else { | ||
|  |         // Make the results look as if they came directly from a <6.2 datasource request
 | ||
|  |         const legacy = data.series.map((v) => toLegacyResponseData(v)); | ||
|  |         this.handleQueryResult({ data: legacy }); | ||
|  |       } | ||
|  | 
 | ||
|  |       this.angularDirtyCheck(); | ||
|  |     }, | ||
|  |   }; | ||
|  | 
 | ||
|  |   updateTimeRange(datasource?: DataSourceApi) { | ||
|  |     this.datasource = datasource || this.datasource; | ||
|  |     this.range = this.timeSrv.timeRange(); | ||
|  | 
 | ||
|  |     const newTimeData = applyPanelTimeOverrides(this.panel, this.range); | ||
|  |     this.timeInfo = newTimeData.timeInfo; | ||
|  |     this.range = newTimeData.timeRange; | ||
|  |   } | ||
|  | 
 | ||
|  |   issueQueries(datasource: DataSourceApi) { | ||
|  |     this.updateTimeRange(datasource); | ||
|  | 
 | ||
|  |     this.datasource = datasource; | ||
|  | 
 | ||
|  |     const panel = this.panel as PanelModel; | ||
|  |     const queryRunner = panel.getQueryRunner(); | ||
|  | 
 | ||
|  |     return queryRunner.run({ | ||
|  |       datasource: panel.datasource, | ||
|  |       queries: panel.targets, | ||
|  |       panelId: panel.id, | ||
|  |       dashboardId: this.dashboard.id, | ||
|  |       timezone: this.dashboard.getTimezone(), | ||
|  |       timeInfo: this.timeInfo, | ||
|  |       timeRange: this.range, | ||
|  |       maxDataPoints: panel.maxDataPoints || this.width, | ||
|  |       minInterval: panel.interval, | ||
|  |       scopedVars: panel.scopedVars, | ||
|  |       cacheTimeout: panel.cacheTimeout, | ||
|  |       transformations: panel.transformations, | ||
|  |     }); | ||
|  |   } | ||
|  | 
 | ||
|  |   handleDataFrames(data: DataFrame[]) { | ||
|  |     this.loading = false; | ||
|  | 
 | ||
|  |     if (this.dashboard && this.dashboard.snapshot) { | ||
|  |       this.panel.snapshotData = data.map((frame) => toDataFrameDTO(frame)); | ||
|  |     } | ||
|  | 
 | ||
|  |     try { | ||
|  |       this.events.emit(PanelEvents.dataFramesReceived, data); | ||
|  |     } catch (err) { | ||
|  |       this.processDataError(err); | ||
|  |     } | ||
|  |   } | ||
|  | 
 | ||
|  |   handleQueryResult(result: DataQueryResponse) { | ||
|  |     this.loading = false; | ||
|  | 
 | ||
|  |     if (this.dashboard.snapshot) { | ||
|  |       this.panel.snapshotData = result.data; | ||
|  |     } | ||
|  | 
 | ||
|  |     if (!result || !result.data) { | ||
|  |       console.log('Data source query result invalid, missing data field:', result); | ||
|  |       result = { data: [] }; | ||
|  |     } | ||
|  | 
 | ||
|  |     try { | ||
|  |       this.events.emit(PanelEvents.dataReceived, result.data); | ||
|  |     } catch (err) { | ||
|  |       this.processDataError(err); | ||
|  |     } | ||
|  |   } | ||
|  | } | ||
|  | 
 | ||
|  | export { MetricsPanelCtrl }; |