فهرست منبع

Explore: Add entry to panel menu to jump to Explore

* panel container menu gets new Explore entry (between Edit and Share)
* entry only shows if datasource has `supportsExplore` set to true (set
 for Prometheus only for now)
* click on Explore entry changes url to `/explore/state` via location provider
* `state` is a JSON representation of the panel queries
* datasources implement `getExploreState()` how to turn a panel config into explore initial
 state
* Explore can parse the state and initialize its query expressions
* ReactContainer now forwards route parameters as props to component
* `pluginlist` and `singlestat` panel subclasses needed to be adapted because
 `panel_ctrl` now has the location provider as a property already
David Kaltschmidt 7 سال پیش
والد
کامیت
05b0bfafe4

+ 16 - 2
public/app/containers/Explore/Explore.tsx

@@ -38,6 +38,19 @@ function makeTimeSeriesList(dataList, options) {
   });
   });
 }
 }
 
 
+function parseInitialQueries(initial) {
+  if (!initial) {
+    return [];
+  }
+  try {
+    const parsed = JSON.parse(initial);
+    return parsed.queries.map(q => q.query);
+  } catch (e) {
+    console.error(e);
+    return [];
+  }
+}
+
 interface IExploreState {
 interface IExploreState {
   datasource: any;
   datasource: any;
   datasourceError: any;
   datasourceError: any;
@@ -58,6 +71,7 @@ export class Explore extends React.Component<any, IExploreState> {
 
 
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
+    const initialQueries = parseInitialQueries(props.routeParams.initial);
     this.state = {
     this.state = {
       datasource: null,
       datasource: null,
       datasourceError: null,
       datasourceError: null,
@@ -65,7 +79,7 @@ export class Explore extends React.Component<any, IExploreState> {
       graphResult: null,
       graphResult: null,
       latency: 0,
       latency: 0,
       loading: false,
       loading: false,
-      queries: ensureQueries(),
+      queries: ensureQueries(initialQueries),
       requestOptions: null,
       requestOptions: null,
       showingGraph: true,
       showingGraph: true,
       showingTable: true,
       showingTable: true,
@@ -77,7 +91,7 @@ export class Explore extends React.Component<any, IExploreState> {
     const datasource = await this.props.datasourceSrv.get();
     const datasource = await this.props.datasourceSrv.get();
     const testResult = await datasource.testDatasource();
     const testResult = await datasource.testDatasource();
     if (testResult.status === 'success') {
     if (testResult.status === 'success') {
-      this.setState({ datasource, datasourceError: null, datasourceLoading: false });
+      this.setState({ datasource, datasourceError: null, datasourceLoading: false }, () => this.handleSubmit());
     } else {
     } else {
       this.setState({ datasource: null, datasourceError: testResult.message, datasourceLoading: false });
       this.setState({ datasource: null, datasourceError: testResult.message, datasourceLoading: false });
     }
     }

+ 15 - 4
public/app/containers/Explore/QueryRows.tsx

@@ -6,13 +6,16 @@ class QueryRow extends PureComponent<any, any> {
   constructor(props) {
   constructor(props) {
     super(props);
     super(props);
     this.state = {
     this.state = {
-      query: '',
+      edited: false,
+      query: props.query || '',
     };
     };
   }
   }
 
 
   handleChangeQuery = value => {
   handleChangeQuery = value => {
     const { index, onChangeQuery } = this.props;
     const { index, onChangeQuery } = this.props;
-    this.setState({ query: value });
+    const { query } = this.state;
+    const edited = query !== value;
+    this.setState({ edited, query: value });
     if (onChangeQuery) {
     if (onChangeQuery) {
       onChangeQuery(value, index);
       onChangeQuery(value, index);
     }
     }
@@ -41,6 +44,7 @@ class QueryRow extends PureComponent<any, any> {
 
 
   render() {
   render() {
     const { request } = this.props;
     const { request } = this.props;
+    const { edited, query } = this.state;
     return (
     return (
       <div className="query-row">
       <div className="query-row">
         <div className="query-row-tools">
         <div className="query-row-tools">
@@ -52,7 +56,12 @@ class QueryRow extends PureComponent<any, any> {
           </button>
           </button>
         </div>
         </div>
         <div className="query-field-wrapper">
         <div className="query-field-wrapper">
-          <QueryField onPressEnter={this.handlePressEnter} onQueryChange={this.handleChangeQuery} request={request} />
+          <QueryField
+            initialQuery={edited ? null : query}
+            onPressEnter={this.handlePressEnter}
+            onQueryChange={this.handleChangeQuery}
+            request={request}
+          />
         </div>
         </div>
       </div>
       </div>
     );
     );
@@ -63,7 +72,9 @@ export default class QueryRows extends PureComponent<any, any> {
   render() {
   render() {
     const { className = '', queries, ...handlers } = this.props;
     const { className = '', queries, ...handlers } = this.props;
     return (
     return (
-      <div className={className}>{queries.map((q, index) => <QueryRow key={q.key} index={index} {...handlers} />)}</div>
+      <div className={className}>
+        {queries.map((q, index) => <QueryRow key={q.key} index={index} query={q.query} {...handlers} />)}
+      </div>
     );
     );
   }
   }
 }
 }

+ 18 - 0
public/app/features/panel/panel_ctrl.ts

@@ -22,6 +22,7 @@ export class PanelCtrl {
   editorTabs: any;
   editorTabs: any;
   $scope: any;
   $scope: any;
   $injector: any;
   $injector: any;
+  $location: any;
   $timeout: any;
   $timeout: any;
   fullscreen: boolean;
   fullscreen: boolean;
   inspector: any;
   inspector: any;
@@ -35,6 +36,7 @@ export class PanelCtrl {
 
 
   constructor($scope, $injector) {
   constructor($scope, $injector) {
     this.$injector = $injector;
     this.$injector = $injector;
+    this.$location = $injector.get('$location');
     this.$scope = $scope;
     this.$scope = $scope;
     this.$timeout = $injector.get('$timeout');
     this.$timeout = $injector.get('$timeout');
     this.editorTabIndex = 0;
     this.editorTabIndex = 0;
@@ -97,6 +99,12 @@ export class PanelCtrl {
     this.changeView(false, false);
     this.changeView(false, false);
   }
   }
 
 
+  explore() {
+    // TS hack :<
+    const initialState = JSON.stringify(this['datasource'].getExploreState(this.panel));
+    this.$location.url(`/explore/${initialState}`);
+  }
+
   initEditMode() {
   initEditMode() {
     this.editorTabs = [];
     this.editorTabs = [];
     this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
     this.addEditorTab('General', 'public/app/partials/panelgeneral.html');
@@ -154,6 +162,16 @@ export class PanelCtrl {
       });
       });
     }
     }
 
 
+    // TS hack :<
+    if ('datasource' in this && this['datasource'].supportsExplore) {
+      menu.push({
+        text: 'Explore',
+        click: 'ctrl.explore();',
+        icon: 'fa fa-fw fa-rocket',
+        shortcut: 'x',
+      });
+    }
+
     menu.push({
     menu.push({
       text: 'Share',
       text: 'Share',
       click: 'ctrl.sharePanel();',
       click: 'ctrl.sharePanel();',

+ 10 - 0
public/app/plugins/datasource/prometheus/datasource.ts

@@ -19,6 +19,7 @@ export class PrometheusDatasource {
   type: string;
   type: string;
   editorSrc: string;
   editorSrc: string;
   name: string;
   name: string;
+  supportsExplore: boolean;
   supportMetrics: boolean;
   supportMetrics: boolean;
   url: string;
   url: string;
   directUrl: string;
   directUrl: string;
@@ -34,6 +35,7 @@ export class PrometheusDatasource {
     this.type = 'prometheus';
     this.type = 'prometheus';
     this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
     this.editorSrc = 'app/features/prometheus/partials/query.editor.html';
     this.name = instanceSettings.name;
     this.name = instanceSettings.name;
+    this.supportsExplore = true;
     this.supportMetrics = true;
     this.supportMetrics = true;
     this.url = instanceSettings.url;
     this.url = instanceSettings.url;
     this.directUrl = instanceSettings.directUrl;
     this.directUrl = instanceSettings.directUrl;
@@ -323,6 +325,14 @@ export class PrometheusDatasource {
     });
     });
   }
   }
 
 
+  getExploreState(panel) {
+    if (!panel.targets) {
+      return {};
+    }
+    const queries = panel.targets.map(t => ({ query: t.expr, format: t.format }));
+    return { queries };
+  }
+
   getPrometheusTime(date, roundUp) {
   getPrometheusTime(date, roundUp) {
     if (_.isString(date)) {
     if (_.isString(date)) {
       date = dateMath.parse(date, roundUp);
       date = dateMath.parse(date, roundUp);

+ 1 - 1
public/app/plugins/panel/pluginlist/module.ts

@@ -12,7 +12,7 @@ class PluginListCtrl extends PanelCtrl {
   panelDefaults = {};
   panelDefaults = {};
 
 
   /** @ngInject */
   /** @ngInject */
-  constructor($scope, $injector, private backendSrv, private $location) {
+  constructor($scope, $injector, private backendSrv) {
     super($scope, $injector);
     super($scope, $injector);
 
 
     _.defaults(this.panel, this.panelDefaults);
     _.defaults(this.panel, this.panelDefaults);

+ 1 - 1
public/app/plugins/panel/singlestat/module.ts

@@ -77,7 +77,7 @@ class SingleStatCtrl extends MetricsPanelCtrl {
   };
   };
 
 
   /** @ngInject */
   /** @ngInject */
-  constructor($scope, $injector, private $location, private linkSrv) {
+  constructor($scope, $injector, private linkSrv) {
     super($scope, $injector);
     super($scope, $injector);
     _.defaults(this.panel, this.panelDefaults);
     _.defaults(this.panel, this.panelDefaults);
 
 

+ 1 - 0
public/app/routes/ReactContainer.tsx

@@ -29,6 +29,7 @@ export function reactContainer($route, $location, backendSrv: BackendSrv, dataso
       const props = {
       const props = {
         backendSrv: backendSrv,
         backendSrv: backendSrv,
         datasourceSrv: datasourceSrv,
         datasourceSrv: datasourceSrv,
+        routeParams: $route.current.params,
       };
       };
 
 
       ReactDOM.render(WrapInProvider(store, component, props), elem[0]);
       ReactDOM.render(WrapInProvider(store, component, props), elem[0]);

+ 1 - 1
public/app/routes/routes.ts

@@ -111,7 +111,7 @@ export function setupAngularRoutes($routeProvider, $locationProvider) {
       controller: 'FolderDashboardsCtrl',
       controller: 'FolderDashboardsCtrl',
       controllerAs: 'ctrl',
       controllerAs: 'ctrl',
     })
     })
-    .when('/explore', {
+    .when('/explore/:initial?', {
       template: '<react-container />',
       template: '<react-container />',
       resolve: {
       resolve: {
         component: () => import(/* webpackChunkName: "explore" */ 'app/containers/Explore/Explore'),
         component: () => import(/* webpackChunkName: "explore" */ 'app/containers/Explore/Explore'),