Commit 46ec9bd6 by ijin08

Merge branch 'panel-edit-in-react' of github.com:grafana/grafana into panel-edit-in-react

parents 2c943d9a 3162dda0
......@@ -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"`
......
......@@ -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;
}
......
......@@ -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<ExploreProps, ExploreState> {
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<ExploreProps, ExploreState> {
};
}
this.modifiedQueries = initialQueries.slice();
this.exploreEvents = new Emitter();
}
async componentDidMount() {
......@@ -155,19 +159,20 @@ export class Explore extends React.PureComponent<ExploreProps, ExploreState> {
} 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<ExploreProps, ExploreState> {
}
};
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<ExploreProps, ExploreState> {
];
// 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<ExploreProps, ExploreState> {
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<ExploreProps, ExploreState> {
}
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<ExploreProps, ExploreState> {
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<ExploreProps, ExploreState> {
onExecuteQuery={this.onSubmit}
onRemoveQueryRow={this.onRemoveQueryRow}
transactions={queryTransactions}
exploreEvents={this.exploreEvents}
range={range}
/>
<main className="m-t-2">
<ErrorBoundary>
......
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<QueryEditorProps, any> {
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 = '<plugin-component type="query-ctrl"> </plugin-component>';
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 <div ref={element => (this.element = element)} style={{ width: '100%' }} />;
}
}
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<QueryRowProps> {
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<QueryRowProps> {
};
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 (
<div className="query-row">
<div className="query-row-status">
<QueryTransactionStatus transactions={transactions} />
</div>
<div className="query-row-field">
<QueryField
datasource={datasource}
error={queryError}
hint={hint}
initialQuery={initialQuery}
history={history}
onClickHintFix={this.onClickHintFix}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
/>
{QueryField ? (
<QueryField
datasource={datasource}
error={queryError}
hint={hint}
initialQuery={initialQuery}
history={history}
onClickHintFix={this.onClickHintFix}
onPressEnter={this.onPressEnter}
onQueryChange={this.onChangeQuery}
/>
) : (
<QueryEditor
datasource={datasource}
error={queryError}
onQueryChange={this.onChangeQuery}
onExecuteQuery={this.onExecuteQuery}
initialQuery={initialQuery}
exploreEvents={exploreEvents}
range={range}
/>
)}
</div>
<div className="query-row-tools">
<button className="btn navbar-button navbar-button--tight" onClick={this.onClickClearButton}>
......
......@@ -3,7 +3,7 @@ import moment from 'moment';
import * as dateMath from 'app/core/utils/datemath';
import * as rangeUtil from 'app/core/utils/rangeutil';
import { RawTimeRange } from 'app/types/series';
import { RawTimeRange, TimeRange } from 'app/types/series';
const DATE_FORMAT = 'YYYY-MM-DD HH:mm:ss';
export const DEFAULT_RANGE = {
......@@ -120,6 +120,12 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
to: moment(nextTo),
};
const nextTimeRange: TimeRange = {
raw: nextRange,
from,
to,
};
this.setState(
{
rangeString: rangeUtil.describeTimeRange(nextRange),
......@@ -127,7 +133,7 @@ export default class TimePicker extends PureComponent<TimePickerProps, TimePicke
toRaw: nextRange.to.format(DATE_FORMAT),
},
() => {
onChangeTime(nextRange);
onChangeTime(nextTimeRange);
}
);
}
......
<div class="gf-form-query">
<div class="gf-form gf-form-query-letter-cell">
<div ng-if="!ctrl.hideEditorRowActions" class="gf-form gf-form-query-letter-cell">
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.toggleCollapse()">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<span ng-class="{muted: !ctrl.canCollapse}" class="gf-form-query-letter-cell-carret">
<i class="fa fa-caret-down" ng-hide="ctrl.collapsed"></i>
<i class="fa fa-caret-right" ng-show="ctrl.collapsed"></i>
</span>
<span class="gf-form-query-letter-cell-letter">{{ctrl.target.refId}}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ctrl.target.datasource}})</em>
<span class="gf-form-query-letter-cell-letter">{{ ctrl.target.refId }}</span>
<em class="gf-form-query-letter-cell-ds" ng-show="ctrl.target.datasource">({{ ctrl.target.datasource }})</em>
</a>
</label>
</label>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ctrl.collapsedText}}
</label>
</div>
</div>
<div class="gf-form-query-content gf-form-query-content--collapsed" ng-if="ctrl.collapsed">
<div class="gf-form">
<label class="gf-form-label pointer gf-form-label--grow" ng-click="ctrl.toggleCollapse()">
{{ ctrl.collapsedText }}
</label>
</div>
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed">
</div>
<div ng-transclude class="gf-form-query-content" ng-if="!ctrl.collapsed"></div>
<div class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1">
<i class="fa fa-bars"></i>
</a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a>
</li>
<li role="menuitem">
<a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a>
</li>
</ul>
</label>
<div ng-if="!ctrl.hideEditorRowActions" class="gf-form">
<label class="gf-form-label dropdown">
<a class="pointer dropdown-toggle" data-toggle="dropdown" tabindex="1"> <i class="fa fa-bars"></i> </a>
<ul class="dropdown-menu pull-right" role="menu">
<li role="menuitem" ng-if="ctrl.hasTextEditMode">
<a tabindex="1" ng-click="ctrl.toggleEditorMode()">Toggle Edit Mode</a>
</li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.duplicateQuery()">Duplicate</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(-1)">Move up</a></li>
<li role="menuitem"><a tabindex="1" ng-click="ctrl.moveQuery(1)">Move down</a></li>
</ul>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.toggleHideQuery()" role="menuitem"> <i class="fa fa-eye"></i> </a>
</label>
<label class="gf-form-label">
<a ng-click="ctrl.toggleHideQuery()" role="menuitem">
<i class="fa fa-eye"></i>
</a>
</label>
<label class="gf-form-label">
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)">
<i class="fa fa-trash"></i>
</a>
</label>
</div>
<a class="pointer" tabindex="1" ng-click="ctrl.removeQuery(ctrl.target)"> <i class="fa fa-trash"></i> </a>
</label>
</div>
</div>
......@@ -11,11 +11,13 @@ export class QueryRowCtrl {
panelCtrl: any;
panel: any;
collapsed: any;
hideEditorRowActions: boolean;
constructor() {
this.panelCtrl = this.queryCtrl.panelCtrl;
this.target = this.queryCtrl.target;
this.panel = this.panelCtrl.panel;
this.hideEditorRowActions = this.panelCtrl.hideEditorRowActions;
if (!this.target.refId) {
this.target.refId = this.panelCtrl.dashboard.getNextQueryLetter(this.panel);
......
......@@ -93,9 +93,7 @@ export class DatasourceSrv {
getExploreSources() {
const { datasources } = config;
const es = Object.keys(datasources)
.map(name => datasources[name])
.filter(ds => ds.meta && ds.meta.explore);
const es = Object.keys(datasources).map(name => datasources[name]);
return _.sortBy(es, ['name']);
}
......
......@@ -3,13 +3,12 @@
"type": "datasource",
"id": "graphite",
"includes": [
{"type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json"}
],
"includes": [{ "type": "dashboard", "name": "Graphite Carbon Metrics", "path": "dashboards/carbon_metrics.json" }],
"metrics": true,
"alerting": true,
"annotations": true,
"tables": false,
"queryOptions": {
"maxDataPoints": true,
......@@ -27,8 +26,11 @@
"large": "img/graphite_logo.png"
},
"links": [
{"name": "Graphite", "url": "https://graphiteapp.org/"},
{"name": "Graphite 1.1 Release", "url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"}
{ "name": "Graphite", "url": "https://graphiteapp.org/" },
{
"name": "Graphite 1.1 Release",
"url": "https://grafana.com/blog/2018/01/11/graphite-1.1-teaching-an-old-dog-new-tricks/"
}
],
"version": "5.0.0"
}
......
......@@ -7,6 +7,7 @@
"annotations": false,
"logs": true,
"explore": true,
"tables": true,
"info": {
"description": "Grafana Logging Data Source for Grafana",
"author": {
......@@ -25,4 +26,4 @@
],
"version": "5.3.0"
}
}
\ No newline at end of file
}
......@@ -18,9 +18,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
......@@ -19,9 +19,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
......@@ -7,6 +7,7 @@
"defaultMatchFormat": "pipe",
"annotations": true,
"alerting": true,
"tables": false,
"info": {
"description": "OpenTSDB Data Source for Grafana",
......
......@@ -19,9 +19,9 @@
"alerting": true,
"annotations": true,
"metrics": true,
"tables": true,
"queryOptions": {
"minInterval": true
}
}
......@@ -23,6 +23,7 @@
"alerting": true,
"annotations": true,
"explore": true,
"tables": true,
"queryOptions": {
"minInterval": true
},
......@@ -44,4 +45,4 @@
],
"version": "5.0.0"
}
}
\ No newline at end of file
}
......@@ -5,6 +5,7 @@
"metrics": true,
"alerting": true,
"annotations": true,
"tables": false,
"state": "beta",
"queryOptions": {
"maxDataPoints": true,
......
......@@ -41,6 +41,7 @@ export interface PluginMeta {
// Datasource-specific
metrics?: boolean;
tables?: boolean;
logs?: boolean;
explore?: boolean;
annotations?: boolean;
......
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment