mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
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:
@@ -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 }) => {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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};
|
||||
}
|
||||
`,
|
||||
};
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user