Explore: Support full inspect drawer (#32005)

* Move InspectSubtitle to grafana/ui

* Move inspect elements to features/inspector

* Move InspectJSON and use it also in Explore

* Move and use QueryInspector

* Move all to features/inspector

* WIP Data tab implementation

* Process dataframes for explores inspector

* Clean up

* Update test

* Clean up

* Fix Explore Inspector button name
This commit is contained in:
Ivana Huckova 2021-03-18 17:38:09 +01:00 committed by GitHub
parent a6cb9fb295
commit 664b13d83e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 129 additions and 211 deletions

View File

@ -27,6 +27,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
tabContent: css` tabContent: css`
padding: ${theme.spacing.md}; padding: ${theme.spacing.md};
background-color: ${theme.colors.bodyBg}; background-color: ${theme.colors.bodyBg};
height: 100%;
`, `,
close: css` close: css`
position: absolute; position: absolute;

View File

@ -24,6 +24,7 @@ export type IconName =
| 'bolt' | 'bolt'
| 'book-open' | 'book-open'
| 'book' | 'book'
| 'brackets-curly'
| 'bug' | 'bug'
| 'calculator-alt' | 'calculator-alt'
| 'calendar-alt' | 'calendar-alt'
@ -157,6 +158,7 @@ export const getAvailableIcons = (): IconName[] => [
'bolt', 'bolt',
'book-open', 'book-open',
'book', 'book',
'brackets-curly',
'bug', 'bug',
'calculator-alt', 'calculator-alt',
'calendar-alt', 'calendar-alt',

View File

@ -2,15 +2,15 @@ import React, { useState } from 'react';
import { DataSourceApi, PanelData, PanelPlugin } from '@grafana/data'; import { DataSourceApi, PanelData, PanelPlugin } from '@grafana/data';
import { getTemplateSrv } from '@grafana/runtime'; import { getTemplateSrv } from '@grafana/runtime';
import { CustomScrollbar, Drawer, TabContent } from '@grafana/ui'; import { CustomScrollbar, Drawer, TabContent } from '@grafana/ui';
import { getPanelInspectorStyles } from './styles'; import { getPanelInspectorStyles } from 'app/features/inspector/styles';
import { InspectSubtitle } from './InspectSubtitle'; import { InspectMetadataTab } from 'app/features/inspector/InspectMetadataTab';
import { InspectDataTab } from './InspectDataTab'; import { InspectSubtitle } from 'app/features/inspector/InspectSubtitle';
import { InspectMetadataTab } from './InspectMetadataTab'; import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
import { InspectJSONTab } from './InspectJSONTab'; import { QueryInspector } from 'app/features/inspector/QueryInspector';
import { InspectErrorTab } from './InspectErrorTab'; import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
import { InspectStatsTab } from './InspectStatsTab'; import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
import { QueryInspector } from './QueryInspector'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
import { InspectTab } from './types'; import { InspectTab } from 'app/features/inspector/types';
import { DashboardModel, PanelModel } from '../../state'; import { DashboardModel, PanelModel } from '../../state';
import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { GetDataOptions } from '../../../query/state/PanelQueryRunner';
@ -94,7 +94,9 @@ export const InspectContent: React.FC<Props> = ({
)} )}
{activeTab === InspectTab.Error && <InspectErrorTab error={error} />} {activeTab === InspectTab.Error && <InspectErrorTab error={error} />}
{data && activeTab === InspectTab.Stats && <InspectStatsTab data={data} timeZone={dashboard.getTimezone()} />} {data && activeTab === InspectTab.Stats && <InspectStatsTab data={data} timeZone={dashboard.getTimezone()} />}
{data && activeTab === InspectTab.Query && <QueryInspector panel={panel} data={data.series} />} {data && activeTab === InspectTab.Query && (
<QueryInspector panel={panel} data={data.series} onRefreshQuery={() => panel.refresh()} />
)}
</TabContent> </TabContent>
</CustomScrollbar> </CustomScrollbar>
</Drawer> </Drawer>

View File

@ -10,7 +10,7 @@ import { usePanelLatestData } from '../PanelEditor/usePanelLatestData';
import { InspectContent } from './InspectContent'; import { InspectContent } from './InspectContent';
import { useDatasourceMetadata, useInspectTabs } from './hooks'; import { useDatasourceMetadata, useInspectTabs } from './hooks';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
import { InspectTab } from './types'; import { InspectTab } from 'app/features/inspector/types';
interface OwnProps { interface OwnProps {
dashboard: DashboardModel; dashboard: DashboardModel;

View File

@ -4,7 +4,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
import { DashboardModel } from 'app/features/dashboard/state'; import { DashboardModel } from 'app/features/dashboard/state';
import { useMemo } from 'react'; import { useMemo } from 'react';
import { supportsDataQuery } from '../PanelEditor/utils'; import { supportsDataQuery } from '../PanelEditor/utils';
import { InspectTab } from './types'; import { InspectTab } from 'app/features/inspector/types';
/** /**
* Given PanelData return first data source supporting metadata inspector * Given PanelData return first data source supporting metadata inspector

View File

@ -6,7 +6,7 @@ import { getLocationSrv, getTemplateSrv } from '@grafana/runtime';
import { PanelModel } from 'app/features/dashboard/state/PanelModel'; import { PanelModel } from 'app/features/dashboard/state/PanelModel';
import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv'; import { getTimeSrv, TimeSrv } from 'app/features/dashboard/services/TimeSrv';
import { InspectTab } from '../../components/Inspector/types'; import { InspectTab } from 'app/features/inspector/types';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
enum InfoMode { enum InfoMode {

View File

@ -7,18 +7,18 @@ import { ExploreQueryInspector } from './ExploreQueryInspector';
type ExploreQueryInspectorProps = ComponentProps<typeof ExploreQueryInspector>; type ExploreQueryInspectorProps = ComponentProps<typeof ExploreQueryInspector>;
jest.mock('../dashboard/components/Inspector/styles', () => ({ jest.mock('../inspector/styles', () => ({
getPanelInspectorStyles: () => ({}), getPanelInspectorStyles: () => ({}),
})); }));
jest.mock('app/core/services/backend_srv', () => ({ jest.mock('app/core/services/backend_srv', () => ({
getBackendSrv: () => ({ backendSrv: {
getInspectorStream: () => getInspectorStream: () =>
new Observable((subscriber) => { new Observable((subscriber) => {
subscriber.next(response()); subscriber.next(response());
subscriber.next(response(true)); subscriber.next(response(true));
}) as any, }) as any,
}), },
})); }));
jest.mock('app/core/services/context_srv', () => ({ jest.mock('app/core/services/context_srv', () => ({
@ -38,6 +38,7 @@ const setup = () => {
series: [], series: [],
timeRange: {} as TimeRange, timeRange: {} as TimeRange,
}, },
runQueries: jest.fn(),
}; };
return render(<ExploreQueryInspector {...props} />); return render(<ExploreQueryInspector {...props} />);

View File

@ -1,65 +1,20 @@
import React, { useState } from 'react'; import React from 'react';
import { Button, JSONFormatter, LoadingPlaceholder, TabbedContainer, TabConfig } from '@grafana/ui'; import { TabbedContainer, TabConfig } from '@grafana/ui';
import { PanelData, TimeZone } from '@grafana/data'; import { PanelData, TimeZone } from '@grafana/data';
import { runQueries } from './state/query';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
import { StoreState, ExploreItemState, ExploreId } from 'app/types'; import { StoreState, ExploreItemState, ExploreId } from 'app/types';
import { hot } from 'react-hot-loader'; import { hot } from 'react-hot-loader';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer'; import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
import { useEffectOnce } from 'react-use'; import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
import { getBackendSrv } from 'app/core/services/backend_srv'; import { QueryInspector } from 'app/features/inspector/QueryInspector';
import { InspectStatsTab } from '../dashboard/components/Inspector/InspectStatsTab'; import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
import { getPanelInspectorStyles } from '../dashboard/components/Inspector/styles'; import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
import { dispatch } from 'app/store/store';
import { notifyApp } from 'app/core/actions';
import { createSuccessNotification } from 'app/core/copy/appNotification';
function stripPropsFromResponse(response: any) { interface DispatchProps {
// ignore silent requests and return early runQueries: typeof runQueries;
if (response.config?.hideFromInspector) {
return;
}
const clonedResponse = { ...response }; // clone - dont modify the response
if (clonedResponse.headers) {
delete clonedResponse.headers;
}
if (clonedResponse.config) {
clonedResponse.request = clonedResponse.config;
delete clonedResponse.config;
delete clonedResponse.request.transformRequest;
delete clonedResponse.request.transformResponse;
delete clonedResponse.request.paramSerializer;
delete clonedResponse.request.jsonpCallbackParam;
delete clonedResponse.request.headers;
delete clonedResponse.request.requestId;
delete clonedResponse.request.inspect;
delete clonedResponse.request.retry;
delete clonedResponse.request.timeout;
}
if (clonedResponse.data) {
clonedResponse.response = clonedResponse.data;
delete clonedResponse.config;
delete clonedResponse.data;
delete clonedResponse.status;
delete clonedResponse.statusText;
delete clonedResponse.ok;
delete clonedResponse.url;
delete clonedResponse.redirected;
delete clonedResponse.type;
delete clonedResponse.$$config;
}
return clonedResponse;
} }
interface Props extends DispatchProps {
interface Props {
loading: boolean; loading: boolean;
width: number; width: number;
exploreId: ExploreId; exploreId: ExploreId;
@ -68,50 +23,9 @@ interface Props {
} }
export function ExploreQueryInspector(props: Props) { export function ExploreQueryInspector(props: Props) {
const [formattedJSON, setFormattedJSON] = useState({});
const getTextForClipboard = () => {
return JSON.stringify(formattedJSON, null, 2);
};
const onClipboardSuccess = () => {
dispatch(notifyApp(createSuccessNotification('Content copied to clipboard')));
};
const [allNodesExpanded, setAllNodesExpanded] = useState(false);
const getOpenNodeCount = () => {
if (allNodesExpanded === null) {
return 3; // 3 is default, ie when state is null
} else if (allNodesExpanded) {
return 20;
}
return 1;
};
const onToggleExpand = () => {
setAllNodesExpanded(!allNodesExpanded);
};
const { loading, width, onClose, queryResponse } = props; const { loading, width, onClose, queryResponse } = props;
const dataFrames = queryResponse?.series || [];
const [response, setResponse] = useState<PanelData>({} as PanelData);
useEffectOnce(() => {
const inspectorStreamSub = getBackendSrv()
.getInspectorStream()
.subscribe((resp) => {
const strippedResponse = stripPropsFromResponse(resp);
if (strippedResponse) {
setResponse(strippedResponse);
}
});
return () => {
inspectorStreamSub?.unsubscribe();
};
});
const haveData = response && Object.keys(response).length > 0;
const styles = getPanelInspectorStyles();
const statsTab: TabConfig = { const statsTab: TabConfig = {
label: 'Stats', label: 'Stats',
value: 'stats', value: 'stats',
@ -119,52 +33,34 @@ export function ExploreQueryInspector(props: Props) {
content: <InspectStatsTab data={queryResponse!} timeZone={queryResponse?.request?.timezone as TimeZone} />, content: <InspectStatsTab data={queryResponse!} timeZone={queryResponse?.request?.timezone as TimeZone} />,
}; };
const inspectorTab: TabConfig = { const jsonTab: TabConfig = {
label: 'Query Inspector', label: 'JSON',
value: 'query_inspector', value: 'json',
icon: 'info-circle', icon: 'brackets-curly',
content: ( content: <InspectJSONTab data={queryResponse} onClose={onClose} />,
<> };
<div className={styles.toolbar}>
{haveData && (
<>
<Button
icon={allNodesExpanded ? 'minus' : 'plus'}
variant="secondary"
className={styles.toolbarItem}
onClick={onToggleExpand}
>
{allNodesExpanded ? 'Collapse' : 'Expand'} all
</Button>
<CopyToClipboard const dataTab: TabConfig = {
text={getTextForClipboard} label: 'Data',
onSuccess={onClipboardSuccess} value: 'data',
elType="div" icon: 'database',
className={styles.toolbarItem} content: (
> <InspectDataTab
<Button icon="copy" variant="secondary"> data={dataFrames}
Copy to clipboard isLoading={loading}
</Button> options={{ withTransforms: false, withFieldConfig: false }}
</CopyToClipboard> />
</>
)}
<div className="flex-grow-1" />
</div>
<div className={styles.contentQueryInspector}>
{loading && <LoadingPlaceholder text="Loading query inspector..." />}
{!loading && haveData && (
<JSONFormatter json={response!} open={getOpenNodeCount()} onDidRender={setFormattedJSON} />
)}
{!loading && !haveData && (
<p className="muted">No request & response collected yet. Run query to collect request & response.</p>
)}
</div>
</>
), ),
}; };
const tabs = [statsTab, inspectorTab]; const queryInspectorTab: TabConfig = {
label: 'Query Inspector',
value: 'query_inspector',
icon: 'info-circle',
content: <QueryInspector data={dataFrames} onRefreshQuery={() => props.runQueries(props.exploreId)} />,
};
const tabs = [statsTab, queryInspectorTab, jsonTab, dataTab];
return ( return (
<ExploreDrawer width={width} onResize={() => {}}> <ExploreDrawer width={width} onResize={() => {}}>
<TabbedContainer tabs={tabs} onClose={onClose} closeIconTooltip="Close query inspector" /> <TabbedContainer tabs={tabs} onClose={onClose} closeIconTooltip="Close query inspector" />
@ -183,4 +79,8 @@ function mapStateToProps(state: StoreState, { exploreId }: { exploreId: ExploreI
}; };
} }
export default hot(module)(connect(mapStateToProps)(ExploreQueryInspector)); const mapDispatchToProps: DispatchProps = {
runQueries,
};
export default hot(module)(connect(mapStateToProps, mapDispatchToProps)(ExploreQueryInspector));

View File

@ -54,7 +54,7 @@ export function SecondaryActions(props: Props) {
onClick={props.onClickQueryInspectorButton} onClick={props.onClickQueryInspectorButton}
icon="info-circle" icon="info-circle"
> >
Query inspector Inspector
</Button> </Button>
</HorizontalGroup> </HorizontalGroup>
</div> </div>

View File

@ -19,17 +19,17 @@ import { getPanelInspectorStyles } from './styles';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { saveAs } from 'file-saver'; import { saveAs } from 'file-saver';
import { css } from 'emotion'; import { css } from 'emotion';
import { GetDataOptions } from '../../../query/state/PanelQueryRunner'; import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow'; import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
import { PanelModel } from 'app/features/dashboard/state'; import { PanelModel } from 'app/features/dashboard/state';
import { DetailText } from './DetailText'; import { DetailText } from 'app/features/inspector/DetailText';
interface Props { interface Props {
panel: PanelModel;
data?: DataFrame[];
isLoading: boolean; isLoading: boolean;
options: GetDataOptions; options: GetDataOptions;
onOptionsChange: (options: GetDataOptions) => void; data?: DataFrame[];
panel?: PanelModel;
onOptionsChange?: (options: GetDataOptions) => void;
} }
interface State { interface State {
@ -93,8 +93,9 @@ export class InspectDataTab extends PureComponent<Props, State> {
const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], { const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], {
type: 'text/csv;charset=utf-8', type: 'text/csv;charset=utf-8',
}); });
const displayTitle = panel ? panel.getDisplayTitle() : 'Explore';
const transformation = transformId !== DataTransformerID.noop ? '-as-' + transformId.toLocaleLowerCase() : ''; const transformation = transformId !== DataTransformerID.noop ? '-as-' + transformId.toLocaleLowerCase() : '';
const fileName = `${panel.getDisplayTitle()}-data${transformation}-${dateTimeFormat(new Date())}.csv`; const fileName = `${displayTitle}-data${transformation}-${dateTimeFormat(new Date())}.csv`;
saveAs(blob, fileName); saveAs(blob, fileName);
}; };
@ -108,10 +109,10 @@ export class InspectDataTab extends PureComponent<Props, State> {
}; };
getProcessedData(): DataFrame[] { getProcessedData(): DataFrame[] {
const { options } = this.props; const { options, panel } = this.props;
const data = this.state.transformedData; const data = this.state.transformedData;
if (!options.withFieldConfig) { if (!options.withFieldConfig || !panel) {
return applyRawFieldOverrides(data); return applyRawFieldOverrides(data);
} }
@ -120,7 +121,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
return applyFieldOverrides({ return applyFieldOverrides({
data, data,
theme: config.theme, theme: config.theme,
fieldConfig: this.props.panel.fieldConfig, fieldConfig: panel.fieldConfig,
replaceVariables: (value: string) => { replaceVariables: (value: string) => {
return value; return value;
}, },
@ -166,6 +167,9 @@ export class InspectDataTab extends PureComponent<Props, State> {
renderDataOptions(dataFrames: DataFrame[]) { renderDataOptions(dataFrames: DataFrame[]) {
const { options, onOptionsChange, panel, data } = this.props; const { options, onOptionsChange, panel, data } = this.props;
if (!panel || !onOptionsChange) {
return null;
}
const { transformId, transformationOptions, selectedDataFrame } = this.state; const { transformId, transformationOptions, selectedDataFrame } = this.state;
const styles = getPanelInspectorStyles(); const styles = getPanelInspectorStyles();

View File

@ -5,12 +5,12 @@ import { Button, CodeEditor, Field, Select } from '@grafana/ui';
import AutoSizer from 'react-virtualized-auto-sizer'; import AutoSizer from 'react-virtualized-auto-sizer';
import { selectors } from '@grafana/e2e-selectors'; import { selectors } from '@grafana/e2e-selectors';
import { appEvents } from 'app/core/core'; import { appEvents } from 'app/core/core';
import { DashboardModel, PanelModel } from '../../state'; import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
import { getPanelInspectorStyles } from './styles'; import { getPanelInspectorStyles } from '../inspector/styles';
enum ShowContent { enum ShowContent {
PanelJSON = 'panel', PanelJSON = 'panel',
PanelData = 'data', DataJSON = 'data',
DataStructure = 'structure', DataStructure = 'structure',
} }
@ -21,9 +21,9 @@ const options: Array<SelectableValue<ShowContent>> = [
value: ShowContent.PanelJSON, value: ShowContent.PanelJSON,
}, },
{ {
label: 'Panel data', label: 'Data',
description: 'The raw model passed to the panel visualization', description: 'The raw model passed to the panel visualization',
value: ShowContent.PanelData, value: ShowContent.DataJSON,
}, },
{ {
label: 'DataFrame structure', label: 'DataFrame structure',
@ -33,10 +33,10 @@ const options: Array<SelectableValue<ShowContent>> = [
]; ];
interface Props { interface Props {
dashboard: DashboardModel;
panel: PanelModel;
data?: PanelData;
onClose: () => void; onClose: () => void;
dashboard?: DashboardModel;
panel?: PanelModel;
data?: PanelData;
} }
interface State { interface State {
@ -45,11 +45,15 @@ interface State {
} }
export class InspectJSONTab extends PureComponent<Props, State> { export class InspectJSONTab extends PureComponent<Props, State> {
hasPanelJSON: boolean;
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
this.hasPanelJSON = !!(props.panel && props.dashboard);
// If we are in panel, we want to show PanelJSON, otherwise show DataJSON
this.state = { this.state = {
show: ShowContent.PanelJSON, show: this.hasPanelJSON ? ShowContent.PanelJSON : ShowContent.DataJSON,
text: getPrettyJSON(props.panel.getSaveModel()), text: this.hasPanelJSON ? getPrettyJSON(props.panel!.getSaveModel()) : getPrettyJSON(props.data),
}; };
} }
@ -65,16 +69,17 @@ export class InspectJSONTab extends PureComponent<Props, State> {
}; };
getJSONObject(show: ShowContent) { getJSONObject(show: ShowContent) {
if (show === ShowContent.PanelData) { const { data, panel } = this.props;
return this.props.data; if (show === ShowContent.DataJSON) {
return data;
} }
if (show === ShowContent.DataStructure) { if (show === ShowContent.DataStructure) {
const series = this.props.data?.series; const series = data?.series;
if (!series) { if (!series) {
return { note: 'Missing Response Data' }; return { note: 'Missing Response Data' };
} }
return this.props.data!.series.map((frame) => { return data!.series.map((frame) => {
const { table, fields, ...rest } = frame as any; // remove 'table' from arrow response const { table, fields, ...rest } = frame as any; // remove 'table' from arrow response
return { return {
...rest, ...rest,
@ -85,8 +90,8 @@ export class InspectJSONTab extends PureComponent<Props, State> {
}); });
} }
if (show === ShowContent.PanelJSON) { if (this.hasPanelJSON && show === ShowContent.PanelJSON) {
return this.props.panel.getSaveModel(); return panel!.getSaveModel();
} }
return { note: `Unknown Object: ${show}` }; return { note: `Unknown Object: ${show}` };
@ -94,39 +99,41 @@ export class InspectJSONTab extends PureComponent<Props, State> {
onApplyPanelModel = () => { onApplyPanelModel = () => {
const { panel, dashboard, onClose } = this.props; const { panel, dashboard, onClose } = this.props;
if (this.hasPanelJSON) {
try { try {
if (!dashboard.meta.canEdit) { if (!dashboard!.meta.canEdit) {
appEvents.emit(AppEvents.alertError, ['Unable to apply']); appEvents.emit(AppEvents.alertError, ['Unable to apply']);
} else { } else {
const updates = JSON.parse(this.state.text); const updates = JSON.parse(this.state.text);
panel.restoreModel(updates); panel!.restoreModel(updates);
panel.refresh(); panel!.refresh();
appEvents.emit(AppEvents.alertSuccess, ['Panel model updated']); appEvents.emit(AppEvents.alertSuccess, ['Panel model updated']);
}
} catch (err) {
console.error('Error applying updates', err);
appEvents.emit(AppEvents.alertError, ['Invalid JSON text']);
} }
} catch (err) {
console.error('Error applying updates', err);
appEvents.emit(AppEvents.alertError, ['Invalid JSON text']);
}
onClose(); onClose();
}
}; };
render() { render() {
const { dashboard } = this.props; const { dashboard } = this.props;
const { show, text } = this.state; const { show, text } = this.state;
const jsonOptions = this.hasPanelJSON ? options : options.slice(1, options.length);
const selected = options.find((v) => v.value === show); const selected = options.find((v) => v.value === show);
const isPanelJSON = show === ShowContent.PanelJSON; const isPanelJSON = show === ShowContent.PanelJSON;
const canEdit = dashboard.meta.canEdit; const canEdit = dashboard && dashboard.meta.canEdit;
const styles = getPanelInspectorStyles(); const styles = getPanelInspectorStyles();
return ( return (
<> <>
<div className={styles.toolbar} aria-label={selectors.components.PanelInspector.Json.content}> <div className={styles.toolbar} aria-label={selectors.components.PanelInspector.Json.content}>
<Field label="Select source" className="flex-grow-1"> <Field label="Select source" className="flex-grow-1">
<Select options={options} value={selected} onChange={this.onSelectChanged} /> <Select options={jsonOptions} value={selected} onChange={this.onSelectChanged} />
</Field> </Field>
{isPanelJSON && canEdit && ( {this.hasPanelJSON && isPanelJSON && canEdit && (
<Button className={styles.toolbarItem} onClick={this.onApplyPanelModel}> <Button className={styles.toolbarItem} onClick={this.onApplyPanelModel}>
Apply Apply
</Button> </Button>

View File

@ -1,8 +1,8 @@
import React, { FC } from 'react'; import React, { FC } from 'react';
import { css } from 'emotion'; import { css } from 'emotion';
import { stylesFactory, Tab, TabsBar, useTheme } from '@grafana/ui'; import { stylesFactory, useTheme, Tab, TabsBar } from '@grafana/ui';
import { GrafanaTheme, SelectableValue, PanelData, getValueFormat, formattedValueToString } from '@grafana/data'; import { GrafanaTheme, SelectableValue, PanelData, getValueFormat, formattedValueToString } from '@grafana/data';
import { InspectTab } from './types'; import { InspectTab } from '../inspector/types';
interface Props { interface Props {
tab: InspectTab; tab: InspectTab;

View File

@ -7,7 +7,7 @@ import appEvents from 'app/core/app_events';
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard'; import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
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 'app/features/dashboard/components/PanelEditor/utils';
import { config } from '@grafana/runtime'; import { config } from '@grafana/runtime';
import { css } from 'emotion'; import { css } from 'emotion';
import { Subscription } from 'rxjs'; import { Subscription } from 'rxjs';
@ -27,8 +27,9 @@ interface ExecutedQueryInfo {
} }
interface Props { interface Props {
panel: PanelModel;
data: DataFrame[]; data: DataFrame[];
onRefreshQuery: () => void;
panel?: PanelModel;
} }
interface State { interface State {
@ -66,8 +67,10 @@ export class QueryInspector extends PureComponent<Props, State> {
}) })
); );
this.subs.add(panel.events.subscribe(RefreshEvent, this.onPanelRefresh)); if (panel) {
this.updateQueryList(); this.subs.add(panel.events.subscribe(RefreshEvent, this.onPanelRefresh));
this.updateQueryList();
}
} }
componentDidUpdate(oldProps: Props) { componentDidUpdate(oldProps: Props) {
@ -111,10 +114,6 @@ export class QueryInspector extends PureComponent<Props, State> {
this.setState({ executedQueries }); this.setState({ executedQueries });
} }
onIssueNewQuery = () => {
this.props.panel.refresh();
};
componentWillUnmount() { componentWillUnmount() {
this.subs.unsubscribe(); this.subs.unsubscribe();
} }
@ -255,12 +254,13 @@ export class QueryInspector extends PureComponent<Props, State> {
render() { render() {
const { allNodesExpanded, executedQueries } = this.state; const { allNodesExpanded, executedQueries } = this.state;
const { panel, onRefreshQuery } = this.props;
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();
const haveData = Object.keys(response).length > 0; const haveData = Object.keys(response).length > 0;
if (!supportsDataQuery(this.props.panel.plugin)) { if (panel && !supportsDataQuery(panel.plugin)) {
return null; return null;
} }
@ -277,7 +277,7 @@ export class QueryInspector extends PureComponent<Props, State> {
<div className={styles.toolbar}> <div className={styles.toolbar}>
<Button <Button
icon="sync" icon="sync"
onClick={this.onIssueNewQuery} onClick={onRefreshQuery}
aria-label={selectors.components.PanelInspector.Query.refreshButton} aria-label={selectors.components.PanelInspector.Query.refreshButton}
> >
Refresh Refresh

View File

@ -24,6 +24,7 @@ export const getPanelInspectorStyles = stylesFactory(() => {
`, `,
content: css` content: css`
flex-grow: 1; flex-grow: 1;
height: 100%;
padding-bottom: 16px; padding-bottom: 16px;
`, `,
contentQueryInspector: css` contentQueryInspector: css`