diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx
index e3cbace1aeb..98cc9adb3ec 100644
--- a/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx
+++ b/public/app/core/components/QueryOperationRow/QueryOperationRowHeader.tsx
@@ -3,6 +3,7 @@ import React, { MouseEventHandler } from 'react';
import { DraggableProvided } from 'react-beautiful-dnd';
import { GrafanaTheme2 } from '@grafana/data';
+import { Stack } from '@grafana/experimental';
import { Icon, IconButton, useStyles2 } from '@grafana/ui';
export interface QueryOperationRowHeaderProps {
@@ -58,7 +59,7 @@ export const QueryOperationRowHeader = ({
{headerElement}
-
+
{actionsElement}
{draggable && (
)}
-
+
);
};
diff --git a/public/app/features/dashboard-scene/inspect/InspectDataTab.tsx b/public/app/features/dashboard-scene/inspect/InspectDataTab.tsx
index 489b39b738a..0c964e7de06 100644
--- a/public/app/features/dashboard-scene/inspect/InspectDataTab.tsx
+++ b/public/app/features/dashboard-scene/inspect/InspectDataTab.tsx
@@ -1,20 +1,77 @@
import React from 'react';
-import { SceneComponentProps, SceneObjectBase, VizPanel } from '@grafana/scenes';
-import { t } from 'app/core/internationalization';
+import { LoadingState } from '@grafana/data';
+import {
+ SceneComponentProps,
+ SceneDataProvider,
+ SceneDataTransformer,
+ sceneGraph,
+ SceneObjectBase,
+} from '@grafana/scenes';
+import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
-import { InspectTab } from '../../inspector/types';
+import { InspectDataTab as InspectDataTabOld } from '../../inspector/InspectDataTab';
import { InspectTabState } from './types';
-export class InspectDataTab extends SceneObjectBase {
- constructor(public panel: VizPanel) {
- super({ label: t('dashboard.inspect.data-tab', 'Data'), value: InspectTab.Data });
+export interface InspectDataTabState extends InspectTabState {
+ options: GetDataOptions;
+}
+
+export class InspectDataTab extends SceneObjectBase {
+ public constructor(state: Omit) {
+ super({
+ ...state,
+ options: {
+ withTransforms: true,
+ withFieldConfig: true,
+ },
+ });
}
- static Component = ({ model }: SceneComponentProps) => {
- //const data = sceneGraph.getData(model.panel).useState();
+ public onOptionsChange = (options: GetDataOptions) => {
+ this.setState({ options });
+ };
- return Data tab
;
+ static Component = ({ model }: SceneComponentProps) => {
+ const { options } = model.useState();
+ const panel = model.state.panelRef.resolve();
+ const dataProvider = sceneGraph.getData(panel);
+ const { data } = getDataProviderToSubscribeTo(dataProvider, options.withTransforms).useState();
+ const timeRange = sceneGraph.getTimeRange(panel);
+
+ if (!data) {
+ No data found
;
+ }
+
+ return (
+
+ );
};
}
+
+function hasTransformations(dataProvider: SceneDataProvider) {
+ if (dataProvider instanceof SceneDataTransformer) {
+ return dataProvider.state.transformations.length > 0;
+ }
+
+ return false;
+}
+
+function getDataProviderToSubscribeTo(dataProvider: SceneDataProvider, withTransforms: boolean) {
+ if (withTransforms && dataProvider instanceof SceneDataTransformer) {
+ return dataProvider.state.$data!;
+ }
+
+ return dataProvider;
+}
diff --git a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx
index 2689c1dbc42..715e92f1e40 100644
--- a/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx
+++ b/public/app/features/dashboard-scene/inspect/InspectJsonTab.tsx
@@ -1,17 +1,10 @@
import React from 'react';
-import { SceneComponentProps, SceneObjectBase, VizPanel } from '@grafana/scenes';
-import { t } from 'app/core/internationalization';
-
-import { InspectTab } from '../../inspector/types';
+import { SceneComponentProps, SceneObjectBase } from '@grafana/scenes';
import { InspectTabState } from './types';
export class InspectJsonTab extends SceneObjectBase {
- constructor(public panel: VizPanel) {
- super({ label: t('dashboard.inspect.json-tab', 'JSON'), value: InspectTab.JSON });
- }
-
static Component = ({ model }: SceneComponentProps) => {
return JSON
;
};
diff --git a/public/app/features/dashboard-scene/inspect/InspectStatsTab.tsx b/public/app/features/dashboard-scene/inspect/InspectStatsTab.tsx
index 9fa07c7896e..6571a5a6aeb 100644
--- a/public/app/features/dashboard-scene/inspect/InspectStatsTab.tsx
+++ b/public/app/features/dashboard-scene/inspect/InspectStatsTab.tsx
@@ -1,21 +1,15 @@
import React from 'react';
-import { SceneComponentProps, sceneGraph, SceneObjectBase, VizPanel } from '@grafana/scenes';
-import { t } from 'app/core/internationalization';
+import { SceneComponentProps, sceneGraph, SceneObjectBase } from '@grafana/scenes';
import { InspectStatsTab as OldInspectStatsTab } from '../../inspector/InspectStatsTab';
-import { InspectTab } from '../../inspector/types';
import { InspectTabState } from './types';
export class InspectStatsTab extends SceneObjectBase {
- constructor(public panel: VizPanel) {
- super({ label: t('dashboard.inspect.stats-tab', 'Stats'), value: InspectTab.Stats });
- }
-
static Component = ({ model }: SceneComponentProps) => {
- const data = sceneGraph.getData(model.panel).useState();
- const timeRange = sceneGraph.getTimeRange(model.panel);
+ const data = sceneGraph.getData(model.state.panelRef.resolve()).useState();
+ const timeRange = sceneGraph.getTimeRange(model.state.panelRef.resolve());
if (!data.data) {
return null;
diff --git a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx
index 940a7849a71..ff193b34b1f 100644
--- a/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx
+++ b/public/app/features/dashboard-scene/inspect/PanelInspectDrawer.tsx
@@ -12,8 +12,10 @@ import {
VizPanel,
SceneObjectRef,
} from '@grafana/scenes';
-import { Drawer, Tab, TabsBar } from '@grafana/ui';
+import { Alert, Drawer, Tab, TabsBar } from '@grafana/ui';
+import { t } from 'app/core/internationalization';
import { supportsDataQuery } from 'app/features/dashboard/components/PanelEditor/utils';
+import { InspectTab } from 'app/features/inspector/types';
import { InspectDataTab } from './InspectDataTab';
import { InspectJsonTab } from './InspectJsonTab';
@@ -23,6 +25,7 @@ import { InspectTabState } from './types';
interface PanelInspectDrawerState extends SceneObjectState {
tabs?: Array>;
panelRef: SceneObjectRef;
+ pluginNotLoaded?: boolean;
}
export class PanelInspectDrawer extends SceneObjectBase {
@@ -31,22 +34,35 @@ export class PanelInspectDrawer extends SceneObjectBase
constructor(state: PanelInspectDrawerState) {
super(state);
- this.buildTabs();
+ this.buildTabs(0);
}
- buildTabs() {
- const panel = this.state.panelRef.resolve();
+ /**
+ * We currently have no async await to get the panel plugin from the VizPanel.
+ * That is why there is a retry argument here and a setTimeout, to try again a bit later.
+ */
+ buildTabs(retry: number) {
+ const panelRef = this.state.panelRef;
+ const panel = panelRef.resolve();
const plugin = panel.getPlugin();
const tabs: Array> = [];
if (plugin) {
if (supportsDataQuery(plugin)) {
- tabs.push(new InspectDataTab(panel));
- tabs.push(new InspectStatsTab(panel));
+ tabs.push(
+ new InspectDataTab({ panelRef, label: t('dashboard.inspect.data-tab', 'Data'), value: InspectTab.Data })
+ );
+ tabs.push(
+ new InspectStatsTab({ panelRef, label: t('dashboard.inspect.stats-tab', 'Stats'), value: InspectTab.Stats })
+ );
}
+ } else if (retry < 2000) {
+ setTimeout(() => this.buildTabs(retry + 100), 100);
+ } else {
+ this.setState({ pluginNotLoaded: true });
}
- tabs.push(new InspectJsonTab(panel));
+ tabs.push(new InspectJsonTab({ panelRef, label: t('dashboard.inspect.json-tab', 'JSON'), value: InspectTab.JSON }));
this.setState({ tabs });
}
@@ -62,7 +78,7 @@ export class PanelInspectDrawer extends SceneObjectBase
}
function PanelInspectRenderer({ model }: SceneComponentProps) {
- const { tabs } = model.useState();
+ const { tabs, pluginNotLoaded } = model.useState();
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
@@ -78,7 +94,7 @@ function PanelInspectRenderer({ model }: SceneComponentProps
title={model.getDrawerTitle()}
scrollableContent
onClose={model.onClose}
- size="md"
+ size="lg"
tabs={
{tabs.map((tab) => {
@@ -94,6 +110,11 @@ function PanelInspectRenderer({ model }: SceneComponentProps
}
>
+ {pluginNotLoaded && (
+
+ Make sure the panel you want to inspect is visible and has been displayed before opening inspect.
+
+ )}
{currentTab.Component && }
);
diff --git a/public/app/features/dashboard-scene/inspect/types.ts b/public/app/features/dashboard-scene/inspect/types.ts
index 209b3218ce2..48b24b8e97b 100644
--- a/public/app/features/dashboard-scene/inspect/types.ts
+++ b/public/app/features/dashboard-scene/inspect/types.ts
@@ -1,7 +1,8 @@
-import { SceneObjectState } from '@grafana/scenes';
+import { SceneObjectRef, SceneObjectState, VizPanel } from '@grafana/scenes';
import { InspectTab } from 'app/features/inspector/types';
export interface InspectTabState extends SceneObjectState {
label: string;
value: InspectTab;
+ panelRef: SceneObjectRef;
}
diff --git a/public/app/features/dashboard/components/Inspector/InspectContent.tsx b/public/app/features/dashboard/components/Inspector/InspectContent.tsx
index 96a81bb7838..b4ac829fce8 100644
--- a/public/app/features/dashboard/components/Inspector/InspectContent.tsx
+++ b/public/app/features/dashboard/components/Inspector/InspectContent.tsx
@@ -88,7 +88,10 @@ export const InspectContent = ({
>
{activeTab === InspectTab.Data && (
{
return (
-
+ <>
{uiConfig.state && }
setShowDeleteModal(false)}
/>
)}
-
+ >
);
};
diff --git a/public/app/features/explore/ExploreQueryInspector.tsx b/public/app/features/explore/ExploreQueryInspector.tsx
index 5a34d588ad5..9b7e93f2870 100644
--- a/public/app/features/explore/ExploreQueryInspector.tsx
+++ b/public/app/features/explore/ExploreQueryInspector.tsx
@@ -56,6 +56,7 @@ export function ExploreQueryInspector(props: Props) {
content: (
>;
selectedDataFrame: number | DataTransformerID;
downloadForExcel: boolean;
onDataFrameChange: (item: SelectableValue) => void;
toggleDownloadForExcel: () => void;
data?: DataFrame[];
- panel?: PanelModel;
+ hasTransformations?: boolean;
onOptionsChange?: (options: GetDataOptions) => void;
+ actions?: React.ReactNode;
}
export const InspectDataOptions = ({
options,
+ actions,
onOptionsChange,
- panel,
+ hasTransformations,
data,
dataFrames,
- transformId,
transformationOptions,
selectedDataFrame,
onDataFrameChange,
@@ -39,10 +38,6 @@ export const InspectDataOptions = ({
}: Props) => {
const styles = useStyles2(getPanelInspectorStyles2);
- const panelTransformations = panel?.getTransformations();
- const showPanelTransformationsOption = Boolean(panelTransformations?.length);
- const showFieldConfigsOption = panel && !panel.plugin?.fieldConfigRegistry.isEmpty();
-
let dataSelect = dataFrames;
if (selectedDataFrame === DataTransformerID.joinByField) {
dataSelect = data!;
@@ -100,6 +95,7 @@ export const InspectDataOptions = ({
title={t('dashboard.inspect-data.data-options', 'Data options')}
headerElement={{getActiveString()}}
isOpen={false}
+ actions={actions}
>
@@ -116,7 +112,7 @@ export const InspectDataOptions = ({
)}
- {showPanelTransformationsOption && onOptionsChange && (
+ {hasTransformations && onOptionsChange && (
)}
- {showFieldConfigsOption && onOptionsChange && (
+ {onOptionsChange && (
void;
}
@@ -91,15 +92,20 @@ export class InspectDataTab extends PureComponent {
}
}
- exportCsv = (dataFrame: DataFrame, csvConfig: CSVConfig = {}) => {
- const { panel } = this.props;
+ exportCsv(dataFrames: DataFrame[], hasLogs: boolean) {
+ const { dataName } = this.props;
const { transformId } = this.state;
+ const dataFrame = dataFrames[this.state.dataFrameIndex];
- downloadDataFrameAsCsv(dataFrame, panel ? panel.getDisplayTitle() : 'Explore', csvConfig, transformId);
- };
+ if (hasLogs) {
+ reportInteraction('grafana_logs_download_clicked', { app: this.props.app, format: 'csv' });
+ }
- exportLogsAsTxt = () => {
- const { data, panel, app } = this.props;
+ downloadDataFrameAsCsv(dataFrame, dataName, { useExcelHeader: this.state.downloadForExcel }, transformId);
+ }
+
+ onExportLogsAsTxt = () => {
+ const { data, dataName, app } = this.props;
reportInteraction('grafana_logs_download_logs_clicked', {
app,
@@ -108,11 +114,11 @@ export class InspectDataTab extends PureComponent {
});
const logsModel = dataFrameToLogsModel(data || []);
- downloadLogsModelAsTxt(logsModel, panel ? panel.getDisplayTitle() : 'Explore');
+ downloadLogsModelAsTxt(logsModel, dataName);
};
- exportTracesAsJson = () => {
- const { data, panel, app } = this.props;
+ onExportTracesAsJson = () => {
+ const { data, dataName, app } = this.props;
if (!data) {
return;
@@ -123,7 +129,8 @@ export class InspectDataTab extends PureComponent {
if (df.meta?.preferredVisualisationType !== 'trace') {
continue;
}
- const traceFormat = downloadTraceAsJson(df, (panel ? panel.getDisplayTitle() : 'Explore') + '-traces');
+
+ const traceFormat = downloadTraceAsJson(df, dataName + '-traces');
reportInteraction('grafana_traces_download_traces_clicked', {
app,
@@ -134,8 +141,9 @@ export class InspectDataTab extends PureComponent {
}
};
- exportServiceGraph = () => {
- const { data, panel, app } = this.props;
+ onExportServiceGraph = () => {
+ const { data, dataName, app } = this.props;
+
reportInteraction('grafana_traces_download_service_graph_clicked', {
app,
grafana_version: config.buildInfo.version,
@@ -146,7 +154,7 @@ export class InspectDataTab extends PureComponent {
return;
}
- downloadAsJson(data, panel ? panel.getDisplayTitle() : 'Explore');
+ downloadAsJson(data, dataName);
};
onDataFrameChange = (item: SelectableValue) => {
@@ -158,28 +166,28 @@ export class InspectDataTab extends PureComponent {
});
};
- toggleDownloadForExcel = () => {
+ onToggleDownloadForExcel = () => {
this.setState((prevState) => ({
downloadForExcel: !prevState.downloadForExcel,
}));
};
getProcessedData(): DataFrame[] {
- const { options, panel, timeZone } = this.props;
+ const { options, panelPluginId, fieldConfig, timeZone } = this.props;
const data = this.state.transformedData;
- if (!options.withFieldConfig || !panel) {
+ if (!options.withFieldConfig || !panelPluginId || !fieldConfig) {
return applyRawFieldOverrides(data);
}
- const fieldConfig = this.cleanTableConfigFromFieldConfig(panel.type, panel.fieldConfig);
+ const fieldConfigCleaned = this.cleanTableConfigFromFieldConfig(panelPluginId, fieldConfig);
// We need to apply field config as it's not done by PanelQueryRunner (even when withFieldConfig is true).
// It's because transformers create new fields and data frames, and we need to clean field config of any table settings.
return applyFieldOverrides({
data,
theme: config.theme2,
- fieldConfig,
+ fieldConfig: fieldConfigCleaned,
timeZone,
replaceVariables: (value: string) => {
return value;
@@ -210,9 +218,34 @@ export class InspectDataTab extends PureComponent {
return fieldConfig;
}
+ renderActions(dataFrames: DataFrame[], hasLogs: boolean, hasTraces: boolean, hasServiceGraph: boolean) {
+ return (
+ <>
+
+ {hasLogs && (
+
+ )}
+ {hasTraces && (
+
+ )}
+ {hasServiceGraph && (
+
+ )}
+ >
+ );
+ }
+
render() {
- const { isLoading, options, data, panel, onOptionsChange, app } = this.props;
- const { dataFrameIndex, transformId, transformationOptions, selectedDataFrame, downloadForExcel } = this.state;
+ const { isLoading, options, data, onOptionsChange, hasTransformations } = this.props;
+ const { dataFrameIndex, transformationOptions, selectedDataFrame, downloadForExcel } = this.state;
const styles = getPanelInspectorStyles();
if (isLoading) {
@@ -241,70 +274,17 @@ export class InspectDataTab extends PureComponent {
-
- {hasLogs && (
-
- )}
- {hasTraces && (
-
- )}
- {hasServiceGraph && (
-
- )}
diff --git a/public/app/features/query/components/QueryEditorRow.tsx b/public/app/features/query/components/QueryEditorRow.tsx
index c380d543157..28456e6b4f8 100644
--- a/public/app/features/query/components/QueryEditorRow.tsx
+++ b/public/app/features/query/components/QueryEditorRow.tsx
@@ -23,7 +23,7 @@ import {
} from '@grafana/data';
import { selectors } from '@grafana/e2e-selectors';
import { AngularComponent, getAngularLoader, getDataSourceSrv } from '@grafana/runtime';
-import { Badge, ErrorBoundaryAlert, HorizontalGroup } from '@grafana/ui';
+import { Badge, ErrorBoundaryAlert } from '@grafana/ui';
import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp';
import {
QueryOperationAction,
@@ -439,7 +439,7 @@ export class QueryEditorRow extends PureComponent
+ <>
{hasEditorHelp && (
extends PureComponent
) : null}
-
+ >
);
};