mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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
This commit is contained in:
parent
e04990752c
commit
47e51cb6b3
@ -1,7 +1,159 @@
|
|||||||
|
import { ComponentClass } from 'react';
|
||||||
import { TimeRange, RawTimeRange } from './time';
|
import { TimeRange, RawTimeRange } from './time';
|
||||||
import { PluginMeta } from './plugin';
|
import { PluginMeta } from './plugin';
|
||||||
import { TableData, TimeSeries, SeriesData } from './data';
|
import { TableData, TimeSeries, SeriesData } from './data';
|
||||||
|
|
||||||
|
export class DataSourcePlugin<TQuery extends DataQuery = DataQuery> {
|
||||||
|
DataSourceClass: DataSourceConstructor<TQuery>;
|
||||||
|
components: DataSourcePluginComponents<TQuery>;
|
||||||
|
|
||||||
|
constructor(DataSourceClass: DataSourceConstructor<TQuery>) {
|
||||||
|
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<QueryEditorProps<DataSourceApi, TQuery>>) {
|
||||||
|
this.components.QueryEditor = QueryEditor;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExploreQueryField(ExploreQueryField: ComponentClass<ExploreQueryFieldProps<DataSourceApi, TQuery>>) {
|
||||||
|
this.components.ExploreQueryField = ExploreQueryField;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
setExploreStartPage(ExploreStartPage: ComponentClass<ExploreStartPageProps>) {
|
||||||
|
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<TQuery extends DataQuery = DataQuery> {
|
||||||
|
QueryCtrl?: any;
|
||||||
|
ConfigCtrl?: any;
|
||||||
|
AnnotationsQueryCtrl?: any;
|
||||||
|
VariableQueryEditor?: any;
|
||||||
|
QueryEditor?: ComponentClass<QueryEditorProps<DataSourceApi, TQuery>>;
|
||||||
|
ExploreQueryField?: ComponentClass<ExploreQueryFieldProps<DataSourceApi, TQuery>>;
|
||||||
|
ExploreStartPage?: ComponentClass<ExploreStartPageProps>;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface DataSourceConstructor<TQuery extends DataQuery = DataQuery> {
|
||||||
|
new (instanceSettings: DataSourceInstanceSettings, ...args: any[]): DataSourceApi<TQuery>;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The main data source abstraction interface, represents an instance of a data source
|
||||||
|
*/
|
||||||
|
export interface DataSourceApi<TQuery extends DataQuery = DataQuery> {
|
||||||
|
/**
|
||||||
|
* min interval range
|
||||||
|
*/
|
||||||
|
interval?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Imports queries from a different datasource
|
||||||
|
*/
|
||||||
|
importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise<TQuery[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes a datasource after instantiation
|
||||||
|
*/
|
||||||
|
init?: () => void;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Main metrics / data query action
|
||||||
|
*/
|
||||||
|
query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test & verify datasource settings & connection details
|
||||||
|
*/
|
||||||
|
testDatasource(): Promise<any>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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<TQuery extends DataQuery = DataQuery> extends DataSourceApi {
|
||||||
|
modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
|
||||||
|
getHighlighterExpression?(query: TQuery): string;
|
||||||
|
languageProvider?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||||
|
datasource: DSType;
|
||||||
|
query: TQuery;
|
||||||
|
onRunQuery: () => void;
|
||||||
|
onChange: (value: TQuery) => void;
|
||||||
|
queryResponse?: SeriesData[];
|
||||||
|
queryError?: DataQueryError;
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum DataSourceStatus {
|
||||||
|
Connected,
|
||||||
|
Disconnected,
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ExploreQueryFieldProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
||||||
|
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
|
* Starting in v6.2 SeriesData can represent both TimeSeries and TableData
|
||||||
*/
|
*/
|
||||||
@ -89,6 +241,9 @@ export interface QueryHint {
|
|||||||
fix?: QueryFix;
|
fix?: QueryFix;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data Source instance edit model
|
||||||
|
*/
|
||||||
export interface DataSourceSettings {
|
export interface DataSourceSettings {
|
||||||
id: number;
|
id: number;
|
||||||
orgId: number;
|
orgId: number;
|
||||||
@ -109,6 +264,29 @@ export interface DataSourceSettings {
|
|||||||
withCredentials: boolean;
|
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 {
|
export interface DataSourceSelectItem {
|
||||||
name: string;
|
name: string;
|
||||||
value: string | null;
|
value: string | null;
|
||||||
|
@ -83,6 +83,16 @@ export class ReactPanelPlugin<TOptions = any> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AngularPanelPlugin {
|
||||||
|
components: {
|
||||||
|
PanelCtrl: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
constructor(PanelCtrl: any) {
|
||||||
|
this.components = { PanelCtrl: PanelCtrl };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export interface PanelSize {
|
export interface PanelSize {
|
||||||
width: number;
|
width: number;
|
||||||
height: number;
|
height: number;
|
||||||
|
@ -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<TQuery extends DataQuery = DataQuery> {
|
|
||||||
/**
|
|
||||||
* min interval range
|
|
||||||
*/
|
|
||||||
interval?: string;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Imports queries from a different datasource
|
|
||||||
*/
|
|
||||||
importQueries?(queries: TQuery[], originMeta: PluginMeta): Promise<TQuery[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes a datasource after instantiation
|
|
||||||
*/
|
|
||||||
init?: () => void;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Main metrics / data query action
|
|
||||||
*/
|
|
||||||
query(options: DataQueryOptions<TQuery>): Promise<DataQueryResponse>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Test & verify datasource settings & connection details
|
|
||||||
*/
|
|
||||||
testDatasource(): Promise<any>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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<TQuery extends DataQuery = DataQuery> extends DataSourceApi {
|
|
||||||
modifyQuery?(query: TQuery, action: QueryFixAction): TQuery;
|
|
||||||
getHighlighterExpression?(query: TQuery): string;
|
|
||||||
languageProvider?: any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface QueryEditorProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
|
||||||
datasource: DSType;
|
|
||||||
query: TQuery;
|
|
||||||
onRunQuery: () => void;
|
|
||||||
onChange: (value: TQuery) => void;
|
|
||||||
queryResponse?: SeriesData[];
|
|
||||||
queryError?: DataQueryError;
|
|
||||||
}
|
|
||||||
|
|
||||||
export enum DatasourceStatus {
|
|
||||||
Connected,
|
|
||||||
Disconnected,
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ExploreQueryFieldProps<DSType extends DataSourceApi, TQuery extends DataQuery> {
|
|
||||||
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<QueryEditorProps<DataSourceApi, DataQuery>>;
|
|
||||||
ConfigCtrl?: any;
|
|
||||||
AnnotationsQueryCtrl?: any;
|
|
||||||
VariableQueryEditor?: any;
|
|
||||||
ExploreQueryField?: ComponentClass<ExploreQueryFieldProps<DataSourceApi, DataQuery>>;
|
|
||||||
ExploreStartPage?: ComponentClass<ExploreStartPageProps>;
|
|
||||||
|
|
||||||
// Panel plugin
|
|
||||||
PanelCtrl?: any;
|
|
||||||
reactPanel?: ReactPanelPlugin;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PluginMeta {
|
export interface PluginMeta {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
info: PluginMetaInfo;
|
info: PluginMetaInfo;
|
||||||
includes: PluginInclude[];
|
includes: PluginInclude[];
|
||||||
|
module: string;
|
||||||
|
baseUrl: string;
|
||||||
|
|
||||||
// Datasource-specific
|
// Datasource-specific
|
||||||
builtIn?: boolean;
|
builtIn?: boolean;
|
||||||
@ -150,3 +50,18 @@ export interface PluginMetaInfo {
|
|||||||
updated: string;
|
updated: string;
|
||||||
version: string;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class AppPlugin {
|
||||||
|
components: {
|
||||||
|
ConfigCtrl?: any;
|
||||||
|
};
|
||||||
|
|
||||||
|
pages: { [str: string]: any };
|
||||||
|
|
||||||
|
constructor(ConfigCtrl: any) {
|
||||||
|
this.components = {
|
||||||
|
ConfigCtrl: ConfigCtrl,
|
||||||
|
};
|
||||||
|
this.pages = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
import { PanelPlugin } from 'app/types/plugins';
|
import { PanelPlugin } from 'app/types/plugins';
|
||||||
import { GrafanaTheme, getTheme, GrafanaThemeType } from '@grafana/ui';
|
import { GrafanaTheme, getTheme, GrafanaThemeType, DataSourceInstanceSettings } from '@grafana/ui';
|
||||||
|
|
||||||
export interface BuildInfo {
|
export interface BuildInfo {
|
||||||
version: string;
|
version: string;
|
||||||
@ -12,7 +12,7 @@ export interface BuildInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Settings {
|
export class Settings {
|
||||||
datasources: any;
|
datasources: { [str: string]: DataSourceInstanceSettings };
|
||||||
panels: { [key: string]: PanelPlugin };
|
panels: { [key: string]: PanelPlugin };
|
||||||
appSubUrl: string;
|
appSubUrl: string;
|
||||||
windowTitlePrefix: string;
|
windowTitlePrefix: string;
|
||||||
|
@ -1,19 +1,24 @@
|
|||||||
|
// Libraries
|
||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import classNames from 'classnames';
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
// Utils & Services
|
||||||
import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader';
|
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 { AddPanelWidget } from '../components/AddPanelWidget';
|
||||||
import { getPanelPluginNotFound } from './PanelPluginNotFound';
|
import { getPanelPluginNotFound } from './PanelPluginNotFound';
|
||||||
import { DashboardRow } from '../components/DashboardRow';
|
import { DashboardRow } from '../components/DashboardRow';
|
||||||
import { PanelChrome } from './PanelChrome';
|
import { PanelChrome } from './PanelChrome';
|
||||||
import { PanelEditor } from '../panel_editor/PanelEditor';
|
import { PanelEditor } from '../panel_editor/PanelEditor';
|
||||||
|
import { PanelResizer } from './PanelResizer';
|
||||||
|
|
||||||
|
// Types
|
||||||
import { PanelModel, DashboardModel } from '../state';
|
import { PanelModel, DashboardModel } from '../state';
|
||||||
import { PanelPlugin } from 'app/types';
|
import { PanelPlugin } from 'app/types';
|
||||||
import { PanelResizer } from './PanelResizer';
|
import { AngularPanelPlugin, ReactPanelPlugin } from '@grafana/ui/src/types/panel';
|
||||||
|
|
||||||
export interface Props {
|
export interface Props {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
@ -73,13 +78,8 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
|||||||
// unmount angular panel
|
// unmount angular panel
|
||||||
this.cleanUpAngularPanel();
|
this.cleanUpAngularPanel();
|
||||||
|
|
||||||
if (!plugin.exports) {
|
// load the actual plugin code
|
||||||
try {
|
plugin = await this.importPanelPluginModule(plugin);
|
||||||
plugin.exports = await importPluginModule(plugin.module);
|
|
||||||
} catch (e) {
|
|
||||||
plugin = getPanelPluginNotFound(pluginId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (panel.type !== pluginId) {
|
if (panel.type !== pluginId) {
|
||||||
panel.changePlugin(plugin);
|
panel.changePlugin(plugin);
|
||||||
@ -91,6 +91,27 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async importPanelPluginModule(plugin: PanelPlugin): Promise<PanelPlugin> {
|
||||||
|
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() {
|
componentDidMount() {
|
||||||
this.loadPlugin(this.props.panel.type);
|
this.loadPlugin(this.props.panel.type);
|
||||||
}
|
}
|
||||||
@ -155,7 +176,7 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// if we have not loaded plugin exports yet, wait
|
// if we have not loaded plugin exports yet, wait
|
||||||
if (!plugin || !plugin.exports) {
|
if (!plugin || !plugin.hasBeenImported) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -178,8 +199,8 @@ export class DashboardPanel extends PureComponent<Props, State> {
|
|||||||
onMouseLeave={this.onMouseLeave}
|
onMouseLeave={this.onMouseLeave}
|
||||||
style={styles}
|
style={styles}
|
||||||
>
|
>
|
||||||
{plugin.exports.reactPanel && this.renderReactPanel()}
|
{plugin.reactPlugin && this.renderReactPanel()}
|
||||||
{plugin.exports.PanelCtrl && this.renderAngularPanel()}
|
{plugin.angularPlugin && this.renderAngularPanel()}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
@ -158,7 +158,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
renderPanelPlugin(loading: LoadingState, data: SeriesData[], width: number, height: number): JSX.Element {
|
renderPanelPlugin(loading: LoadingState, data: SeriesData[], width: number, height: number): JSX.Element {
|
||||||
const { panel, plugin } = this.props;
|
const { panel, plugin } = this.props;
|
||||||
const { timeRange, renderCounter } = this.state;
|
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
|
// This is only done to increase a counter that is used by backend
|
||||||
// image rendering (phantomjs/headless chrome) to know when to capture image
|
// image rendering (phantomjs/headless chrome) to know when to capture image
|
||||||
@ -172,7 +172,7 @@ export class PanelChrome extends PureComponent<Props, State> {
|
|||||||
loading={loading}
|
loading={loading}
|
||||||
data={data}
|
data={data}
|
||||||
timeRange={timeRange}
|
timeRange={timeRange}
|
||||||
options={panel.getOptions(plugin.exports.reactPanel.defaults)}
|
options={panel.getOptions(plugin.reactPlugin.defaults)}
|
||||||
width={width - 2 * config.theme.panelPadding.horizontal}
|
width={width - 2 * config.theme.panelPadding.horizontal}
|
||||||
height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical}
|
height={height - PANEL_HEADER_HEIGHT - config.theme.panelPadding.vertical}
|
||||||
renderCounter={renderCounter}
|
renderCounter={renderCounter}
|
||||||
|
@ -62,9 +62,7 @@ export function getPanelPluginNotFound(id: string): PanelPlugin {
|
|||||||
updated: '',
|
updated: '',
|
||||||
version: '',
|
version: '',
|
||||||
},
|
},
|
||||||
|
reactPlugin: new ReactPanelPlugin(NotFound),
|
||||||
exports: {
|
angularPlugin: null,
|
||||||
reactPanel: new ReactPanelPlugin(NotFound),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -176,12 +176,13 @@ export class QueryEditorRow extends PureComponent<Props, State> {
|
|||||||
const { query, onChange } = this.props;
|
const { query, onChange } = this.props;
|
||||||
const { datasource, queryResponse, queryError } = this.state;
|
const { datasource, queryResponse, queryError } = this.state;
|
||||||
|
|
||||||
if (datasource.pluginExports.QueryCtrl) {
|
if (datasource.components.QueryCtrl) {
|
||||||
return <div ref={element => (this.element = element)} />;
|
return <div ref={element => (this.element = element)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (datasource.pluginExports.QueryEditor) {
|
if (datasource.components.QueryEditor) {
|
||||||
const QueryEditor = datasource.pluginExports.QueryEditor;
|
const QueryEditor = datasource.components.QueryEditor;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryEditor
|
<QueryEditor
|
||||||
query={query}
|
query={query}
|
||||||
|
@ -53,7 +53,7 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
|
|
||||||
getReactPanelOptions = () => {
|
getReactPanelOptions = () => {
|
||||||
const { panel, plugin } = this.props;
|
const { panel, plugin } = this.props;
|
||||||
return panel.getOptions(plugin.exports.reactPanel.defaults);
|
return panel.getOptions(plugin.reactPlugin.defaults);
|
||||||
};
|
};
|
||||||
|
|
||||||
renderPanelOptions() {
|
renderPanelOptions() {
|
||||||
@ -63,8 +63,8 @@ export class VisualizationTab extends PureComponent<Props, State> {
|
|||||||
return <div ref={element => (this.element = element)} />;
|
return <div ref={element => (this.element = element)} />;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (plugin.exports.reactPanel) {
|
if (plugin.reactPlugin) {
|
||||||
const PanelEditor = plugin.exports.reactPanel.editor;
|
const PanelEditor = plugin.reactPlugin.editor;
|
||||||
|
|
||||||
if (PanelEditor) {
|
if (PanelEditor) {
|
||||||
return <PanelEditor options={this.getReactPanelOptions()} onOptionsChange={this.onPanelOptionsChanged} />;
|
return <PanelEditor options={this.getReactPanelOptions()} onOptionsChange={this.onPanelOptionsChanged} />;
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import { PanelModel } from './PanelModel';
|
import { PanelModel } from './PanelModel';
|
||||||
import { getPanelPlugin } from '../../plugins/__mocks__/pluginMocks';
|
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('PanelModel', () => {
|
||||||
describe('when creating new panel model', () => {
|
describe('when creating new panel model', () => {
|
||||||
@ -28,7 +30,12 @@ describe('PanelModel', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
model = new PanelModel(modelJson);
|
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', () => {
|
it('should apply defaults', () => {
|
||||||
@ -79,7 +86,7 @@ describe('PanelModel', () => {
|
|||||||
|
|
||||||
describe('when changing panel type', () => {
|
describe('when changing panel type', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
model.changePlugin(getPanelPlugin({ id: 'graph', exports: {} }));
|
model.changePlugin(getPanelPlugin({ id: 'graph' }));
|
||||||
model.alert = { id: 2 };
|
model.alert = { id: 2 };
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -88,12 +95,12 @@ describe('PanelModel', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should restore table properties when changing back', () => {
|
it('should restore table properties when changing back', () => {
|
||||||
model.changePlugin(getPanelPlugin({ id: 'table', exports: {} }));
|
model.changePlugin(getPanelPlugin({ id: 'table' }));
|
||||||
expect(model.showColumns).toBe(true);
|
expect(model.showColumns).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should remove alert rule when changing type that does not support it', () => {
|
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);
|
expect(model.alert).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -105,7 +112,7 @@ describe('PanelModel', () => {
|
|||||||
model.events.on('panel-teardown', () => {
|
model.events.on('panel-teardown', () => {
|
||||||
tearDownPublished = true;
|
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', () => {
|
it('should teardown / destroy panel so angular panels event subscriptions are removed', () => {
|
||||||
@ -116,15 +123,13 @@ describe('PanelModel', () => {
|
|||||||
|
|
||||||
describe('when changing to react panel', () => {
|
describe('when changing to react panel', () => {
|
||||||
const onPanelTypeChanged = jest.fn();
|
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(() => {
|
beforeEach(() => {
|
||||||
model.changePlugin(
|
model.changePlugin(
|
||||||
getPanelPlugin({
|
getPanelPlugin({
|
||||||
id: 'react',
|
id: 'react',
|
||||||
exports: {
|
reactPlugin: reactPlugin,
|
||||||
reactPanel,
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -252,13 +252,10 @@ export class PanelModel {
|
|||||||
pluginLoaded(plugin: PanelPlugin) {
|
pluginLoaded(plugin: PanelPlugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
|
|
||||||
const { reactPanel } = plugin.exports;
|
if (plugin.reactPlugin && plugin.reactPlugin.onPanelMigration) {
|
||||||
|
|
||||||
// Call PanelMigration Handler if the version has changed
|
|
||||||
if (reactPanel && reactPanel.onPanelMigration) {
|
|
||||||
const version = this.getPluginVersion(plugin);
|
const version = this.getPluginVersion(plugin);
|
||||||
if (version !== this.pluginVersion) {
|
if (version !== this.pluginVersion) {
|
||||||
this.options = reactPanel.onPanelMigration(this);
|
this.options = plugin.reactPlugin.onPanelMigration(this);
|
||||||
this.pluginVersion = version;
|
this.pluginVersion = version;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,10 +265,9 @@ export class PanelModel {
|
|||||||
const pluginId = newPlugin.id;
|
const pluginId = newPlugin.id;
|
||||||
const oldOptions: any = this.getOptionsToRemember();
|
const oldOptions: any = this.getOptionsToRemember();
|
||||||
const oldPluginId = this.type;
|
const oldPluginId = this.type;
|
||||||
const reactPanel = newPlugin.exports.reactPanel;
|
|
||||||
|
|
||||||
// for angular panels we must remove all events and let angular panels do some cleanup
|
// 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();
|
this.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,12 +288,15 @@ export class PanelModel {
|
|||||||
this.plugin = newPlugin;
|
this.plugin = newPlugin;
|
||||||
|
|
||||||
// Let panel plugins inspect options from previous panel and keep any that it can use
|
// Let panel plugins inspect options from previous panel and keep any that it can use
|
||||||
|
const reactPanel = newPlugin.reactPlugin;
|
||||||
|
|
||||||
if (reactPanel) {
|
if (reactPanel) {
|
||||||
if (reactPanel.onPanelTypeChanged) {
|
if (reactPanel.onPanelTypeChanged) {
|
||||||
this.options = this.options || {};
|
this.options = this.options || {};
|
||||||
const old = oldOptions && oldOptions.options ? oldOptions.options : {};
|
const old = oldOptions && oldOptions.options ? oldOptions.options : {};
|
||||||
Object.assign(this.options, reactPanel.onPanelTypeChanged(this.options, oldPluginId, old));
|
Object.assign(this.options, reactPanel.onPanelTypeChanged(this.options, oldPluginId, old));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (reactPanel.onPanelMigration) {
|
if (reactPanel.onPanelMigration) {
|
||||||
this.pluginVersion = this.getPluginVersion(newPlugin);
|
this.pluginVersion = this.getPluginVersion(newPlugin);
|
||||||
}
|
}
|
||||||
|
@ -84,6 +84,8 @@ export function getDataSourceLoadingNav(pageName: string): NavModel {
|
|||||||
version: '',
|
version: '',
|
||||||
},
|
},
|
||||||
includes: [{ type: '', name: '', path: '' }],
|
includes: [{ type: '', name: '', path: '' }],
|
||||||
|
module: '',
|
||||||
|
baseUrl: '',
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -21,7 +21,7 @@ import {
|
|||||||
ExploreDataSourceApi,
|
ExploreDataSourceApi,
|
||||||
QueryHint,
|
QueryHint,
|
||||||
QueryFixAction,
|
QueryFixAction,
|
||||||
DatasourceStatus,
|
DataSourceStatus,
|
||||||
} from '@grafana/ui';
|
} from '@grafana/ui';
|
||||||
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
import { QueryTransaction, HistoryItem, ExploreItemState, ExploreId } from 'app/types/explore';
|
||||||
import { Emitter } from 'app/core/utils/emitter';
|
import { Emitter } from 'app/core/utils/emitter';
|
||||||
@ -41,7 +41,7 @@ interface QueryRowProps {
|
|||||||
className?: string;
|
className?: string;
|
||||||
exploreId: ExploreId;
|
exploreId: ExploreId;
|
||||||
datasourceInstance: ExploreDataSourceApi;
|
datasourceInstance: ExploreDataSourceApi;
|
||||||
datasourceStatus: DatasourceStatus;
|
datasourceStatus: DataSourceStatus;
|
||||||
highlightLogsExpressionAction: typeof highlightLogsExpressionAction;
|
highlightLogsExpressionAction: typeof highlightLogsExpressionAction;
|
||||||
history: HistoryItem[];
|
history: HistoryItem[];
|
||||||
index: number;
|
index: number;
|
||||||
@ -115,11 +115,13 @@ export class QueryRow extends PureComponent<QueryRowProps> {
|
|||||||
range,
|
range,
|
||||||
datasourceStatus,
|
datasourceStatus,
|
||||||
} = this.props;
|
} = this.props;
|
||||||
|
|
||||||
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
const transactions = queryTransactions.filter(t => t.rowIndex === index);
|
||||||
const transactionWithError = transactions.find(t => t.error !== undefined);
|
const transactionWithError = transactions.find(t => t.error !== undefined);
|
||||||
const hint = getFirstHintFromTransactions(transactions);
|
const hint = getFirstHintFromTransactions(transactions);
|
||||||
const queryError = transactionWithError ? transactionWithError.error : null;
|
const queryError = transactionWithError ? transactionWithError.error : null;
|
||||||
const QueryField = datasourceInstance.pluginExports.ExploreQueryField;
|
const QueryField = datasourceInstance.components.ExploreQueryField;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="query-row">
|
<div className="query-row">
|
||||||
<div className="query-row-status">
|
<div className="query-row-status">
|
||||||
@ -183,7 +185,7 @@ function mapStateToProps(state: StoreState, { exploreId, index }: QueryRowProps)
|
|||||||
query,
|
query,
|
||||||
queryTransactions,
|
queryTransactions,
|
||||||
range,
|
range,
|
||||||
datasourceStatus: datasourceError ? DatasourceStatus.Disconnected : DatasourceStatus.Connected,
|
datasourceStatus: datasourceError ? DataSourceStatus.Disconnected : DataSourceStatus.Connected,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,7 +136,7 @@ describe('Explore item reducer', () => {
|
|||||||
logs: {},
|
logs: {},
|
||||||
tables: {},
|
tables: {},
|
||||||
},
|
},
|
||||||
pluginExports: {
|
components: {
|
||||||
ExploreStartPage: StartPage,
|
ExploreStartPage: StartPage,
|
||||||
},
|
},
|
||||||
} as DataSourceApi;
|
} as DataSourceApi;
|
||||||
|
@ -226,8 +226,9 @@ export const itemReducer = reducerFactory<ExploreItemState>({} as ExploreItemSta
|
|||||||
const supportsGraph = datasourceInstance.meta.metrics;
|
const supportsGraph = datasourceInstance.meta.metrics;
|
||||||
const supportsLogs = datasourceInstance.meta.logs;
|
const supportsLogs = datasourceInstance.meta.logs;
|
||||||
const supportsTable = datasourceInstance.meta.tables;
|
const supportsTable = datasourceInstance.meta.tables;
|
||||||
|
|
||||||
// Custom components
|
// Custom components
|
||||||
const StartPage = datasourceInstance.pluginExports.ExploreStartPage;
|
const StartPage = datasourceInstance.components.ExploreStartPage;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
@ -31,7 +31,7 @@ describe('DashboardImportCtrl', () => {
|
|||||||
config.datasources = {
|
config.datasources = {
|
||||||
ds: {
|
ds: {
|
||||||
type: 'test-db',
|
type: 'test-db',
|
||||||
},
|
} as any,
|
||||||
};
|
};
|
||||||
|
|
||||||
ctx.ctrl.onUpload({
|
ctx.ctrl.onUpload({
|
||||||
|
@ -56,7 +56,8 @@ export const getPanelPlugin = (options: Partial<PanelPlugin>): PanelPlugin => {
|
|||||||
hideFromList: options.hideFromList === true,
|
hideFromList: options.hideFromList === true,
|
||||||
module: '',
|
module: '',
|
||||||
baseUrl: '',
|
baseUrl: '',
|
||||||
exports: options.exports,
|
reactPlugin: options.reactPlugin,
|
||||||
|
angularPlugin: options.angularPlugin,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ import coreModule from 'app/core/core_module';
|
|||||||
|
|
||||||
// Services & Utils
|
// Services & Utils
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import { importPluginModule } from './plugin_loader';
|
import { importDataSourcePlugin } from './plugin_loader';
|
||||||
|
|
||||||
// Types
|
// Types
|
||||||
import { DataSourceApi, DataSourceSelectItem, ScopedVars } from '@grafana/ui/src/types';
|
import { DataSourceApi, DataSourceSelectItem, ScopedVars } from '@grafana/ui/src/types';
|
||||||
@ -52,25 +52,24 @@ export class DatasourceSrv {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deferred = this.$q.defer();
|
const deferred = this.$q.defer();
|
||||||
const pluginDef = dsConfig.meta;
|
|
||||||
|
|
||||||
importPluginModule(pluginDef.module)
|
importDataSourcePlugin(dsConfig.meta.module)
|
||||||
.then(plugin => {
|
.then(dsPlugin => {
|
||||||
// check if its in cache now
|
// check if its in cache now
|
||||||
if (this.datasources[name]) {
|
if (this.datasources[name]) {
|
||||||
deferred.resolve(this.datasources[name]);
|
deferred.resolve(this.datasources[name]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// plugin module needs to export a constructor function named Datasource
|
const instance: DataSourceApi = this.$injector.instantiate(dsPlugin.DataSourceClass, {
|
||||||
if (!plugin.Datasource) {
|
instanceSettings: dsConfig,
|
||||||
throw new Error('Plugin module is missing Datasource constructor');
|
});
|
||||||
}
|
|
||||||
|
|
||||||
const instance: DataSourceApi = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig });
|
|
||||||
instance.meta = pluginDef;
|
|
||||||
instance.name = name;
|
instance.name = name;
|
||||||
instance.pluginExports = plugin;
|
instance.components = dsPlugin.components;
|
||||||
|
instance.meta = dsConfig.meta;
|
||||||
|
|
||||||
|
// store in instance cache
|
||||||
this.datasources[name] = instance;
|
this.datasources[name] = instance;
|
||||||
deferred.resolve(instance);
|
deferred.resolve(instance);
|
||||||
})
|
})
|
||||||
|
@ -3,7 +3,9 @@ import _ from 'lodash';
|
|||||||
|
|
||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import coreModule from 'app/core/core_module';
|
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 */
|
/** @ngInject */
|
||||||
function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $templateCache, $timeout) {
|
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];
|
const panelInfo = config.panels[scope.panel.type];
|
||||||
let panelCtrlPromise = Promise.resolve(null);
|
return importPanelPlugin(panelInfo.module).then(panelPlugin => {
|
||||||
if (panelInfo) {
|
const angularPanelPlugin = panelPlugin as AngularPanelPlugin;
|
||||||
panelCtrlPromise = importPluginModule(panelInfo.module).then(panelModule => {
|
const PanelCtrl = angularPanelPlugin.components.PanelCtrl;
|
||||||
return panelModule.PanelCtrl;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return panelCtrlPromise.then((PanelCtrl: any) => {
|
|
||||||
componentInfo.Component = PanelCtrl;
|
componentInfo.Component = PanelCtrl;
|
||||||
|
|
||||||
if (!PanelCtrl || PanelCtrl.registered) {
|
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) {
|
switch (attrs.type) {
|
||||||
// QueryCtrl
|
// QueryCtrl
|
||||||
case 'query-ctrl': {
|
case 'query-ctrl': {
|
||||||
const ds = scope.ctrl.datasource;
|
const ds: DataSourceApi = scope.ctrl.datasource as DataSourceApi;
|
||||||
|
|
||||||
return $q.when({
|
return $q.when({
|
||||||
baseUrl: ds.meta.baseUrl,
|
baseUrl: ds.meta.baseUrl,
|
||||||
name: 'query-ctrl-' + ds.meta.id,
|
name: 'query-ctrl-' + ds.meta.id,
|
||||||
@ -115,12 +113,12 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
'panel-ctrl': 'ctrl',
|
'panel-ctrl': 'ctrl',
|
||||||
datasource: 'ctrl.datasource',
|
datasource: 'ctrl.datasource',
|
||||||
},
|
},
|
||||||
Component: ds.pluginExports.QueryCtrl,
|
Component: ds.components.QueryCtrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Annotations
|
// Annotations
|
||||||
case 'annotations-query-ctrl': {
|
case 'annotations-query-ctrl': {
|
||||||
return importPluginModule(scope.ctrl.currentDatasource.meta.module).then(dsModule => {
|
return importDataSourcePlugin(scope.ctrl.currentDatasource.meta.module).then(dsPlugin => {
|
||||||
return {
|
return {
|
||||||
baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
|
baseUrl: scope.ctrl.currentDatasource.meta.baseUrl,
|
||||||
name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
|
name: 'annotations-query-ctrl-' + scope.ctrl.currentDatasource.meta.id,
|
||||||
@ -129,60 +127,54 @@ function pluginDirectiveLoader($compile, datasourceSrv, $rootScope, $q, $http, $
|
|||||||
annotation: 'ctrl.currentAnnotation',
|
annotation: 'ctrl.currentAnnotation',
|
||||||
datasource: 'ctrl.currentDatasource',
|
datasource: 'ctrl.currentDatasource',
|
||||||
},
|
},
|
||||||
Component: dsModule.AnnotationsQueryCtrl,
|
Component: dsPlugin.components.AnnotationsQueryCtrl,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// Datasource ConfigCtrl
|
// Datasource ConfigCtrl
|
||||||
case 'datasource-config-ctrl': {
|
case 'datasource-config-ctrl': {
|
||||||
const dsMeta = scope.ctrl.datasourceMeta;
|
const dsMeta = scope.ctrl.datasourceMeta;
|
||||||
return importPluginModule(dsMeta.module).then(
|
return importDataSourcePlugin(dsMeta.module).then(dsPlugin => {
|
||||||
(dsModule): any => {
|
scope.$watch(
|
||||||
if (!dsModule.ConfigCtrl) {
|
'ctrl.current',
|
||||||
return { notFound: true };
|
() => {
|
||||||
}
|
scope.onModelChanged(scope.ctrl.current);
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
|
||||||
scope.$watch(
|
return {
|
||||||
'ctrl.current',
|
baseUrl: dsMeta.baseUrl,
|
||||||
() => {
|
name: 'ds-config-' + dsMeta.id,
|
||||||
scope.onModelChanged(scope.ctrl.current);
|
bindings: { meta: '=', current: '=' },
|
||||||
},
|
attrs: { meta: 'ctrl.datasourceMeta', current: 'ctrl.current' },
|
||||||
true
|
Component: dsPlugin.components.ConfigCtrl,
|
||||||
);
|
};
|
||||||
|
});
|
||||||
return {
|
|
||||||
baseUrl: dsMeta.baseUrl,
|
|
||||||
name: 'ds-config-' + dsMeta.id,
|
|
||||||
bindings: { meta: '=', current: '=' },
|
|
||||||
attrs: { meta: 'ctrl.datasourceMeta', current: 'ctrl.current' },
|
|
||||||
Component: dsModule.ConfigCtrl,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
// AppConfigCtrl
|
// AppConfigCtrl
|
||||||
case 'app-config-ctrl': {
|
case 'app-config-ctrl': {
|
||||||
const model = scope.ctrl.model;
|
const model = scope.ctrl.model;
|
||||||
return importPluginModule(model.module).then(appModule => {
|
return importAppPlugin(model.module).then(appPlugin => {
|
||||||
return {
|
return {
|
||||||
baseUrl: model.baseUrl,
|
baseUrl: model.baseUrl,
|
||||||
name: 'app-config-' + model.id,
|
name: 'app-config-' + model.id,
|
||||||
bindings: { appModel: '=', appEditCtrl: '=' },
|
bindings: { appModel: '=', appEditCtrl: '=' },
|
||||||
attrs: { 'app-model': 'ctrl.model', 'app-edit-ctrl': 'ctrl' },
|
attrs: { 'app-model': 'ctrl.model', 'app-edit-ctrl': 'ctrl' },
|
||||||
Component: appModule.ConfigCtrl,
|
Component: appPlugin.components.ConfigCtrl,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
// App Page
|
// App Page
|
||||||
case 'app-page': {
|
case 'app-page': {
|
||||||
const appModel = scope.ctrl.appModel;
|
const appModel = scope.ctrl.appModel;
|
||||||
return importPluginModule(appModel.module).then(appModule => {
|
return importAppPlugin(appModel.module).then(appPlugin => {
|
||||||
return {
|
return {
|
||||||
baseUrl: appModel.baseUrl,
|
baseUrl: appModel.baseUrl,
|
||||||
name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug,
|
name: 'app-page-' + appModel.id + '-' + scope.ctrl.page.slug,
|
||||||
bindings: { appModel: '=' },
|
bindings: { appModel: '=' },
|
||||||
attrs: { 'app-model': 'ctrl.appModel' },
|
attrs: { 'app-model': 'ctrl.appModel' },
|
||||||
Component: appModule[scope.ctrl.page.component],
|
Component: appPlugin.pages[scope.ctrl.page.component],
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -18,7 +18,7 @@ import config from 'app/core/config';
|
|||||||
import TimeSeries from 'app/core/time_series2';
|
import TimeSeries from 'app/core/time_series2';
|
||||||
import TableModel from 'app/core/table_model';
|
import TableModel from 'app/core/table_model';
|
||||||
import { coreModule, appEvents, contextSrv } from 'app/core/core';
|
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 datemath from 'app/core/utils/datemath';
|
||||||
import * as fileExport from 'app/core/utils/file_export';
|
import * as fileExport from 'app/core/utils/file_export';
|
||||||
import * as flatten from 'app/core/utils/flatten';
|
import * as flatten from 'app/core/utils/flatten';
|
||||||
@ -141,7 +141,7 @@ for (const flotDep of flotDeps) {
|
|||||||
exposeToPlugin(flotDep, { fakeDep: 1 });
|
exposeToPlugin(flotDep, { fakeDep: 1 });
|
||||||
}
|
}
|
||||||
|
|
||||||
export function importPluginModule(path: string): Promise<PluginExports> {
|
function importPluginModule(path: string): Promise<any> {
|
||||||
const builtIn = builtInPlugins[path];
|
const builtIn = builtInPlugins[path];
|
||||||
if (builtIn) {
|
if (builtIn) {
|
||||||
return Promise.resolve(builtIn);
|
return Promise.resolve(builtIn);
|
||||||
@ -149,6 +149,38 @@ export function importPluginModule(path: string): Promise<PluginExports> {
|
|||||||
return System.import(path);
|
return System.import(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function importDataSourcePlugin(path: string): Promise<DataSourcePlugin> {
|
||||||
|
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<AppPlugin> {
|
||||||
|
return importPluginModule(path).then(pluginExports => {
|
||||||
|
return new AppPlugin(pluginExports.ConfigCtrl);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export function importPanelPlugin(path: string): Promise<AngularPanelPlugin | ReactPanelPlugin> {
|
||||||
|
return importPluginModule(path).then(pluginExports => {
|
||||||
|
if (pluginExports.reactPanel) {
|
||||||
|
return pluginExports.reactPanel as ReactPanelPlugin;
|
||||||
|
} else {
|
||||||
|
return new AngularPanelPlugin(pluginExports.PanelCtrl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function loadPluginCss(options) {
|
export function loadPluginCss(options) {
|
||||||
if (config.bootData.user.lightTheme) {
|
if (config.bootData.user.lightTheme) {
|
||||||
System.import(options.light + '!css');
|
System.import(options.light + '!css');
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
import 'app/features/plugins/datasource_srv';
|
import 'app/features/plugins/datasource_srv';
|
||||||
import { DatasourceSrv } from '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'
|
// Datasource variable $datasource with current value 'BBB'
|
||||||
const templateSrv = {
|
const templateSrv = {
|
||||||
@ -22,16 +23,22 @@ describe('datasource_srv', () => {
|
|||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.datasources = {
|
config.datasources = {
|
||||||
buildInDs: {
|
buildInDs: {
|
||||||
|
type: 'b',
|
||||||
name: 'buildIn',
|
name: 'buildIn',
|
||||||
meta: { builtIn: true },
|
meta: { builtIn: true } as PluginMeta,
|
||||||
|
jsonData: {},
|
||||||
},
|
},
|
||||||
nonBuildIn: {
|
nonBuildIn: {
|
||||||
|
type: 'e',
|
||||||
name: 'external1',
|
name: 'external1',
|
||||||
meta: { builtIn: false },
|
meta: { builtIn: false } as PluginMeta,
|
||||||
|
jsonData: {},
|
||||||
},
|
},
|
||||||
nonExplore: {
|
nonExplore: {
|
||||||
|
type: 'e2',
|
||||||
name: 'external2',
|
name: 'external2',
|
||||||
meta: {},
|
meta: {} as PluginMeta,
|
||||||
|
jsonData: {},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@ -73,7 +80,7 @@ describe('datasource_srv', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
config.datasources = unsortedDatasources;
|
config.datasources = unsortedDatasources as any;
|
||||||
metricSources = _datasourceSrv.getMetricSources({});
|
metricSources = _datasourceSrv.getMetricSources({});
|
||||||
config.defaultDatasource = 'BBB';
|
config.defaultDatasource = 'BBB';
|
||||||
});
|
});
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import coreModule from 'app/core/core_module';
|
import coreModule from 'app/core/core_module';
|
||||||
import { importPluginModule } from './plugin_loader';
|
import { importDataSourcePlugin } from './plugin_loader';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ReactDOM from 'react-dom';
|
import ReactDOM from 'react-dom';
|
||||||
import DefaultVariableQueryEditor from '../templating/DefaultVariableQueryEditor';
|
import DefaultVariableQueryEditor from '../templating/DefaultVariableQueryEditor';
|
||||||
|
|
||||||
async function loadComponent(module) {
|
async function loadComponent(module) {
|
||||||
const component = await importPluginModule(module);
|
const dsPlugin = await importDataSourcePlugin(module);
|
||||||
if (component && component.VariableQueryEditor) {
|
if (dsPlugin.components.VariableQueryEditor) {
|
||||||
return component.VariableQueryEditor;
|
return dsPlugin.components.VariableQueryEditor;
|
||||||
} else {
|
} else {
|
||||||
return DefaultVariableQueryEditor;
|
return DefaultVariableQueryEditor;
|
||||||
}
|
}
|
||||||
|
@ -17,10 +17,10 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
|||||||
// Types
|
// Types
|
||||||
import { LokiQuery } from '../types';
|
import { LokiQuery } from '../types';
|
||||||
import { TypeaheadOutput, HistoryItem } from 'app/types/explore';
|
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) {
|
function getChooserText(hasSyntax: boolean, hasLogLabels: boolean, datasourceStatus: DataSourceStatus) {
|
||||||
if (datasourceStatus === DatasourceStatus.Disconnected) {
|
if (datasourceStatus === DataSourceStatus.Disconnected) {
|
||||||
return '(Disconnected)';
|
return '(Disconnected)';
|
||||||
}
|
}
|
||||||
if (!hasSyntax) {
|
if (!hasSyntax) {
|
||||||
@ -169,7 +169,7 @@ export class LokiQueryFieldForm extends React.PureComponent<LokiQueryFieldFormPr
|
|||||||
const cleanText = datasource.languageProvider ? datasource.languageProvider.cleanText : undefined;
|
const cleanText = datasource.languageProvider ? datasource.languageProvider.cleanText : undefined;
|
||||||
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
|
const hasLogLabels = logLabelOptions && logLabelOptions.length > 0;
|
||||||
const chooserText = getChooserText(syntaxLoaded, hasLogLabels, datasourceStatus);
|
const chooserText = getChooserText(syntaxLoaded, hasLogLabels, datasourceStatus);
|
||||||
const buttonDisabled = !syntaxLoaded || datasourceStatus === DatasourceStatus.Disconnected;
|
const buttonDisabled = !syntaxLoaded || datasourceStatus === DataSourceStatus.Disconnected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { renderHook, act } from 'react-hooks-testing-library';
|
import { renderHook, act } from 'react-hooks-testing-library';
|
||||||
import LanguageProvider from 'app/plugins/datasource/loki/language_provider';
|
import LanguageProvider from 'app/plugins/datasource/loki/language_provider';
|
||||||
import { useLokiLabels } from './useLokiLabels';
|
import { useLokiLabels } from './useLokiLabels';
|
||||||
import { DatasourceStatus } from '@grafana/ui/src/types/plugin';
|
import { DataSourceStatus } from '@grafana/ui/src/types/datasource';
|
||||||
|
|
||||||
describe('useLokiLabels hook', () => {
|
describe('useLokiLabels hook', () => {
|
||||||
it('should refresh labels', async () => {
|
it('should refresh labels', async () => {
|
||||||
@ -17,7 +17,7 @@ describe('useLokiLabels hook', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { result, waitForNextUpdate } = renderHook(() =>
|
const { result, waitForNextUpdate } = renderHook(() =>
|
||||||
useLokiLabels(languageProvider, true, [], DatasourceStatus.Connected, DatasourceStatus.Connected)
|
useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Connected)
|
||||||
);
|
);
|
||||||
act(() => result.current.refreshLabels());
|
act(() => result.current.refreshLabels());
|
||||||
expect(result.current.logLabelOptions).toEqual([]);
|
expect(result.current.logLabelOptions).toEqual([]);
|
||||||
@ -33,7 +33,7 @@ describe('useLokiLabels hook', () => {
|
|||||||
languageProvider.refreshLogLabels = jest.fn();
|
languageProvider.refreshLogLabels = jest.fn();
|
||||||
|
|
||||||
renderHook(() =>
|
renderHook(() =>
|
||||||
useLokiLabels(languageProvider, true, [], DatasourceStatus.Connected, DatasourceStatus.Disconnected)
|
useLokiLabels(languageProvider, true, [], DataSourceStatus.Connected, DataSourceStatus.Disconnected)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(languageProvider.refreshLogLabels).toBeCalledTimes(1);
|
expect(languageProvider.refreshLogLabels).toBeCalledTimes(1);
|
||||||
@ -48,7 +48,7 @@ describe('useLokiLabels hook', () => {
|
|||||||
languageProvider.refreshLogLabels = jest.fn();
|
languageProvider.refreshLogLabels = jest.fn();
|
||||||
|
|
||||||
renderHook(() =>
|
renderHook(() =>
|
||||||
useLokiLabels(languageProvider, true, [], DatasourceStatus.Disconnected, DatasourceStatus.Connected)
|
useLokiLabels(languageProvider, true, [], DataSourceStatus.Disconnected, DataSourceStatus.Connected)
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(languageProvider.refreshLogLabels).not.toBeCalled();
|
expect(languageProvider.refreshLogLabels).not.toBeCalled();
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { useState, useEffect } from 'react';
|
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 LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
|
||||||
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
|
import { CascaderOption } from 'app/plugins/datasource/loki/components/LokiQueryFieldForm';
|
||||||
@ -17,8 +17,8 @@ export const useLokiLabels = (
|
|||||||
languageProvider: LokiLanguageProvider,
|
languageProvider: LokiLanguageProvider,
|
||||||
languageProviderInitialised: boolean,
|
languageProviderInitialised: boolean,
|
||||||
activeOption: CascaderOption[],
|
activeOption: CascaderOption[],
|
||||||
datasourceStatus: DatasourceStatus,
|
datasourceStatus: DataSourceStatus,
|
||||||
initialDatasourceStatus?: DatasourceStatus // used for test purposes
|
initialDatasourceStatus?: DataSourceStatus // used for test purposes
|
||||||
) => {
|
) => {
|
||||||
const mounted = useRefMounted();
|
const mounted = useRefMounted();
|
||||||
|
|
||||||
@ -26,7 +26,7 @@ export const useLokiLabels = (
|
|||||||
const [logLabelOptions, setLogLabelOptions] = useState([]);
|
const [logLabelOptions, setLogLabelOptions] = useState([]);
|
||||||
const [shouldTryRefreshLabels, setRefreshLabels] = useState(false);
|
const [shouldTryRefreshLabels, setRefreshLabels] = useState(false);
|
||||||
const [prevDatasourceStatus, setPrevDatasourceStatus] = useState(
|
const [prevDatasourceStatus, setPrevDatasourceStatus] = useState(
|
||||||
initialDatasourceStatus || DatasourceStatus.Connected
|
initialDatasourceStatus || DataSourceStatus.Connected
|
||||||
);
|
);
|
||||||
const [shouldForceRefreshLabels, setForceRefreshLabels] = useState(false);
|
const [shouldForceRefreshLabels, setForceRefreshLabels] = useState(false);
|
||||||
|
|
||||||
@ -84,7 +84,7 @@ export const useLokiLabels = (
|
|||||||
// This effect is performed on datasourceStatus state change only.
|
// 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
|
// We want to make sure to only force refresh AFTER a disconnected state thats why we store the previous datasourceStatus in state
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (datasourceStatus === DatasourceStatus.Connected && prevDatasourceStatus === DatasourceStatus.Disconnected) {
|
if (datasourceStatus === DataSourceStatus.Connected && prevDatasourceStatus === DataSourceStatus.Disconnected) {
|
||||||
setForceRefreshLabels(true);
|
setForceRefreshLabels(true);
|
||||||
}
|
}
|
||||||
setPrevDatasourceStatus(datasourceStatus);
|
setPrevDatasourceStatus(datasourceStatus);
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { renderHook, act } from 'react-hooks-testing-library';
|
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 LanguageProvider from 'app/plugins/datasource/loki/language_provider';
|
||||||
import { useLokiSyntax } from './useLokiSyntax';
|
import { useLokiSyntax } from './useLokiSyntax';
|
||||||
@ -30,7 +30,7 @@ describe('useLokiSyntax hook', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
it('should provide Loki syntax when used', async () => {
|
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);
|
expect(result.current.syntax).toEqual(null);
|
||||||
|
|
||||||
await waitForNextUpdate();
|
await waitForNextUpdate();
|
||||||
@ -39,7 +39,7 @@ describe('useLokiSyntax hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fetch labels on first call', async () => {
|
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.isSyntaxReady).toBeFalsy();
|
||||||
expect(result.current.logLabelOptions).toEqual([]);
|
expect(result.current.logLabelOptions).toEqual([]);
|
||||||
|
|
||||||
@ -50,7 +50,7 @@ describe('useLokiSyntax hook', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should try to fetch missing options when active option changes', async () => {
|
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();
|
await waitForNextUpdate();
|
||||||
expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2);
|
expect(result.current.logLabelOptions).toEqual(logLabelOptionsMock2);
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { useState, useEffect } from 'react';
|
import { useState, useEffect } from 'react';
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
import Prism from 'prismjs';
|
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 LokiLanguageProvider from 'app/plugins/datasource/loki/language_provider';
|
||||||
import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels';
|
import { useLokiLabels } from 'app/plugins/datasource/loki/components/useLokiLabels';
|
||||||
@ -15,7 +15,7 @@ const PRISM_SYNTAX = 'promql';
|
|||||||
* @param languageProvider
|
* @param languageProvider
|
||||||
* @description Initializes given language provider, exposes Loki syntax and enables loading label option values
|
* @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();
|
const mounted = useRefMounted();
|
||||||
// State
|
// State
|
||||||
const [languageProviderInitialized, setLanguageProviderInitilized] = useState(false);
|
const [languageProviderInitialized, setLanguageProviderInitilized] = useState(false);
|
||||||
|
@ -17,15 +17,15 @@ import RunnerPlugin from 'app/features/explore/slate-plugins/runner';
|
|||||||
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
import QueryField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField';
|
||||||
import { PromQuery } from '../types';
|
import { PromQuery } from '../types';
|
||||||
import { CancelablePromise, makePromiseCancelable } from 'app/core/utils/CancelablePromise';
|
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 HISTOGRAM_GROUP = '__histograms__';
|
||||||
const METRIC_MARK = 'metric';
|
const METRIC_MARK = 'metric';
|
||||||
const PRISM_SYNTAX = 'promql';
|
const PRISM_SYNTAX = 'promql';
|
||||||
export const RECORDING_RULES_GROUP = '__recording_rules__';
|
export const RECORDING_RULES_GROUP = '__recording_rules__';
|
||||||
|
|
||||||
function getChooserText(hasSyntax: boolean, datasourceStatus: DatasourceStatus) {
|
function getChooserText(hasSyntax: boolean, datasourceStatus: DataSourceStatus) {
|
||||||
if (datasourceStatus === DatasourceStatus.Disconnected) {
|
if (datasourceStatus === DataSourceStatus.Disconnected) {
|
||||||
return '(Disconnected)';
|
return '(Disconnected)';
|
||||||
}
|
}
|
||||||
if (!hasSyntax) {
|
if (!hasSyntax) {
|
||||||
@ -153,8 +153,8 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
|
|
||||||
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
componentDidUpdate(prevProps: PromQueryFieldProps) {
|
||||||
const reconnected =
|
const reconnected =
|
||||||
prevProps.datasourceStatus === DatasourceStatus.Disconnected &&
|
prevProps.datasourceStatus === DataSourceStatus.Disconnected &&
|
||||||
this.props.datasourceStatus === DatasourceStatus.Connected;
|
this.props.datasourceStatus === DataSourceStatus.Connected;
|
||||||
if (!reconnected) {
|
if (!reconnected) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -278,7 +278,7 @@ class PromQueryField extends React.PureComponent<PromQueryFieldProps, PromQueryF
|
|||||||
const { metricsOptions, syntaxLoaded } = this.state;
|
const { metricsOptions, syntaxLoaded } = this.state;
|
||||||
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
const cleanText = this.languageProvider ? this.languageProvider.cleanText : undefined;
|
||||||
const chooserText = getChooserText(syntaxLoaded, datasourceStatus);
|
const chooserText = getChooserText(syntaxLoaded, datasourceStatus);
|
||||||
const buttonDisabled = !syntaxLoaded || datasourceStatus === DatasourceStatus.Disconnected;
|
const buttonDisabled = !syntaxLoaded || datasourceStatus === DataSourceStatus.Disconnected;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
13
public/app/plugins/datasource/testdata/module.ts
vendored
13
public/app/plugins/datasource/testdata/module.ts
vendored
@ -1,18 +1,13 @@
|
|||||||
|
import { DataSourcePlugin } from '@grafana/ui';
|
||||||
import { TestDataDatasource } from './datasource';
|
import { TestDataDatasource } from './datasource';
|
||||||
import { TestDataQueryCtrl } from './query_ctrl';
|
import { TestDataQueryCtrl } from './query_ctrl';
|
||||||
// import { QueryEditor } from './QueryEditor';
|
|
||||||
|
|
||||||
class TestDataAnnotationsQueryCtrl {
|
class TestDataAnnotationsQueryCtrl {
|
||||||
annotation: any;
|
annotation: any;
|
||||||
|
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
static template = '<h2>Annotation scenario</h2>';
|
static template = '<h2>Annotation scenario</h2>';
|
||||||
}
|
}
|
||||||
|
|
||||||
export {
|
export const plugin = new DataSourcePlugin(TestDataDatasource)
|
||||||
// QueryEditor,
|
.setQueryCtrl(TestDataQueryCtrl)
|
||||||
TestDataDatasource as Datasource,
|
.setAnnotationQueryCtrl(TestDataAnnotationsQueryCtrl);
|
||||||
TestDataQueryCtrl as QueryCtrl,
|
|
||||||
TestDataAnnotationsQueryCtrl as AnnotationsQueryCtrl,
|
|
||||||
};
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { PluginExports, PluginMetaInfo } from '@grafana/ui/src/types';
|
import { AngularPanelPlugin, ReactPanelPlugin, PluginMetaInfo } from '@grafana/ui/src/types';
|
||||||
|
|
||||||
export interface PanelPlugin {
|
export interface PanelPlugin {
|
||||||
id: string;
|
id: string;
|
||||||
@ -8,7 +8,9 @@ export interface PanelPlugin {
|
|||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
info: PluginMetaInfo;
|
info: PluginMetaInfo;
|
||||||
sort: number;
|
sort: number;
|
||||||
exports?: PluginExports;
|
angularPlugin: AngularPanelPlugin | null;
|
||||||
|
reactPlugin: ReactPanelPlugin | null;
|
||||||
|
hasBeenImported?: boolean;
|
||||||
dataFormats: PanelDataFormat[];
|
dataFormats: PanelDataFormat[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@ echo -e "Collecting code stats (typescript errors & more)"
|
|||||||
|
|
||||||
ERROR_COUNT_LIMIT=6843
|
ERROR_COUNT_LIMIT=6843
|
||||||
DIRECTIVES_LIMIT=173
|
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+)')"
|
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)"
|
DIRECTIVES="$(grep -r -o directive public/app/**/* | wc -l)"
|
||||||
|
Loading…
Reference in New Issue
Block a user