From 2a6ac88a73e8f7624d326251c8073accc4712efc Mon Sep 17 00:00:00 2001
From: Ryan McKinley
Date: Fri, 29 May 2020 11:06:33 -0700
Subject: [PATCH] QueryInspector: add common way to show the raw query (#25204)
---
packages/grafana-data/src/types/data.ts | 7 +-
pkg/tsdb/mssql/mssql_test.go | 4 +-
pkg/tsdb/mysql/mysql_test.go | 4 +-
pkg/tsdb/postgres/postgres_test.go | 4 +-
pkg/tsdb/sqleng/sql_engine.go | 5 +-
pkg/tsdb/stackdriver/stackdriver.go | 3 +-
.../components/Inspector/PanelInspector.tsx | 2 +-
.../components/Inspector/QueryInspector.tsx | 85 ++++++++++++++++++-
.../mssql/partials/query.editor.html | 7 +-
.../plugins/datasource/mssql/query_ctrl.ts | 19 ++---
.../mysql/partials/query.editor.html | 11 +--
.../plugins/datasource/mysql/query_ctrl.ts | 17 ++--
.../postgres/partials/query.editor.html | 10 +--
.../plugins/datasource/postgres/query_ctrl.ts | 17 ++--
.../stackdriver/components/QueryEditor.tsx | 5 +-
15 files changed, 140 insertions(+), 60 deletions(-)
diff --git a/packages/grafana-data/src/types/data.ts b/packages/grafana-data/src/types/data.ts
index 3927be404e0..04af0024e30 100644
--- a/packages/grafana-data/src/types/data.ts
+++ b/packages/grafana-data/src/types/data.ts
@@ -33,11 +33,16 @@ export interface QueryResultMeta {
/** Currently used to show results in Explore only in preferred visualisation option */
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
* */
gmdMeta?: any[]; // used by cloudwatch
- rawQuery?: string; // used by stackdriver
alignmentPeriod?: string; // used by stackdriver
query?: string; // used by azure log
searchWords?: string[]; // used by log models and loki
diff --git a/pkg/tsdb/mssql/mssql_test.go b/pkg/tsdb/mssql/mssql_test.go
index a36d2d8fb75..1819137129c 100644
--- a/pkg/tsdb/mssql/mssql_test.go
+++ b/pkg/tsdb/mssql/mssql_test.go
@@ -333,7 +333,7 @@ func TestMSSQL(t *testing.T) {
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
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)
queryResult := resp.Results["A"]
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")
})
diff --git a/pkg/tsdb/mysql/mysql_test.go b/pkg/tsdb/mysql/mysql_test.go
index 720aca8cc36..19e75cd351f 100644
--- a/pkg/tsdb/mysql/mysql_test.go
+++ b/pkg/tsdb/mysql/mysql_test.go
@@ -336,7 +336,7 @@ func TestMySQL(t *testing.T) {
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
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)
queryResult := resp.Results["A"]
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")
})
diff --git a/pkg/tsdb/postgres/postgres_test.go b/pkg/tsdb/postgres/postgres_test.go
index edf87eff1dc..92d0481d631 100644
--- a/pkg/tsdb/postgres/postgres_test.go
+++ b/pkg/tsdb/postgres/postgres_test.go
@@ -261,7 +261,7 @@ func TestPostgres(t *testing.T) {
So(err, ShouldBeNil)
queryResult := resp.Results["A"]
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)
queryResult := resp.Results["A"]
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")
})
})
diff --git a/pkg/tsdb/sqleng/sql_engine.go b/pkg/tsdb/sqleng/sql_engine.go
index 730d366be09..0d66fc06b32 100644
--- a/pkg/tsdb/sqleng/sql_engine.go
+++ b/pkg/tsdb/sqleng/sql_engine.go
@@ -25,6 +25,9 @@ import (
"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
// timeRange to be able to generate queries that use from and to.
type SqlMacroEngine interface {
@@ -153,7 +156,7 @@ func (e *sqlQueryEndpoint) Query(ctx context.Context, dsInfo *models.DataSource,
continue
}
- queryResult.Meta.Set("sql", rawSQL)
+ queryResult.Meta.Set(MetaKeyExecutedQueryString, rawSQL)
wg.Add(1)
diff --git a/pkg/tsdb/stackdriver/stackdriver.go b/pkg/tsdb/stackdriver/stackdriver.go
index 9fccdc5dc51..db7fc94c588 100644
--- a/pkg/tsdb/stackdriver/stackdriver.go
+++ b/pkg/tsdb/stackdriver/stackdriver.go
@@ -23,6 +23,7 @@ import (
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/tsdb"
+ "github.com/grafana/grafana/pkg/tsdb/sqleng"
"github.com/opentracing/opentracing-go"
"golang.org/x/net/context/ctxhttp"
"golang.org/x/oauth2/google"
@@ -328,7 +329,7 @@ func (e *StackdriverExecutor) executeQuery(ctx context.Context, query *stackdriv
}
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"]
if ok {
diff --git a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx
index bed7fe01f3e..974eb0b9f49 100644
--- a/public/app/features/dashboard/components/Inspector/PanelInspector.tsx
+++ b/public/app/features/dashboard/components/Inspector/PanelInspector.tsx
@@ -354,7 +354,7 @@ export class PanelInspectorUnconnected extends PureComponent {
)}
{activeTab === InspectTab.Error && this.renderErrorTab(error)}
{activeTab === InspectTab.Stats && this.renderStatsTab()}
- {activeTab === InspectTab.Query && }
+ {activeTab === InspectTab.Query && }
diff --git a/public/app/features/dashboard/components/Inspector/QueryInspector.tsx b/public/app/features/dashboard/components/Inspector/QueryInspector.tsx
index 0f71b6767c6..a5df84432d3 100644
--- a/public/app/features/dashboard/components/Inspector/QueryInspector.tsx
+++ b/public/app/features/dashboard/components/Inspector/QueryInspector.tsx
@@ -1,7 +1,7 @@
import React, { PureComponent } from 'react';
import { Button, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
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 { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
@@ -9,14 +9,24 @@ import { CoreEvents } from 'app/types';
import { PanelModel } from 'app/features/dashboard/state';
import { getPanelInspectorStyles } from './styles';
import { supportsDataQuery } from '../PanelEditor/utils';
+import { config } from '@grafana/runtime';
+import { css } from 'emotion';
interface DsQuery {
isLoading: boolean;
response: {};
}
+interface ExecutedQueryInfo {
+ refId: string;
+ query: string;
+ frames: number;
+ rows: number;
+}
+
interface Props {
panel: PanelModel;
+ data: DataFrame[];
}
interface State {
@@ -24,6 +34,7 @@ interface State {
isMocking: boolean;
mockedResponse: string;
dsQuery: DsQuery;
+ executedQueries: ExecutedQueryInfo[];
}
export class QueryInspector extends PureComponent {
@@ -33,6 +44,7 @@ export class QueryInspector extends PureComponent {
constructor(props: Props) {
super(props);
this.state = {
+ executedQueries: [],
allNodesExpanded: null,
isMocking: false,
mockedResponse: '',
@@ -47,6 +59,43 @@ export class QueryInspector extends PureComponent {
appEvents.on(CoreEvents.dsRequestResponse, this.onDataSourceResponse);
appEvents.on(CoreEvents.dsRequestError, this.onRequestError);
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 = () => {
@@ -182,8 +231,39 @@ export class QueryInspector extends PureComponent {
}));
};
+ 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 (
+
+ {executedQueries.map(info => {
+ return (
+
+
+ {info.refId}:
+ {info.frames > 1 && {info.frames} frames, }
+ {info.rows} rows
+
+
{info.query}
+
+ );
+ })}
+
+ );
+ }
+
render() {
- const { allNodesExpanded } = this.state;
+ const { allNodesExpanded, executedQueries } = this.state;
const { response, isLoading } = this.state.dsQuery;
const openNodes = this.getNrOfOpenNodes();
const styles = getPanelInspectorStyles();
@@ -202,6 +282,7 @@ export class QueryInspector extends PureComponent {
new query. Hit refresh button below to trigger a new query.
+ {this.renderExecutedQueries(executedQueries)}
-