mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 18:30:41 -06:00
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:
parent
a6cb9fb295
commit
664b13d83e
@ -27,6 +27,7 @@ const getStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
tabContent: css`
|
||||
padding: ${theme.spacing.md};
|
||||
background-color: ${theme.colors.bodyBg};
|
||||
height: 100%;
|
||||
`,
|
||||
close: css`
|
||||
position: absolute;
|
||||
|
@ -24,6 +24,7 @@ export type IconName =
|
||||
| 'bolt'
|
||||
| 'book-open'
|
||||
| 'book'
|
||||
| 'brackets-curly'
|
||||
| 'bug'
|
||||
| 'calculator-alt'
|
||||
| 'calendar-alt'
|
||||
@ -157,6 +158,7 @@ export const getAvailableIcons = (): IconName[] => [
|
||||
'bolt',
|
||||
'book-open',
|
||||
'book',
|
||||
'brackets-curly',
|
||||
'bug',
|
||||
'calculator-alt',
|
||||
'calendar-alt',
|
||||
|
@ -2,15 +2,15 @@ import React, { useState } from 'react';
|
||||
import { DataSourceApi, PanelData, PanelPlugin } from '@grafana/data';
|
||||
import { getTemplateSrv } from '@grafana/runtime';
|
||||
import { CustomScrollbar, Drawer, TabContent } from '@grafana/ui';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { InspectSubtitle } from './InspectSubtitle';
|
||||
import { InspectDataTab } from './InspectDataTab';
|
||||
import { InspectMetadataTab } from './InspectMetadataTab';
|
||||
import { InspectJSONTab } from './InspectJSONTab';
|
||||
import { InspectErrorTab } from './InspectErrorTab';
|
||||
import { InspectStatsTab } from './InspectStatsTab';
|
||||
import { QueryInspector } from './QueryInspector';
|
||||
import { InspectTab } from './types';
|
||||
import { getPanelInspectorStyles } from 'app/features/inspector/styles';
|
||||
import { InspectMetadataTab } from 'app/features/inspector/InspectMetadataTab';
|
||||
import { InspectSubtitle } from 'app/features/inspector/InspectSubtitle';
|
||||
import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
|
||||
import { QueryInspector } from 'app/features/inspector/QueryInspector';
|
||||
import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
|
||||
import { InspectErrorTab } from 'app/features/inspector/InspectErrorTab';
|
||||
import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
|
||||
import { InspectTab } from 'app/features/inspector/types';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { GetDataOptions } from '../../../query/state/PanelQueryRunner';
|
||||
|
||||
@ -94,7 +94,9 @@ export const InspectContent: React.FC<Props> = ({
|
||||
)}
|
||||
{activeTab === InspectTab.Error && <InspectErrorTab error={error} />}
|
||||
{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>
|
||||
</CustomScrollbar>
|
||||
</Drawer>
|
||||
|
@ -10,7 +10,7 @@ import { usePanelLatestData } from '../PanelEditor/usePanelLatestData';
|
||||
import { InspectContent } from './InspectContent';
|
||||
import { useDatasourceMetadata, useInspectTabs } from './hooks';
|
||||
import { useLocation } from 'react-router-dom';
|
||||
import { InspectTab } from './types';
|
||||
import { InspectTab } from 'app/features/inspector/types';
|
||||
|
||||
interface OwnProps {
|
||||
dashboard: DashboardModel;
|
||||
|
@ -4,7 +4,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { DashboardModel } from 'app/features/dashboard/state';
|
||||
import { useMemo } from 'react';
|
||||
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
|
||||
|
@ -6,7 +6,7 @@ import { getLocationSrv, getTemplateSrv } from '@grafana/runtime';
|
||||
|
||||
import { PanelModel } from 'app/features/dashboard/state/PanelModel';
|
||||
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';
|
||||
|
||||
enum InfoMode {
|
||||
|
@ -7,18 +7,18 @@ import { ExploreQueryInspector } from './ExploreQueryInspector';
|
||||
|
||||
type ExploreQueryInspectorProps = ComponentProps<typeof ExploreQueryInspector>;
|
||||
|
||||
jest.mock('../dashboard/components/Inspector/styles', () => ({
|
||||
jest.mock('../inspector/styles', () => ({
|
||||
getPanelInspectorStyles: () => ({}),
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/backend_srv', () => ({
|
||||
getBackendSrv: () => ({
|
||||
backendSrv: {
|
||||
getInspectorStream: () =>
|
||||
new Observable((subscriber) => {
|
||||
subscriber.next(response());
|
||||
subscriber.next(response(true));
|
||||
}) as any,
|
||||
}),
|
||||
},
|
||||
}));
|
||||
|
||||
jest.mock('app/core/services/context_srv', () => ({
|
||||
@ -38,6 +38,7 @@ const setup = () => {
|
||||
series: [],
|
||||
timeRange: {} as TimeRange,
|
||||
},
|
||||
runQueries: jest.fn(),
|
||||
};
|
||||
|
||||
return render(<ExploreQueryInspector {...props} />);
|
||||
|
@ -1,65 +1,20 @@
|
||||
import React, { useState } from 'react';
|
||||
import { Button, JSONFormatter, LoadingPlaceholder, TabbedContainer, TabConfig } from '@grafana/ui';
|
||||
import React from 'react';
|
||||
import { TabbedContainer, TabConfig } from '@grafana/ui';
|
||||
import { PanelData, TimeZone } from '@grafana/data';
|
||||
|
||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||
import { runQueries } from './state/query';
|
||||
import { StoreState, ExploreItemState, ExploreId } from 'app/types';
|
||||
import { hot } from 'react-hot-loader';
|
||||
import { connect } from 'react-redux';
|
||||
import { ExploreDrawer } from 'app/features/explore/ExploreDrawer';
|
||||
import { useEffectOnce } from 'react-use';
|
||||
import { getBackendSrv } from 'app/core/services/backend_srv';
|
||||
import { InspectStatsTab } from '../dashboard/components/Inspector/InspectStatsTab';
|
||||
import { getPanelInspectorStyles } from '../dashboard/components/Inspector/styles';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { InspectJSONTab } from 'app/features/inspector/InspectJSONTab';
|
||||
import { QueryInspector } from 'app/features/inspector/QueryInspector';
|
||||
import { InspectStatsTab } from 'app/features/inspector/InspectStatsTab';
|
||||
import { InspectDataTab } from 'app/features/inspector/InspectDataTab';
|
||||
|
||||
function stripPropsFromResponse(response: any) {
|
||||
// ignore silent requests and return early
|
||||
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 DispatchProps {
|
||||
runQueries: typeof runQueries;
|
||||
}
|
||||
|
||||
interface Props {
|
||||
interface Props extends DispatchProps {
|
||||
loading: boolean;
|
||||
width: number;
|
||||
exploreId: ExploreId;
|
||||
@ -68,50 +23,9 @@ interface 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 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 = {
|
||||
label: 'Stats',
|
||||
value: 'stats',
|
||||
@ -119,52 +33,34 @@ export function ExploreQueryInspector(props: Props) {
|
||||
content: <InspectStatsTab data={queryResponse!} timeZone={queryResponse?.request?.timezone as TimeZone} />,
|
||||
};
|
||||
|
||||
const inspectorTab: TabConfig = {
|
||||
label: 'Query Inspector',
|
||||
value: 'query_inspector',
|
||||
icon: 'info-circle',
|
||||
content: (
|
||||
<>
|
||||
<div className={styles.toolbar}>
|
||||
{haveData && (
|
||||
<>
|
||||
<Button
|
||||
icon={allNodesExpanded ? 'minus' : 'plus'}
|
||||
variant="secondary"
|
||||
className={styles.toolbarItem}
|
||||
onClick={onToggleExpand}
|
||||
>
|
||||
{allNodesExpanded ? 'Collapse' : 'Expand'} all
|
||||
</Button>
|
||||
const jsonTab: TabConfig = {
|
||||
label: 'JSON',
|
||||
value: 'json',
|
||||
icon: 'brackets-curly',
|
||||
content: <InspectJSONTab data={queryResponse} onClose={onClose} />,
|
||||
};
|
||||
|
||||
<CopyToClipboard
|
||||
text={getTextForClipboard}
|
||||
onSuccess={onClipboardSuccess}
|
||||
elType="div"
|
||||
className={styles.toolbarItem}
|
||||
>
|
||||
<Button icon="copy" variant="secondary">
|
||||
Copy to clipboard
|
||||
</Button>
|
||||
</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 dataTab: TabConfig = {
|
||||
label: 'Data',
|
||||
value: 'data',
|
||||
icon: 'database',
|
||||
content: (
|
||||
<InspectDataTab
|
||||
data={dataFrames}
|
||||
isLoading={loading}
|
||||
options={{ withTransforms: false, withFieldConfig: false }}
|
||||
/>
|
||||
),
|
||||
};
|
||||
|
||||
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 (
|
||||
<ExploreDrawer width={width} onResize={() => {}}>
|
||||
<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));
|
||||
|
@ -54,7 +54,7 @@ export function SecondaryActions(props: Props) {
|
||||
onClick={props.onClickQueryInspectorButton}
|
||||
icon="info-circle"
|
||||
>
|
||||
Query inspector
|
||||
Inspector
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
|
@ -19,17 +19,17 @@ import { getPanelInspectorStyles } from './styles';
|
||||
import { config } from 'app/core/config';
|
||||
import { saveAs } from 'file-saver';
|
||||
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 { PanelModel } from 'app/features/dashboard/state';
|
||||
import { DetailText } from './DetailText';
|
||||
import { DetailText } from 'app/features/inspector/DetailText';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
data?: DataFrame[];
|
||||
isLoading: boolean;
|
||||
options: GetDataOptions;
|
||||
onOptionsChange: (options: GetDataOptions) => void;
|
||||
data?: DataFrame[];
|
||||
panel?: PanelModel;
|
||||
onOptionsChange?: (options: GetDataOptions) => void;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -93,8 +93,9 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
const blob = new Blob([String.fromCharCode(0xfeff), dataFrameCsv], {
|
||||
type: 'text/csv;charset=utf-8',
|
||||
});
|
||||
const displayTitle = panel ? panel.getDisplayTitle() : 'Explore';
|
||||
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);
|
||||
};
|
||||
|
||||
@ -108,10 +109,10 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
getProcessedData(): DataFrame[] {
|
||||
const { options } = this.props;
|
||||
const { options, panel } = this.props;
|
||||
const data = this.state.transformedData;
|
||||
|
||||
if (!options.withFieldConfig) {
|
||||
if (!options.withFieldConfig || !panel) {
|
||||
return applyRawFieldOverrides(data);
|
||||
}
|
||||
|
||||
@ -120,7 +121,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
return applyFieldOverrides({
|
||||
data,
|
||||
theme: config.theme,
|
||||
fieldConfig: this.props.panel.fieldConfig,
|
||||
fieldConfig: panel.fieldConfig,
|
||||
replaceVariables: (value: string) => {
|
||||
return value;
|
||||
},
|
||||
@ -166,6 +167,9 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
|
||||
renderDataOptions(dataFrames: DataFrame[]) {
|
||||
const { options, onOptionsChange, panel, data } = this.props;
|
||||
if (!panel || !onOptionsChange) {
|
||||
return null;
|
||||
}
|
||||
const { transformId, transformationOptions, selectedDataFrame } = this.state;
|
||||
|
||||
const styles = getPanelInspectorStyles();
|
@ -5,12 +5,12 @@ import { Button, CodeEditor, Field, Select } from '@grafana/ui';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { DashboardModel, PanelModel } from '../../state';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
import { getPanelInspectorStyles } from '../inspector/styles';
|
||||
|
||||
enum ShowContent {
|
||||
PanelJSON = 'panel',
|
||||
PanelData = 'data',
|
||||
DataJSON = 'data',
|
||||
DataStructure = 'structure',
|
||||
}
|
||||
|
||||
@ -21,9 +21,9 @@ const options: Array<SelectableValue<ShowContent>> = [
|
||||
value: ShowContent.PanelJSON,
|
||||
},
|
||||
{
|
||||
label: 'Panel data',
|
||||
label: 'Data',
|
||||
description: 'The raw model passed to the panel visualization',
|
||||
value: ShowContent.PanelData,
|
||||
value: ShowContent.DataJSON,
|
||||
},
|
||||
{
|
||||
label: 'DataFrame structure',
|
||||
@ -33,10 +33,10 @@ const options: Array<SelectableValue<ShowContent>> = [
|
||||
];
|
||||
|
||||
interface Props {
|
||||
dashboard: DashboardModel;
|
||||
panel: PanelModel;
|
||||
data?: PanelData;
|
||||
onClose: () => void;
|
||||
dashboard?: DashboardModel;
|
||||
panel?: PanelModel;
|
||||
data?: PanelData;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -45,11 +45,15 @@ interface State {
|
||||
}
|
||||
|
||||
export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
hasPanelJSON: boolean;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.hasPanelJSON = !!(props.panel && props.dashboard);
|
||||
// If we are in panel, we want to show PanelJSON, otherwise show DataJSON
|
||||
this.state = {
|
||||
show: ShowContent.PanelJSON,
|
||||
text: getPrettyJSON(props.panel.getSaveModel()),
|
||||
show: this.hasPanelJSON ? ShowContent.PanelJSON : ShowContent.DataJSON,
|
||||
text: this.hasPanelJSON ? getPrettyJSON(props.panel!.getSaveModel()) : getPrettyJSON(props.data),
|
||||
};
|
||||
}
|
||||
|
||||
@ -65,16 +69,17 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
};
|
||||
|
||||
getJSONObject(show: ShowContent) {
|
||||
if (show === ShowContent.PanelData) {
|
||||
return this.props.data;
|
||||
const { data, panel } = this.props;
|
||||
if (show === ShowContent.DataJSON) {
|
||||
return data;
|
||||
}
|
||||
|
||||
if (show === ShowContent.DataStructure) {
|
||||
const series = this.props.data?.series;
|
||||
const series = data?.series;
|
||||
if (!series) {
|
||||
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
|
||||
return {
|
||||
...rest,
|
||||
@ -85,8 +90,8 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
if (show === ShowContent.PanelJSON) {
|
||||
return this.props.panel.getSaveModel();
|
||||
if (this.hasPanelJSON && show === ShowContent.PanelJSON) {
|
||||
return panel!.getSaveModel();
|
||||
}
|
||||
|
||||
return { note: `Unknown Object: ${show}` };
|
||||
@ -94,39 +99,41 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
|
||||
onApplyPanelModel = () => {
|
||||
const { panel, dashboard, onClose } = this.props;
|
||||
|
||||
try {
|
||||
if (!dashboard.meta.canEdit) {
|
||||
appEvents.emit(AppEvents.alertError, ['Unable to apply']);
|
||||
} else {
|
||||
const updates = JSON.parse(this.state.text);
|
||||
panel.restoreModel(updates);
|
||||
panel.refresh();
|
||||
appEvents.emit(AppEvents.alertSuccess, ['Panel model updated']);
|
||||
if (this.hasPanelJSON) {
|
||||
try {
|
||||
if (!dashboard!.meta.canEdit) {
|
||||
appEvents.emit(AppEvents.alertError, ['Unable to apply']);
|
||||
} else {
|
||||
const updates = JSON.parse(this.state.text);
|
||||
panel!.restoreModel(updates);
|
||||
panel!.refresh();
|
||||
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() {
|
||||
const { dashboard } = this.props;
|
||||
const { show, text } = this.state;
|
||||
const jsonOptions = this.hasPanelJSON ? options : options.slice(1, options.length);
|
||||
const selected = options.find((v) => v.value === show);
|
||||
const isPanelJSON = show === ShowContent.PanelJSON;
|
||||
const canEdit = dashboard.meta.canEdit;
|
||||
const canEdit = dashboard && dashboard.meta.canEdit;
|
||||
const styles = getPanelInspectorStyles();
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={styles.toolbar} aria-label={selectors.components.PanelInspector.Json.content}>
|
||||
<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>
|
||||
{isPanelJSON && canEdit && (
|
||||
{this.hasPanelJSON && isPanelJSON && canEdit && (
|
||||
<Button className={styles.toolbarItem} onClick={this.onApplyPanelModel}>
|
||||
Apply
|
||||
</Button>
|
@ -1,8 +1,8 @@
|
||||
import React, { FC } from 'react';
|
||||
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 { InspectTab } from './types';
|
||||
import { InspectTab } from '../inspector/types';
|
||||
|
||||
interface Props {
|
||||
tab: InspectTab;
|
@ -7,7 +7,7 @@ import appEvents from 'app/core/app_events';
|
||||
import { CopyToClipboard } from 'app/core/components/CopyToClipboard/CopyToClipboard';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { supportsDataQuery } from '../PanelEditor/utils';
|
||||
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
|
||||
import { config } from '@grafana/runtime';
|
||||
import { css } from 'emotion';
|
||||
import { Subscription } from 'rxjs';
|
||||
@ -27,8 +27,9 @@ interface ExecutedQueryInfo {
|
||||
}
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
data: DataFrame[];
|
||||
onRefreshQuery: () => void;
|
||||
panel?: PanelModel;
|
||||
}
|
||||
|
||||
interface State {
|
||||
@ -66,8 +67,10 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
})
|
||||
);
|
||||
|
||||
this.subs.add(panel.events.subscribe(RefreshEvent, this.onPanelRefresh));
|
||||
this.updateQueryList();
|
||||
if (panel) {
|
||||
this.subs.add(panel.events.subscribe(RefreshEvent, this.onPanelRefresh));
|
||||
this.updateQueryList();
|
||||
}
|
||||
}
|
||||
|
||||
componentDidUpdate(oldProps: Props) {
|
||||
@ -111,10 +114,6 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
this.setState({ executedQueries });
|
||||
}
|
||||
|
||||
onIssueNewQuery = () => {
|
||||
this.props.panel.refresh();
|
||||
};
|
||||
|
||||
componentWillUnmount() {
|
||||
this.subs.unsubscribe();
|
||||
}
|
||||
@ -255,12 +254,13 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
|
||||
render() {
|
||||
const { allNodesExpanded, executedQueries } = this.state;
|
||||
const { panel, onRefreshQuery } = this.props;
|
||||
const { response, isLoading } = this.state.dsQuery;
|
||||
const openNodes = this.getNrOfOpenNodes();
|
||||
const styles = getPanelInspectorStyles();
|
||||
const haveData = Object.keys(response).length > 0;
|
||||
|
||||
if (!supportsDataQuery(this.props.panel.plugin)) {
|
||||
if (panel && !supportsDataQuery(panel.plugin)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@ -277,7 +277,7 @@ export class QueryInspector extends PureComponent<Props, State> {
|
||||
<div className={styles.toolbar}>
|
||||
<Button
|
||||
icon="sync"
|
||||
onClick={this.onIssueNewQuery}
|
||||
onClick={onRefreshQuery}
|
||||
aria-label={selectors.components.PanelInspector.Query.refreshButton}
|
||||
>
|
||||
Refresh
|
@ -24,6 +24,7 @@ export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
`,
|
||||
content: css`
|
||||
flex-grow: 1;
|
||||
height: 100%;
|
||||
padding-bottom: 16px;
|
||||
`,
|
||||
contentQueryInspector: css`
|
Loading…
Reference in New Issue
Block a user