mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
QueryInspector: add common way to show the raw query (#25204)
This commit is contained in:
@@ -354,7 +354,7 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
|
||||
)}
|
||||
{activeTab === InspectTab.Error && this.renderErrorTab(error)}
|
||||
{activeTab === InspectTab.Stats && this.renderStatsTab()}
|
||||
{activeTab === InspectTab.Query && <QueryInspector panel={panel} />}
|
||||
{activeTab === InspectTab.Query && <QueryInspector panel={panel} data={last.series} />}
|
||||
</TabContent>
|
||||
</CustomScrollbar>
|
||||
</Drawer>
|
||||
|
||||
@@ -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<Props, State> {
|
||||
@@ -33,6 +44,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
executedQueries: [],
|
||||
allNodesExpanded: null,
|
||||
isMocking: false,
|
||||
mockedResponse: '',
|
||||
@@ -47,6 +59,43 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
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<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() {
|
||||
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<Props, State> {
|
||||
new query. Hit refresh button below to trigger a new query.
|
||||
</p>
|
||||
</div>
|
||||
{this.renderExecutedQueries(executedQueries)}
|
||||
<div className={styles.toolbar}>
|
||||
<Button
|
||||
icon="sync"
|
||||
|
||||
@@ -20,11 +20,10 @@
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword" ng-click="ctrl.showQueryInspector()">
|
||||
Generated SQL
|
||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
|
||||
@@ -2,6 +2,7 @@ import _ from 'lodash';
|
||||
import { QueryCtrl } from 'app/plugins/sdk';
|
||||
import { auto } from 'angular';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
|
||||
export interface MssqlQuery {
|
||||
refId: string;
|
||||
@@ -28,10 +29,8 @@ ORDER BY
|
||||
export class MssqlQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
showLastQuerySQL: boolean;
|
||||
formats: any[];
|
||||
target: MssqlQuery;
|
||||
lastQueryMeta: QueryMeta;
|
||||
lastQueryError: string;
|
||||
showHelp: boolean;
|
||||
|
||||
@@ -60,21 +59,21 @@ export class MssqlQueryCtrl extends QueryCtrl {
|
||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||
}
|
||||
|
||||
onDataReceived(dataList: any) {
|
||||
this.lastQueryMeta = null;
|
||||
this.lastQueryError = null;
|
||||
showQueryInspector() {
|
||||
getLocationSrv().update({
|
||||
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
|
||||
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
||||
if (anySeriesFromQuery) {
|
||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
||||
}
|
||||
onDataReceived(dataList: any) {
|
||||
this.lastQueryError = null;
|
||||
}
|
||||
|
||||
onDataError(err: any) {
|
||||
if (err.data && err.data.results) {
|
||||
const queryRes = err.data.results[this.target.refId];
|
||||
if (queryRes) {
|
||||
this.lastQueryMeta = queryRes.meta;
|
||||
this.lastQueryError = queryRes.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +120,10 @@
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showQueryInspector()">
|
||||
Generated SQL
|
||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
@@ -132,10 +131,6 @@
|
||||
</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">
|
||||
<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.
|
||||
|
||||
@@ -10,6 +10,7 @@ import { TemplateSrv } from 'app/features/templating/template_srv';
|
||||
import { CoreEvents } from 'app/types';
|
||||
import { PanelEvents } from '@grafana/data';
|
||||
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
|
||||
export interface QueryMeta {
|
||||
sql: string;
|
||||
@@ -27,9 +28,7 @@ ORDER BY <time_column> ASC
|
||||
export class MysqlQueryCtrl extends QueryCtrl {
|
||||
static templateUrl = 'partials/query.editor.html';
|
||||
|
||||
showLastQuerySQL: boolean;
|
||||
formats: any[];
|
||||
lastQueryMeta: QueryMeta;
|
||||
lastQueryError: string;
|
||||
showHelp: boolean;
|
||||
|
||||
@@ -110,6 +109,13 @@ export class MysqlQueryCtrl extends QueryCtrl {
|
||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||
}
|
||||
|
||||
showQueryInspector() {
|
||||
getLocationSrv().update({
|
||||
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
|
||||
updateRawSqlAndRefresh() {
|
||||
if (!this.target.rawQuery) {
|
||||
this.target.rawSql = this.queryModel.buildQuery();
|
||||
@@ -273,20 +279,13 @@ export class MysqlQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
onDataReceived(dataList: any) {
|
||||
this.lastQueryMeta = null;
|
||||
this.lastQueryError = null;
|
||||
|
||||
const anySeriesFromQuery: any = _.find(dataList, { refId: this.target.refId });
|
||||
if (anySeriesFromQuery) {
|
||||
this.lastQueryMeta = anySeriesFromQuery.meta;
|
||||
}
|
||||
}
|
||||
|
||||
onDataError(err: any) {
|
||||
if (err.data && err.data.results) {
|
||||
const queryRes = err.data.results[this.target.refId];
|
||||
if (queryRes) {
|
||||
this.lastQueryMeta = queryRes.meta;
|
||||
this.lastQueryError = queryRes.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,11 +120,10 @@
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showHelp" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form" ng-show="ctrl.lastQueryMeta">
|
||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showLastQuerySQL = !ctrl.showLastQuerySQL">
|
||||
<div class="gf-form">
|
||||
<label class="gf-form-label query-keyword pointer" ng-click="ctrl.showQueryInspector()">
|
||||
Generated SQL
|
||||
<icon name="'angle-down'" ng-show="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" ng-hide="ctrl.showLastQuerySQL" style="margin-top: 3px;"></icon>
|
||||
<icon name="'angle-right'" style="margin-top: 3px;"></icon>
|
||||
</label>
|
||||
</div>
|
||||
<div class="gf-form gf-form--grow">
|
||||
@@ -132,9 +131,6 @@
|
||||
</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">
|
||||
<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 { PanelEvents } from '@grafana/data';
|
||||
import { VariableWithMultiSupport } from 'app/features/templating/types';
|
||||
import { getLocationSrv } from '@grafana/runtime';
|
||||
|
||||
export interface QueryMeta {
|
||||
sql: string;
|
||||
@@ -31,7 +32,6 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
||||
formats: any[];
|
||||
queryModel: PostgresQuery;
|
||||
metaBuilder: PostgresMetaQuery;
|
||||
lastQueryMeta: QueryMeta;
|
||||
lastQueryError: string;
|
||||
showHelp: boolean;
|
||||
tableSegment: any;
|
||||
@@ -108,6 +108,13 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
||||
this.panelCtrl.events.on(PanelEvents.dataError, this.onDataError.bind(this), $scope);
|
||||
}
|
||||
|
||||
showQueryInspector() {
|
||||
getLocationSrv().update({
|
||||
query: { inspect: this.panel.id, inspectTab: 'query' },
|
||||
partial: true,
|
||||
});
|
||||
}
|
||||
|
||||
updateRawSqlAndRefresh() {
|
||||
if (!this.target.rawQuery) {
|
||||
this.target.rawSql = this.queryModel.buildQuery();
|
||||
@@ -306,21 +313,13 @@ export class PostgresQueryCtrl extends QueryCtrl {
|
||||
}
|
||||
|
||||
onDataReceived(dataList: any) {
|
||||
this.lastQueryMeta = 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) {
|
||||
if (err.data && err.data.results) {
|
||||
const queryRes = err.data.results[this.target.refId];
|
||||
if (queryRes) {
|
||||
this.lastQueryMeta = queryRes.meta;
|
||||
this.lastQueryError = queryRes.error;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +107,10 @@ export class QueryEditor extends PureComponent<Props, State> {
|
||||
query={sloQuery}
|
||||
></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