Inspect: Allow showing data without transformations and field config is applied (#24314)

* Inspect: Should not subscribe to transformed data

* PQR- allow controll whether or not field overrides and transformations should be applied

* UI for inspector data options

* fix

* Null check fix

* Update public/app/features/dashboard/components/Inspector/InspectDataTab.tsx

* Update public/app/features/dashboard/components/Inspector/InspectDataTab.tsx

* Apply transformations by default

* Update panel inspect docs

* Fix apply overrides

* Apply time formatting in panel inspect

* fix ts

* Post review update

* Update docs/sources/panels/inspect-panel.md

Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>

* lazy numbering

* fix ts

* Renames

* Renames 2

* Layout update

* Run shared request without field config

* Minor details

* fix ts

Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
Co-authored-by: Diana Payton <52059945+oddlittlebird@users.noreply.github.com>
This commit is contained in:
Torkel Ödegaard
2020-05-13 13:03:34 +02:00
committed by GitHub
parent 9e24c0944f
commit f23ecc40b4
12 changed files with 233 additions and 60 deletions

View File

@@ -8,18 +8,27 @@ import {
transformDataFrame,
getFrameDisplayName,
} from '@grafana/data';
import { Button, Field, Icon, Select, Table } from '@grafana/ui';
import { Button, Field, Icon, LegacyForms, Select, Table } from '@grafana/ui';
import { selectors } from '@grafana/e2e-selectors';
import AutoSizer from 'react-virtualized-auto-sizer';
import { getPanelInspectorStyles } from './styles';
import { config } from 'app/core/config';
import { saveAs } from 'file-saver';
import { cx } from 'emotion';
import { css, cx } from 'emotion';
import { GetDataOptions } from '../../state/PanelQueryRunner';
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
import { DashboardModel, PanelModel } from 'app/features/dashboard/state';
const { Switch } = LegacyForms;
interface Props {
dashboard: DashboardModel;
panel: PanelModel;
data: DataFrame[];
isLoading: boolean;
options: GetDataOptions;
onOptionsChange: (options: GetDataOptions) => void;
}
interface State {
@@ -55,6 +64,10 @@ export class InspectDataTab extends PureComponent<Props, State> {
onTransformationChange = (value: SelectableValue<DataTransformerID>) => {
this.setState({ transformId: value.value, dataFrameIndex: 0 });
this.props.onOptionsChange({
...this.props.options,
withTransforms: false,
});
};
getTransformedData(): DataFrame[] {
@@ -74,10 +87,19 @@ export class InspectDataTab extends PureComponent<Props, State> {
}
getProcessedData(): DataFrame[] {
const { options } = this.props;
let data = this.props.data;
if (this.state.transformId !== DataTransformerID.noop) {
data = this.getTransformedData();
}
// We need to apply field config even though it was already applied in the PanelQueryRunner.
// That's because transformers create new fields and data frames, so i.e. display processor is no longer there
return applyFieldOverrides({
data: this.getTransformedData(),
data,
theme: config.theme,
fieldConfig: { defaults: {}, overrides: [] },
fieldConfig: options.withFieldConfig ? this.props.panel.fieldConfig : { defaults: {}, overrides: [] },
replaceVariables: (value: string) => {
return value;
},
@@ -85,7 +107,7 @@ export class InspectDataTab extends PureComponent<Props, State> {
}
render() {
const { isLoading, data } = this.props;
const { isLoading, data, options, onOptionsChange } = this.props;
const { dataFrameIndex, transformId, transformationOptions } = this.state;
const styles = getPanelInspectorStyles();
@@ -110,25 +132,73 @@ export class InspectDataTab extends PureComponent<Props, State> {
};
});
const panelTransformations = this.props.panel.getTransformations();
return (
<div className={styles.dataTabContent} aria-label={selectors.components.PanelInspector.Data.content}>
<div className={styles.toolbar}>
{data.length > 1 && (
<Field label="Transform data" className="flex-grow-1">
<Select options={transformationOptions} value={transformId} onChange={this.onTransformationChange} />
</Field>
)}
{choices.length > 1 && (
<Field label="Select result" className={cx(styles.toolbarItem, 'flex-grow-1')}>
<Select options={choices} value={dataFrameIndex} onChange={this.onSelectedFrameChanged} />
</Field>
)}
<div className={styles.downloadCsv}>
<div className={styles.actionsWrapper}>
<div className={styles.leftActions}>
<div className={styles.selects}>
{data.length > 1 && (
<Field
label="Transformer"
className={css`
margin-bottom: 0;
`}
>
<Select
options={transformationOptions}
value={transformId}
onChange={this.onTransformationChange}
width={15}
/>
</Field>
)}
{choices.length > 1 && (
<Field
label="Select result"
className={css`
margin-bottom: 0;
`}
>
<Select options={choices} value={dataFrameIndex} onChange={this.onSelectedFrameChanged} />
</Field>
)}
</div>
<div className={cx(styles.options, styles.dataDisplayOptions)}>
<QueryOperationRow title={'Data display options'} isOpen={false}>
{panelTransformations && panelTransformations.length > 0 && (transformId as any) !== 'join by time' && (
<div className="gf-form-inline">
<Switch
tooltip="Data shown in the table will be transformed using transformations defined in the panel"
label="Apply panel transformations"
labelClass="width-12"
checked={!!options.withTransforms}
onChange={() => onOptionsChange({ ...options, withTransforms: !options.withTransforms })}
/>
</div>
)}
<div className="gf-form-inline">
<Switch
tooltip="Data shown in the table will have panel field configuration applied, for example units or display name"
label="Apply field configuration"
labelClass="width-12"
checked={!!options.withFieldConfig}
onChange={() => onOptionsChange({ ...options, withFieldConfig: !options.withFieldConfig })}
/>
</div>
</QueryOperationRow>
</div>
</div>
<div className={styles.options}>
<Button variant="primary" onClick={() => this.exportCsv(dataFrames[dataFrameIndex])}>
Download CSV
</Button>
</div>
</div>
<div style={{ flexGrow: 1 }}>
<AutoSizer>
{({ width, height }) => {

View File

@@ -28,6 +28,7 @@ import { getPanelInspectorStyles } from './styles';
import { StoreState } from 'app/types';
import { InspectDataTab } from './InspectDataTab';
import { supportsDataQuery } from '../PanelEditor/utils';
import { GetDataOptions } from '../../state/PanelQueryRunner';
interface OwnProps {
dashboard: DashboardModel;
@@ -62,6 +63,8 @@ interface State {
metaDS?: DataSourceApi;
// drawer width
drawerWidth: string;
withTransforms: boolean;
withFieldConfig: boolean;
}
export class PanelInspectorUnconnected extends PureComponent<Props, State> {
@@ -76,6 +79,8 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
data: [],
currentTab: props.defaultTab ?? InspectTab.Data,
drawerWidth: '50%',
withTransforms: true,
withFieldConfig: false,
};
}
@@ -87,8 +92,12 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
}
}
componentDidUpdate(prevProps: Props) {
if (prevProps.plugin !== this.props.plugin) {
componentDidUpdate(prevProps: Props, prevState: State) {
if (
prevProps.plugin !== this.props.plugin ||
this.state.withTransforms !== prevState.withTransforms ||
this.state.withFieldConfig !== prevState.withFieldConfig
) {
this.init();
}
}
@@ -99,11 +108,15 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
*/
init() {
const { plugin, panel } = this.props;
const { withTransforms, withFieldConfig } = this.state;
if (plugin && !plugin.meta.skipDataQuery) {
if (this.querySubscription) {
this.querySubscription.unsubscribe();
}
this.querySubscription = panel
.getQueryRunner()
.getData()
.getData({ withTransforms, withFieldConfig })
.subscribe({
next: data => this.onUpdateData(data),
});
@@ -164,6 +177,9 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
onSelectTab = (item: SelectableValue<InspectTab>) => {
this.setState({ currentTab: item.value || InspectTab.Data });
};
onDataTabOptionsChange = (options: GetDataOptions) => {
this.setState({ withTransforms: !!options.withTransforms, withFieldConfig: !!options.withFieldConfig });
};
renderMetadataInspector() {
const { metaDS, data } = this.state;
@@ -174,8 +190,20 @@ export class PanelInspectorUnconnected extends PureComponent<Props, State> {
}
renderDataTab() {
const { last, isLoading } = this.state;
return <InspectDataTab data={last.series} isLoading={isLoading} />;
const { last, isLoading, withFieldConfig, withTransforms } = this.state;
return (
<InspectDataTab
dashboard={this.props.dashboard}
panel={this.props.panel}
data={last.series}
isLoading={isLoading}
options={{
withFieldConfig,
withTransforms,
}}
onOptionsChange={this.onDataTabOptionsChange}
/>
);
}
renderErrorTab(error?: DataQueryError) {

View File

@@ -41,9 +41,6 @@ export const getPanelInspectorStyles = stylesFactory(() => {
dataFrameSelect: css`
flex-grow: 2;
`,
downloadCsv: css`
margin-left: 16px;
`,
tabContent: css`
height: 100%;
display: flex;
@@ -55,5 +52,27 @@ export const getPanelInspectorStyles = stylesFactory(() => {
height: 100%;
width: 100%;
`,
actionsWrapper: css`
display: flex;
flex-wrap: wrap;
`,
leftActions: css`
display: flex;
flex-grow: 1;
`,
options: css`
margin-top: 19px;
`,
dataDisplayOptions: css`
flex-grow: 1;
min-width: 300px;
margin-right: ${config.theme.spacing.sm};
`,
selects: css`
display: flex;
> * {
margin-right: ${config.theme.spacing.sm};
}
`,
};
});