mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
DashboardScene: Inspect panel data tab (#74646)
* Refactor data tab to be usable from scenes * DashboardScene: Inspect data tab * Everything seem to work now * don't change drawer size in this PR * Remove uncommented code * Fix layout issues for data actions * Added comment explaining retry
This commit is contained in:
parent
c8a0ebe0e8
commit
73a675af02
@ -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}
|
||||
</div>
|
||||
|
||||
<div className={styles.column}>
|
||||
<Stack gap={1} alignItems="center" wrap={false}>
|
||||
{actionsElement}
|
||||
{draggable && (
|
||||
<Icon
|
||||
@ -70,7 +71,7 @@ export const QueryOperationRowHeader = ({
|
||||
{...dragHandleProps}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
</Stack>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
@ -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<InspectTabState> {
|
||||
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<InspectDataTabState> {
|
||||
public constructor(state: Omit<InspectDataTabState, 'options'>) {
|
||||
super({
|
||||
...state,
|
||||
options: {
|
||||
withTransforms: true,
|
||||
withFieldConfig: true,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<InspectDataTab>) => {
|
||||
//const data = sceneGraph.getData(model.panel).useState();
|
||||
public onOptionsChange = (options: GetDataOptions) => {
|
||||
this.setState({ options });
|
||||
};
|
||||
|
||||
return <div>Data tab</div>;
|
||||
static Component = ({ model }: SceneComponentProps<InspectDataTab>) => {
|
||||
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) {
|
||||
<div>No data found</div>;
|
||||
}
|
||||
|
||||
return (
|
||||
<InspectDataTabOld
|
||||
isLoading={data?.state === LoadingState.Loading}
|
||||
data={data?.series}
|
||||
options={options}
|
||||
hasTransformations={hasTransformations(dataProvider)}
|
||||
timeZone={timeRange.getTimeZone()}
|
||||
panelPluginId={panel.state.pluginId}
|
||||
dataName={panel.state.title}
|
||||
fieldConfig={panel.state.fieldConfig}
|
||||
onOptionsChange={model.onOptionsChange}
|
||||
/>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
@ -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<InspectTabState> {
|
||||
constructor(public panel: VizPanel) {
|
||||
super({ label: t('dashboard.inspect.json-tab', 'JSON'), value: InspectTab.JSON });
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<InspectJsonTab>) => {
|
||||
return <div>JSON</div>;
|
||||
};
|
||||
|
@ -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<InspectTabState> {
|
||||
constructor(public panel: VizPanel) {
|
||||
super({ label: t('dashboard.inspect.stats-tab', 'Stats'), value: InspectTab.Stats });
|
||||
}
|
||||
|
||||
static Component = ({ model }: SceneComponentProps<InspectStatsTab>) => {
|
||||
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;
|
||||
|
@ -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<SceneObject<InspectTabState>>;
|
||||
panelRef: SceneObjectRef<VizPanel>;
|
||||
pluginNotLoaded?: boolean;
|
||||
}
|
||||
|
||||
export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState> {
|
||||
@ -31,22 +34,35 @@ export class PanelInspectDrawer extends SceneObjectBase<PanelInspectDrawerState>
|
||||
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<SceneObject<InspectTabState>> = [];
|
||||
|
||||
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<PanelInspectDrawerState>
|
||||
}
|
||||
|
||||
function PanelInspectRenderer({ model }: SceneComponentProps<PanelInspectDrawer>) {
|
||||
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<PanelInspectDrawer>
|
||||
title={model.getDrawerTitle()}
|
||||
scrollableContent
|
||||
onClose={model.onClose}
|
||||
size="md"
|
||||
size="lg"
|
||||
tabs={
|
||||
<TabsBar>
|
||||
{tabs.map((tab) => {
|
||||
@ -94,6 +110,11 @@ function PanelInspectRenderer({ model }: SceneComponentProps<PanelInspectDrawer>
|
||||
</TabsBar>
|
||||
}
|
||||
>
|
||||
{pluginNotLoaded && (
|
||||
<Alert title="Panel plugin not loaded">
|
||||
Make sure the panel you want to inspect is visible and has been displayed before opening inspect.
|
||||
</Alert>
|
||||
)}
|
||||
{currentTab.Component && <currentTab.Component model={currentTab} />}
|
||||
</Drawer>
|
||||
);
|
||||
|
@ -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<VizPanel>;
|
||||
}
|
||||
|
@ -88,7 +88,10 @@ export const InspectContent = ({
|
||||
>
|
||||
{activeTab === InspectTab.Data && (
|
||||
<InspectDataTab
|
||||
panel={panel}
|
||||
dataName={panel.getDisplayTitle()}
|
||||
panelPluginId={panel.type}
|
||||
fieldConfig={panel.fieldConfig}
|
||||
hasTransformations={Boolean(panel.transformations?.length)}
|
||||
data={data && data.series}
|
||||
isLoading={isDataLoading}
|
||||
options={dataOptions}
|
||||
|
@ -3,7 +3,7 @@ import { useToggle } from 'react-use';
|
||||
|
||||
import { DataFrame, DataTransformerConfig, TransformerRegistryItem, FrameMatcherID } from '@grafana/data';
|
||||
import { reportInteraction } from '@grafana/runtime';
|
||||
import { ConfirmModal, HorizontalGroup } from '@grafana/ui';
|
||||
import { ConfirmModal } from '@grafana/ui';
|
||||
import { OperationRowHelp } from 'app/core/components/QueryOperationRow/OperationRowHelp';
|
||||
import {
|
||||
QueryOperationAction,
|
||||
@ -103,7 +103,7 @@ export const TransformationOperationRow = ({
|
||||
|
||||
const renderActions = ({ isOpen }: QueryOperationRowRenderProps) => {
|
||||
return (
|
||||
<HorizontalGroup align="center" width="auto">
|
||||
<>
|
||||
{uiConfig.state && <PluginStateInfo state={uiConfig.state} />}
|
||||
<QueryOperationToggleAction
|
||||
title="Show transform help"
|
||||
@ -152,7 +152,7 @@ export const TransformationOperationRow = ({
|
||||
onDismiss={() => setShowDeleteModal(false)}
|
||||
/>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -56,6 +56,7 @@ export function ExploreQueryInspector(props: Props) {
|
||||
content: (
|
||||
<InspectDataTab
|
||||
data={dataFrames}
|
||||
dataName={'Explore'}
|
||||
isLoading={loading}
|
||||
options={{ withTransforms: false, withFieldConfig: false }}
|
||||
timeZone={timeZone}
|
||||
|
@ -4,7 +4,6 @@ import { DataFrame, DataTransformerID, getFrameDisplayName, SelectableValue } fr
|
||||
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';
|
||||
|
||||
@ -13,24 +12,24 @@ import { getPanelInspectorStyles2 } from './styles';
|
||||
interface Props {
|
||||
options: GetDataOptions;
|
||||
dataFrames: DataFrame[];
|
||||
transformId: DataTransformerID;
|
||||
transformationOptions: Array<SelectableValue<DataTransformerID>>;
|
||||
selectedDataFrame: number | DataTransformerID;
|
||||
downloadForExcel: boolean;
|
||||
onDataFrameChange: (item: SelectableValue<DataTransformerID | number>) => 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={<DetailText>{getActiveString()}</DetailText>}
|
||||
isOpen={false}
|
||||
actions={actions}
|
||||
>
|
||||
<div className={styles.options} data-testid="dataOptions">
|
||||
<VerticalGroup spacing="none">
|
||||
@ -116,7 +112,7 @@ export const InspectDataOptions = ({
|
||||
)}
|
||||
|
||||
<HorizontalGroup>
|
||||
{showPanelTransformationsOption && onOptionsChange && (
|
||||
{hasTransformations && onOptionsChange && (
|
||||
<Field
|
||||
label={t('dashboard.inspect-data.transformations-label', 'Apply panel transformations')}
|
||||
description={t(
|
||||
@ -130,7 +126,7 @@ export const InspectDataOptions = ({
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
{showFieldConfigsOption && onOptionsChange && (
|
||||
{onOptionsChange && (
|
||||
<Field
|
||||
label={t('dashboard.inspect-data.formatted-data-label', 'Formatted data')}
|
||||
description={t(
|
||||
|
@ -1,4 +1,3 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { cloneDeep } from 'lodash';
|
||||
import React, { PureComponent } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
@ -7,7 +6,6 @@ import {
|
||||
applyFieldOverrides,
|
||||
applyRawFieldOverrides,
|
||||
CoreApp,
|
||||
CSVConfig,
|
||||
DataFrame,
|
||||
DataTransformerID,
|
||||
FieldConfigSource,
|
||||
@ -20,7 +18,6 @@ import { reportInteraction } from '@grafana/runtime';
|
||||
import { Button, Spinner, Table } from '@grafana/ui';
|
||||
import { config } from 'app/core/config';
|
||||
import { t, Trans } from 'app/core/internationalization';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { GetDataOptions } from 'app/features/query/state/PanelQueryRunner';
|
||||
|
||||
import { dataFrameToLogsModel } from '../logs/logsModel';
|
||||
@ -35,7 +32,11 @@ interface Props {
|
||||
timeZone: TimeZone;
|
||||
app?: CoreApp;
|
||||
data?: DataFrame[];
|
||||
panel?: PanelModel;
|
||||
/** The title of the panel or other context name */
|
||||
dataName: string;
|
||||
panelPluginId?: string;
|
||||
fieldConfig?: FieldConfigSource;
|
||||
hasTransformations?: boolean;
|
||||
onOptionsChange?: (options: GetDataOptions) => void;
|
||||
}
|
||||
|
||||
@ -91,15 +92,20 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
}
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
});
|
||||
|
||||
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<Props, State> {
|
||||
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<Props, State> {
|
||||
}
|
||||
};
|
||||
|
||||
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<Props, State> {
|
||||
return;
|
||||
}
|
||||
|
||||
downloadAsJson(data, panel ? panel.getDisplayTitle() : 'Explore');
|
||||
downloadAsJson(data, dataName);
|
||||
};
|
||||
|
||||
onDataFrameChange = (item: SelectableValue<DataTransformerID | number>) => {
|
||||
@ -158,28 +166,28 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
});
|
||||
};
|
||||
|
||||
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<Props, State> {
|
||||
return fieldConfig;
|
||||
}
|
||||
|
||||
renderActions(dataFrames: DataFrame[], hasLogs: boolean, hasTraces: boolean, hasServiceGraph: boolean) {
|
||||
return (
|
||||
<>
|
||||
<Button variant="primary" onClick={() => this.exportCsv(dataFrames, hasLogs)} size="sm">
|
||||
<Trans i18nKey="dashboard.inspect-data.download-csv">Download CSV</Trans>
|
||||
</Button>
|
||||
{hasLogs && (
|
||||
<Button variant="primary" onClick={this.onExportLogsAsTxt} size="sm">
|
||||
<Trans i18nKey="dashboard.inspect-data.download-logs">Download logs</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{hasTraces && (
|
||||
<Button variant="primary" onClick={this.onExportTracesAsJson} size="sm">
|
||||
<Trans i18nKey="dashboard.inspect-data.download-traces">Download traces</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{hasServiceGraph && (
|
||||
<Button variant="primary" onClick={this.onExportServiceGraph} size="sm">
|
||||
<Trans i18nKey="dashboard.inspect-data.download-service">Download service graph</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
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<Props, State> {
|
||||
<div className={styles.toolbar}>
|
||||
<InspectDataOptions
|
||||
data={data}
|
||||
panel={panel}
|
||||
hasTransformations={hasTransformations}
|
||||
options={options}
|
||||
dataFrames={dataFrames}
|
||||
transformId={transformId}
|
||||
transformationOptions={transformationOptions}
|
||||
selectedDataFrame={selectedDataFrame}
|
||||
downloadForExcel={downloadForExcel}
|
||||
onOptionsChange={onOptionsChange}
|
||||
onDataFrameChange={this.onDataFrameChange}
|
||||
toggleDownloadForExcel={this.toggleDownloadForExcel}
|
||||
toggleDownloadForExcel={this.onToggleDownloadForExcel}
|
||||
actions={this.renderActions(dataFrames, hasLogs, hasTraces, hasServiceGraph)}
|
||||
/>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => {
|
||||
if (hasLogs) {
|
||||
reportInteraction('grafana_logs_download_clicked', {
|
||||
app,
|
||||
format: 'csv',
|
||||
});
|
||||
}
|
||||
this.exportCsv(dataFrames[dataFrameIndex], { useExcelHeader: this.state.downloadForExcel });
|
||||
}}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
`}
|
||||
>
|
||||
<Trans i18nKey="dashboard.inspect-data.download-csv">Download CSV</Trans>
|
||||
</Button>
|
||||
{hasLogs && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={this.exportLogsAsTxt}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
`}
|
||||
>
|
||||
<Trans i18nKey="dashboard.inspect-data.download-logs">Download logs</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{hasTraces && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={this.exportTracesAsJson}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
`}
|
||||
>
|
||||
<Trans i18nKey="dashboard.inspect-data.download-traces">Download traces</Trans>
|
||||
</Button>
|
||||
)}
|
||||
{hasServiceGraph && (
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={this.exportServiceGraph}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
margin-left: 10px;
|
||||
`}
|
||||
>
|
||||
<Trans i18nKey="dashboard.inspect-data.download-service">Download service graph</Trans>
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
<div className={styles.content}>
|
||||
<AutoSizer>
|
||||
|
@ -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<TQuery extends DataQuery> extends PureComponent<Prop
|
||||
const hasEditorHelp = datasource?.components?.QueryEditorHelp;
|
||||
|
||||
return (
|
||||
<HorizontalGroup width="auto">
|
||||
<>
|
||||
{hasEditorHelp && (
|
||||
<QueryOperationToggleAction
|
||||
title="Show data source help"
|
||||
@ -468,7 +468,7 @@ export class QueryEditorRow<TQuery extends DataQuery> extends PureComponent<Prop
|
||||
/>
|
||||
) : null}
|
||||
<QueryOperationAction title="Remove query" icon="trash-alt" onClick={this.onRemoveQuery} />
|
||||
</HorizontalGroup>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user