mirror of
https://github.com/grafana/grafana.git
synced 2025-07-30 12:22:15 +08:00
QueryInspector: add common way to show the raw query (#25204)
This commit is contained in:
@ -33,11 +33,16 @@ export interface QueryResultMeta {
|
|||||||
/** Currently used to show results in Explore only in preferred visualisation option */
|
/** Currently used to show results in Explore only in preferred visualisation option */
|
||||||
preferredVisualisationType?: PreferredVisualisationType;
|
preferredVisualisationType?: PreferredVisualisationType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is the raw query sent to the underlying system. All macros and templating
|
||||||
|
* as been applied. When metadata contains this value, it will be shown in the query inspector
|
||||||
|
*/
|
||||||
|
executedQueryString?: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Legacy data source specific, should be moved to custom
|
* Legacy data source specific, should be moved to custom
|
||||||
* */
|
* */
|
||||||
gmdMeta?: any[]; // used by cloudwatch
|
gmdMeta?: any[]; // used by cloudwatch
|
||||||
rawQuery?: string; // used by stackdriver
|
|
||||||
alignmentPeriod?: string; // used by stackdriver
|
alignmentPeriod?: string; // used by stackdriver
|
||||||
query?: string; // used by azure log
|
query?: string; // used by azure log
|
||||||
searchWords?: string[]; // used by log models and loki
|
searchWords?: string[]; // used by log models and loki
|
||||||
|
@ -333,7 +333,7 @@ func TestMSSQL(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY FLOOR(DATEDIFF(second, '1970-01-01', time)/60)*60 ORDER BY 1")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -698,7 +698,7 @@ func TestMSSQL(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -336,7 +336,7 @@ func TestMySQL(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT UNIX_TIMESTAMP(time) DIV 60 * 60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT UNIX_TIMESTAMP(time) DIV 60 * 60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -778,7 +778,7 @@ func TestMySQL(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > FROM_UNIXTIME(1521118500) OR time < FROM_UNIXTIME(1521118800) OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > FROM_UNIXTIME(1521118500) OR time < FROM_UNIXTIME(1521118800) OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
@ -261,7 +261,7 @@ func TestPostgres(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT floor(extract(epoch from time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT floor(extract(epoch from time)/60)*60 AS time, avg(value) as value FROM metric GROUP BY 1 ORDER BY 1")
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -708,7 +708,7 @@ func TestPostgres(t *testing.T) {
|
|||||||
So(err, ShouldBeNil)
|
So(err, ShouldBeNil)
|
||||||
queryResult := resp.Results["A"]
|
queryResult := resp.Results["A"]
|
||||||
So(queryResult.Error, ShouldBeNil)
|
So(queryResult.Error, ShouldBeNil)
|
||||||
So(queryResult.Meta.Get("sql").MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
So(queryResult.Meta.Get(sqleng.MetaKeyExecutedQueryString).MustString(), ShouldEqual, "SELECT time FROM metric_values WHERE time > '2018-03-15T12:55:00Z' OR time < '2018-03-15T12:55:00Z' OR 1 < 1521118500 OR 1521118800 > 1 ORDER BY 1")
|
||||||
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -25,6 +25,9 @@ import (
|
|||||||
"xorm.io/xorm"
|
"xorm.io/xorm"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// MetaKeyExecutedQueryString is the key where the executed query should get stored
|
||||||
|
const MetaKeyExecutedQueryString = "executedQueryString"
|
||||||
|
|
||||||
// SqlMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and
|
// SqlMacroEngine interpolates macros into sql. It takes in the Query to have access to query context and
|
||||||
// timeRange to be able to generate queries that use from and to.
|
// timeRange to be able to generate queries that use from and to.
|
||||||
type SqlMacroEngine interface {
|
type SqlMacroEngine interface {
|
||||||
@ -153,7 +156,7 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
queryResult.Meta.Set("sql", rawSQL)
|
queryResult.Meta.Set(MetaKeyExecutedQueryString, rawSQL)
|
||||||
|
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/plugins"
|
"github.com/grafana/grafana/pkg/plugins"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
"github.com/grafana/grafana/pkg/tsdb"
|
"github.com/grafana/grafana/pkg/tsdb"
|
||||||
|
"github.com/grafana/grafana/pkg/tsdb/sqleng"
|
||||||
"github.com/opentracing/opentracing-go"
|
"github.com/opentracing/opentracing-go"
|
||||||
"golang.org/x/net/context/ctxhttp"
|
"golang.org/x/net/context/ctxhttp"
|
||||||
"golang.org/x/oauth2/google"
|
"golang.org/x/oauth2/google"
|
||||||
@ -328,7 +329,7 @@ func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *stackdriv
|
|||||||
}
|
}
|
||||||
|
|
||||||
req.URL.RawQuery = query.Params.Encode()
|
req.URL.RawQuery = query.Params.Encode()
|
||||||
queryResult.Meta.Set("rawQuery", req.URL.RawQuery)
|
queryResult.Meta.Set(sqleng.MetaKeyExecutedQueryString, req.URL.RawQuery)
|
||||||
alignmentPeriod, ok := req.URL.Query()["aggregation.alignmentPeriod"]
|
alignmentPeriod, ok := req.URL.Query()["aggregation.alignmentPeriod"]
|
||||||
|
|
||||||
if ok {
|
if ok {
|
||||||
|
@ -354,7 +354,7 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
|
|||||||
)}
|
)}
|
||||||
{activeTab === InspectTab.Error && this.renderErrorTab(error)}
|
{activeTab === InspectTab.Error && this.renderErrorTab(error)}
|
||||||
{activeTab === InspectTab.Stats && this.renderStatsTab()}
|
{activeTab === InspectTab.Stats && this.renderStatsTab()}
|
||||||
{activeTab === InspectTab.Query && <QueryInspector panel={panel} />}
|
{activeTab === InspectTab.Query && <QueryInspector panel={panel} data={last.series} />}
|
||||||
</TabContent>
|
</TabContent>
|
||||||
</CustomScrollbar>
|
</CustomScrollbar>
|
||||||
</Drawer>
|
</Drawer>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import { Button, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
import { Button, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
|
||||||
import { selectors } from '@grafana/e2e-selectors';
|
import { selectors } from '@grafana/e2e-selectors';
|
||||||
import { AppEvents, PanelEvents } from '@grafana/data';
|
import { AppEvents, PanelEvents, DataFrame } from '@grafana/data';
|
||||||
|
|
||||||
import appEvents from 'app/core/app_events';
|
import appEvents from 'app/core/app_events';
|
||||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||||
@ -9,14 +9,24 @@ import { CoreEvents } from 'app/types';
|
|||||||
import { PanelModel } from 'app/features/dashboard/state';
|
import { PanelModel } from 'app/features/dashboard/state';
|
||||||
import { getPanelInspectorStyles } from './styles';
|
import { getPanelInspectorStyles } from './styles';
|
||||||
import { supportsDataQuery } from '../PanelEditor/utils';
|
import { supportsDataQuery } from '../PanelEditor/utils';
|
||||||
|
import { config } from '@grafana/runtime';
|
||||||
|
import { css } from 'emotion';
|
||||||
|
|
||||||
interface DsQuery {
|
interface DsQuery {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
response: {};
|
response: {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ExecutedQueryInfo {
|
||||||
|
refId: string;
|
||||||
|
query: string;
|
||||||
|
frames: number;
|
||||||
|
rows: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
panel: PanelModel;
|
panel: PanelModel;
|
||||||
|
data: DataFrame[];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface State {
|
interface State {
|
||||||
@ -24,6 +34,7 @@ interface State {
|
|||||||
isMocking: boolean;
|
isMocking: boolean;
|
||||||
mockedResponse: string;
|
mockedResponse: string;
|
||||||
dsQuery: DsQuery;
|
dsQuery: DsQuery;
|
||||||
|
executedQueries: ExecutedQueryInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export class QueryInspector extends PureComponent<Props, State> {
|
export class QueryInspector extends PureComponent<Props, State> {
|
||||||
@ -33,6 +44,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
constructor(props: Props) {
|
constructor(props: Props) {
|
||||||
super(props);
|
super(props);
|
||||||
this.state = {
|
this.state = {
|
||||||
|
executedQueries: [],
|
||||||
allNodesExpanded: null,
|
allNodesExpanded: null,
|
||||||
isMocking: false,
|
isMocking: false,
|
||||||
mockedResponse: '',
|
mockedResponse: '',
|
||||||
@ -47,6 +59,43 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
appEvents.on(CoreEvents.dsRequestResponse, this.onDataSourceResponse);
|
appEvents.on(CoreEvents.dsRequestResponse, this.onDataSourceResponse);
|
||||||
appEvents.on(CoreEvents.dsRequestError, this.onRequestError);
|
appEvents.on(CoreEvents.dsRequestError, this.onRequestError);
|
||||||
this.props.panel.events.on(PanelEvents.refresh, this.onPanelRefresh);
|
this.props.panel.events.on(PanelEvents.refresh, this.onPanelRefresh);
|
||||||
|
this.updateQueryList();
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(oldProps: Props) {
|
||||||
|
if (this.props.data !== oldProps.data) {
|
||||||
|
this.updateQueryList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find the list of executed queries
|
||||||
|
*/
|
||||||
|
updateQueryList() {
|
||||||
|
const { data } = this.props;
|
||||||
|
const executedQueries: ExecutedQueryInfo[] = [];
|
||||||
|
if (data?.length) {
|
||||||
|
let last: ExecutedQueryInfo | undefined = undefined;
|
||||||
|
data.forEach((frame, idx) => {
|
||||||
|
const query = frame.meta?.executedQueryString;
|
||||||
|
if (query) {
|
||||||
|
const refId = frame.refId || '?';
|
||||||
|
if (last?.refId === refId) {
|
||||||
|
last.frames++;
|
||||||
|
last.rows += frame.length;
|
||||||
|
} else {
|
||||||
|
last = {
|
||||||
|
refId,
|
||||||
|
frames: 0,
|
||||||
|
rows: frame.length,
|
||||||
|
query,
|
||||||
|
};
|
||||||
|
executedQueries.push(last);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
this.setState({ executedQueries });
|
||||||
}
|
}
|
||||||
|
|
||||||
onIssueNewQuery = () => {
|
onIssueNewQuery = () => {
|
||||||
@ -182,8 +231,39 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
renderExecutedQueries(executedQueries: ExecutedQueryInfo[]) {
|
||||||
|
if (!executedQueries.length) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const styles = {
|
||||||
|
refId: css`
|
||||||
|
font-weight: ${config.theme.typography.weight.semibold};
|
||||||
|
color: ${config.theme.colors.textBlue};
|
||||||
|
margin-right: 8px;
|
||||||
|
`,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
{executedQueries.map(info => {
|
||||||
|
return (
|
||||||
|
<div key={info.refId}>
|
||||||
|
<div>
|
||||||
|
<span className={styles.refId}>{info.refId}:</span>
|
||||||
|
{info.frames > 1 && <span>{info.frames} frames, </span>}
|
||||||
|
<span>{info.rows} rows</span>
|
||||||
|
</div>
|
||||||
|
<pre>{info.query}</pre>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { allNodesExpanded } = this.state;
|
const { allNodesExpanded, executedQueries } = this.state;
|
||||||
const { response, isLoading } = this.state.dsQuery;
|
const { response, isLoading } = this.state.dsQuery;
|
||||||
const openNodes = this.getNrOfOpenNodes();
|
const openNodes = this.getNrOfOpenNodes();
|
||||||
const styles = getPanelInspectorStyles();
|
const styles = getPanelInspectorStyles();
|
||||||
@ -202,6 +282,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
|||||||
new query. Hit refresh button below to trigger a new query.
|
new query. Hit refresh button below to trigger a new query.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
{this.renderExecutedQueries(executedQueries)}
|
||||||
<div className={styles.toolbar}>
|
<div className={styles.toolbar}>
|
||||||
<Button
|
<Button
|
||||||
icon="sync"
|
icon="sync"
|
||||||
|
@ -20,11 +20,10 @@
|
|||||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
<label class="gf-form-label query-keyword" ng-click="ctrl.showQueryInspector()">
|
||||||
Generated SQL
|
Generated SQL
|
||||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
|
@ -2,6 +2,7 @@ import _ from 'lodash';
|
|||||||
import { QueryCtrl } from 'app/plugins/sdk';
|
import { QueryCtrl } from 'app/plugins/sdk';
|
||||||
import { auto } from 'angular';
|
import { auto } from 'angular';
|
||||||
import { PanelEvents } from '@grafana/data';
|
import { PanelEvents } from '@grafana/data';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export interface MssqlQuery {
|
export interface MssqlQuery {
|
||||||
refId: string;
|
refId: string;
|
||||||
@ -28,10 +29,8 @@ ORDER BY
|
|||||||
export class MssqlQueryCtrl extends QueryCtrl {
|
export class MssqlQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
|
|
||||||
showLastQuerySQL: boolean;
|
|
||||||
formats: any[];
|
formats: any[];
|
||||||
target: MssqlQuery;
|
target: MssqlQuery;
|
||||||
lastQueryMeta: QueryMeta;
|
|
||||||
lastQueryError: string;
|
lastQueryError: string;
|
||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
|
|
||||||
@ -60,21 +59,21 @@ export class MssqlQueryCtrl extends QueryCtrl {
|
|||||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
onDataReceived(dataList: any) {
|
showQueryInspector() {
|
||||||
this.lastQueryMeta = null;
|
getLocationSrv().update({
|
||||||
this.lastQueryError = null;
|
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
onDataReceived(dataList: any) {
|
||||||
if (anySeriesFromQuery) {
|
this.lastQueryError = null;
|
||||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDataError(err: any) {
|
onDataError(err: any) {
|
||||||
if (err.data && err.data.results) {
|
if (err.data && err.data.results) {
|
||||||
const queryRes = err.data.results[this.target.refId];
|
const queryRes = err.data.results[this.target.refId];
|
||||||
if (queryRes) {
|
if (queryRes) {
|
||||||
this.lastQueryMeta = queryRes.meta;
|
|
||||||
this.lastQueryError = queryRes.error;
|
this.lastQueryError = queryRes.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,10 @@
|
|||||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showQueryInspector()">
|
||||||
Generated SQL
|
Generated SQL
|
||||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
@ -132,10 +131,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form" ng-show="ctrl.showLastQuerySQL">
|
|
||||||
<pre class="gf-form-pre">{{ctrl.lastQueryMeta.sql}}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||||
<pre class="gf-form-pre alert alert-info">Time series:
|
<pre class="gf-form-pre alert alert-info">Time series:
|
||||||
- return column named time or time_sec (in UTC), as a unix time stamp or any sql native date data type. You can use the macros below.
|
- return column named time or time_sec (in UTC), as a unix time stamp or any sql native date data type. You can use the macros below.
|
||||||
|
@ -10,6 +10,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
|||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { PanelEvents } from '@grafana/data';
|
import { PanelEvents } from '@grafana/data';
|
||||||
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export interface QueryMeta {
|
export interface QueryMeta {
|
||||||
sql: string;
|
sql: string;
|
||||||
@ -27,9 +28,7 @@ ORDER BY <time_column> ASC
|
|||||||
export class MysqlQueryCtrl extends QueryCtrl {
|
export class MysqlQueryCtrl extends QueryCtrl {
|
||||||
static templateUrl = 'partials/query.editor.html';
|
static templateUrl = 'partials/query.editor.html';
|
||||||
|
|
||||||
showLastQuerySQL: boolean;
|
|
||||||
formats: any[];
|
formats: any[];
|
||||||
lastQueryMeta: QueryMeta;
|
|
||||||
lastQueryError: string;
|
lastQueryError: string;
|
||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
|
|
||||||
@ -110,6 +109,13 @@ export class MysqlQueryCtrl extends QueryCtrl {
|
|||||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showQueryInspector() {
|
||||||
|
getLocationSrv().update({
|
||||||
|
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateRawSqlAndRefresh() {
|
updateRawSqlAndRefresh() {
|
||||||
if (!this.target.rawQuery) {
|
if (!this.target.rawQuery) {
|
||||||
this.target.rawSql = this.queryModel.buildQuery();
|
this.target.rawSql = this.queryModel.buildQuery();
|
||||||
@ -273,20 +279,13 @@ export class MysqlQueryCtrl extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDataReceived(dataList: any) {
|
onDataReceived(dataList: any) {
|
||||||
this.lastQueryMeta = null;
|
|
||||||
this.lastQueryError = null;
|
this.lastQueryError = null;
|
||||||
|
|
||||||
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
|
||||||
if (anySeriesFromQuery) {
|
|
||||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDataError(err: any) {
|
onDataError(err: any) {
|
||||||
if (err.data && err.data.results) {
|
if (err.data && err.data.results) {
|
||||||
const queryRes = err.data.results[this.target.refId];
|
const queryRes = err.data.results[this.target.refId];
|
||||||
if (queryRes) {
|
if (queryRes) {
|
||||||
this.lastQueryMeta = queryRes.meta;
|
|
||||||
this.lastQueryError = queryRes.error;
|
this.lastQueryError = queryRes.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -120,11 +120,10 @@
|
|||||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
<div class="gf-form">
|
||||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showQueryInspector()">
|
||||||
Generated SQL
|
Generated SQL
|
||||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
<div class="gf-form gf-form--grow">
|
<div class="gf-form gf-form--grow">
|
||||||
@ -132,9 +131,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="gf-form" ng-show="ctrl.showLastQuerySQL">
|
|
||||||
<pre class="gf-form-pre">{{ctrl.lastQueryMeta.sql}}</pre>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="gf-form" ng-show="ctrl.showHelp">
|
<div class="gf-form" ng-show="ctrl.showHelp">
|
||||||
<pre class="gf-form-pre alert alert-info">Time series:
|
<pre class="gf-form-pre alert alert-info">Time series:
|
||||||
|
@ -10,6 +10,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
|||||||
import { CoreEvents } from 'app/types';
|
import { CoreEvents } from 'app/types';
|
||||||
import { PanelEvents } from '@grafana/data';
|
import { PanelEvents } from '@grafana/data';
|
||||||
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
||||||
|
import { getLocationSrv } from '@grafana/runtime';
|
||||||
|
|
||||||
export interface QueryMeta {
|
export interface QueryMeta {
|
||||||
sql: string;
|
sql: string;
|
||||||
@ -31,7 +32,6 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
|||||||
formats: any[];
|
formats: any[];
|
||||||
queryModel: PostgresQuery;
|
queryModel: PostgresQuery;
|
||||||
metaBuilder: PostgresMetaQuery;
|
metaBuilder: PostgresMetaQuery;
|
||||||
lastQueryMeta: QueryMeta;
|
|
||||||
lastQueryError: string;
|
lastQueryError: string;
|
||||||
showHelp: boolean;
|
showHelp: boolean;
|
||||||
tableSegment: any;
|
tableSegment: any;
|
||||||
@ -108,6 +108,13 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
|||||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
showQueryInspector() {
|
||||||
|
getLocationSrv().update({
|
||||||
|
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||||
|
partial: true,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
updateRawSqlAndRefresh() {
|
updateRawSqlAndRefresh() {
|
||||||
if (!this.target.rawQuery) {
|
if (!this.target.rawQuery) {
|
||||||
this.target.rawSql = this.queryModel.buildQuery();
|
this.target.rawSql = this.queryModel.buildQuery();
|
||||||
@ -306,21 +313,13 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
|||||||
}
|
}
|
||||||
|
|
||||||
onDataReceived(dataList: any) {
|
onDataReceived(dataList: any) {
|
||||||
this.lastQueryMeta = null;
|
|
||||||
this.lastQueryError = null;
|
this.lastQueryError = null;
|
||||||
console.log('postgres query data received', dataList);
|
|
||||||
|
|
||||||
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
|
||||||
if (anySeriesFromQuery) {
|
|
||||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onDataError(err: any) {
|
onDataError(err: any) {
|
||||||
if (err.data && err.data.results) {
|
if (err.data && err.data.results) {
|
||||||
const queryRes = err.data.results[this.target.refId];
|
const queryRes = err.data.results[this.target.refId];
|
||||||
if (queryRes) {
|
if (queryRes) {
|
||||||
this.lastQueryMeta = queryRes.meta;
|
|
||||||
this.lastQueryError = queryRes.error;
|
this.lastQueryError = queryRes.error;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -107,7 +107,10 @@ export class QueryEditor extends PureComponent<Props, State> {
|
|||||||
query={sloQuery}
|
query={sloQuery}
|
||||||
></SLOQueryEditor>
|
></SLOQueryEditor>
|
||||||
)}
|
)}
|
||||||
<Help rawQuery={decodeURIComponent(meta?.rawQuery ?? '')} lastQueryError={this.state.lastQueryError} />
|
<Help
|
||||||
|
rawQuery={decodeURIComponent(meta?.executedQueryString ?? '')}
|
||||||
|
lastQueryError={this.state.lastQueryError}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user