import { isEqual } from 'lodash'; 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, 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 { getPanelInspectorStyles2 } from '../inspector/styles'; import { reportPanelInspectInteraction } from '../search/page/reporting'; import { InspectTab } from './types'; enum ShowContent { PanelJSON = 'panel', PanelData = 'data', DataFrames = 'frames', } const options: Array> = [ { label: t('dashboard.inspect-json.panel-json-label', 'Panel JSON'), description: t( 'dashboard.inspect-json.panel-json-description', 'The model saved in the dashboard JSON that configures how everything works.' ), value: ShowContent.PanelJSON, }, { label: t('dashboard.inspect-json.panel-data-label', 'Panel data'), description: t('dashboard.inspect-json.panel-data-description', 'The raw model passed to the panel visualization'), value: ShowContent.PanelData, }, { label: t('dashboard.inspect-json.dataframe-label', 'DataFrame JSON (from Query)'), description: t( 'dashboard.inspect-json.dataframe-description', 'Raw data without transformations and field config applied. ' ), value: ShowContent.DataFrames, }, ]; interface Props { onClose: () => void; dashboard?: DashboardModel; panel?: PanelModel; data?: PanelData; } 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']); } onClose(); } }, [panel, dashboard, onClose, text]); 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 (