From 47e51cb6b331aa792f1ab203892a2842c1a72902 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Torkel=20=C3=96degaard?= Date: Thu, 4 Apr 2019 18:30:15 +0200 Subject: [PATCH] Refactor: Plugin exports & data source / panel types (#16364) * wip: began work off removing meta and pluginExports from DataSourceApi interface * WIP: changing how plugins are exports and loaded * Down the refactoring rabit hole that keeps expanding * TestData now returns DataSourcePlugin * Refactoring: fixed app config page loading, type renamings and more typings * Refactor: Correct casing on DatasourceStatus => DataSourceStatus --- packages/grafana-ui/src/types/datasource.ts | 178 ++++++++++++++++++ packages/grafana-ui/src/types/panel.ts | 10 + packages/grafana-ui/src/types/plugin.ts | 119 ++---------- public/app/core/config.ts | 4 +- .../dashboard/dashgrid/DashboardPanel.tsx | 45 +++-- .../dashboard/dashgrid/PanelChrome.tsx | 4 +- .../dashgrid/PanelPluginNotFound.tsx | 6 +- .../dashboard/panel_editor/QueryEditorRow.tsx | 7 +- .../panel_editor/VisualizationTab.tsx | 6 +- .../dashboard/state/PanelModel.test.ts | 25 ++- .../features/dashboard/state/PanelModel.ts | 13 +- .../features/datasources/state/navModel.ts | 2 + public/app/features/explore/QueryRow.tsx | 10 +- .../features/explore/state/reducers.test.ts | 2 +- public/app/features/explore/state/reducers.ts | 3 +- .../DashboardImportCtrl.test.ts | 2 +- .../features/plugins/__mocks__/pluginMocks.ts | 3 +- public/app/features/plugins/datasource_srv.ts | 21 +-- .../app/features/plugins/plugin_component.ts | 72 ++++--- public/app/features/plugins/plugin_loader.ts | 36 +++- .../plugins/specs/datasource_srv.test.ts | 15 +- .../plugins/variableQueryEditorLoader.tsx | 8 +- .../loki/components/LokiQueryFieldForm.tsx | 8 +- .../loki/components/useLokiLabels.test.ts | 8 +- .../loki/components/useLokiLabels.ts | 10 +- .../loki/components/useLokiSyntax.test.ts | 8 +- .../loki/components/useLokiSyntax.ts | 4 +- .../prometheus/components/PromQueryField.tsx | 12 +- .../app/plugins/datasource/testdata/module.ts | 13 +- public/app/types/plugins.ts | 6 +- scripts/ci-frontend-metrics.sh | 2 +- 31 files changed, 411 insertions(+), 251 deletions(-) diff --git a/packages/grafana-ui/src/types/datasource.ts b/packages/grafana-ui/src/types/datasource.ts index 23fa22937ab..78a2b2b7545 100644 --- a/packages/grafana-ui/src/types/datasource.ts +++ b/packages/grafana-ui/src/types/datasource.ts @@ -1,7 +1,159 @@ +import { ComponentClass } from 'react'; import { TimeRange, RawTimeRange } from './time'; import { PluginMeta } from './plugin'; import { TableData, TimeSeries, SeriesData } from './data'; +export class DataSourcePlugin { + DataSourceClass: DataSourceConstructor; + components: DataSourcePluginComponents; + + constructor(DataSourceClass: DataSourceConstructor) { + this.DataSourceClass = DataSourceClass; + this.components = {}; + } + + setConfigCtrl(ConfigCtrl: any) { + this.components.ConfigCtrl = ConfigCtrl; + return this; + } + + setQueryCtrl(QueryCtrl: any) { + this.components.QueryCtrl = QueryCtrl; + return this; + } + + setAnnotationQueryCtrl(AnnotationsQueryCtrl: any) { + this.components.AnnotationsQueryCtrl = AnnotationsQueryCtrl; + return this; + } + + setQueryEditor(QueryEditor: ComponentClass>) { + this.components.QueryEditor = QueryEditor; + return this; + } + + setExploreQueryField(ExploreQueryField: ComponentClass>) { + this.components.ExploreQueryField = ExploreQueryField; + return this; + } + + setExploreStartPage(ExploreStartPage: ComponentClass) { + this.components.ExploreStartPage = ExploreStartPage; + return this; + } + + setVariableQueryEditor(VariableQueryEditor: any) { + this.components.VariableQueryEditor = VariableQueryEditor; + return this; + } + + setComponentsFromLegacyExports(exports: any) { + this.components.ConfigCtrl = exports.ConfigCtrl; + this.components.QueryCtrl = exports.QueryCtrl; + this.components.AnnotationsQueryCtrl = exports.AnnotationsQueryCtrl; + this.components.ExploreQueryField = exports.ExploreQueryField; + this.components.ExploreStartPage = exports.ExploreStartPage; + this.components.QueryEditor = exports.QueryEditor; + } +} + +export interface DataSourcePluginComponents { + QueryCtrl?: any; + ConfigCtrl?: any; + AnnotationsQueryCtrl?: any; + VariableQueryEditor?: any; + QueryEditor?: ComponentClass>; + ExploreQueryField?: ComponentClass>; + ExploreStartPage?: ComponentClass; +} + +interface DataSourceConstructor { + new (instanceSettings: DataSourceInstanceSettings, ...args: any[]): DataSourceApi; +} + +/** + * The main data source abstraction interface, represents an instance of a data source + */ +export interface DataSourceApi { + /** + * min interval range + */ + interval?: string; + + /** + * Imports queries from a different datasource + */ + importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise; + + /** + * Initializes a datasource after instantiation + */ + init?: () => void; + + /** + * Main metrics / data query action + */ + query(options: DataQueryOptions): Promise; + + /** + * Test & verify datasource settings & connection details + */ + testDatasource(): Promise; + + /** + * Get hints for query improvements + */ + getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[]; + + /** + * Set after constructor is called by Grafana + */ + name?: string; + + /** + * Set after constructor call, as the data source instance is the most common thing to pass around + * we attach the components to this instance for easy access + */ + components?: DataSourcePluginComponents; + meta?: PluginMeta; +} + +export interface ExploreDataSourceApi extends DataSourceApi { + modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; + getHighlighterExpression?(query: TQuery): string; + languageProvider?: any; +} + +export interface QueryEditorProps { + datasource: DSType; + query: TQuery; + onRunQuery: () => void; + onChange: (value: TQuery) => void; + queryResponse?: SeriesData[]; + queryError?: DataQueryError; +} + +export enum DataSourceStatus { + Connected, + Disconnected, +} + +export interface ExploreQueryFieldProps { + datasource: DSType; + datasourceStatus: DataSourceStatus; + query: TQuery; + error?: string | JSX.Element; + hint?: QueryHint; + history: any[]; + onExecuteQuery?: () => void; + onQueryChange?: (value: TQuery) => void; + onExecuteHint?: (action: QueryFixAction) => void; +} + +export interface ExploreStartPageProps { + onClickExample: (query: DataQuery) => void; +} + /** * Starting in v6.2 SeriesData can represent both TimeSeries and TableData */ @@ -89,6 +241,9 @@ export interface QueryHint { fix?: QueryFix; } +/** + * Data Source instance edit model + */ export interface DataSourceSettings { id: number; orgId: number; @@ -109,6 +264,29 @@ export interface DataSourceSettings { withCredentials: boolean; } +/** + * Frontend settings model that is passed to Datasource constructor. This differs a bit from the model above + * as this data model is available to every user who has access to a data source (Viewers+). + */ +export interface DataSourceInstanceSettings { + type: string; + name: string; + meta: PluginMeta; + url?: string; + jsonData: { [str: string]: any }; + username?: string; + password?: string; // when access is direct, for some legacy datasources + + /** + * This is the full Authorization header if basic auth is ennabled. + * Only available here when access is Browser (direct), when acess is Server (proxy) + * The basic auth header, username & password is never exposted to browser/Frontend + * so this will be emtpy then. + */ + basicAuth?: string; + withCredentials?: boolean; +} + export interface DataSourceSelectItem { name: string; value: string | null; diff --git a/packages/grafana-ui/src/types/panel.ts b/packages/grafana-ui/src/types/panel.ts index a6cde9e5f88..cd990dcaa61 100644 --- a/packages/grafana-ui/src/types/panel.ts +++ b/packages/grafana-ui/src/types/panel.ts @@ -83,6 +83,16 @@ export class ReactPanelPlugin { } } +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 211fbad418e..d7899c79acc 100644 --- a/packages/grafana-ui/src/types/plugin.ts +++ b/packages/grafana-ui/src/types/plugin.ts @@ -1,110 +1,10 @@ -import { ComponentClass } from 'react'; -import { ReactPanelPlugin } from './panel'; -import { - DataQueryOptions, - DataQuery, - DataQueryResponse, - QueryHint, - QueryFixAction, - DataQueryError, -} from './datasource'; -import { SeriesData } from './data'; - -export interface DataSourceApi { - /** - * min interval range - */ - interval?: string; - - /** - * Imports queries from a different datasource - */ - importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise; - - /** - * Initializes a datasource after instantiation - */ - init?: () => void; - - /** - * Main metrics / data query action - */ - query(options: DataQueryOptions): Promise; - - /** - * Test & verify datasource settings & connection details - */ - testDatasource(): Promise; - - /** - * Get hints for query improvements - */ - getQueryHints?(query: TQuery, results: any[], ...rest: any): QueryHint[]; - - /** - * Set after constructor is called by Grafana - */ - name?: string; - meta?: PluginMeta; - pluginExports?: PluginExports; -} - -export interface ExploreDataSourceApi extends DataSourceApi { - modifyQuery?(query: TQuery, action: QueryFixAction): TQuery; - getHighlighterExpression?(query: TQuery): string; - languageProvider?: any; -} - -export interface QueryEditorProps { - datasource: DSType; - query: TQuery; - onRunQuery: () => void; - onChange: (value: TQuery) => void; - queryResponse?: SeriesData[]; - queryError?: DataQueryError; -} - -export enum DatasourceStatus { - Connected, - Disconnected, -} - -export interface ExploreQueryFieldProps { - datasource: DSType; - datasourceStatus: DatasourceStatus; - query: TQuery; - error?: string | JSX.Element; - hint?: QueryHint; - history: any[]; - onExecuteQuery?: () => void; - onQueryChange?: (value: TQuery) => void; - onExecuteHint?: (action: QueryFixAction) => void; -} - -export interface ExploreStartPageProps { - onClickExample: (query: DataQuery) => void; -} - -export interface PluginExports { - Datasource?: DataSourceApi; - QueryCtrl?: any; - QueryEditor?: ComponentClass>; - ConfigCtrl?: any; - AnnotationsQueryCtrl?: any; - VariableQueryEditor?: any; - ExploreQueryField?: ComponentClass>; - ExploreStartPage?: ComponentClass; - - // Panel plugin - PanelCtrl?: any; - reactPanel?: ReactPanelPlugin; -} - export interface PluginMeta { id: string; name: string; info: PluginMetaInfo; includes: PluginInclude[]; + module: string; + baseUrl: string; // Datasource-specific builtIn?: boolean; @@ -150,3 +50,18 @@ export interface PluginMetaInfo { updated: string; version: string; } + +export class AppPlugin { + components: { + ConfigCtrl?: any; + }; + + pages: { [str: string]: any }; + + constructor(ConfigCtrl: any) { + this.components = { + ConfigCtrl: ConfigCtrl, + }; + this.pages = {}; + } +} diff --git a/public/app/core/config.ts b/public/app/core/config.ts index fe9005973b8..26c7fa17698 100644 --- a/public/app/core/config.ts +++ b/public/app/core/config.ts @@ -1,6 +1,6 @@ import _ from 'lodash'; import { PanelPlugin } from 'app/types/plugins'; -import { GrafanaTheme, getTheme, GrafanaThemeType } from '@grafana/ui'; +import { GrafanaTheme, getTheme, GrafanaThemeType, DataSourceInstanceSettings } from '@grafana/ui'; export interface BuildInfo { version: string; @@ -12,7 +12,7 @@ export interface BuildInfo { } export class Settings { - datasources: any; + datasources: { [str: string]: DataSourceInstanceSettings }; panels: { [key: string]: PanelPlugin }; appSubUrl: string; windowTitlePrefix: string; diff --git a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx index 4fe90c7bcc5..8f03bef954b 100644 --- a/public/app/features/dashboard/dashgrid/DashboardPanel.tsx +++ b/public/app/features/dashboard/dashgrid/DashboardPanel.tsx @@ -1,19 +1,24 @@ +// Libraries import React, { PureComponent } from 'react'; import config from 'app/core/config'; import classNames from 'classnames'; +// Utils & Services import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; -import { importPluginModule } from 'app/features/plugins/plugin_loader'; +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'; +import { PanelResizer } from './PanelResizer'; +// Types import { PanelModel, DashboardModel } from '../state'; import { PanelPlugin } from 'app/types'; -import { PanelResizer } from './PanelResizer'; +import { AngularPanelPlugin, ReactPanelPlugin } from '@grafana/ui/src/types/panel'; export interface Props { panel: PanelModel; @@ -73,13 +78,8 @@ export class DashboardPanel extends PureComponent { // unmount angular panel this.cleanUpAngularPanel(); - if (!plugin.exports) { - try { - plugin.exports = await importPluginModule(plugin.module); - } catch (e) { - plugin = getPanelPluginNotFound(pluginId); - } - } + // load the actual plugin code + plugin = await this.importPanelPluginModule(plugin); if (panel.type !== pluginId) { panel.changePlugin(plugin); @@ -91,6 +91,27 @@ export class DashboardPanel extends PureComponent { } } + async importPanelPluginModule(plugin: PanelPlugin): 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 ReactPanelPlugin) { + plugin.reactPlugin = importedPlugin as ReactPanelPlugin; + } + } 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); } @@ -155,7 +176,7 @@ export class DashboardPanel extends PureComponent { } // if we have not loaded plugin exports yet, wait - if (!plugin || !plugin.exports) { + if (!plugin || !plugin.hasBeenImported) { return null; } @@ -178,8 +199,8 @@ export class DashboardPanel extends PureComponent { onMouseLeave={this.onMouseLeave} style={styles} > - {plugin.exports.reactPanel && this.renderReactPanel()} - {plugin.exports.PanelCtrl && this.renderAngularPanel()} + {plugin.reactPlugin && this.renderReactPanel()} + {plugin.angularPlugin && this.renderAngularPanel()} )} /> diff --git a/public/app/features/dashboard/dashgrid/PanelChrome.tsx b/public/app/features/dashboard/dashgrid/PanelChrome.tsx index 3b92754538d..0d1ccbbda7a 100644 --- a/public/app/features/dashboard/dashgrid/PanelChrome.tsx +++ b/public/app/features/dashboard/dashgrid/PanelChrome.tsx @@ -158,7 +158,7 @@ export class PanelChrome extends PureComponent { renderPanelPlugin(loading: LoadingState, data: SeriesData[], width: number, height: number): JSX.Element { const { panel, plugin } = this.props; const { timeRange, renderCounter } = this.state; - const PanelComponent = plugin.exports.reactPanel.panel; + const PanelComponent = plugin.reactPlugin.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 @@ -172,7 +172,7 @@ export class PanelChrome extends PureComponent { loading={loading} data={data} timeRange={timeRange} - options={panel.getOptions(plugin.exports.reactPanel.defaults)} + options={panel.getOptions(plugin.reactPlugin.defaults)} width={width - 2 * config.theme.panelPadding.horizontal} height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical} renderCounter={renderCounter} diff --git a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx index fa16be55e75..f0c7eff1fa3 100644 --- a/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx +++ b/public/app/features/dashboard/dashgrid/PanelPluginNotFound.tsx @@ -62,9 +62,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin { updated: '', version: '', }, - - exports: { - reactPanel: new ReactPanelPlugin(NotFound), - }, + reactPlugin: new ReactPanelPlugin(NotFound), + angularPlugin: null, }; } diff --git a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx index 02fdd1af818..48a18913043 100644 --- a/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx +++ b/public/app/features/dashboard/panel_editor/QueryEditorRow.tsx @@ -176,12 +176,13 @@ export class QueryEditorRow extends PureComponent { const { query, onChange } = this.props; const { datasource, queryResponse, queryError } = this.state; - if (datasource.pluginExports.QueryCtrl) { + if (datasource.components.QueryCtrl) { return
(this.element = element)} />; } - if (datasource.pluginExports.QueryEditor) { - const QueryEditor = datasource.pluginExports.QueryEditor; + if (datasource.components.QueryEditor) { + const QueryEditor = datasource.components.QueryEditor; + return ( { getReactPanelOptions = () => { const { panel, plugin } = this.props; - return panel.getOptions(plugin.exports.reactPanel.defaults); + return panel.getOptions(plugin.reactPlugin.defaults); }; renderPanelOptions() { @@ -63,8 +63,8 @@ export class VisualizationTab extends PureComponent { return
(this.element = element)} />; } - if (plugin.exports.reactPanel) { - const PanelEditor = plugin.exports.reactPanel.editor; + if (plugin.reactPlugin) { + const PanelEditor = plugin.reactPlugin.editor; if (PanelEditor) { return ; diff --git a/public/app/features/dashboard/state/PanelModel.test.ts b/public/app/features/dashboard/state/PanelModel.test.ts index 2a8fcbbfbaa..366c933d58d 100644 --- a/public/app/features/dashboard/state/PanelModel.test.ts +++ b/public/app/features/dashboard/state/PanelModel.test.ts @@ -1,6 +1,8 @@ import { PanelModel } from './PanelModel'; import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks'; -import { ReactPanelPlugin } from '@grafana/ui/src/types/panel'; +import { ReactPanelPlugin, AngularPanelPlugin } from '@grafana/ui/src/types/panel'; + +class TablePanelCtrl {} describe('PanelModel', () => { describe('when creating new panel model', () => { @@ -28,7 +30,12 @@ describe('PanelModel', () => { }, }; model = new PanelModel(modelJson); - model.pluginLoaded(getPanelPlugin({ id: 'table', exports: { PanelCtrl: {} as any } })); + model.pluginLoaded( + getPanelPlugin({ + id: 'table', + angularPlugin: new AngularPanelPlugin(TablePanelCtrl), + }) + ); }); it('should apply defaults', () => { @@ -79,7 +86,7 @@ describe('PanelModel', () => { describe('when changing panel type', () => { beforeEach(() => { - model.changePlugin(getPanelPlugin({ id: 'graph', exports: {} })); + model.changePlugin(getPanelPlugin({ id: 'graph' })); model.alert = { id: 2 }; }); @@ -88,12 +95,12 @@ describe('PanelModel', () => { }); it('should restore table properties when changing back', () => { - model.changePlugin(getPanelPlugin({ id: 'table', exports: {} })); + model.changePlugin(getPanelPlugin({ id: 'table' })); expect(model.showColumns).toBe(true); }); it('should remove alert rule when changing type that does not support it', () => { - model.changePlugin(getPanelPlugin({ id: 'table', exports: {} })); + model.changePlugin(getPanelPlugin({ id: 'table' })); expect(model.alert).toBe(undefined); }); }); @@ -105,7 +112,7 @@ describe('PanelModel', () => { model.events.on('panel-teardown', () => { tearDownPublished = true; }); - model.changePlugin(getPanelPlugin({ id: 'graph', exports: {} })); + model.changePlugin(getPanelPlugin({ id: 'graph' })); }); it('should teardown / destroy panel so angular panels event subscriptions are removed', () => { @@ -116,15 +123,13 @@ describe('PanelModel', () => { describe('when changing to react panel', () => { const onPanelTypeChanged = jest.fn(); - const reactPanel = new ReactPanelPlugin({} as any).setPanelChangeHandler(onPanelTypeChanged as any); + const reactPlugin = new ReactPanelPlugin({} as any).setPanelChangeHandler(onPanelTypeChanged as any); beforeEach(() => { model.changePlugin( getPanelPlugin({ id: 'react', - exports: { - reactPanel, - }, + reactPlugin: reactPlugin, }) ); }); diff --git a/public/app/features/dashboard/state/PanelModel.ts b/public/app/features/dashboard/state/PanelModel.ts index cf713d85ff5..f43ee538779 100644 --- a/public/app/features/dashboard/state/PanelModel.ts +++ b/public/app/features/dashboard/state/PanelModel.ts @@ -252,13 +252,10 @@ export class PanelModel { pluginLoaded(plugin: PanelPlugin) { this.plugin = plugin; - const { reactPanel } = plugin.exports; - - // Call PanelMigration Handler if the version has changed - if (reactPanel && reactPanel.onPanelMigration) { + if (plugin.reactPlugin && plugin.reactPlugin.onPanelMigration) { const version = this.getPluginVersion(plugin); if (version !== this.pluginVersion) { - this.options = reactPanel.onPanelMigration(this); + this.options = plugin.reactPlugin.onPanelMigration(this); this.pluginVersion = version; } } @@ -268,10 +265,9 @@ export class PanelModel { const pluginId = newPlugin.id; const oldOptions: any = this.getOptionsToRemember(); const oldPluginId = this.type; - const reactPanel = newPlugin.exports.reactPanel; // for angular panels we must remove all events and let angular panels do some cleanup - if (this.plugin.exports.PanelCtrl) { + if (this.plugin.angularPlugin) { this.destroy(); } @@ -292,12 +288,15 @@ export class PanelModel { this.plugin = newPlugin; // Let panel plugins inspect options from previous panel and keep any that it can use + const reactPanel = newPlugin.reactPlugin; + 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); } diff --git a/public/app/features/datasources/state/navModel.ts b/public/app/features/datasources/state/navModel.ts index b0b121a9997..6e215a46c69 100644 --- a/public/app/features/datasources/state/navModel.ts +++ b/public/app/features/datasources/state/navModel.ts @@ -84,6 +84,8 @@ export function getDataSourceLoadingNav(pageName: string): NavModel { version: '', }, includes: [{ type: '', name: '', path: '' }], + module: '', + baseUrl: '', } ); diff --git a/public/app/features/explore/QueryRow.tsx b/public/app/features/explore/QueryRow.tsx index e35e62c36a1..64920cc1fb7 100644 --- a/public/app/features/explore/QueryRow.tsx +++ b/public/app/features/explore/QueryRow.tsx @@ -21,7 +21,7 @@ import { ExploreDataSourceApi, QueryHint, QueryFixAction, - DatasourceStatus, + DataSourceStatus, } from '@grafana/ui'; import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore'; import { Emitter } from 'app/core/utils/emitter'; @@ -41,7 +41,7 @@ interface QueryRowProps { className?: string; exploreId: ExploreId; datasourceInstance: ExploreDataSourceApi; - datasourceStatus: DatasourceStatus; + datasourceStatus: DataSourceStatus; highlightLogsExpressionAction: typeof highlightLogsExpressionAction; history: HistoryItem[]; index: number; @@ -115,11 +115,13 @@ export class QueryRow extends PureComponent { range, datasourceStatus, } = this.props; + const transactions = queryTransactions.filter(t => t.rowIndex === index); const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - const QueryField = datasourceInstance.pluginExports.ExploreQueryField; + const QueryField = datasourceInstance.components.ExploreQueryField; + return (
@@ -183,7 +185,7 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps) query, queryTransactions, range, - datasourceStatus: datasourceError ? DatasourceStatus.Disconnected : DatasourceStatus.Connected, + datasourceStatus: datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected, }; } diff --git a/public/app/features/explore/state/reducers.test.ts b/public/app/features/explore/state/reducers.test.ts index 27970868697..91d910db41c 100644 --- a/public/app/features/explore/state/reducers.test.ts +++ b/public/app/features/explore/state/reducers.test.ts @@ -136,7 +136,7 @@ describe('Explore item reducer', () => { logs: {}, tables: {}, }, - pluginExports: { + components: { ExploreStartPage: StartPage, }, } as DataSourceApi; diff --git a/public/app/features/explore/state/reducers.ts b/public/app/features/explore/state/reducers.ts index 3683b87ba73..441d87a47fe 100644 --- a/public/app/features/explore/state/reducers.ts +++ b/public/app/features/explore/state/reducers.ts @@ -226,8 +226,9 @@ export const itemReducer = reducerFactory({} as ExploreItemSta const supportsGraph = datasourceInstance.meta.metrics; const supportsLogs = datasourceInstance.meta.logs; const supportsTable = datasourceInstance.meta.tables; + // Custom components - const StartPage = datasourceInstance.pluginExports.ExploreStartPage; + const StartPage = datasourceInstance.components.ExploreStartPage; return { ...state, diff --git a/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts b/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts index c9037c0a62d..5a47972e316 100644 --- a/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts +++ b/public/app/features/manage-dashboards/DashboardImportCtrl.test.ts @@ -31,7 +31,7 @@ describe('DashboardImportCtrl', () => { config.datasources = { ds: { type: 'test-db', - }, + } as any, }; ctx.ctrl.onUpload({ diff --git a/public/app/features/plugins/__mocks__/pluginMocks.ts b/public/app/features/plugins/__mocks__/pluginMocks.ts index 5350aee0baa..10c38140ff8 100644 --- a/public/app/features/plugins/__mocks__/pluginMocks.ts +++ b/public/app/features/plugins/__mocks__/pluginMocks.ts @@ -56,7 +56,8 @@ export const getPanelPlugin = (options: Partial): PanelPlugin => { hideFromList: options.hideFromList === true, module: '', baseUrl: '', - exports: options.exports, + reactPlugin: options.reactPlugin, + angularPlugin: options.angularPlugin, }; }; diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index fde17ba8f48..0754bf6c592 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -4,7 +4,7 @@ import coreModule from 'app/core/core_module'; // Services & Utils import config from 'app/core/config'; -import { importPluginModule } from './plugin_loader'; +import { importDataSourcePlugin } from './plugin_loader'; // Types import { DataSourceApi, DataSourceSelectItem, ScopedVars } from '@grafana/ui/src/types'; @@ -52,25 +52,24 @@ export class DatasourceSrv { } const deferred = this.$q.defer(); - const pluginDef = dsConfig.meta; - importPluginModule(pluginDef.module) - .then(plugin => { + importDataSourcePlugin(dsConfig.meta.module) + .then(dsPlugin => { // check if its in cache now if (this.datasources[name]) { deferred.resolve(this.datasources[name]); return; } - // plugin module needs to export a constructor function named Datasource - if (!plugin.Datasource) { - throw new Error('Plugin module is missing Datasource constructor'); - } + const instance: DataSourceApi = this.$injector.instantiate(dsPlugin.DataSourceClass, { + instanceSettings: dsConfig, + }); - const instance: DataSourceApi = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig }); - instance.meta = pluginDef; instance.name = name; - instance.pluginExports = plugin; + instance.components = dsPlugin.components; + instance.meta = dsConfig.meta; + + // store in instance cache this.datasources[name] = instance; deferred.resolve(instance); }) diff --git a/public/app/features/plugins/plugin_component.ts b/public/app/features/plugins/plugin_component.ts index 23f736e0698..f208de5a9f8 100644 --- a/public/app/features/plugins/plugin_component.ts +++ b/public/app/features/plugins/plugin_component.ts @@ -3,7 +3,9 @@ import _ from 'lodash'; import config from 'app/core/config'; import coreModule from 'app/core/core_module'; -import { importPluginModule } from './plugin_loader'; + +import { AngularPanelPlugin, DataSourceApi } from '@grafana/ui/src/types'; +import { importPanelPlugin, importDataSourcePlugin, importAppPlugin } from './plugin_loader'; /** @ngInject */ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache, $timeout) { @@ -67,14 +69,9 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ }; const panelInfo = config.panels[scope.panel.type]; - let panelCtrlPromise = Promise.resolve(null); - if (panelInfo) { - panelCtrlPromise = importPluginModule(panelInfo.module).then(panelModule => { - return panelModule.PanelCtrl; - }); - } - - return panelCtrlPromise.then((PanelCtrl: any) => { + return importPanelPlugin(panelInfo.module).then(panelPlugin => { + const angularPanelPlugin = panelPlugin as AngularPanelPlugin; + const PanelCtrl = angularPanelPlugin.components.PanelCtrl; componentInfo.Component = PanelCtrl; if (!PanelCtrl || PanelCtrl.registered) { @@ -101,11 +98,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ }); } - function getModule(scope, attrs) { + function getModule(scope: any, attrs: any) { switch (attrs.type) { // QueryCtrl case 'query-ctrl': { - const ds = scope.ctrl.datasource; + const ds: DataSourceApi = scope.ctrl.datasource as DataSourceApi; + return $q.when({ baseUrl: ds.meta.baseUrl, name: 'query-ctrl-' + ds.meta.id, @@ -115,12 +113,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ 'panel-ctrl': 'ctrl', datasource: 'ctrl.datasource', }, - Component: ds.pluginExports.QueryCtrl, + Component: ds.components.QueryCtrl, }); } // Annotations case 'annotations-query-ctrl': { - return importPluginModule(scope.ctrl.currentDatasource.meta.module).then(dsModule => { + return importDataSourcePlugin(scope.ctrl.currentDatasource.meta.module).then(dsPlugin => { return { baseUrl: scope.ctrl.currentDatasource.meta.baseUrl, name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id, @@ -129,60 +127,54 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $ annotation: 'ctrl.currentAnnotation', datasource: 'ctrl.currentDatasource', }, - Component: dsModule.AnnotationsQueryCtrl, + Component: dsPlugin.components.AnnotationsQueryCtrl, }; }); } // Datasource ConfigCtrl case 'datasource-config-ctrl': { const dsMeta = scope.ctrl.datasourceMeta; - return importPluginModule(dsMeta.module).then( - (dsModule): any => { - if (!dsModule.ConfigCtrl) { - return { notFound: true }; - } + return importDataSourcePlugin(dsMeta.module).then(dsPlugin => { + scope.$watch( + 'ctrl.current', + () => { + scope.onModelChanged(scope.ctrl.current); + }, + true + ); - scope.$watch( - 'ctrl.current', - () => { - scope.onModelChanged(scope.ctrl.current); - }, - true - ); - - return { - baseUrl: dsMeta.baseUrl, - name: 'ds-config-' + dsMeta.id, - bindings: { meta: '=', current: '=' }, - attrs: { meta: 'ctrl.datasourceMeta', current: 'ctrl.current' }, - Component: dsModule.ConfigCtrl, - }; - } - ); + return { + baseUrl: dsMeta.baseUrl, + name: 'ds-config-' + dsMeta.id, + bindings: { meta: '=', current: '=' }, + attrs: { meta: 'ctrl.datasourceMeta', current: 'ctrl.current' }, + Component: dsPlugin.components.ConfigCtrl, + }; + }); } // AppConfigCtrl case 'app-config-ctrl': { const model = scope.ctrl.model; - return importPluginModule(model.module).then(appModule => { + return importAppPlugin(model.module).then(appPlugin => { return { baseUrl: model.baseUrl, name: 'app-config-' + model.id, bindings: { appModel: '=', appEditCtrl: '=' }, attrs: { 'app-model': 'ctrl.model', 'app-edit-ctrl': 'ctrl' }, - Component: appModule.ConfigCtrl, + Component: appPlugin.components.ConfigCtrl, }; }); } // App Page case 'app-page': { const appModel = scope.ctrl.appModel; - return importPluginModule(appModel.module).then(appModule => { + return importAppPlugin(appModel.module).then(appPlugin => { return { baseUrl: appModel.baseUrl, name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug, bindings: { appModel: '=' }, attrs: { 'app-model': 'ctrl.appModel' }, - Component: appModule[scope.ctrl.page.component], + Component: appPlugin.pages[scope.ctrl.page.component], }; }); } diff --git a/public/app/features/plugins/plugin_loader.ts b/public/app/features/plugins/plugin_loader.ts index 206edf0bd9d..dd7667f5ae0 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 { PluginExports } from '@grafana/ui'; +import { DataSourcePlugin, AppPlugin, ReactPanelPlugin, AngularPanelPlugin } 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'; @@ -141,7 +141,7 @@ for (const flotDep of flotDeps) { exposeToPlugin(flotDep, { fakeDep: 1 }); } -export function importPluginModule(path: string): Promise { +function importPluginModule(path: string): Promise { const builtIn = builtInPlugins[path]; if (builtIn) { return Promise.resolve(builtIn); @@ -149,6 +149,38 @@ export function importPluginModule(path: string): Promise { return System.import(path); } +export function importDataSourcePlugin(path: string): Promise { + return importPluginModule(path).then(pluginExports => { + if (pluginExports.plugin) { + return pluginExports.plugin as DataSourcePlugin; + } + + if (pluginExports.Datasource) { + const dsPlugin = new DataSourcePlugin(pluginExports.Datasource); + dsPlugin.setComponentsFromLegacyExports(pluginExports); + return dsPlugin; + } + + throw new Error('Plugin module is missing DataSourcePlugin or Datasource constructor export'); + }); +} + +export function importAppPlugin(path: string): Promise { + return importPluginModule(path).then(pluginExports => { + return new AppPlugin(pluginExports.ConfigCtrl); + }); +} + +export function importPanelPlugin(path: string): Promise { + return importPluginModule(path).then(pluginExports => { + if (pluginExports.reactPanel) { + return pluginExports.reactPanel as ReactPanelPlugin; + } else { + return new AngularPanelPlugin(pluginExports.PanelCtrl); + } + }); +} + export function loadPluginCss(options) { if (config.bootData.user.lightTheme) { System.import(options.light + '!css'); diff --git a/public/app/features/plugins/specs/datasource_srv.test.ts b/public/app/features/plugins/specs/datasource_srv.test.ts index 51b83efb3f5..322b4fcd81e 100644 --- a/public/app/features/plugins/specs/datasource_srv.test.ts +++ b/public/app/features/plugins/specs/datasource_srv.test.ts @@ -1,6 +1,7 @@ import config from 'app/core/config'; import 'app/features/plugins/datasource_srv'; import { DatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { PluginMeta } from '@grafana/ui/src/types'; // Datasource variable $datasource with current value 'BBB' const templateSrv = { @@ -22,16 +23,22 @@ describe('datasource_srv', () => { beforeEach(() => { config.datasources = { buildInDs: { + type: 'b', name: 'buildIn', - meta: { builtIn: true }, + meta: { builtIn: true } as PluginMeta, + jsonData: {}, }, nonBuildIn: { + type: 'e', name: 'external1', - meta: { builtIn: false }, + meta: { builtIn: false } as PluginMeta, + jsonData: {}, }, nonExplore: { + type: 'e2', name: 'external2', - meta: {}, + meta: {} as PluginMeta, + jsonData: {}, }, }; }); @@ -73,7 +80,7 @@ describe('datasource_srv', () => { }, }; beforeEach(() => { - config.datasources = unsortedDatasources; + config.datasources = unsortedDatasources as any; metricSources = _datasourceSrv.getMetricSources({}); config.defaultDatasource = 'BBB'; }); diff --git a/public/app/features/plugins/variableQueryEditorLoader.tsx b/public/app/features/plugins/variableQueryEditorLoader.tsx index 631161b4e9b..beb5e99898f 100644 --- a/public/app/features/plugins/variableQueryEditorLoader.tsx +++ b/public/app/features/plugins/variableQueryEditorLoader.tsx @@ -1,13 +1,13 @@ import coreModule from 'app/core/core_module'; -import { importPluginModule } from './plugin_loader'; +import { importDataSourcePlugin } from './plugin_loader'; import React from 'react'; import ReactDOM from 'react-dom'; import DefaultVariableQueryEditor from '../templating/DefaultVariableQueryEditor'; async function loadComponent(module) { - const component = await importPluginModule(module); - if (component && component.VariableQueryEditor) { - return component.VariableQueryEditor; + const dsPlugin = await importDataSourcePlugin(module); + if (dsPlugin.components.VariableQueryEditor) { + return dsPlugin.components.VariableQueryEditor; } else { return DefaultVariableQueryEditor; } diff --git a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx index dbe72c00885..6662e385e4a 100644 --- a/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx +++ b/public/app/plugins/datasource/loki/components/LokiQueryFieldForm.tsx @@ -17,10 +17,10 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; // Types import { LokiQuery } from '../types'; import { TypeaheadOutput, HistoryItem } from 'app/types/explore'; -import { ExploreDataSourceApi, ExploreQueryFieldProps, DatasourceStatus } from '@grafana/ui'; +import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus } from '@grafana/ui'; -function getChooserText(hasSyntax: boolean, hasLogLabels: boolean, datasourceStatus: DatasourceStatus) { - if (datasourceStatus === DatasourceStatus.Disconnected) { +function getChooserText(hasSyntax: boolean, hasLogLabels: boolean, datasourceStatus: DataSourceStatus) { + if (datasourceStatus === DataSourceStatus.Disconnected) { return '(Disconnected)'; } if (!hasSyntax) { @@ -169,7 +169,7 @@ export class LokiQueryFieldForm extends React.PureComponent 0; const chooserText = getChooserText(syntaxLoaded, hasLogLabels, datasourceStatus); - const buttonDisabled = !syntaxLoaded || datasourceStatus === DatasourceStatus.Disconnected; + const buttonDisabled = !syntaxLoaded || datasourceStatus === DataSourceStatus.Disconnected; return ( <> diff --git a/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts b/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts index a662b92ea0e..b86c9eafbf6 100644 --- a/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts +++ b/public/app/plugins/datasource/loki/components/useLokiLabels.test.ts @@ -1,7 +1,7 @@ import { renderHook, act } from 'react-hooks-testing-library'; import LanguageProvider from 'app/plugins/datasource/loki/language_provider'; import { useLokiLabels } from './useLokiLabels'; -import { DatasourceStatus } from '@grafana/ui/src/types/plugin'; +import { DataSourceStatus } from '@grafana/ui/src/types/datasource'; describe('useLokiLabels hook', () => { it('should refresh labels', async () => { @@ -17,7 +17,7 @@ describe('useLokiLabels hook', () => { }; const { result, waitForNextUpdate } = renderHook(() => - useLokiLabels(languageProvider, true, [], DatasourceStatus.Connected, DatasourceStatus.Connected) + useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Connected) ); act(() => result.current.refreshLabels()); expect(result.current.logLabelOptions).toEqual([]); @@ -33,7 +33,7 @@ describe('useLokiLabels hook', () => { languageProvider.refreshLogLabels = jest.fn(); renderHook(() => - useLokiLabels(languageProvider, true, [], DatasourceStatus.Connected, DatasourceStatus.Disconnected) + useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Disconnected) ); expect(languageProvider.refreshLogLabels).toBeCalledTimes(1); @@ -48,7 +48,7 @@ describe('useLokiLabels hook', () => { languageProvider.refreshLogLabels = jest.fn(); renderHook(() => - useLokiLabels(languageProvider, true, [], DatasourceStatus.Disconnected, DatasourceStatus.Connected) + useLokiLabels(languageProvider, true, [], DataSourceStatus.Disconnected, DataSourceStatus.Connected) ); expect(languageProvider.refreshLogLabels).not.toBeCalled(); diff --git a/public/app/plugins/datasource/loki/components/useLokiLabels.ts b/public/app/plugins/datasource/loki/components/useLokiLabels.ts index d76766d52dc..b73f4a3d916 100644 --- a/public/app/plugins/datasource/loki/components/useLokiLabels.ts +++ b/public/app/plugins/datasource/loki/components/useLokiLabels.ts @@ -1,5 +1,5 @@ import { useState, useEffect } from 'react'; -import { DatasourceStatus } from '@grafana/ui/src/types/plugin'; +import { DataSourceStatus } from '@grafana/ui/src/types/datasource'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm'; @@ -17,8 +17,8 @@ export const useLokiLabels = ( languageProvider: LokiLanguageProvider, languageProviderInitialised: boolean, activeOption: CascaderOption[], - datasourceStatus: DatasourceStatus, - initialDatasourceStatus?: DatasourceStatus // used for test purposes + datasourceStatus: DataSourceStatus, + initialDatasourceStatus?: DataSourceStatus // used for test purposes ) => { const mounted = useRefMounted(); @@ -26,7 +26,7 @@ export const useLokiLabels = ( const [logLabelOptions, setLogLabelOptions] = useState([]); const [shouldTryRefreshLabels, setRefreshLabels] = useState(false); const [prevDatasourceStatus, setPrevDatasourceStatus] = useState( - initialDatasourceStatus || DatasourceStatus.Connected + initialDatasourceStatus || DataSourceStatus.Connected ); const [shouldForceRefreshLabels, setForceRefreshLabels] = useState(false); @@ -84,7 +84,7 @@ export const useLokiLabels = ( // This effect is performed on datasourceStatus state change only. // We want to make sure to only force refresh AFTER a disconnected state thats why we store the previous datasourceStatus in state useEffect(() => { - if (datasourceStatus === DatasourceStatus.Connected && prevDatasourceStatus === DatasourceStatus.Disconnected) { + if (datasourceStatus === DataSourceStatus.Connected && prevDatasourceStatus === DataSourceStatus.Disconnected) { setForceRefreshLabels(true); } setPrevDatasourceStatus(datasourceStatus); diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts b/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts index 64312fc4929..bee092ecd87 100644 --- a/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts +++ b/public/app/plugins/datasource/loki/components/useLokiSyntax.test.ts @@ -1,5 +1,5 @@ import { renderHook, act } from 'react-hooks-testing-library'; -import { DatasourceStatus } from '@grafana/ui/src/types/plugin'; +import { DataSourceStatus } from '@grafana/ui/src/types/datasource'; import LanguageProvider from 'app/plugins/datasource/loki/language_provider'; import { useLokiSyntax } from './useLokiSyntax'; @@ -30,7 +30,7 @@ describe('useLokiSyntax hook', () => { }; it('should provide Loki syntax when used', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DatasourceStatus.Connected)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected)); expect(result.current.syntax).toEqual(null); await waitForNextUpdate(); @@ -39,7 +39,7 @@ describe('useLokiSyntax hook', () => { }); it('should fetch labels on first call', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DatasourceStatus.Connected)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected)); expect(result.current.isSyntaxReady).toBeFalsy(); expect(result.current.logLabelOptions).toEqual([]); @@ -50,7 +50,7 @@ describe('useLokiSyntax hook', () => { }); it('should try to fetch missing options when active option changes', async () => { - const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DatasourceStatus.Connected)); + const { result, waitForNextUpdate } = renderHook(() => useLokiSyntax(languageProvider, DataSourceStatus.Connected)); await waitForNextUpdate(); expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2); diff --git a/public/app/plugins/datasource/loki/components/useLokiSyntax.ts b/public/app/plugins/datasource/loki/components/useLokiSyntax.ts index 83baa4f0e83..a48afe19ca1 100644 --- a/public/app/plugins/datasource/loki/components/useLokiSyntax.ts +++ b/public/app/plugins/datasource/loki/components/useLokiSyntax.ts @@ -1,7 +1,7 @@ import { useState, useEffect } from 'react'; // @ts-ignore import Prism from 'prismjs'; -import { DatasourceStatus } from '@grafana/ui/src/types/plugin'; +import { DataSourceStatus } from '@grafana/ui/src/types/datasource'; import LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider'; import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels'; @@ -15,7 +15,7 @@ const PRISM_SYNTAX = 'promql'; * @param languageProvider * @description Initializes given language provider, exposes Loki syntax and enables loading label option values */ -export const useLokiSyntax = (languageProvider: LokiLanguageProvider, datasourceStatus: DatasourceStatus) => { +export const useLokiSyntax = (languageProvider: LokiLanguageProvider, datasourceStatus: DataSourceStatus) => { const mounted = useRefMounted(); // State const [languageProviderInitialized, setLanguageProviderInitilized] = useState(false); diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 45e665fcb71..9a9a3b90223 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -17,15 +17,15 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; import { PromQuery } from '../types'; import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise'; -import { ExploreDataSourceApi, ExploreQueryFieldProps, DatasourceStatus } from '@grafana/ui'; +import { ExploreDataSourceApi, ExploreQueryFieldProps, DataSourceStatus } from '@grafana/ui'; const HISTOGRAM_GROUP = '__histograms__'; const METRIC_MARK = 'metric'; const PRISM_SYNTAX = 'promql'; export const RECORDING_RULES_GROUP = '__recording_rules__'; -function getChooserText(hasSyntax: boolean, datasourceStatus: DatasourceStatus) { - if (datasourceStatus === DatasourceStatus.Disconnected) { +function getChooserText(hasSyntax: boolean, datasourceStatus: DataSourceStatus) { + if (datasourceStatus === DataSourceStatus.Disconnected) { return '(Disconnected)'; } if (!hasSyntax) { @@ -153,8 +153,8 @@ class PromQueryField extends React.PureComponent diff --git a/public/app/plugins/datasource/testdata/module.ts b/public/app/plugins/datasource/testdata/module.ts index d3b376e3307..d4cef1df16c 100644 --- a/public/app/plugins/datasource/testdata/module.ts +++ b/public/app/plugins/datasource/testdata/module.ts @@ -1,18 +1,13 @@ +import { DataSourcePlugin } from '@grafana/ui'; import { TestDataDatasource } from './datasource'; import { TestDataQueryCtrl } from './query_ctrl'; -// import { QueryEditor } from './QueryEditor'; class TestDataAnnotationsQueryCtrl { annotation: any; - constructor() {} - static template = '

Annotation scenario

'; } -export { - // QueryEditor, - TestDataDatasource as Datasource, - TestDataQueryCtrl as QueryCtrl, - TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl, -}; +export const plugin = new DataSourcePlugin(TestDataDatasource) + .setQueryCtrl(TestDataQueryCtrl) + .setAnnotationQueryCtrl(TestDataAnnotationsQueryCtrl); diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 86ad0799e11..d90bf28332a 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -1,4 +1,4 @@ -import { PluginExports, PluginMetaInfo } from '@grafana/ui/src/types'; +import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo } from '@grafana/ui/src/types'; export interface PanelPlugin { id: string; @@ -8,7 +8,9 @@ export interface PanelPlugin { baseUrl: string; info: PluginMetaInfo; sort: number; - exports?: PluginExports; + angularPlugin: AngularPanelPlugin | null; + reactPlugin: ReactPanelPlugin | null; + hasBeenImported?: boolean; dataFormats: PanelDataFormat[]; } diff --git a/scripts/ci-frontend-metrics.sh b/scripts/ci-frontend-metrics.sh index 24f5b245128..94f99f48369 100755 --- a/scripts/ci-frontend-metrics.sh +++ b/scripts/ci-frontend-metrics.sh @@ -4,7 +4,7 @@ echo -e "Collecting code stats (typescript errors & more)" ERROR_COUNT_LIMIT=6843 DIRECTIVES_LIMIT=173 -CONTROLLERS_LIMIT=136 +CONTROLLERS_LIMIT=137 ERROR_COUNT="$(./node_modules/.bin/tsc --project tsconfig.json --noEmit --noImplicitAny true | grep -oP 'Found \K(\d+)')" DIRECTIVES="$(grep -r -o directive public/app/**/* | wc -l)"