From d0776937b5e854c532574a9e7233b944a1957626 Mon Sep 17 00:00:00 2001 From: David Kaltschmidt Date: Tue, 9 Oct 2018 19:46:31 +0200 Subject: [PATCH 1/2] Pluggable components from datasource plugins - when instantiating a datasource, the datasource service checks if the plugin module exports Explore components, and if so, attaches them to the datasource - Explore component makes all major internal pluggable from a datasource `exploreComponents` property - Moved Prometheus query field to promehteus datasource and registered it as an exported Explore component - Added new Start page for Explore, also exported from the datasource --- public/app/core/utils/explore.test.ts | 1 + public/app/features/explore/Explore.tsx | 124 +++++++++++------- public/app/features/explore/QueryField.tsx | 30 +++-- public/app/features/explore/QueryRows.tsx | 17 ++- ...actions.tsx => QueryTransactionStatus.tsx} | 16 +-- public/app/features/plugins/datasource_srv.ts | 1 + .../prometheus/components/PromCheatSheet.tsx | 35 +++++ .../components}/PromQueryField.test.tsx | 0 .../prometheus/components}/PromQueryField.tsx | 33 +++-- .../prometheus/components/PromStart.tsx | 60 +++++++++ .../plugins/datasource/prometheus/module.ts | 9 ++ public/app/types/explore.ts | 1 + public/app/types/plugins.ts | 1 + public/sass/pages/_explore.scss | 18 ++- scripts/webpack/webpack.common.js | 2 +- 15 files changed, 251 insertions(+), 97 deletions(-) rename public/app/features/explore/{QueryTransactions.tsx => QueryTransactionStatus.tsx} (60%) create mode 100644 public/app/plugins/datasource/prometheus/components/PromCheatSheet.tsx rename public/app/{features/explore => plugins/datasource/prometheus/components}/PromQueryField.test.tsx (100%) rename public/app/{features/explore => plugins/datasource/prometheus/components}/PromQueryField.tsx (92%) create mode 100644 public/app/plugins/datasource/prometheus/components/PromStart.tsx diff --git a/public/app/core/utils/explore.test.ts b/public/app/core/utils/explore.test.ts index 4252730338d..e32a458fc1c 100644 --- a/public/app/core/utils/explore.test.ts +++ b/public/app/core/utils/explore.test.ts @@ -2,6 +2,7 @@ import { DEFAULT_RANGE, serializeStateToUrlParam, parseUrlState } from './explor import { ExploreState } from 'app/types/explore'; const DEFAULT_EXPLORE_STATE: ExploreState = { + customComponents: {}, datasource: null, datasourceError: null, datasourceLoading: null, diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index d29f4c22e0c..4f2247dd607 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -4,6 +4,7 @@ import Select from 'react-select'; import _ from 'lodash'; import { ExploreState, ExploreUrlState, HistoryItem, Query, QueryTransaction, ResultType } from 'app/types/explore'; +import { RawTimeRange } from 'app/types/series'; import kbn from 'app/core/utils/kbn'; import colors from 'app/core/utils/colors'; import store from 'app/core/store'; @@ -16,14 +17,13 @@ import IndicatorsContainer from 'app/core/components/Picker/IndicatorsContainer' import NoOptionsMessage from 'app/core/components/Picker/NoOptionsMessage'; import TableModel, { mergeTablesIntoModel } from 'app/core/table_model'; +import DefaultQueryRows from './QueryRows'; +import DefaultGraph from './Graph'; +import DefaultLogs from './Logs'; +import DefaultTable from './Table'; import ErrorBoundary from './ErrorBoundary'; -import QueryRows from './QueryRows'; -import Graph from './Graph'; -import Logs from './Logs'; -import Table from './Table'; import TimePicker from './TimePicker'; import { ensureQueries, generateQueryKey, hasQuery } from './utils/query'; -import { RawTimeRange } from 'app/types/series'; const MAX_HISTORY_ITEMS = 100; @@ -96,6 +96,7 @@ export class Explore extends React.PureComponent { initialQueries = ensureQueries(queries); const initialRange = range || { ...DEFAULT_RANGE }; this.state = { + customComponents: {}, datasource: null, datasourceError: null, datasourceLoading: null, @@ -176,8 +177,13 @@ export class Explore extends React.PureComponent { query: this.queryExpressions[i], })); + const customComponents = { + ...datasource.exploreComponents, + }; + this.setState( { + customComponents, datasource, datasourceError, history, @@ -330,6 +336,13 @@ export class Explore extends React.PureComponent { ); }; + // Use this in help pages to set page to a single query + onClickQuery = query => { + const nextQueries = [{ query, key: generateQueryKey() }]; + this.queryExpressions = nextQueries.map(q => q.query); + this.setState({ queries: nextQueries }, this.onSubmit); + }; + onClickSplit = () => { const { onChangeSplit } = this.props; if (onChangeSplit) { @@ -385,9 +398,9 @@ export class Explore extends React.PureComponent { q.query = this.queryExpressions[i]; return i === index ? { - key: generateQueryKey(index), - query: datasource.modifyQuery(q.query, action), - } + key: generateQueryKey(index), + query: datasource.modifyQuery(q.query, action), + } : q; }); nextQueryTransactions = queryTransactions @@ -721,6 +734,7 @@ export class Explore extends React.PureComponent { render() { const { position, split } = this.props; const { + customComponents, datasource, datasourceError, datasourceLoading, @@ -760,6 +774,14 @@ export class Explore extends React.PureComponent { queryTransactions.filter(qt => qt.resultType === 'Logs' && qt.done && qt.result).map(qt => qt.result) ); const loading = queryTransactions.some(qt => !qt.done); + const showStartPages = queryTransactions.length === 0 && customComponents.StartPage; + + // Custom components + const Graph = customComponents.Graph || DefaultGraph; + const Logs = customComponents.Logs || DefaultLogs; + const QueryRows = customComponents.QueryRows || DefaultQueryRows; + const StartPage = customComponents.StartPage; + const Table = customComponents.Table || DefaultTable; return (
@@ -772,12 +794,12 @@ export class Explore extends React.PureComponent {
) : ( -
- -
- )} + + )} {!datasourceMissing ? (
{ {datasource && !datasourceError ? (
{ {supportsGraph ? ( + ) : null} {supportsTable ? ( + ) : null} {supportsLogs ? ( + ) : null}
diff --git a/public/app/features/explore/QueryField.tsx b/public/app/features/explore/QueryField.tsx index bdcf3f358d7..73d743fe8e6 100644 --- a/public/app/features/explore/QueryField.tsx +++ b/public/app/features/explore/QueryField.tsx @@ -27,7 +27,7 @@ function hasSuggestions(suggestions: CompletionItemGroup[]): boolean { return suggestions && suggestions.length > 0; } -interface TypeaheadFieldProps { +interface QueryFieldProps { additionalPlugins?: any[]; cleanText?: (text: string) => string; initialValue: string | null; @@ -35,14 +35,14 @@ interface TypeaheadFieldProps { onFocus?: () => void; onTypeahead?: (typeahead: TypeaheadInput) => TypeaheadOutput; onValueChanged?: (value: Value) => void; - onWillApplySuggestion?: (suggestion: string, state: TypeaheadFieldState) => string; + onWillApplySuggestion?: (suggestion: string, state: QueryFieldState) => string; placeholder?: string; portalOrigin?: string; syntax?: string; syntaxLoaded?: boolean; } -export interface TypeaheadFieldState { +export interface QueryFieldState { suggestions: CompletionItemGroup[]; typeaheadContext: string | null; typeaheadIndex: number; @@ -60,7 +60,7 @@ export interface TypeaheadInput { wrapperNode: Element; } -class QueryField extends React.PureComponent { +export class QueryField extends React.PureComponent { menuEl: HTMLElement | null; placeholdersBuffer: PlaceholdersBuffer; plugins: any[]; @@ -102,7 +102,7 @@ class QueryField extends React.PureComponent qt.hints && qt.hints.length > 0); @@ -23,8 +24,7 @@ interface QueryRowEventHandlers { interface QueryRowCommonProps { className?: string; - customComponents: any; - datasource: any; + datasource: DataSource; history: HistoryItem[]; // Temporarily supportsLogs?: boolean; @@ -37,7 +37,7 @@ type QueryRowProps = QueryRowCommonProps & query: string; }; -class DefaultQueryRow extends PureComponent { +class QueryRow extends PureComponent { onChangeQuery = (value, override?: boolean) => { const { index, onChangeQuery } = this.props; if (onChangeQuery) { @@ -78,11 +78,11 @@ class DefaultQueryRow extends PureComponent { }; render() { - const { customComponents, datasource, history, query, supportsLogs, transactions } = this.props; + const { datasource, history, query, supportsLogs, transactions } = this.props; const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - const QueryField = customComponents.QueryField || DefaultQueryField; + const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; return (
@@ -124,14 +124,12 @@ type QueryRowsProps = QueryRowCommonProps & export default class QueryRows extends PureComponent { render() { - const { className = '', customComponents, queries, transactions, ...handlers } = this.props; - const QueryRow = customComponents.QueryRow || DefaultQueryRow; + const { className = '', queries, transactions, ...handlers } = this.props; return (
{queries.map((q, index) => ( t.rowIndex === index)} diff --git a/public/app/features/plugins/datasource_srv.ts b/public/app/features/plugins/datasource_srv.ts index 296fb06b31c..fed455472c9 100644 --- a/public/app/features/plugins/datasource_srv.ts +++ b/public/app/features/plugins/datasource_srv.ts @@ -8,9 +8,10 @@ import { importPluginModule } from './plugin_loader'; // Types import { DataSourceApi } from 'app/types/series'; +import { DataSource } from 'app/types'; export class DatasourceSrv { - datasources: any; + datasources: { [name: string]: DataSource }; /** @ngInject */ constructor(private $q, private $injector, private $rootScope, private templateSrv) { @@ -61,10 +62,10 @@ export class DatasourceSrv { throw new Error('Plugin module is missing Datasource constructor'); } - const instance = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig }); + const instance: DataSource = this.$injector.instantiate(plugin.Datasource, { instanceSettings: dsConfig }); instance.meta = pluginDef; instance.name = name; - instance.exploreComponents = plugin.ExploreComponents; + instance.pluginExports = plugin; this.datasources[name] = instance; deferred.resolve(instance); }) diff --git a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx index 7360e67d45a..b6f2c0f2e08 100644 --- a/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx +++ b/public/app/plugins/datasource/prometheus/components/PromQueryField.tsx @@ -10,7 +10,7 @@ import { TypeaheadOutput } from 'app/types/explore'; import { getNextCharacter, getPreviousCousin } from 'app/features/explore/utils/dom'; import BracesPlugin from 'app/features/explore/slate-plugins/braces'; import RunnerPlugin from 'app/features/explore/slate-plugins/runner'; -import TypeaheadField, { TypeaheadInput, TypeaheadFieldState } from 'app/features/explore/QueryField'; +import TypeaheadField, { TypeaheadInput, QueryFieldState } from 'app/features/explore/QueryField'; const HISTOGRAM_GROUP = '__histograms__'; const METRIC_MARK = 'metric'; @@ -50,10 +50,7 @@ export function groupMetricsByPrefix(metrics: string[], delimiter = '_'): Cascad return [...options, ...metricsOptions]; } -export function willApplySuggestion( - suggestion: string, - { typeaheadContext, typeaheadText }: TypeaheadFieldState -): string { +export function willApplySuggestion(suggestion: string, { typeaheadContext, typeaheadText }: QueryFieldState): string { // Modify suggestion based on context switch (typeaheadContext) { case 'context-labels': { diff --git a/public/app/plugins/datasource/prometheus/module.ts b/public/app/plugins/datasource/prometheus/module.ts index 07f3d103498..814f6fbe60a 100644 --- a/public/app/plugins/datasource/prometheus/module.ts +++ b/public/app/plugins/datasource/prometheus/module.ts @@ -9,15 +9,11 @@ class PrometheusAnnotationsQueryCtrl { static templateUrl = 'partials/annotations.editor.html'; } -const ExploreComponents = { - QueryField: PromQueryField, - StartPage: PrometheusStartPage, -}; - export { PrometheusDatasource as Datasource, PrometheusQueryCtrl as QueryCtrl, PrometheusConfigCtrl as ConfigCtrl, PrometheusAnnotationsQueryCtrl as AnnotationsQueryCtrl, - ExploreComponents, + PromQueryField as ExploreQueryField, + PrometheusStartPage as ExploreStartPage, }; diff --git a/public/app/types/datasources.ts b/public/app/types/datasources.ts index 8e1991dcd9f..5522f3a11ce 100644 --- a/public/app/types/datasources.ts +++ b/public/app/types/datasources.ts @@ -1,5 +1,5 @@ import { LayoutMode } from '../core/components/LayoutSelector/LayoutSelector'; -import { Plugin } from './plugins'; +import { Plugin, PluginExports, PluginMeta } from './plugins'; export interface DataSource { id: number; @@ -16,6 +16,10 @@ export interface DataSource { isDefault: boolean; jsonData: { authType: string; defaultRegion: string }; readOnly: boolean; + meta?: PluginMeta; + pluginExports?: PluginExports; + init?: () => void; + testDatasource?: () => Promise; } export interface DataSourcesState { diff --git a/public/app/types/explore.ts b/public/app/types/explore.ts index 91c3cf95ea5..a4fb74634fe 100644 --- a/public/app/types/explore.ts +++ b/public/app/types/explore.ts @@ -146,7 +146,7 @@ export interface TextMatch { } export interface ExploreState { - customComponents: any; + StartPage?: any; datasource: any; datasourceError: any; datasourceLoading: boolean | null; diff --git a/public/app/types/plugins.ts b/public/app/types/plugins.ts index 625390b39bb..1b5499f88b7 100644 --- a/public/app/types/plugins.ts +++ b/public/app/types/plugins.ts @@ -6,7 +6,8 @@ export interface PluginExports { ConfigCtrl?: any; AnnotationsQueryCtrl?: any; PanelOptions?: any; - ExploreComponents?: any; + ExploreQueryField?: any; + ExploreStartPage?: any; } export interface PanelPlugin { @@ -26,6 +27,12 @@ export interface PluginMeta { name: string; info: PluginMetaInfo; includes: PluginInclude[]; + + // Datasource-specific + metrics?: boolean; + logs?: boolean; + explore?: boolean; + annotations?: boolean; } export interface PluginInclude {