From 51a98565dc3e53cae7477badc28b00bee721ec81 Mon Sep 17 00:00:00 2001 From: Ryan McKinley Date: Tue, 30 Apr 2019 22:36:46 -0700 Subject: [PATCH] Refactor: consistant plugin/meta usage (#16834) --- packages/grafana-ui/src/types/datasource.ts | 7 ++- packages/grafana-ui/src/types/panel.ts | 23 +++---- packages/grafana-ui/src/types/plugin.ts | 15 +++-- .../dashboard/dashgrid/DashboardPanel.tsx | 39 ++---------- .../dashboard/dashgrid/PanelChrome.tsx | 10 +-- .../dashgrid/PanelPluginNotFound.tsx | 12 ++-- .../dashboard/panel_editor/PanelEditor.tsx | 10 +-- .../panel_editor/VisualizationTab.tsx | 32 +++++----- .../dashboard/state/PanelModel.test.ts | 21 +++---- .../features/dashboard/state/PanelModel.ts | 44 ++++++------- .../settings/DataSourceSettingsPage.tsx | 2 +- .../features/plugins/__mocks__/pluginMocks.ts | 16 +++-- public/app/features/plugins/datasource_srv.ts | 2 +- .../app/features/plugins/plugin_component.ts | 13 ++-- public/app/features/plugins/plugin_loader.ts | 61 +++++++++++++++---- .../plugins/variableQueryEditorLoader.tsx | 10 +-- 16 files changed, 162 insertions(+), 155 deletions(-) diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index f1baa8ad7d0..015d7cd10a1 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -1,6 +1,6 @@ import { ComponentClass } from 'react'; import { TimeRange } from './time'; -import { PluginMeta } from './plugin'; +import { PluginMeta, GrafanaPlugin } from './plugin'; import { TableData, TimeSeries, SeriesData, LoadingState } from './data'; import { PanelData } from './panel'; @@ -8,11 +8,14 @@ export interface DataSourcePluginOptionsEditorProps { options: TOptions; onOptionsChange: (options: TOptions) => void; } -export class DataSourcePlugin { +export class DataSourcePlugin extends GrafanaPlugin< + DataSourcePluginMeta +> { DataSourceClass: DataSourceConstructor; components: DataSourcePluginComponents; constructor(DataSourceClass: DataSourceConstructor) { + super(); this.DataSourceClass = DataSourceClass; this.components = {}; } diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index 888e1eb5924..ea28ca2fb47 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -2,16 +2,13 @@ import { ComponentClass, ComponentType } from 'react'; import { LoadingState, SeriesData } from './data'; import { TimeRange } from './time'; import { ScopedVars, DataQueryRequest, DataQueryError, LegacyResponseData } from './datasource'; -import { PluginMeta } from './plugin'; +import { PluginMeta, GrafanaPlugin } from './plugin'; export type InterpolateFunction = (value: string, scopedVars?: ScopedVars, format?: string | Function) => string; export interface PanelPluginMeta extends PluginMeta { hideFromList?: boolean; sort: number; - angularPlugin: AngularPanelPlugin | null; - panelPlugin: PanelPlugin | null; - hasBeenImported?: boolean; // if length>0 the query tab will show up // Before 6.2 this could be table and/or series, but 6.2+ supports both transparently @@ -72,14 +69,20 @@ export type PanelTypeChangedHandler = ( prevOptions: any ) => Partial; -export class PanelPlugin { +export class PanelPlugin extends GrafanaPlugin { panel: ComponentType>; editor?: ComponentClass>; defaults?: TOptions; onPanelMigration?: PanelMigrationHandler; onPanelTypeChanged?: PanelTypeChangedHandler; + /** + * Legacy angular ctrl. If this exists it will be used instead of the panel + */ + angularPanelCtrl?: any; + constructor(panel: ComponentType>) { + super(); this.panel = panel; } @@ -114,16 +117,6 @@ export class PanelPlugin { } } -export class AngularPanelPlugin { - components: { - PanelCtrl: any; - }; - - constructor(PanelCtrl: any) { - this.components = { PanelCtrl: PanelCtrl }; - } -} - export interface PanelSize { width: number; height: number; diff --git a/packages/grafana-ui/src/types/plugin.ts b/packages/grafana-ui/src/types/plugin.ts index 272e5e50145..af5382426ba 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -69,16 +69,20 @@ export interface PluginMetaInfo { version: string; } -export class AppPlugin { - meta: PluginMeta; +export class GrafanaPlugin { + // Meta is filled in by the plugin loading system + meta?: T; + // Soon this will also include common config options +} + +export class AppPlugin extends GrafanaPlugin { angular?: { ConfigCtrl?: any; pages: { [component: string]: any }; }; - constructor(meta: PluginMeta, pluginExports: any) { - this.meta = meta; + setComponentsFromLegacyExports(pluginExports: any) { const legacy = { ConfigCtrl: undefined, pages: {} as any, @@ -89,7 +93,8 @@ export class AppPlugin { this.angular = legacy; } - if (meta.includes) { + const { meta } = this; + if (meta && meta.includes) { for (const include of meta.includes) { const { type, component } = include; if (type === PluginIncludeType.page && component) { diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 0d2fc80ab71..e3b0b93f31d 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,6 +1,5 @@ // Libraries import React, { PureComponent } from 'react'; -import config from 'app/core/config'; import classNames from 'classnames'; // Utils & Services @@ -9,7 +8,6 @@ import { importPanelPlugin } from 'app/features/plugins/plugin_loader'; // Components import { AddPanelWidget } from '../components/AddPanelWidget'; -import { getPanelPluginNotFound } from './PanelPluginNotFound'; import { DashboardRow } from '../components/DashboardRow'; import { PanelChrome } from './PanelChrome'; import { PanelEditor } from '../panel_editor/PanelEditor'; @@ -17,7 +15,7 @@ import { PanelResizer } from './PanelResizer'; // Types import { PanelModel, DashboardModel } from '../state'; -import { PanelPluginMeta, AngularPanelPlugin, PanelPlugin } from '@grafana/ui/src/types/panel'; +import { PanelPluginMeta, PanelPlugin } from '@grafana/ui/src/types/panel'; import { AutoSizer } from 'react-virtualized'; export interface Props { @@ -28,7 +26,7 @@ export interface Props { } export interface State { - plugin: PanelPluginMeta; + plugin: PanelPlugin; angularPanel: AngularComponent; } @@ -72,15 +70,12 @@ export class DashboardPanel extends PureComponent { const { panel } = this.props; // handle plugin loading & changing of plugin type - if (!this.state.plugin || this.state.plugin.id !== pluginId) { - let plugin = config.panels[pluginId] || getPanelPluginNotFound(pluginId); + if (!this.state.plugin || this.state.plugin.meta.id !== pluginId) { + const plugin = await importPanelPlugin(pluginId); // unmount angular panel this.cleanUpAngularPanel(); - // load the actual plugin code - plugin = await this.importPanelPluginModule(plugin); - if (panel.type !== pluginId) { panel.changePlugin(plugin); } else { @@ -91,27 +86,6 @@ export class DashboardPanel extends PureComponent { } } - async importPanelPluginModule(plugin: PanelPluginMeta): Promise { - if (plugin.hasBeenImported) { - return plugin; - } - - try { - const importedPlugin = await importPanelPlugin(plugin.module); - if (importedPlugin instanceof AngularPanelPlugin) { - plugin.angularPlugin = importedPlugin as AngularPanelPlugin; - } else if (importedPlugin instanceof PanelPlugin) { - plugin.panelPlugin = importedPlugin as PanelPlugin; - } - } catch (e) { - plugin = getPanelPluginNotFound(plugin.id); - console.log('Failed to import plugin module', e); - } - - plugin.hasBeenImported = true; - return plugin; - } - componentDidMount() { this.loadPlugin(this.props.panel.type); } @@ -186,7 +160,7 @@ export class DashboardPanel extends PureComponent { } // if we have not loaded plugin exports yet, wait - if (!plugin || !plugin.hasBeenImported) { + if (!plugin) { return null; } @@ -209,8 +183,7 @@ export class DashboardPanel extends PureComponent { onMouseLeave={this.onMouseLeave} style={styles} > - {plugin.panelPlugin && this.renderReactPanel()} - {plugin.angularPlugin && this.renderAngularPanel()} + {plugin.angularPanelCtrl ? this.renderAngularPanel() : this.renderReactPanel()} )} /> diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 967bb67153b..b6f9bed46aa 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -16,7 +16,7 @@ import config from 'app/core/config'; // Types import { DashboardModel, PanelModel } from '../state'; -import { PanelPluginMeta, LoadingState, PanelData } from '@grafana/ui'; +import { LoadingState, PanelData, PanelPlugin } from '@grafana/ui'; import { ScopedVars } from '@grafana/ui'; import templateSrv from 'app/features/templating/template_srv'; @@ -29,7 +29,7 @@ const DEFAULT_PLUGIN_ERROR = 'Error in plugin'; export interface Props { panel: PanelModel; dashboard: DashboardModel; - plugin: PanelPluginMeta; + plugin: PanelPlugin; isFullscreen: boolean; width: number; height: number; @@ -209,13 +209,13 @@ export class PanelChrome extends PureComponent { } get wantsQueryExecution() { - return this.props.plugin.dataFormats.length > 0 && !this.hasPanelSnapshot; + return this.props.plugin.meta.dataFormats.length > 0 && !this.hasPanelSnapshot; } renderPanel(width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { renderCounter, data, isFirstLoad } = this.state; - const PanelComponent = plugin.panelPlugin.panel; + const PanelComponent = plugin.panel; // This is only done to increase a counter that is used by backend // image rendering (phantomjs/headless chrome) to know when to capture image @@ -236,7 +236,7 @@ export class PanelChrome extends PureComponent { { - constructor(props) { + constructor(props: Props) { super(props); } @@ -34,14 +34,15 @@ class PanelPluginNotFound extends PureComponent { } } -export function getPanelPluginNotFound(id: string): PanelPluginMeta { +export function getPanelPluginNotFound(id: string): PanelPlugin { const NotFound = class NotFound extends PureComponent { render() { return ; } }; - return { + const plugin = new PanelPlugin(NotFound); + plugin.meta = { id: id, name: id, sort: 100, @@ -63,7 +64,6 @@ export function getPanelPluginNotFound(id: string): PanelPluginMeta { updated: '', version: '', }, - panelPlugin: new PanelPlugin(NotFound), - angularPlugin: null, }; + return plugin; } diff --git a/public/app/features/dashboard/panel_editor/PanelEditor.tsx b/public/app/features/dashboard/panel_editor/PanelEditor.tsx index bae52158ea8..5cf7ad78d1f 100644 --- a/public/app/features/dashboard/panel_editor/PanelEditor.tsx +++ b/public/app/features/dashboard/panel_editor/PanelEditor.tsx @@ -13,12 +13,12 @@ import { AngularComponent } from 'app/core/services/AngularLoader'; import { PanelModel } from '../state/PanelModel'; import { DashboardModel } from '../state/DashboardModel'; -import { PanelPluginMeta, Tooltip } from '@grafana/ui'; +import { Tooltip, PanelPlugin, PanelPluginMeta } from '@grafana/ui'; interface PanelEditorProps { panel: PanelModel; dashboard: DashboardModel; - plugin: PanelPluginMeta; + plugin: PanelPlugin; angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPluginMeta) => void; } @@ -55,7 +55,7 @@ const getPanelEditorTab = (tabId: PanelEditorTabIds): PanelEditorTab => { }; export class PanelEditor extends PureComponent { - constructor(props) { + constructor(props: PanelEditorProps) { super(props); } @@ -105,7 +105,7 @@ export class PanelEditor extends PureComponent { ]; // handle panels that do not have queries tab - if (plugin.dataFormats.length === 0) { + if (plugin.meta.dataFormats.length === 0) { // remove queries tab tabs.shift(); // switch tab @@ -114,7 +114,7 @@ export class PanelEditor extends PureComponent { } } - if (config.alertingEnabled && plugin.id === 'graph') { + if (config.alertingEnabled && plugin.meta.id === 'graph') { tabs.push(getPanelEditorTab(PanelEditorTabIds.Alert)); } diff --git a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx index e8e5a302fd2..0cfa96da2a4 100644 --- a/public/app/features/dashboard/panel_editor/VisualizationTab.tsx +++ b/public/app/features/dashboard/panel_editor/VisualizationTab.tsx @@ -18,12 +18,12 @@ import { PanelModel } from '../state'; import { DashboardModel } from '../state'; import { VizPickerSearch } from './VizPickerSearch'; import PluginStateinfo from 'app/features/plugins/PluginStateInfo'; -import { PanelPluginMeta } from '@grafana/ui'; +import { PanelPlugin, PanelPluginMeta } from '@grafana/ui'; interface Props { panel: PanelModel; dashboard: DashboardModel; - plugin: PanelPluginMeta; + plugin: PanelPlugin; angularPanel?: AngularComponent; onTypeChanged: (newType: PanelPluginMeta) => void; updateLocation: typeof updateLocation; @@ -41,7 +41,7 @@ export class VisualizationTab extends PureComponent { element: HTMLElement; angularOptions: AngularComponent; - constructor(props) { + constructor(props: Props) { super(props); this.state = { @@ -54,7 +54,7 @@ export class VisualizationTab extends PureComponent { getReactPanelOptions = () => { const { panel, plugin } = this.props; - return panel.getOptions(plugin.panelPlugin.defaults); + return panel.getOptions(plugin.defaults); }; renderPanelOptions() { @@ -64,12 +64,8 @@ export class VisualizationTab extends PureComponent { return
(this.element = element)} />; } - if (plugin.panelPlugin) { - const PanelEditor = plugin.panelPlugin.editor; - - if (PanelEditor) { - return ; - } + if (plugin.editor) { + return ; } return

Visualization has no options

; @@ -176,11 +172,12 @@ export class VisualizationTab extends PureComponent { renderToolbar = (): JSX.Element => { const { plugin } = this.props; const { isVizPickerOpen, searchQuery } = this.state; + const { meta } = plugin; if (isVizPickerOpen) { return ( { } else { return (
- -
{plugin.name}
+ +
{meta.name}
); @@ -198,14 +195,14 @@ export class VisualizationTab extends PureComponent { }; onTypeChanged = (plugin: PanelPluginMeta) => { - if (plugin.id === this.props.plugin.id) { + if (plugin.id === this.props.plugin.meta.id) { this.setState({ isVizPickerOpen: false }); } else { this.props.onTypeChanged(plugin); } }; - renderHelp = () => ; + renderHelp = () => ; setScrollTop = (event: React.MouseEvent) => { const target = event.target as HTMLElement; @@ -215,6 +212,7 @@ export class VisualizationTab extends PureComponent { render() { const { plugin } = this.props; const { isVizPickerOpen, searchQuery, scrollTop } = this.state; + const { meta } = plugin; const pluginHelp: EditorToolbarView = { heading: 'Help', @@ -233,13 +231,13 @@ export class VisualizationTab extends PureComponent { <> - + {this.renderPanelOptions()} diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index a16a9c30cab..ca88584ad08 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -1,6 +1,5 @@ import { PanelModel } from './PanelModel'; import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; -import { PanelPlugin, AngularPanelPlugin } from '@grafana/ui/src/types/panel'; class TablePanelCtrl {} @@ -31,10 +30,13 @@ describe('PanelModel', () => { }; model = new PanelModel(modelJson); model.pluginLoaded( - getPanelPlugin({ - id: 'table', - angularPlugin: new AngularPanelPlugin(TablePanelCtrl), - }) + getPanelPlugin( + { + id: 'table', + }, + null, // react + TablePanelCtrl // angular + ) ); }); @@ -123,15 +125,10 @@ describe('PanelModel', () => { describe('when changing to react panel', () => { const onPanelTypeChanged = jest.fn(); - const reactPlugin = new PanelPlugin({} as any).setPanelChangeHandler(onPanelTypeChanged as any); + const reactPlugin = getPanelPlugin({ id: 'react' }).setPanelChangeHandler(onPanelTypeChanged as any); beforeEach(() => { - model.changePlugin( - getPanelPlugin({ - id: 'react', - panelPlugin: reactPlugin, - }) - ); + model.changePlugin(reactPlugin); }); it('should call react onPanelTypeChanged', () => { diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index 30ee3bfa3f9..a09540adc9a 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -6,7 +6,7 @@ import { Emitter } from 'app/core/utils/emitter'; import { getNextRefIdChar } from 'app/core/utils/query'; // Types -import { PanelPluginMeta, DataQuery, Threshold, ScopedVars, DataQueryResponseData } from '@grafana/ui'; +import { DataQuery, Threshold, ScopedVars, DataQueryResponseData, PanelPlugin } from '@grafana/ui'; import config from 'app/core/config'; import { PanelQueryRunner } from './PanelQueryRunner'; @@ -116,7 +116,7 @@ export class PanelModel { cacheTimeout?: any; cachedPluginOptions?: any; legend?: { show: boolean }; - plugin?: PanelPluginMeta; + plugin?: PanelPlugin; private queryRunner?: PanelQueryRunner; constructor(model: any) { @@ -248,29 +248,25 @@ export class PanelModel { }); } - private getPluginVersion(plugin: PanelPluginMeta): string { - return this.plugin && this.plugin.info.version ? this.plugin.info.version : config.buildInfo.version; - } - - pluginLoaded(plugin: PanelPluginMeta) { + pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; - if (plugin.panelPlugin && plugin.panelPlugin.onPanelMigration) { - const version = this.getPluginVersion(plugin); + if (plugin.panel && plugin.onPanelMigration) { + const version = getPluginVersion(plugin); if (version !== this.pluginVersion) { - this.options = plugin.panelPlugin.onPanelMigration(this); + this.options = plugin.onPanelMigration(this); this.pluginVersion = version; } } } - changePlugin(newPlugin: PanelPluginMeta) { - const pluginId = newPlugin.id; + changePlugin(newPlugin: PanelPlugin) { + const pluginId = newPlugin.meta.id; const oldOptions: any = this.getOptionsToRemember(); const oldPluginId = this.type; // for angular panels we must remove all events and let angular panels do some cleanup - if (this.plugin.angularPlugin) { + if (this.plugin.angularPanelCtrl) { this.destroy(); } @@ -291,18 +287,14 @@ export class PanelModel { this.plugin = newPlugin; // Let panel plugins inspect options from previous panel and keep any that it can use - const reactPanel = newPlugin.panelPlugin; + if (newPlugin.onPanelTypeChanged) { + this.options = this.options || {}; + const old = oldOptions && oldOptions.options ? oldOptions.options : {}; + Object.assign(this.options, newPlugin.onPanelTypeChanged(this.options, oldPluginId, old)); + } - if (reactPanel) { - if (reactPanel.onPanelTypeChanged) { - this.options = this.options || {}; - const old = oldOptions && oldOptions.options ? oldOptions.options : {}; - Object.assign(this.options, reactPanel.onPanelTypeChanged(this.options, oldPluginId, old)); - } - - if (reactPanel.onPanelMigration) { - this.pluginVersion = this.getPluginVersion(newPlugin); - } + if (newPlugin.onPanelMigration) { + this.pluginVersion = getPluginVersion(newPlugin); } } @@ -341,3 +333,7 @@ export class PanelModel { } } } + +function getPluginVersion(plugin: PanelPlugin): string { + return plugin && plugin.meta.info.version ? plugin.meta.info.version : config.buildInfo.version; +} diff --git a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx index b4c78656266..fd2fa3ebf26 100644 --- a/public/app/features/datasources/settings/DataSourceSettingsPage.tsx +++ b/public/app/features/datasources/settings/DataSourceSettingsPage.tsx @@ -63,7 +63,7 @@ export class DataSourceSettingsPage extends PureComponent { let importedPlugin: DataSourcePlugin; try { - importedPlugin = await importDataSourcePlugin(dataSourceMeta.module); + importedPlugin = await importDataSourcePlugin(dataSourceMeta); } catch (e) { console.log('Failed to import plugin module', e); } diff --git a/public/app/features/plugins/__mocks__/pluginMocks.ts b/public/app/features/plugins/__mocks__/pluginMocks.ts index 3d2cb4c5fdc..7106af6f071 100644 --- a/public/app/features/plugins/__mocks__/pluginMocks.ts +++ b/public/app/features/plugins/__mocks__/pluginMocks.ts @@ -1,4 +1,5 @@ -import { PanelPluginMeta, PluginMeta, PluginType, PanelDataFormat } from '@grafana/ui'; +import { PanelPluginMeta, PluginMeta, PluginType, PanelDataFormat, PanelPlugin, PanelProps } from '@grafana/ui'; +import { ComponentType } from 'enzyme'; export const getMockPlugins = (amount: number): PluginMeta[] => { const plugins = []; @@ -33,8 +34,14 @@ export const getMockPlugins = (amount: number): PluginMeta[] => { return plugins; }; -export const getPanelPlugin = (options: Partial): PanelPluginMeta => { - return { +export const getPanelPlugin = ( + options: Partial, + reactPanel?: ComponentType, + angularPanel?: any +): PanelPlugin => { + const plugin = new PanelPlugin(reactPanel); + plugin.angularPanelCtrl = angularPanel; + plugin.meta = { id: options.id, type: PluginType.panel, name: options.id, @@ -57,9 +64,8 @@ export const getPanelPlugin = (options: Partial): PanelPluginMe hideFromList: options.hideFromList === true, module: '', baseUrl: '', - panelPlugin: options.panelPlugin, - angularPlugin: options.angularPlugin, }; + return plugin; }; export const getMockPlugin = () => { diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index a6222c77e32..d9c891ca8ba 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -53,7 +53,7 @@ export class DatasourceSrv { const deferred = this.$q.defer(); - importDataSourcePlugin(dsConfig.meta.module) + importDataSourcePlugin(dsConfig.meta) .then(dsPlugin => { // check if its in cache now if (this.datasources[name]) { diff --git a/public/app/features/plugins/plugin_component.ts b/public/app/features/plugins/plugin_component.ts index 690d423197a..6f6679886dd 100644 --- a/public/app/features/plugins/plugin_component.ts +++ b/public/app/features/plugins/plugin_component.ts @@ -4,7 +4,7 @@ import _ from 'lodash'; import config from 'app/core/config'; import coreModule from 'app/core/core_module'; -import { AngularPanelPlugin, DataSourceApi } from '@grafana/ui/src/types'; +import { DataSourceApi } from '@grafana/ui/src/types'; import { importPanelPlugin, importDataSourcePlugin, importAppPlugin } from './plugin_loader'; /** @ngInject */ @@ -22,7 +22,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ }); } - function relativeTemplateUrlToAbs(templateUrl, baseUrl) { + function relativeTemplateUrlToAbs(templateUrl: string, baseUrl: string) { if (!templateUrl) { return undefined; } @@ -69,9 +69,8 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ }; const panelInfo = config.panels[scope.panel.type]; - return importPanelPlugin(panelInfo.module).then(panelPlugin => { - const angularPanelPlugin = panelPlugin as AngularPanelPlugin; - const PanelCtrl = angularPanelPlugin.components.PanelCtrl; + return importPanelPlugin(panelInfo.id).then(panelPlugin => { + const PanelCtrl = panelPlugin.angularPanelCtrl; componentInfo.Component = PanelCtrl; if (!PanelCtrl || PanelCtrl.registered) { @@ -118,7 +117,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ } // Annotations case 'annotations-query-ctrl': { - return importDataSourcePlugin(scope.ctrl.currentDatasource.meta.module).then(dsPlugin => { + return importDataSourcePlugin(scope.ctrl.currentDatasource.meta).then(dsPlugin => { return { baseUrl: scope.ctrl.currentDatasource.meta.baseUrl, name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id, @@ -134,7 +133,7 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ // Datasource ConfigCtrl case 'datasource-config-ctrl': { const dsMeta = scope.ctrl.datasourceMeta; - return importDataSourcePlugin(dsMeta.module).then(dsPlugin => { + return importDataSourcePlugin(dsMeta).then(dsPlugin => { scope.$watch( 'ctrl.current', () => { diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 756724ace9d..3bef9c93621 100644 --- a/public/app/features/plugins/plugin_loader.ts +++ b/public/app/features/plugins/plugin_loader.ts @@ -18,7 +18,7 @@ import config from 'app/core/config'; import TimeSeries from 'app/core/time_series2'; import TableModel from 'app/core/table_model'; import { coreModule, appEvents, contextSrv } from 'app/core/core'; -import { DataSourcePlugin, AppPlugin, PanelPlugin, AngularPanelPlugin, PluginMeta } from '@grafana/ui/src/types'; +import { DataSourcePlugin, AppPlugin, PanelPlugin, PluginMeta, DataSourcePluginMeta } from '@grafana/ui/src/types'; import * as datemath from 'app/core/utils/datemath'; import * as fileExport from 'app/core/utils/file_export'; import * as flatten from 'app/core/utils/flatten'; @@ -160,15 +160,18 @@ export function importPluginModule(path: string): Promise { return System.import(path); } -export function importDataSourcePlugin(path: string): Promise> { - return importPluginModule(path).then(pluginExports => { +export function importDataSourcePlugin(meta: DataSourcePluginMeta): Promise> { + return importPluginModule(meta.module).then(pluginExports => { if (pluginExports.plugin) { - return pluginExports.plugin as DataSourcePlugin; + const dsPlugin = pluginExports.plugin as DataSourcePlugin; + dsPlugin.meta = meta; + return dsPlugin; } if (pluginExports.Datasource) { const dsPlugin = new DataSourcePlugin(pluginExports.Datasource); dsPlugin.setComponentsFromLegacyExports(pluginExports); + dsPlugin.meta = meta; return dsPlugin; } @@ -178,18 +181,50 @@ export function importDataSourcePlugin(path: string): Promise { return importPluginModule(meta.module).then(pluginExports => { - return new AppPlugin(meta, pluginExports); + const plugin = pluginExports.plugin ? (pluginExports.plugin as AppPlugin) : new AppPlugin(); + plugin.meta = meta; + plugin.setComponentsFromLegacyExports(pluginExports); + return plugin; }); } -export function importPanelPlugin(path: string): Promise { - return importPluginModule(path).then(pluginExports => { - if (pluginExports.plugin) { - return pluginExports.plugin as PanelPlugin; - } else { - return new AngularPanelPlugin(pluginExports.PanelCtrl); - } - }); +import { getPanelPluginNotFound } from '../dashboard/dashgrid/PanelPluginNotFound'; + +interface PanelCache { + [key: string]: PanelPlugin; +} +const panelCache: PanelCache = {}; + +export function importPanelPlugin(id: string): Promise { + const loaded = panelCache[id]; + if (loaded) { + return Promise.resolve(loaded); + } + const meta = config.panels[id]; + if (!meta) { + return Promise.resolve(getPanelPluginNotFound(id)); + } + + return importPluginModule(meta.module) + .then(pluginExports => { + if (pluginExports.plugin) { + return pluginExports.plugin as PanelPlugin; + } else if (pluginExports.PanelCtrl) { + const plugin = new PanelPlugin(null); + plugin.angularPanelCtrl = pluginExports.PanelCtrl; + return plugin; + } + throw new Error('missing export: plugin or PanelCtrl'); + }) + .then(plugin => { + plugin.meta = meta; + return (panelCache[meta.id] = plugin); + }) + .catch(err => { + // TODO, maybe a different error plugin + console.log('Error loading panel plugin', err); + return getPanelPluginNotFound(id); + }); } export function loadPluginCss(options) { diff --git a/public/app/features/plugins/variableQueryEditorLoader.tsx b/public/app/features/plugins/variableQueryEditorLoader.tsx index beb5e99898f..92d6bfcad28 100644 --- a/public/app/features/plugins/variableQueryEditorLoader.tsx +++ b/public/app/features/plugins/variableQueryEditorLoader.tsx @@ -3,9 +3,11 @@ import { importDataSourcePlugin } from './plugin_loader'; import React from 'react'; import ReactDOM from 'react-dom'; import DefaultVariableQueryEditor from '../templating/DefaultVariableQueryEditor'; +import { DataSourcePluginMeta } from '@grafana/ui'; +import { TemplateSrv } from '../templating/template_srv'; -async function loadComponent(module) { - const dsPlugin = await importDataSourcePlugin(module); +async function loadComponent(meta: DataSourcePluginMeta) { + const dsPlugin = await importDataSourcePlugin(meta); if (dsPlugin.components.VariableQueryEditor) { return dsPlugin.components.VariableQueryEditor; } else { @@ -14,11 +16,11 @@ async function loadComponent(module) { } /** @ngInject */ -function variableQueryEditorLoader(templateSrv) { +function variableQueryEditorLoader(templateSrv: TemplateSrv) { return { restrict: 'E', link: async (scope, elem) => { - const Component = await loadComponent(scope.currentDatasource.meta.module); + const Component = await loadComponent(scope.currentDatasource.meta); const props = { datasource: scope.currentDatasource, query: scope.current.query,