DashboardScene: Inspect / query tab (#74795)

This commit is contained in:
Torkel Ödegaard 2023-09-14 12:45:13 +02:00 committed by GitHub
parent 8874a8d398
commit 05f01dee0c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 72 additions and 63 deletions

View File

@ -125,7 +125,7 @@ export class InspectJsonTab extends SceneObjectBase<InspectJsonTabState> {
panel_type_changed: panel.state.pluginId !== panelModel.type, panel_type_changed: panel.state.pluginId !== panelModel.type,
panel_id_changed: getPanelIdForVizPanel(panel) !== panelModel.id, panel_id_changed: getPanelIdForVizPanel(panel) !== panelModel.id,
panel_grid_pos_changed: hasGridPosChanged(panel.parent.state, newState), panel_grid_pos_changed: hasGridPosChanged(panel.parent.state, newState),
panel_targets_changed: hasQueriesChanged(getQueryRunnerFor(panel), getQueryRunnerFor(gridItem.state.body)), panel_targets_changed: hasQueriesChanged(getQueryRunnerFor(panel), getQueryRunnerFor(newState.$data)),
}); });
}; };
@ -218,7 +218,6 @@ function getJsonText(show: ShowContent, panel: VizPanel): string {
case 'data-frames': { case 'data-frames': {
reportPanelInspectInteraction(InspectTab.JSON, 'dataFrame'); reportPanelInspectInteraction(InspectTab.JSON, 'dataFrame');
const dataProvider = sceneGraph.getData(panel); const dataProvider = sceneGraph.getData(panel);
if (dataProvider.state.data) { if (dataProvider.state.data) {

View File

@ -0,0 +1,47 @@
import React from 'react';
import {
SceneComponentProps,
sceneGraph,
SceneObjectBase,
SceneObjectState,
SceneObjectRef,
VizPanel,
} from '@grafana/scenes';
import { t } from 'app/core/internationalization';
import { QueryInspector } from 'app/features/inspector/QueryInspector';
import { InspectTab } from 'app/features/inspector/types';
import { getQueryRunnerFor } from '../utils/utils';
export interface InspectQueryTabState extends SceneObjectState {
panelRef: SceneObjectRef<VizPanel>;
}
export class InspectQueryTab extends SceneObjectBase<InspectQueryTabState> {
public getTabLabel() {
return t('dashboard.inspect.query-tab', 'Query');
}
public getTabValue() {
return InspectTab.Query;
}
public onRefreshQuery = () => {
const queryRunner = getQueryRunnerFor(this.state.panelRef.resolve());
if (queryRunner) {
queryRunner.runQueries();
}
};
static Component = ({ model }: SceneComponentProps<InspectQueryTab>) => {
const data = sceneGraph.getData(model.state.panelRef.resolve()).useState();
if (!data.data) {
return null;
}
return <QueryInspector data={data.data} onRefreshQuery={model.onRefreshQuery} />;
};
}

View File

@ -16,6 +16,7 @@ import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor
import { InspectDataTab } from './InspectDataTab'; import { InspectDataTab } from './InspectDataTab';
import { InspectJsonTab } from './InspectJsonTab'; import { InspectJsonTab } from './InspectJsonTab';
import { InspectQueryTab } from './InspectQueryTab';
import { InspectStatsTab } from './InspectStatsTab'; import { InspectStatsTab } from './InspectStatsTab';
import { SceneInspectTab } from './types'; import { SceneInspectTab } from './types';
@ -56,6 +57,7 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
if (supportsDataQuery(plugin)) { if (supportsDataQuery(plugin)) {
tabs.push(new InspectDataTab({ panelRef })); tabs.push(new InspectDataTab({ panelRef }));
tabs.push(new InspectStatsTab({ panelRef })); tabs.push(new InspectStatsTab({ panelRef }));
tabs.push(new InspectQueryTab({ panelRef }));
} }
tabs.push(new InspectJsonTab({ panelRef, onClose: this.onClose })); tabs.push(new InspectJsonTab({ panelRef, onClose: this.onClose }));

View File

@ -109,9 +109,7 @@ export const InspectContent = ({
)} )}
{activeTab === InspectTab.Error && <InspectErrorTab errors={errors} />} {activeTab === InspectTab.Error && <InspectErrorTab errors={errors} />}
{data && activeTab === InspectTab.Stats && <InspectStatsTab data={data} timeZone={dashboard.getTimezone()} />} {data && activeTab === InspectTab.Stats && <InspectStatsTab data={data} timeZone={dashboard.getTimezone()} />}
{data && activeTab === InspectTab.Query && ( {data && activeTab === InspectTab.Query && <QueryInspector data={data} onRefreshQuery={() => panel.refresh()} />}
<QueryInspector panel={panel} data={data.series} onRefreshQuery={() => panel.refresh()} />
)}
</Drawer> </Drawer>
); );
}; };

View File

@ -34,7 +34,7 @@ const PanelInspectorUnconnected = ({ panel, dashboard, plugin }: Props) => {
}); });
const location = useLocation(); const location = useLocation();
const { data, isLoading, error } = usePanelLatestData(panel, dataOptions, true); const { data, isLoading, error } = usePanelLatestData(panel, dataOptions, false);
const metaDs = useDatasourceMetadata(data); const metaDs = useDatasourceMetadata(data);
const tabs = useInspectTabs(panel, dashboard, plugin, error, metaDs); const tabs = useInspectTabs(panel, dashboard, plugin, error, metaDs);
const defaultTab = new URLSearchParams(location.search).get('inspectTab') as InspectTab; const defaultTab = new URLSearchParams(location.search).get('inspectTab') as InspectTab;

View File

@ -64,7 +64,7 @@ export const usePanelLatestData = (
return { return {
data: latestData, data: latestData,
error: latestData && latestData.error, error: latestData && latestData.error,
isLoading: latestData ? latestData.state === LoadingState.Loading : true, isLoading: latestData?.state === LoadingState.Loading,
hasSeries: latestData ? !!latestData.series : false, hasSeries: latestData ? !!latestData.series : false,
}; };
}; };

View File

@ -35,7 +35,6 @@ jest.mock('@grafana/runtime', () => ({
const setup = (propOverrides = {}) => { const setup = (propOverrides = {}) => {
const props: ExploreQueryInspectorProps = { const props: ExploreQueryInspectorProps = {
loading: false,
width: 100, width: 100,
exploreId: 'left', exploreId: 'left',
onClose: jest.fn(), onClose: jest.fn(),

View File

@ -1,7 +1,7 @@
import React, { useEffect } from 'react'; import React, { useEffect } from 'react';
import { connect, ConnectedProps } from 'react-redux'; import { connect, ConnectedProps } from 'react-redux';
import { CoreApp, TimeZone } from '@grafana/data'; import { CoreApp, LoadingState, TimeZone } from '@grafana/data';
import { reportInteraction } from '@grafana/runtime/src'; import { reportInteraction } from '@grafana/runtime/src';
import { TabbedContainer, TabConfig } from '@grafana/ui'; import { TabbedContainer, TabConfig } from '@grafana/ui';
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer'; import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
@ -12,7 +12,7 @@ import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
import { QueryInspector } from 'app/features/inspector/QueryInspector'; import { QueryInspector } from 'app/features/inspector/QueryInspector';
import { StoreState, ExploreItemState } from 'app/types'; import { StoreState, ExploreItemState } from 'app/types';
import { runQueries, selectIsWaitingForData } from './state/query'; import { runQueries } from './state/query';
interface DispatchProps { interface DispatchProps {
width: number; width: number;
@ -24,7 +24,7 @@ interface DispatchProps {
type Props = DispatchProps & ConnectedProps<typeof connector>; type Props = DispatchProps & ConnectedProps<typeof connector>;
export function ExploreQueryInspector(props: Props) { export function ExploreQueryInspector(props: Props) {
const { loading, width, onClose, queryResponse, timeZone } = props; const { width, onClose, queryResponse, timeZone } = props;
const dataFrames = queryResponse?.series || []; const dataFrames = queryResponse?.series || [];
let errors = queryResponse?.errors; let errors = queryResponse?.errors;
if (!errors?.length && queryResponse?.error) { if (!errors?.length && queryResponse?.error) {
@ -57,7 +57,7 @@ export function ExploreQueryInspector(props: Props) {
<InspectDataTab <InspectDataTab
data={dataFrames} data={dataFrames}
dataName={'Explore'} dataName={'Explore'}
isLoading={loading} isLoading={queryResponse.state === LoadingState.Loading}
options={{ withTransforms: false, withFieldConfig: false }} options={{ withTransforms: false, withFieldConfig: false }}
timeZone={timeZone} timeZone={timeZone}
app={CoreApp.Explore} app={CoreApp.Explore}
@ -70,7 +70,7 @@ export function ExploreQueryInspector(props: Props) {
value: 'query', value: 'query',
icon: 'info-circle', icon: 'info-circle',
content: ( content: (
<QueryInspector data={dataFrames} onRefreshQuery={() => props.runQueries({ exploreId: props.exploreId })} /> <QueryInspector data={queryResponse} onRefreshQuery={() => props.runQueries({ exploreId: props.exploreId })} />
), ),
}; };
@ -97,7 +97,6 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: string }
const { queryResponse } = item; const { queryResponse } = item;
return { return {
loading: selectIsWaitingForData(exploreId)(state),
queryResponse, queryResponse,
}; };
} }

View File

@ -2,22 +2,15 @@ import { css } from '@emotion/css';
import React, { PureComponent } from 'react'; import React, { PureComponent } from 'react';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
import { DataFrame } from '@grafana/data'; import { LoadingState, PanelData } from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { Stack } from '@grafana/experimental'; import { Stack } from '@grafana/experimental';
import { config, RefreshEvent } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui'; import { Button, ClipboardButton, JSONFormatter, LoadingPlaceholder } from '@grafana/ui';
import { backendSrv } from 'app/core/services/backend_srv'; import { backendSrv } from 'app/core/services/backend_srv';
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
import { PanelModel } from 'app/features/dashboard/state';
import { getPanelInspectorStyles } from './styles'; import { getPanelInspectorStyles } from './styles';
interface DsQuery {
isLoading: boolean;
response: {};
}
interface ExecutedQueryInfo { interface ExecutedQueryInfo {
refId: string; refId: string;
query: string; query: string;
@ -26,16 +19,15 @@ interface ExecutedQueryInfo {
} }
interface Props { interface Props {
data: DataFrame[]; data: PanelData;
onRefreshQuery: () => void; onRefreshQuery: () => void;
panel?: PanelModel;
} }
interface State { interface State {
allNodesExpanded: boolean | null; allNodesExpanded: boolean | null;
isMocking: boolean; isMocking: boolean;
mockedResponse: string; mockedResponse: string;
dsQuery: DsQuery; response: {};
executedQueries: ExecutedQueryInfo[]; executedQueries: ExecutedQueryInfo[];
} }
@ -50,26 +42,16 @@ export class QueryInspector extends PureComponent<Props, State> {
allNodesExpanded: null, allNodesExpanded: null,
isMocking: false, isMocking: false,
mockedResponse: '', mockedResponse: '',
dsQuery: {
isLoading: false,
response: {}, response: {},
},
}; };
} }
componentDidMount() { componentDidMount() {
const { panel } = this.props;
this.subs.add( this.subs.add(
backendSrv.getInspectorStream().subscribe({ backendSrv.getInspectorStream().subscribe({
next: (response) => this.onDataSourceResponse(response), next: (response) => this.onDataSourceResponse(response),
}) })
); );
if (panel) {
this.subs.add(panel.events.subscribe(RefreshEvent, this.onPanelRefresh));
this.updateQueryList();
}
} }
componentDidUpdate(oldProps: Props) { componentDidUpdate(oldProps: Props) {
@ -83,12 +65,13 @@ export class QueryInspector extends PureComponent<Props, State> {
*/ */
updateQueryList() { updateQueryList() {
const { data } = this.props; const { data } = this.props;
const frames = data.series;
const executedQueries: ExecutedQueryInfo[] = []; const executedQueries: ExecutedQueryInfo[] = [];
if (data?.length) { if (frames?.length) {
let last: ExecutedQueryInfo | undefined = undefined; let last: ExecutedQueryInfo | undefined = undefined;
data.forEach((frame, idx) => { frames.forEach((frame, idx) => {
const query = frame.meta?.executedQueryString; const query = frame.meta?.executedQueryString;
if (query) { if (query) {
@ -117,16 +100,6 @@ export class QueryInspector extends PureComponent<Props, State> {
this.subs.unsubscribe(); this.subs.unsubscribe();
} }
onPanelRefresh = () => {
this.setState((prevState) => ({
...prevState,
dsQuery: {
isLoading: true,
response: {},
},
}));
};
onDataSourceResponse(response: any) { onDataSourceResponse(response: any) {
// ignore silent requests // ignore silent requests
if (response.config?.hideFromInspector) { if (response.config?.hideFromInspector) {
@ -168,13 +141,9 @@ export class QueryInspector extends PureComponent<Props, State> {
delete response.$$config; delete response.$$config;
} }
this.setState((prevState) => ({ this.setState({
...prevState,
dsQuery: {
isLoading: false,
response: response, response: response,
}, });
}));
} }
setFormattedJson = (formattedJson: any) => { setFormattedJson = (formattedJson: any) => {
@ -240,16 +209,12 @@ export class QueryInspector extends PureComponent<Props, State> {
} }
render() { render() {
const { allNodesExpanded, executedQueries } = this.state; const { allNodesExpanded, executedQueries, response } = this.state;
const { panel, onRefreshQuery } = this.props; const { onRefreshQuery, data } = this.props;
const { response, isLoading } = this.state.dsQuery;
const openNodes = this.getNrOfOpenNodes(); const openNodes = this.getNrOfOpenNodes();
const styles = getPanelInspectorStyles(); const styles = getPanelInspectorStyles();
const haveData = Object.keys(response).length > 0; const haveData = Object.keys(response).length > 0;
const isLoading = data.state === LoadingState.Loading;
if (panel && !supportsDataQuery(panel.plugin)) {
return null;
}
return ( return (
<div className={styles.wrap}> <div className={styles.wrap}>