From ed034b4288a21399e0be2141820b487d0cdda1a9 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 5 Dec 2018 14:56:29 +0100 Subject: [PATCH 01/12] Trigger panel.render on title, description, links change #14333 --- public/app/features/panel/GeneralTabCtrl.ts | 24 +++++++++++++++++++ .../features/panel/partials/general_tab.html | 4 ++-- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts index c692106c92d..59d2d0e9cda 100644 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -1,11 +1,35 @@ import coreModule from 'app/core/core_module'; +const obj2string = obj => { + return Object.keys(obj) + .reduce((acc, curr) => acc.concat(curr + '=' + obj[curr]), []) + .join(); +}; + export class GeneralTabCtrl { panelCtrl: any; /** @ngInject */ constructor($scope) { this.panelCtrl = $scope.ctrl; + + const updatePanel = () => { + console.log('panel.render()'); + this.panelCtrl.panel.render(); + }; + + const generateValueFromPanel = scope => { + const { panel } = scope.ctrl; + const panelPropsToTrack = ['title', 'description', 'transparent', 'repeat', 'repeatDirection', 'minSpan']; + const panelPropsString = panelPropsToTrack + .map(prop => (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) + .join(); + const panelLinks = panel.links; + const panelLinksString = panelLinks.map(obj2string).join(); + return panelPropsString + panelLinksString; + }; + + $scope.$watch(generateValueFromPanel, updatePanel, true); } } diff --git a/public/app/features/panel/partials/general_tab.html b/public/app/features/panel/partials/general_tab.html index 797c252331c..03aab78c407 100644 --- a/public/app/features/panel/partials/general_tab.html +++ b/public/app/features/panel/partials/general_tab.html @@ -3,11 +3,11 @@
Info
Title - +
Description - +
From 827ffaccd36b102545e0eb687492e7b423970e22 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Wed, 5 Dec 2018 14:57:57 +0100 Subject: [PATCH 02/12] Pass some panel props down as strings to trigger render #14333 --- .../features/dashboard/dashgrid/PanelChrome.tsx | 11 ++++++++++- .../dashgrid/PanelHeader/PanelHeader.tsx | 16 +++++++++++++--- .../dashgrid/PanelHeader/PanelHeaderCorner.tsx | 4 ++++ 3 files changed, 27 insertions(+), 4 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index fe1d1ebf145..931f11d937e 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -95,7 +95,16 @@ export class PanelChrome extends PureComponent { return (
- + + { +export class PanelHeader extends Component { state = { panelMenuOpen: false, }; @@ -44,7 +48,13 @@ export class PanelHeader extends PureComponent { const { panel, dashboard, timeInfo } = this.props; return ( <> - +
{isLoading && ( diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index e8483ec467a..270a4a38dc5 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -14,6 +14,10 @@ enum InfoModes { interface Props { panel: PanelModel; + title?: string; + description?: string; + scopedVars?: string; + links?: []; } export class PanelHeaderCorner extends PureComponent { From a516ff81c97259dd19b48ceb037a7896f4624c9e Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 09:39:17 +0100 Subject: [PATCH 03/12] Add prop key to panelPropsString to avoid a bug when changing another value and the render doesnt trigger --- public/app/features/panel/GeneralTabCtrl.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/public/app/features/panel/GeneralTabCtrl.ts b/public/app/features/panel/GeneralTabCtrl.ts index 59d2d0e9cda..75edd72027b 100644 --- a/public/app/features/panel/GeneralTabCtrl.ts +++ b/public/app/features/panel/GeneralTabCtrl.ts @@ -22,7 +22,7 @@ export class GeneralTabCtrl { const { panel } = scope.ctrl; const panelPropsToTrack = ['title', 'description', 'transparent', 'repeat', 'repeatDirection', 'minSpan']; const panelPropsString = panelPropsToTrack - .map(prop => (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) + .map(prop => prop + '=' + (panel[prop] && panel[prop].toString ? panel[prop].toString() : panel[prop])) .join(); const panelLinks = panel.links; const panelLinksString = panelLinks.map(obj2string).join(); From d62cd8a5c53ff6423eca8884ebbc2a5479be1337 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 10:34:27 +0100 Subject: [PATCH 04/12] Fix transparent option #14333 --- public/app/features/dashboard/dashgrid/PanelChrome.tsx | 5 +++-- public/app/features/dashboard/panel_model.ts | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 931f11d937e..5df6f20fc23 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -83,8 +83,9 @@ export class PanelChrome extends PureComponent { const { panel, dashboard, plugin } = this.props; const { refreshCounter, timeRange, timeInfo, renderCounter } = this.state; - const { datasource, targets } = panel; + const { datasource, targets, transparent } = panel; const PanelComponent = plugin.exports.Panel; + const containerClassNames = `panel-container panel-container--absolute ${transparent ? 'panel-transparent' : ''}`; return ( @@ -94,7 +95,7 @@ export class PanelChrome extends PureComponent { } return ( -
+
Date: Thu, 6 Dec 2018 10:54:31 +0100 Subject: [PATCH 05/12] Changing from PureComponent to Component to re-render on link updates made in Angular #14333 --- .../dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx index 270a4a38dc5..331e469a60d 100644 --- a/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx +++ b/public/app/features/dashboard/dashgrid/PanelHeader/PanelHeaderCorner.tsx @@ -1,4 +1,4 @@ -import React, { PureComponent } from 'react'; +import React, { Component } from 'react'; import { PanelModel } from 'app/features/dashboard/panel_model'; import Tooltip from 'app/core/components/Tooltip/Tooltip'; import templateSrv from 'app/features/templating/template_srv'; @@ -20,7 +20,7 @@ interface Props { links?: []; } -export class PanelHeaderCorner extends PureComponent { +export class PanelHeaderCorner extends Component { timeSrv: TimeSrv = getTimeSrv(); getInfoMode = () => { From 8460b48c24e845c2af311e7b5f670fecab679175 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 11:19:41 +0100 Subject: [PATCH 06/12] Always open panel links in new window if user asked for it #14333 --- public/app/features/dashboard/panellinks/link_srv.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/public/app/features/dashboard/panellinks/link_srv.ts b/public/app/features/dashboard/panellinks/link_srv.ts index f9ad40c50da..d6b2cd3636b 100644 --- a/public/app/features/dashboard/panellinks/link_srv.ts +++ b/public/app/features/dashboard/panellinks/link_srv.ts @@ -73,6 +73,7 @@ export class LinkSrv { getPanelLinkAnchorInfo(link, scopedVars) { const info: any = {}; + info.target = link.targetBlank ? '_blank' : ''; if (link.type === 'absolute') { info.target = link.targetBlank ? '_blank' : '_self'; info.href = this.templateSrv.replace(link.url || '', scopedVars); @@ -80,11 +81,9 @@ export class LinkSrv { } else if (link.url) { info.href = link.url; info.title = this.templateSrv.replace(link.title || '', scopedVars); - info.target = link.targetBlank ? '_blank' : ''; } else if (link.dashUri) { info.href = 'dashboard/' + link.dashUri + '?'; info.title = this.templateSrv.replace(link.title || '', scopedVars); - info.target = link.targetBlank ? '_blank' : ''; } else { info.title = this.templateSrv.replace(link.title || '', scopedVars); const slug = kbn.slugifyForUrl(link.dashboard || ''); From fadabd997a21cd9528b477e4f26b427afc9967a4 Mon Sep 17 00:00:00 2001 From: Johannes Schill Date: Thu, 6 Dec 2018 16:06:47 +0100 Subject: [PATCH 07/12] Restore PluginEditCtrl accidently removed --- public/app/features/plugins/all.ts | 1 + .../app/features/plugins/plugin_edit_ctrl.ts | 179 ++++++++++++++++++ 2 files changed, 180 insertions(+) create mode 100644 public/app/features/plugins/plugin_edit_ctrl.ts diff --git a/public/app/features/plugins/all.ts b/public/app/features/plugins/all.ts index cece501112f..757a9a2f491 100644 --- a/public/app/features/plugins/all.ts +++ b/public/app/features/plugins/all.ts @@ -1,3 +1,4 @@ +import './plugin_edit_ctrl'; import './plugin_page_ctrl'; import './import_list/import_list'; import './ds_edit_ctrl'; diff --git a/public/app/features/plugins/plugin_edit_ctrl.ts b/public/app/features/plugins/plugin_edit_ctrl.ts new file mode 100644 index 00000000000..44d3d31f996 --- /dev/null +++ b/public/app/features/plugins/plugin_edit_ctrl.ts @@ -0,0 +1,179 @@ +import angular from 'angular'; +import _ from 'lodash'; +import Remarkable from 'remarkable'; + +export class PluginEditCtrl { + model: any; + pluginIcon: string; + pluginId: any; + includes: any; + readmeHtml: any; + includedDatasources: any; + tab: string; + navModel: any; + hasDashboards: any; + preUpdateHook: () => any; + postUpdateHook: () => any; + + /** @ngInject */ + constructor(private $scope, private $rootScope, private backendSrv, private $sce, private $routeParams, navModelSrv) { + this.pluginId = $routeParams.pluginId; + this.preUpdateHook = () => Promise.resolve(); + this.postUpdateHook = () => Promise.resolve(); + + this.init(); + } + + setNavModel(model) { + let defaultTab = 'readme'; + + this.navModel = { + main: { + img: model.info.logos.large, + subTitle: model.info.author.name, + url: '', + text: model.name, + breadcrumbs: [{ title: 'Plugins', url: 'plugins' }], + children: [ + { + icon: 'fa fa-fw fa-file-text-o', + id: 'readme', + text: 'Readme', + url: `plugins/${this.model.id}/edit?tab=readme`, + }, + ], + }, + }; + + if (model.type === 'app') { + this.navModel.main.children.push({ + icon: 'gicon gicon-cog', + id: 'config', + text: 'Config', + url: `plugins/${this.model.id}/edit?tab=config`, + }); + + const hasDashboards = _.find(model.includes, { type: 'dashboard' }); + + if (hasDashboards) { + this.navModel.main.children.push({ + icon: 'gicon gicon-dashboard', + id: 'dashboards', + text: 'Dashboards', + url: `plugins/${this.model.id}/edit?tab=dashboards`, + }); + } + + defaultTab = 'config'; + } + + this.tab = this.$routeParams.tab || defaultTab; + + for (const tab of this.navModel.main.children) { + if (tab.id === this.tab) { + tab.active = true; + } + } + } + + init() { + return this.backendSrv.get(`/api/plugins/${this.pluginId}/settings`).then(result => { + this.model = result; + this.pluginIcon = this.getPluginIcon(this.model.type); + + this.model.dependencies.plugins.forEach(plug => { + plug.icon = this.getPluginIcon(plug.type); + }); + + this.includes = _.map(result.includes, plug => { + plug.icon = this.getPluginIcon(plug.type); + return plug; + }); + + this.setNavModel(this.model); + return this.initReadme(); + }); + } + + initReadme() { + return this.backendSrv.get(`/api/plugins/${this.pluginId}/markdown/readme`).then(res => { + const md = new Remarkable({ + linkify: true, + }); + this.readmeHtml = this.$sce.trustAsHtml(md.render(res)); + }); + } + + getPluginIcon(type) { + switch (type) { + case 'datasource': + return 'icon-gf icon-gf-datasources'; + case 'panel': + return 'icon-gf icon-gf-panel'; + case 'app': + return 'icon-gf icon-gf-apps'; + case 'page': + return 'icon-gf icon-gf-endpoint-tiny'; + case 'dashboard': + return 'icon-gf icon-gf-dashboard'; + default: + return 'icon-gf icon-gf-apps'; + } + } + + update() { + this.preUpdateHook() + .then(() => { + const updateCmd = _.extend( + { + enabled: this.model.enabled, + pinned: this.model.pinned, + jsonData: this.model.jsonData, + secureJsonData: this.model.secureJsonData, + }, + {} + ); + return this.backendSrv.post(`/api/plugins/${this.pluginId}/settings`, updateCmd); + }) + .then(this.postUpdateHook) + .then(res => { + window.location.href = window.location.href; + }); + } + + importDashboards() { + return Promise.resolve(); + } + + setPreUpdateHook(callback: () => any) { + this.preUpdateHook = callback; + } + + setPostUpdateHook(callback: () => any) { + this.postUpdateHook = callback; + } + + updateAvailable() { + const modalScope = this.$scope.$new(true); + modalScope.plugin = this.model; + + this.$rootScope.appEvent('show-modal', { + src: 'public/app/features/plugins/partials/update_instructions.html', + scope: modalScope, + }); + } + + enable() { + this.model.enabled = true; + this.model.pinned = true; + this.update(); + } + + disable() { + this.model.enabled = false; + this.model.pinned = false; + this.update(); + } +} + +angular.module('grafana.controllers').controller('PluginEditCtrl', PluginEditCtrl); From 97180d51065b3b5ee99391a0c8be62ec40c2cbf8 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 10:28:51 +0100 Subject: [PATCH 08/12] add scoped vars to query options --- public/app/features/explore/Explore.tsx | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 386e8d46597..8e8d215e8b6 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -625,6 +625,10 @@ export class Explore extends React.PureComponent { raw: range, }, rangeRaw: range, + scopedVars: { + __interval: { text: interval, value: interval }, + __interval_ms: { text: intervalMs, value: intervalMs }, + }, }; } From 260d0189b64fc983341f18d01c44d18897aca7cf Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 10:29:38 +0100 Subject: [PATCH 09/12] add table support flag in influx config --- public/app/plugins/datasource/influxdb/plugin.json | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/plugins/datasource/influxdb/plugin.json b/public/app/plugins/datasource/influxdb/plugin.json index 973c4ac52cd..ab8f36c29cb 100644 --- a/public/app/plugins/datasource/influxdb/plugin.json +++ b/public/app/plugins/datasource/influxdb/plugin.json @@ -7,6 +7,7 @@ "metrics": true, "annotations": true, "alerting": true, + "tables": true, "queryOptions": { "minInterval": true From 45fbbe8021e7c080e25e83a54a3f9947a46c62aa Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 11:27:11 +0100 Subject: [PATCH 10/12] initialize empty variables array in constructor so that datasources can use the array in explore --- public/app/features/templating/template_srv.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/public/app/features/templating/template_srv.ts b/public/app/features/templating/template_srv.ts index 485d244e9ae..74da017bb93 100644 --- a/public/app/features/templating/template_srv.ts +++ b/public/app/features/templating/template_srv.ts @@ -17,6 +17,7 @@ export class TemplateSrv { constructor() { this.builtIns['__interval'] = { text: '1s', value: '1s' }; this.builtIns['__interval_ms'] = { text: '100', value: '100' }; + this.variables = []; } init(variables) { From 68c2d2631e0b3312e2be6ec0aa3c511a1100c629 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 14:11:40 +0100 Subject: [PATCH 11/12] filter out build in datasources. add unit test --- public/app/features/explore/Explore.tsx | 4 ++- public/app/features/plugins/datasource_srv.ts | 5 ++++ .../plugins/specs/datasource_srv.test.ts | 26 +++++++++++++++++++ 3 files changed, 34 insertions(+), 1 deletion(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index 8e8d215e8b6..f7d4abf4f46 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -158,7 +158,7 @@ export class Explore extends React.PureComponent { if (!datasourceSrv) { throw new Error('No datasource service passed as props.'); } - const datasources = datasourceSrv.getAll(); + const datasources = datasourceSrv.getExternal(); const exploreDatasources = datasources.map(ds => ({ value: ds.name, label: ds.name, @@ -574,6 +574,7 @@ export class Explore extends React.PureComponent { 'Table', { format: 'table', + resultFormat: 'table', instant: true, valueWithRefId: true, }, @@ -585,6 +586,7 @@ export class Explore extends React.PureComponent { 'Graph', { format: 'time_series', + resultFormat: 'time_series', instant: false, }, makeTimeSeriesList diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index d5e4c247073..0d68cbc71ba 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -78,6 +78,11 @@ export class DatasourceSrv { return Object.keys(datasources).map(name => datasources[name]); } + getExternal() { + const datasources = this.getAll().filter(ds => !ds.meta.builtIn); + return _.sortBy(datasources, ['name']); + } + getAnnotationSources() { const sources = []; diff --git a/public/app/features/plugins/specs/datasource_srv.test.ts b/public/app/features/plugins/specs/datasource_srv.test.ts index b04e9fa10c2..51b83efb3f5 100644 --- a/public/app/features/plugins/specs/datasource_srv.test.ts +++ b/public/app/features/plugins/specs/datasource_srv.test.ts @@ -18,6 +18,32 @@ const templateSrv = { describe('datasource_srv', () => { const _datasourceSrv = new DatasourceSrv({}, {}, {}, templateSrv); + describe('when loading external datasources', () => { + beforeEach(() => { + config.datasources = { + buildInDs: { + name: 'buildIn', + meta: { builtIn: true }, + }, + nonBuildIn: { + name: 'external1', + meta: { builtIn: false }, + }, + nonExplore: { + name: 'external2', + meta: {}, + }, + }; + }); + + it('should return list of explore sources', () => { + const externalSources = _datasourceSrv.getExternal(); + expect(externalSources.length).toBe(2); + expect(externalSources[0].name).toBe('external1'); + expect(externalSources[1].name).toBe('external2'); + }); + }); + describe('when loading metric sources', () => { let metricSources; const unsortedDatasources = { From 569f5e8d5edc3993bf824832322c2d2187f6dc85 Mon Sep 17 00:00:00 2001 From: Erik Sundell Date: Fri, 7 Dec 2018 14:13:05 +0100 Subject: [PATCH 12/12] remove result format. might add this later --- public/app/features/explore/Explore.tsx | 2 -- 1 file changed, 2 deletions(-) diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index f7d4abf4f46..d3c4a832a13 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -574,7 +574,6 @@ export class Explore extends React.PureComponent { 'Table', { format: 'table', - resultFormat: 'table', instant: true, valueWithRefId: true, }, @@ -586,7 +585,6 @@ export class Explore extends React.PureComponent { 'Graph', { format: 'time_series', - resultFormat: 'time_series', instant: false, }, makeTimeSeriesList