diff --git a/pkg/plugins/datasource_plugin.go b/pkg/plugins/datasource_plugin.go index ff44805e35f..04b77a892c5 100644 --- a/pkg/plugins/datasource_plugin.go +++ b/pkg/plugins/datasource_plugin.go @@ -24,6 +24,7 @@ type DataSourcePlugin struct { Metrics bool `json:"metrics"` Alerting bool `json:"alerting"` Explore bool `json:"explore"` + Table bool `json:"tables"` Logs bool `json:"logs"` QueryOptions map[string]bool `json:"queryOptions,omitempty"` BuiltIn bool `json:"builtIn,omitempty"` diff --git a/public/app/core/table_model.ts b/public/app/core/table_model.ts index 91a7cd0c1fb..fd99db69249 100644 --- a/public/app/core/table_model.ts +++ b/public/app/core/table_model.ts @@ -86,11 +86,10 @@ export function mergeTablesIntoModel(dst?: TableModel, ...tables: TableModel[]): if (arguments.length === 1) { return model; } - // Single query returns data columns and rows as is if (arguments.length === 2) { - model.columns = [...tables[0].columns]; - model.rows = [...tables[0].rows]; + model.columns = tables[0].hasOwnProperty('columns') ? [...tables[0].columns] : []; + model.rows = tables[0].hasOwnProperty('rows') ? [...tables[0].rows] : []; return model; } diff --git a/public/app/features/explore/Explore.tsx b/public/app/features/explore/Explore.tsx index d4e9b689495..99fdcc18175 100644 --- a/public/app/features/explore/Explore.tsx +++ b/public/app/features/explore/Explore.tsx @@ -12,7 +12,7 @@ import { QueryHintGetter, QueryHint, } from 'app/types/explore'; -import { RawTimeRange, DataQuery } from 'app/types/series'; +import { TimeRange, DataQuery } from 'app/types/series'; import store from 'app/core/store'; import { DEFAULT_RANGE, @@ -30,6 +30,8 @@ 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 { DatasourceSrv } from 'app/features/plugins/datasource_srv'; +import { Emitter } from 'app/core/utils/emitter'; +import * as dateMath from 'app/core/utils/datemath'; import Panel from './Panel'; import QueryRows from './QueryRows'; @@ -88,6 +90,7 @@ interface ExploreProps { */ export class Explore extends React.PureComponent { el: any; + exploreEvents: Emitter; /** * Current query expressions of the rows including their modifications, used for running queries. * Not kept in component state to prevent edit-render roundtrips. @@ -132,6 +135,7 @@ export class Explore extends React.PureComponent { }; } this.modifiedQueries = initialQueries.slice(); + this.exploreEvents = new Emitter(); } async componentDidMount() { @@ -155,19 +159,20 @@ export class Explore extends React.PureComponent { } else { datasource = await datasourceSrv.get(); } - if (!datasource.meta.explore) { - datasource = await datasourceSrv.get(datasources[0].name); - } await this.setDatasource(datasource); } else { this.setState({ datasourceMissing: true }); } } + componentWillUnmount() { + this.exploreEvents.removeAllListeners(); + } + async setDatasource(datasource: any, origin?: DataSource) { const supportsGraph = datasource.meta.metrics; const supportsLogs = datasource.meta.logs; - const supportsTable = datasource.meta.metrics; + const supportsTable = datasource.meta.tables; const datasourceId = datasource.meta.id; let datasourceError = null; @@ -317,8 +322,14 @@ export class Explore extends React.PureComponent { } }; - onChangeTime = (nextRange: RawTimeRange) => { - const range: RawTimeRange = { + // onChangeTime = (nextRange: RawTimeRange) => { + // const range: RawTimeRange = { + // ...nextRange, + // }; + // this.setState({ range }, () => this.onSubmit()); + // }; + onChangeTime = (nextRange: TimeRange) => { + const range: TimeRange = { ...nextRange, }; this.setState({ range }, () => this.onSubmit()); @@ -538,8 +549,8 @@ export class Explore extends React.PureComponent { ]; // Clone range for query request - const queryRange: RawTimeRange = { ...range }; - + // const queryRange: RawTimeRange = { ...range }; + // const { from, to, raw } = this.timeSrv.timeRange(); // Datasource is using `panelId + query.refId` for cancellation logic. // Using `format` here because it relates to the view panel that the request is for. const panelId = queryOptions.format; @@ -549,7 +560,12 @@ export class Explore extends React.PureComponent { intervalMs, panelId, targets: configuredQueries, // Datasources rely on DataQueries being passed under the targets key. - range: queryRange, + range: { + from: dateMath.parse(range.from, false), + to: dateMath.parse(range.to, true), + raw: range, + }, + rangeRaw: range, }; } @@ -696,17 +712,19 @@ export class Explore extends React.PureComponent { } const { datasource } = this.state; const datasourceId = datasource.meta.id; - // Run all queries concurrently + // Run all queries concurrentlyso queries.forEach(async (query, rowIndex) => { const transaction = this.startQueryTransaction(query, rowIndex, resultType, queryOptions); try { const now = Date.now(); const res = await datasource.query(transaction.options); + this.exploreEvents.emit('data-received', res); const latency = Date.now() - now; const results = resultGetter ? resultGetter(res.data) : res.data; this.completeQueryTransaction(transaction.id, results, latency, queries, datasourceId); this.setState({ graphRange: transaction.options.range }); } catch (response) { + this.exploreEvents.emit('data-error', response); this.failQueryTransaction(transaction.id, response, datasourceId); } }); @@ -759,7 +777,10 @@ export class Explore extends React.PureComponent { const graphResult = _.flatten( queryTransactions.filter(qt => qt.resultType === 'Graph' && qt.done && qt.result).map(qt => qt.result) ); - const tableResult = mergeTablesIntoModel( + + //Temp solution... How do detect if ds supports table format? + let tableResult; + tableResult = mergeTablesIntoModel( new TableModel(), ...queryTransactions.filter(qt => qt.resultType === 'Table' && qt.done && qt.result).map(qt => qt.result) ); @@ -858,6 +879,8 @@ export class Explore extends React.PureComponent { onExecuteQuery={this.onSubmit} onRemoveQueryRow={this.onRemoveQueryRow} transactions={queryTransactions} + exploreEvents={this.exploreEvents} + range={range} />
diff --git a/public/app/features/explore/QueryEditor.tsx b/public/app/features/explore/QueryEditor.tsx new file mode 100644 index 00000000000..0a4c0b78c3c --- /dev/null +++ b/public/app/features/explore/QueryEditor.tsx @@ -0,0 +1,77 @@ +import React, { PureComponent } from 'react'; +import { getAngularLoader, AngularComponent } from 'app/core/services/AngularLoader'; +import { Emitter } from 'app/core/utils/emitter'; +import { getIntervals } from 'app/core/utils/explore'; +import { DataQuery } from 'app/types'; +import { RawTimeRange } from 'app/types/series'; +import { getTimeSrv } from 'app/features/dashboard/time_srv'; +import 'app/features/plugins/plugin_loader'; + +interface QueryEditorProps { + datasource: any; + error?: string | JSX.Element; + onExecuteQuery?: () => void; + onQueryChange?: (value: DataQuery, override?: boolean) => void; + initialQuery: DataQuery; + exploreEvents: Emitter; + range: RawTimeRange; +} + +export default class QueryEditor extends PureComponent { + element: any; + component: AngularComponent; + + async componentDidMount() { + if (!this.element) { + return; + } + + const { datasource, initialQuery, exploreEvents, range } = this.props; + this.initTimeSrv(range); + + const loader = getAngularLoader(); + const template = ' '; + const target = { datasource: datasource.name, ...initialQuery }; + const scopeProps = { + target, + ctrl: { + refresh: () => { + this.props.onQueryChange({ refId: initialQuery.refId, ...target }, false); + this.props.onExecuteQuery(); + }, + events: exploreEvents, + panel: { + datasource, + targets: [target], + }, + dashboard: { + getNextQueryLetter: x => '', + }, + hideEditorRowActions: true, + ...getIntervals(range, datasource, null), // Possible to get resolution? + }, + }; + + this.component = loader.load(this.element, scopeProps, template); + } + + componentWillUnmount() { + if (this.component) { + this.component.destroy(); + } + } + + initTimeSrv(range) { + const timeSrv = getTimeSrv(); + timeSrv.init({ + time: range, + refresh: false, + getTimezone: () => 'utc', + timeRangeUpdated: () => console.log('refreshDashboard!'), + }); + } + + render() { + return
(this.element = element)} style={{ width: '100%' }} />; + } +} diff --git a/public/app/features/explore/QueryRows.tsx b/public/app/features/explore/QueryRows.tsx index 52c705c469c..36d1db8f546 100644 --- a/public/app/features/explore/QueryRows.tsx +++ b/public/app/features/explore/QueryRows.tsx @@ -1,10 +1,13 @@ import React, { PureComponent } from 'react'; import { QueryTransaction, HistoryItem, QueryHint } from 'app/types/explore'; +import { Emitter } from 'app/core/utils/emitter'; -import DefaultQueryField from './QueryField'; +// import DefaultQueryField from './QueryField'; +import QueryEditor from './QueryEditor'; import QueryTransactionStatus from './QueryTransactionStatus'; import { DataSource, DataQuery } from 'app/types'; +import { RawTimeRange } from 'app/types/series'; function getFirstHintFromTransactions(transactions: QueryTransaction[]): QueryHint { const transaction = transactions.find(qt => qt.hints && qt.hints.length > 0); @@ -27,6 +30,8 @@ interface QueryRowCommonProps { datasource: DataSource; history: HistoryItem[]; transactions: QueryTransaction[]; + exploreEvents: Emitter; + range: RawTimeRange; } type QueryRowProps = QueryRowCommonProps & @@ -36,6 +41,11 @@ type QueryRowProps = QueryRowCommonProps & }; class QueryRow extends PureComponent { + onExecuteQuery = () => { + const { onExecuteQuery } = this.props; + onExecuteQuery(); + }; + onChangeQuery = (value: DataQuery, override?: boolean) => { const { index, onChangeQuery } = this.props; if (onChangeQuery) { @@ -76,27 +86,41 @@ class QueryRow extends PureComponent { }; render() { - const { datasource, history, initialQuery, transactions } = this.props; + const { datasource, history, initialQuery, transactions, exploreEvents, range } = this.props; const transactionWithError = transactions.find(t => t.error !== undefined); const hint = getFirstHintFromTransactions(transactions); const queryError = transactionWithError ? transactionWithError.error : null; - const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; + // const QueryField = datasource.pluginExports.ExploreQueryField || DefaultQueryField; + const QueryField = datasource.pluginExports.ExploreQueryField; + // const QueryEditor = datasource.pluginExports.QueryCtrl; return (
- + {QueryField ? ( + + ) : ( + + )}