From 54c9beb14635b57048d8163e263feeca8b0176be Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Mon, 24 Sep 2018 17:47:43 +0200 Subject: [PATCH] Explore: jump to explore from panels with mixed datasources - extends handlers for panel menu and keypress 'x' - in a mixed-datasource panel finds first datasource that supports explore and collects its targets - passes those targets to the found datasource to be serialized for explore state - removed `supportMetrics` and `supportsExplore` - use datasource metadata instead (set in plugin.json) - Use angular timeout to wrap url change for explore jump - Extract getExploreUrl into core/utils/explore --- public/app/core/services/keybindingSrv.ts | 22 ++++---- public/app/core/utils/explore.ts | 52 +++++++++++++++++++ public/app/core/utils/location_util.ts | 5 -- .../app/features/panel/metrics_panel_ctrl.ts | 22 ++++---- .../panel/specs/metrics_panel_ctrl.test.ts | 2 +- .../datasource/cloudwatch/datasource.ts | 2 - .../plugins/datasource/influxdb/datasource.ts | 4 -- .../plugins/datasource/opentsdb/datasource.ts | 2 - .../datasource/prometheus/datasource.ts | 10 ++-- 9 files changed, 80 insertions(+), 41 deletions(-) create mode 100644 public/app/core/utils/explore.ts diff --git a/public/app/core/services/keybindingSrv.ts b/public/app/core/services/keybindingSrv.ts index a0c7cdec3cb..d8dfc958dd4 100644 --- a/public/app/core/services/keybindingSrv.ts +++ b/public/app/core/services/keybindingSrv.ts @@ -4,7 +4,7 @@ import _ from 'lodash'; import config from 'app/core/config'; import coreModule from 'app/core/core_module'; import appEvents from 'app/core/app_events'; -import { renderUrl } from 'app/core/utils/url'; +import { getExploreUrl } from 'app/core/utils/explore'; import Mousetrap from 'mousetrap'; import 'mousetrap-global-bind'; @@ -15,7 +15,14 @@ export class KeybindingSrv { timepickerOpen = false; /** @ngInject */ - constructor(private $rootScope, private $location, private datasourceSrv, private timeSrv, private contextSrv) { + constructor( + private $rootScope, + private $location, + private $timeout, + private datasourceSrv, + private timeSrv, + private contextSrv + ) { // clear out all shortcuts on route change $rootScope.$on('$routeChangeSuccess', () => { Mousetrap.reset(); @@ -194,14 +201,9 @@ export class KeybindingSrv { if (dashboard.meta.focusPanelId) { const panel = dashboard.getPanelById(dashboard.meta.focusPanelId); const datasource = await this.datasourceSrv.get(panel.datasource); - if (datasource && datasource.supportsExplore) { - const range = this.timeSrv.timeRangeForUrl(); - const state = { - ...datasource.getExploreState(panel), - range, - }; - const exploreState = JSON.stringify(state); - this.$location.url(renderUrl('/explore', { state: exploreState })); + const url = await getExploreUrl(panel, panel.targets, datasource, this.datasourceSrv, this.timeSrv); + if (url) { + this.$timeout(() => this.$location.url(url)); } } }); diff --git a/public/app/core/utils/explore.ts b/public/app/core/utils/explore.ts new file mode 100644 index 00000000000..cd26898259c --- /dev/null +++ b/public/app/core/utils/explore.ts @@ -0,0 +1,52 @@ +import { renderUrl } from 'app/core/utils/url'; + +/** + * Returns an Explore-URL that contains a panel's queries and the dashboard time range. + * + * @param panel Origin panel of the jump to Explore + * @param panelTargets The origin panel's query targets + * @param panelDatasource The origin panel's datasource + * @param datasourceSrv Datasource service to query other datasources in case the panel datasource is mixed + * @param timeSrv Time service to get the current dashboard range from + */ +export async function getExploreUrl( + panel: any, + panelTargets: any[], + panelDatasource: any, + datasourceSrv: any, + timeSrv: any +) { + let exploreDatasource = panelDatasource; + let exploreTargets = panelTargets; + let url; + + // Mixed datasources need to choose only one datasource + if (panelDatasource.meta.id === 'mixed' && panelTargets) { + // Find first explore datasource among targets + let mixedExploreDatasource; + for (const t of panel.targets) { + const datasource = await datasourceSrv.get(t.datasource); + if (datasource && datasource.meta.explore) { + mixedExploreDatasource = datasource; + break; + } + } + + // Add all its targets + if (mixedExploreDatasource) { + exploreDatasource = mixedExploreDatasource; + exploreTargets = panelTargets.filter(t => t.datasource === mixedExploreDatasource.name); + } + } + + if (exploreDatasource && exploreDatasource.meta.explore) { + const range = timeSrv.timeRangeForUrl(); + const state = { + ...exploreDatasource.getExploreState(exploreTargets), + range, + }; + const exploreState = JSON.stringify(state); + url = renderUrl('/explore', { state: exploreState }); + } + return url; +} diff --git a/public/app/core/utils/location_util.ts b/public/app/core/utils/location_util.ts index 735272285ff..76f2fc5881f 100644 --- a/public/app/core/utils/location_util.ts +++ b/public/app/core/utils/location_util.ts @@ -1,10 +1,5 @@ import config from 'app/core/config'; -// Slash encoding for angular location provider, see https://github.com/angular/angular.js/issues/10479 -const SLASH = ''; -export const decodePathComponent = (pc: string) => decodeURIComponent(pc).replace(new RegExp(SLASH, 'g'), '/'); -export const encodePathComponent = (pc: string) => encodeURIComponent(pc.replace(/\//g, SLASH)); - export const stripBaseFromUrl = url => { const appSubUrl = config.appSubUrl; const stripExtraChars = appSubUrl.endsWith('/') ? 1 : 0; diff --git a/public/app/features/panel/metrics_panel_ctrl.ts b/public/app/features/panel/metrics_panel_ctrl.ts index c74c0716cc8..b42b06f1238 100644 --- a/public/app/features/panel/metrics_panel_ctrl.ts +++ b/public/app/features/panel/metrics_panel_ctrl.ts @@ -6,7 +6,7 @@ import kbn from 'app/core/utils/kbn'; import { PanelCtrl } from 'app/features/panel/panel_ctrl'; import * as rangeUtil from 'app/core/utils/rangeutil'; import * as dateMath from 'app/core/utils/datemath'; -import { renderUrl } from 'app/core/utils/url'; +import { getExploreUrl } from 'app/core/utils/explore'; import { metricsTabDirective } from './metrics_tab'; @@ -314,7 +314,12 @@ class MetricsPanelCtrl extends PanelCtrl { getAdditionalMenuItems() { const items = []; - if (config.exploreEnabled && this.contextSrv.isEditor && this.datasource && this.datasource.supportsExplore) { + if ( + config.exploreEnabled && + this.contextSrv.isEditor && + this.datasource && + (this.datasource.meta.explore || this.datasource.meta.id === 'mixed') + ) { items.push({ text: 'Explore', click: 'ctrl.explore();', @@ -325,14 +330,11 @@ class MetricsPanelCtrl extends PanelCtrl { return items; } - explore() { - const range = this.timeSrv.timeRangeForUrl(); - const state = { - ...this.datasource.getExploreState(this.panel), - range, - }; - const exploreState = JSON.stringify(state); - this.$location.url(renderUrl('/explore', { state: exploreState })); + async explore() { + const url = await getExploreUrl(this.panel, this.panel.targets, this.datasource, this.datasourceSrv, this.timeSrv); + if (url) { + this.$timeout(() => this.$location.url(url)); + } } addQuery(target) { diff --git a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts index a28bf92e63b..913a2461fd0 100644 --- a/public/app/features/panel/specs/metrics_panel_ctrl.test.ts +++ b/public/app/features/panel/specs/metrics_panel_ctrl.test.ts @@ -38,7 +38,7 @@ describe('MetricsPanelCtrl', () => { describe('and has datasource set that supports explore and user has powers', () => { beforeEach(() => { ctrl.contextSrv = { isEditor: true }; - ctrl.datasource = { supportsExplore: true }; + ctrl.datasource = { meta: { explore: true } }; additionalItems = ctrl.getAdditionalMenuItems(); }); diff --git a/public/app/plugins/datasource/cloudwatch/datasource.ts b/public/app/plugins/datasource/cloudwatch/datasource.ts index 34771618095..e2b99d69df9 100644 --- a/public/app/plugins/datasource/cloudwatch/datasource.ts +++ b/public/app/plugins/datasource/cloudwatch/datasource.ts @@ -8,7 +8,6 @@ import * as templatingVariable from 'app/features/templating/variable'; export default class CloudWatchDatasource { type: any; name: any; - supportMetrics: any; proxyUrl: any; defaultRegion: any; instanceSettings: any; @@ -17,7 +16,6 @@ export default class CloudWatchDatasource { constructor(instanceSettings, private $q, private backendSrv, private templateSrv, private timeSrv) { this.type = 'cloudwatch'; this.name = instanceSettings.name; - this.supportMetrics = true; this.proxyUrl = instanceSettings.url; this.defaultRegion = instanceSettings.jsonData.defaultRegion; this.instanceSettings = instanceSettings; diff --git a/public/app/plugins/datasource/influxdb/datasource.ts b/public/app/plugins/datasource/influxdb/datasource.ts index 5ffbf7cf418..cf9b95882bc 100644 --- a/public/app/plugins/datasource/influxdb/datasource.ts +++ b/public/app/plugins/datasource/influxdb/datasource.ts @@ -16,8 +16,6 @@ export default class InfluxDatasource { basicAuth: any; withCredentials: any; interval: any; - supportAnnotations: boolean; - supportMetrics: boolean; responseParser: any; /** @ngInject */ @@ -34,8 +32,6 @@ export default class InfluxDatasource { this.basicAuth = instanceSettings.basicAuth; this.withCredentials = instanceSettings.withCredentials; this.interval = (instanceSettings.jsonData || {}).timeInterval; - this.supportAnnotations = true; - this.supportMetrics = true; this.responseParser = new ResponseParser(); } diff --git a/public/app/plugins/datasource/opentsdb/datasource.ts b/public/app/plugins/datasource/opentsdb/datasource.ts index 7cb0806359d..772f2aa7ff9 100644 --- a/public/app/plugins/datasource/opentsdb/datasource.ts +++ b/public/app/plugins/datasource/opentsdb/datasource.ts @@ -10,7 +10,6 @@ export default class OpenTsDatasource { basicAuth: any; tsdbVersion: any; tsdbResolution: any; - supportMetrics: any; tagKeys: any; aggregatorsPromise: any; @@ -26,7 +25,6 @@ export default class OpenTsDatasource { instanceSettings.jsonData = instanceSettings.jsonData || {}; this.tsdbVersion = instanceSettings.jsonData.tsdbVersion || 1; this.tsdbResolution = instanceSettings.jsonData.tsdbResolution || 1; - this.supportMetrics = true; this.tagKeys = {}; this.aggregatorsPromise = null; diff --git a/public/app/plugins/datasource/prometheus/datasource.ts b/public/app/plugins/datasource/prometheus/datasource.ts index b53b9eb34c1..17a4b6b0f95 100644 --- a/public/app/plugins/datasource/prometheus/datasource.ts +++ b/public/app/plugins/datasource/prometheus/datasource.ts @@ -149,8 +149,6 @@ export class PrometheusDatasource { editorSrc: string; name: string; ruleMappings: { [index: string]: string }; - supportsExplore: boolean; - supportMetrics: boolean; url: string; directUrl: string; basicAuth: any; @@ -166,8 +164,6 @@ export class PrometheusDatasource { this.type = 'prometheus'; this.editorSrc = 'app/features/prometheus/partials/query.editor.html'; this.name = instanceSettings.name; - this.supportsExplore = true; - this.supportMetrics = true; this.url = instanceSettings.url; this.directUrl = instanceSettings.directUrl; this.basicAuth = instanceSettings.basicAuth; @@ -522,10 +518,10 @@ export class PrometheusDatasource { }); } - getExploreState(panel) { + getExploreState(targets: any[]) { let state = {}; - if (panel.targets) { - const queries = panel.targets.map(t => ({ + if (targets && targets.length > 0) { + const queries = targets.map(t => ({ query: this.templateSrv.replace(t.expr, {}, this.interpolateQueryExpr), format: t.format, }));