mirror of
https://github.com/grafana/grafana.git
synced 2025-02-14 09:33:34 -06:00
Inspect: Refactor InspectJSONTab to FC (#61106)
This commit is contained in:
parent
9799ac252b
commit
a3e341f24b
@ -1,14 +1,14 @@
|
||||
import React from 'react';
|
||||
|
||||
import { DataFrame, DataTransformerID, getFrameDisplayName, SelectableValue } from '@grafana/data';
|
||||
import { Field, HorizontalGroup, Select, Switch, VerticalGroup } from '@grafana/ui';
|
||||
import { Field, HorizontalGroup, Select, Switch, VerticalGroup, useStyles2 } from '@grafana/ui';
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { DetailText } from 'app/features/inspector/DetailText';
|
||||
import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
|
||||
|
||||
import { getPanelInspectorStyles } from './styles';
|
||||
import { getPanelInspectorStyles2 } from './styles';
|
||||
|
||||
interface Props {
|
||||
options: GetDataOptions;
|
||||
@ -37,7 +37,7 @@ export const InspectDataOptions = ({
|
||||
downloadForExcel,
|
||||
toggleDownloadForExcel,
|
||||
}: Props) => {
|
||||
const styles = getPanelInspectorStyles();
|
||||
const styles = useStyles2(getPanelInspectorStyles2);
|
||||
|
||||
const panelTransformations = panel?.getTransformations();
|
||||
const showPanelTransformationsOption =
|
||||
|
@ -20,7 +20,7 @@ const parseErrorMessage = (message: string): { msg: string; json?: any } => {
|
||||
}
|
||||
};
|
||||
|
||||
export const InspectErrorTab: React.FC<InspectErrorTabProps> = ({ error }) => {
|
||||
export const InspectErrorTab = ({ error }: InspectErrorTabProps) => {
|
||||
if (!error) {
|
||||
return null;
|
||||
}
|
||||
|
@ -1,18 +1,19 @@
|
||||
import { isEqual } from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
import React, { useState, useCallback, useMemo } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
|
||||
import { AppEvents, PanelData, SelectableValue, LoadingState } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { locationService } from '@grafana/runtime';
|
||||
import { Button, CodeEditor, Field, Select } from '@grafana/ui';
|
||||
import { Button, CodeEditor, Field, Select, useStyles2 } from '@grafana/ui';
|
||||
import { appEvents } from 'app/core/core';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
|
||||
|
||||
import { getPanelDataFrames } from '../dashboard/components/HelpWizard/utils';
|
||||
import { getPanelInspectorStyles } from '../inspector/styles';
|
||||
import { getPanelInspectorStyles2 } from '../inspector/styles';
|
||||
import { reportPanelInspectInteraction } from '../search/page/reporting';
|
||||
|
||||
import { InspectTab } from './types';
|
||||
@ -54,42 +55,109 @@ interface Props {
|
||||
data?: PanelData;
|
||||
}
|
||||
|
||||
interface State {
|
||||
show: ShowContent;
|
||||
text: string;
|
||||
export function InspectJSONTab({ panel, dashboard, data, onClose }: Props) {
|
||||
const styles = useStyles2(getPanelInspectorStyles2);
|
||||
const jsonOptions = useMemo(() => {
|
||||
if (panel) {
|
||||
if (panel.plugin?.meta.skipDataQuery) {
|
||||
return [options[0]];
|
||||
}
|
||||
return options;
|
||||
}
|
||||
return options.slice(1, options.length);
|
||||
}, [panel]);
|
||||
const [show, setShow] = useState(panel ? ShowContent.PanelJSON : ShowContent.DataFrames);
|
||||
const [text, setText] = useState('');
|
||||
|
||||
useAsync(async () => {
|
||||
const v = await getJSONObject(show, panel, data);
|
||||
setText(getPrettyJSON(v));
|
||||
}, [show, panel, data]);
|
||||
|
||||
const onApplyPanelModel = useCallback(() => {
|
||||
if (panel && dashboard && text) {
|
||||
try {
|
||||
if (!dashboard!.meta.canEdit) {
|
||||
appEvents.emit(AppEvents.alertError, ['Unable to apply']);
|
||||
} else {
|
||||
const updates = JSON.parse(text);
|
||||
dashboard!.shouldUpdateDashboardPanelFromJSON(updates, panel!);
|
||||
|
||||
//Report relevant updates
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'apply', {
|
||||
panel_type_changed: panel!.type !== updates.type,
|
||||
panel_id_changed: panel!.id !== updates.id,
|
||||
panel_grid_pos_changed: !isEqual(panel!.gridPos, updates.gridPos),
|
||||
panel_targets_changed: !isEqual(panel!.targets, updates.targets),
|
||||
});
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
hasPanelJSON: boolean;
|
||||
onClose();
|
||||
}
|
||||
}, [panel, dashboard, onClose, text]);
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
this.hasPanelJSON = !!(props.panel && props.dashboard);
|
||||
// If we are in panel, we want to show PanelJSON, otherwise show DataFrames
|
||||
this.state = {
|
||||
show: this.hasPanelJSON ? ShowContent.PanelJSON : ShowContent.DataFrames,
|
||||
text: this.hasPanelJSON ? getPrettyJSON(props.panel!.getSaveModel()) : getPrettyJSON(props.data),
|
||||
};
|
||||
const onShowHelpWizard = useCallback(() => {
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'supportWizard');
|
||||
const queryParms = locationService.getSearch();
|
||||
queryParms.set('inspectTab', InspectTab.Help.toString());
|
||||
locationService.push('?' + queryParms.toString());
|
||||
}, []);
|
||||
|
||||
const isPanelJSON = show === ShowContent.PanelJSON;
|
||||
const canEdit = dashboard && dashboard.meta.canEdit;
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.toolbar} aria-label={selectors.components.PanelInspector.Json.content}>
|
||||
<Field label={t('dashboard.inspect-json.select-source', 'Select source')} className="flex-grow-1">
|
||||
<Select
|
||||
inputId="select-source-dropdown"
|
||||
options={jsonOptions}
|
||||
value={jsonOptions.find((v) => v.value === show) ?? jsonOptions[0].value}
|
||||
onChange={(v) => setShow(v.value!)}
|
||||
/>
|
||||
</Field>
|
||||
{panel && isPanelJSON && canEdit && (
|
||||
<Button className={styles.toolbarItem} onClick={onApplyPanelModel}>
|
||||
Apply
|
||||
</Button>
|
||||
)}
|
||||
{show === ShowContent.DataFrames && (
|
||||
<Button className={styles.toolbarItem} onClick={onShowHelpWizard}>
|
||||
Support
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={styles.content}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
height={height}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={(text && text.length) > 100}
|
||||
value={text || ''}
|
||||
readOnly={!isPanelJSON}
|
||||
onBlur={setText}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
// when opening the inspector we want to report the interaction
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'panelJSON');
|
||||
}
|
||||
|
||||
onSelectChanged = async (item: SelectableValue<ShowContent>) => {
|
||||
const show = await this.getJSONObject(item.value!);
|
||||
const text = getPrettyJSON(show);
|
||||
this.setState({ text, show: item.value! });
|
||||
};
|
||||
|
||||
// Called onBlur
|
||||
onTextChanged = (text: string) => {
|
||||
this.setState({ text });
|
||||
};
|
||||
|
||||
async getJSONObject(show: ShowContent) {
|
||||
const { data, panel } = this.props;
|
||||
async function getJSONObject(show: ShowContent, panel?: PanelModel, data?: PanelData) {
|
||||
if (show === ShowContent.PanelData) {
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'panelData');
|
||||
return data;
|
||||
@ -112,7 +180,7 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
return getPanelDataFrames(d);
|
||||
}
|
||||
|
||||
if (this.hasPanelJSON && show === ShowContent.PanelJSON) {
|
||||
if (show === ShowContent.PanelJSON && panel) {
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'panelJSON');
|
||||
return panel!.getSaveModel();
|
||||
}
|
||||
@ -120,96 +188,6 @@ export class InspectJSONTab extends PureComponent<Props, State> {
|
||||
return { note: t('dashboard.inspect-json.unknown', 'Unknown Object: {{show}}', { show }) };
|
||||
}
|
||||
|
||||
onApplyPanelModel = () => {
|
||||
const { panel, dashboard, onClose } = this.props;
|
||||
if (this.hasPanelJSON) {
|
||||
try {
|
||||
if (!dashboard!.meta.canEdit) {
|
||||
appEvents.emit(AppEvents.alertError, ['Unable to apply']);
|
||||
} else {
|
||||
const updates = JSON.parse(this.state.text);
|
||||
dashboard!.shouldUpdateDashboardPanelFromJSON(updates, panel!);
|
||||
|
||||
//Report relevant updates
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'apply', {
|
||||
panel_type_changed: panel!.type !== updates.type,
|
||||
panel_id_changed: panel!.id !== updates.id,
|
||||
panel_grid_pos_changed: !isEqual(panel!.gridPos, updates.gridPos),
|
||||
panel_targets_changed: !isEqual(panel!.targets, updates.targets),
|
||||
});
|
||||
|
||||
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']);
|
||||
}
|
||||
|
||||
onClose();
|
||||
}
|
||||
};
|
||||
|
||||
onShowHelpWizard = () => {
|
||||
reportPanelInspectInteraction(InspectTab.JSON, 'supportWizard');
|
||||
const queryParms = locationService.getSearch();
|
||||
queryParms.set('inspectTab', InspectTab.Help.toString());
|
||||
locationService.push('?' + queryParms.toString());
|
||||
};
|
||||
|
||||
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 && dashboard.meta.canEdit;
|
||||
const styles = getPanelInspectorStyles();
|
||||
|
||||
return (
|
||||
<div className={styles.wrap}>
|
||||
<div className={styles.toolbar} aria-label={selectors.components.PanelInspector.Json.content}>
|
||||
<Field label={t('dashboard.inspect-json.select-source', 'Select source')} className="flex-grow-1">
|
||||
<Select
|
||||
inputId="select-source-dropdown"
|
||||
options={jsonOptions}
|
||||
value={selected}
|
||||
onChange={this.onSelectChanged}
|
||||
/>
|
||||
</Field>
|
||||
{this.hasPanelJSON && isPanelJSON && canEdit && (
|
||||
<Button className={styles.toolbarItem} onClick={this.onApplyPanelModel}>
|
||||
Apply
|
||||
</Button>
|
||||
)}
|
||||
{show === ShowContent.DataFrames && (
|
||||
<Button className={styles.toolbarItem} onClick={this.onShowHelpWizard}>
|
||||
Support
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<AutoSizer disableWidth>
|
||||
{({ height }) => (
|
||||
<CodeEditor
|
||||
width="100%"
|
||||
height={height}
|
||||
language="json"
|
||||
showLineNumbers={true}
|
||||
showMiniMap={(text && text.length) > 100}
|
||||
value={text || ''}
|
||||
readOnly={!isPanelJSON}
|
||||
onBlur={this.onTextChanged}
|
||||
/>
|
||||
)}
|
||||
</AutoSizer>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
function getPrettyJSON(obj: any): string {
|
||||
let r = '';
|
||||
try {
|
||||
|
@ -7,7 +7,7 @@ interface InspectMetadataTabProps {
|
||||
data: PanelData;
|
||||
metadataDatasource?: DataSourceApi;
|
||||
}
|
||||
export const InspectMetadataTab: React.FC<InspectMetadataTabProps> = ({ data, metadataDatasource }) => {
|
||||
export const InspectMetadataTab = ({ data, metadataDatasource }: InspectMetadataTabProps) => {
|
||||
if (!metadataDatasource || !metadataDatasource.components?.MetadataInspector) {
|
||||
return <Trans i18nKey="dashboard.inspect-meta.no-inspector">No Metadata Inspector</Trans>;
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ interface InspectStatsTabProps {
|
||||
timeZone: TimeZone;
|
||||
}
|
||||
|
||||
export const InspectStatsTab: React.FC<InspectStatsTabProps> = ({ data, timeZone }) => {
|
||||
export const InspectStatsTab = ({ data, timeZone }: InspectStatsTabProps) => {
|
||||
if (!data.request) {
|
||||
return null;
|
||||
}
|
||||
|
@ -17,7 +17,7 @@ interface InspectStatsTableProps {
|
||||
stats: QueryResultMetaStat[];
|
||||
}
|
||||
|
||||
export const InspectStatsTable: React.FC<InspectStatsTableProps> = ({ timeZone, name, stats }) => {
|
||||
export const InspectStatsTable = ({ timeZone, name, stats }: InspectStatsTableProps) => {
|
||||
const theme = useTheme2();
|
||||
const styles = getStyles(theme);
|
||||
|
||||
|
@ -1,9 +1,15 @@
|
||||
import { css } from '@emotion/css';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { stylesFactory } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
|
||||
/** @deprecated */
|
||||
export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
return getPanelInspectorStyles2(config.theme2);
|
||||
});
|
||||
|
||||
export const getPanelInspectorStyles2 = (theme: GrafanaTheme2) => {
|
||||
return {
|
||||
wrap: css`
|
||||
display: flex;
|
||||
@ -18,10 +24,10 @@ export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
flex-grow: 0;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
margin-bottom: ${config.theme.spacing.sm};
|
||||
margin-bottom: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
toolbarItem: css`
|
||||
margin-left: ${config.theme.spacing.md};
|
||||
margin-left: ${theme.v1.spacing.md};
|
||||
`,
|
||||
content: css`
|
||||
flex-grow: 1;
|
||||
@ -49,18 +55,18 @@ export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
}
|
||||
`,
|
||||
options: css`
|
||||
padding-top: ${config.theme.spacing.sm};
|
||||
padding-top: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
dataDisplayOptions: css`
|
||||
flex-grow: 1;
|
||||
min-width: 300px;
|
||||
margin-right: ${config.theme.spacing.sm};
|
||||
margin-right: ${theme.v1.spacing.sm};
|
||||
`,
|
||||
selects: css`
|
||||
display: flex;
|
||||
> * {
|
||||
margin-right: ${config.theme.spacing.sm};
|
||||
margin-right: ${theme.v1.spacing.sm};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user