mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
PanelInspect: Update UI for Data display options (#25478)
* Add TableDataOptions component * A bit along the way * Make transforms work * Make it work * Add collapsed text * Fix transformations and overflow for QueryOperationRow * Fix layout * Minor fixes * Fix feedback * Remove unused imports * Remove unused Container * Horizontal layout for Switches * Fix Swicth bug Co-authored-by: Dominik Prokop <dominik.prokop@grafana.com>
This commit is contained in:
parent
460b01f1fe
commit
61a7f6e2f3
@ -82,11 +82,11 @@ const getQueryOperationRowStyles = stylesFactory((theme: GrafanaTheme) => {
|
||||
margin-bottom: ${theme.spacing.md};
|
||||
`,
|
||||
header: css`
|
||||
padding: 0 ${theme.spacing.sm};
|
||||
padding: ${theme.spacing.xs} ${theme.spacing.sm};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
background: ${theme.colors.bg2};
|
||||
height: ${theme.spacing.formInputHeight}px;
|
||||
line-height: ${theme.spacing.formInputHeight}px;
|
||||
min-height: ${theme.spacing.formInputHeight}px;
|
||||
line-height: ${theme.spacing.sm}px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
|
@ -0,0 +1,16 @@
|
||||
import React, { FC } from 'react';
|
||||
import { useStyles } from '@grafana/ui';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
|
||||
const getStyles = (theme: GrafanaTheme) => css`
|
||||
margin: 0;
|
||||
margin-left: ${theme.spacing.md};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.textWeak};
|
||||
`;
|
||||
|
||||
export const DetailText: FC = ({ children }) => {
|
||||
const collapsedTextStyles = useStyles(getStyles);
|
||||
return <p className={collapsedTextStyles}>{children}</p>;
|
||||
};
|
@ -14,28 +14,18 @@ import {
|
||||
DisplayProcessor,
|
||||
getDisplayProcessor,
|
||||
} from '@grafana/data';
|
||||
import {
|
||||
Button,
|
||||
Container,
|
||||
Field,
|
||||
HorizontalGroup,
|
||||
Icon,
|
||||
LegacyForms,
|
||||
Select,
|
||||
Table,
|
||||
VerticalGroup,
|
||||
} from '@grafana/ui';
|
||||
import { Button, Field, Icon, Switch, Select, Table, VerticalGroup, Container, HorizontalGroup } 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 { css, cx } from 'emotion';
|
||||
import { css } from 'emotion';
|
||||
import { GetDataOptions } from '../../state/PanelQueryRunner';
|
||||
import { QueryOperationRow } from '../../../../core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { PanelModel } from '../../state';
|
||||
const { Switch } = LegacyForms;
|
||||
import { QueryOperationRow } from 'app/core/components/QueryOperationRow/QueryOperationRow';
|
||||
import { PanelModel } from 'app/features/dashboard/state';
|
||||
import { DetailText } from './DetailText';
|
||||
|
||||
interface Props {
|
||||
panel: PanelModel;
|
||||
@ -46,9 +36,11 @@ interface Props {
|
||||
}
|
||||
|
||||
interface State {
|
||||
/** The string is seriesToColumns transformation. Otherwise it is a dataframe index */
|
||||
selectedDataFrame: number | DataTransformerID;
|
||||
transformId: DataTransformerID;
|
||||
dataFrameIndex: number;
|
||||
transformationOptions: Array<SelectableValue<string>>;
|
||||
transformationOptions: Array<SelectableValue<DataTransformerID>>;
|
||||
}
|
||||
|
||||
export class InspectDataTab extends PureComponent<Props, State> {
|
||||
@ -56,8 +48,9 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
selectedDataFrame: DataTransformerID.seriesToColumns,
|
||||
dataFrameIndex: 0,
|
||||
transformId: DataTransformerID.noop,
|
||||
transformId: DataTransformerID.seriesToColumns,
|
||||
transformationOptions: buildTransformationOptions(),
|
||||
};
|
||||
}
|
||||
@ -101,15 +94,15 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
saveAs(blob, fileName);
|
||||
};
|
||||
|
||||
onSelectedFrameChanged = (item: SelectableValue<number>) => {
|
||||
this.setState({ dataFrameIndex: item.value || 0 });
|
||||
};
|
||||
|
||||
onTransformationChange = (value: SelectableValue<DataTransformerID>) => {
|
||||
this.setState({ transformId: value.value, dataFrameIndex: 0 });
|
||||
onDataFrameChange = (item: SelectableValue<DataTransformerID | number>) => {
|
||||
this.setState({
|
||||
transformId:
|
||||
item.value === DataTransformerID.seriesToColumns ? DataTransformerID.seriesToColumns : DataTransformerID.noop,
|
||||
dataFrameIndex: typeof item.value === 'number' ? item.value : 0,
|
||||
selectedDataFrame: item.value,
|
||||
});
|
||||
this.props.onOptionsChange({
|
||||
...this.props.options,
|
||||
withTransforms: false,
|
||||
});
|
||||
};
|
||||
|
||||
@ -149,9 +142,35 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
});
|
||||
}
|
||||
|
||||
renderDataOptions = () => {
|
||||
const { options, onOptionsChange, panel } = this.props;
|
||||
const { transformId } = this.state;
|
||||
getActiveString = () => {
|
||||
const { selectedDataFrame } = this.state;
|
||||
const { options, data } = this.props;
|
||||
let activeString = '';
|
||||
if (selectedDataFrame === DataTransformerID.seriesToColumns) {
|
||||
activeString = 'series joined by time';
|
||||
} else {
|
||||
activeString = getFrameDisplayName(data[selectedDataFrame as number]);
|
||||
}
|
||||
if (options.withTransforms || options.withFieldConfig) {
|
||||
activeString += ' - applied ';
|
||||
if (options.withTransforms) {
|
||||
activeString += 'panel transformations ';
|
||||
}
|
||||
|
||||
if (options.withTransforms && options.withFieldConfig) {
|
||||
activeString += 'and ';
|
||||
}
|
||||
|
||||
if (options.withFieldConfig) {
|
||||
activeString += 'field configuration';
|
||||
}
|
||||
}
|
||||
return activeString;
|
||||
};
|
||||
|
||||
renderDataOptions = (dataFrames: DataFrame[]) => {
|
||||
const { options, onOptionsChange, panel, data } = this.props;
|
||||
const { transformId, transformationOptions, selectedDataFrame } = this.state;
|
||||
const styles = getPanelInspectorStyles();
|
||||
|
||||
const panelTransformations = panel.getTransformations();
|
||||
@ -160,43 +179,80 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
const showFieldConfigsOption = !panel.plugin?.fieldConfigRegistry.isEmpty();
|
||||
const showDataOptions = showPanelTransformationsOption || showFieldConfigsOption;
|
||||
|
||||
let dataSelect = dataFrames;
|
||||
if (selectedDataFrame === DataTransformerID.seriesToColumns) {
|
||||
dataSelect = data;
|
||||
}
|
||||
|
||||
const choices = dataSelect.map((frame, index) => {
|
||||
return {
|
||||
value: index,
|
||||
label: `${getFrameDisplayName(frame)} (${index})`,
|
||||
} as SelectableValue<number>;
|
||||
});
|
||||
|
||||
const selectableOptions = [...transformationOptions, ...choices];
|
||||
|
||||
if (!showDataOptions) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={cx(styles.options, styles.dataDisplayOptions)}>
|
||||
<QueryOperationRow title={'Data display options'} isOpen={false}>
|
||||
{showPanelTransformationsOption && (
|
||||
<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>
|
||||
)}
|
||||
{showFieldConfigsOption && (
|
||||
<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>
|
||||
<QueryOperationRow
|
||||
title="Table data options"
|
||||
headerElement={<DetailText>{this.getActiveString()}</DetailText>}
|
||||
isOpen={false}
|
||||
>
|
||||
<div className={styles.options}>
|
||||
<VerticalGroup spacing="lg">
|
||||
{data.length > 1 && (
|
||||
<Field
|
||||
label="Show data frame"
|
||||
className={css`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Select
|
||||
options={selectableOptions}
|
||||
value={selectedDataFrame}
|
||||
onChange={this.onDataFrameChange}
|
||||
width={30}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
<HorizontalGroup>
|
||||
{showPanelTransformationsOption && (
|
||||
<Field
|
||||
label="Apply panel transformations"
|
||||
description="Table data is displayed with transformations defined in the panel Transform tab."
|
||||
>
|
||||
<Switch
|
||||
value={!!options.withTransforms}
|
||||
onChange={() => onOptionsChange({ ...options, withTransforms: !options.withTransforms })}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
{showFieldConfigsOption && (
|
||||
<Field
|
||||
label="Apply field configuration"
|
||||
description="Table data is displayed with options defined in the Field and Override tabs."
|
||||
>
|
||||
<Switch
|
||||
value={!!options.withFieldConfig}
|
||||
onChange={() => onOptionsChange({ ...options, withFieldConfig: !options.withFieldConfig })}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
</VerticalGroup>
|
||||
</div>
|
||||
</QueryOperationRow>
|
||||
);
|
||||
};
|
||||
|
||||
render() {
|
||||
const { isLoading, data, options, onOptionsChange } = this.props;
|
||||
const { dataFrameIndex, transformId, transformationOptions } = this.state;
|
||||
const { isLoading } = this.props;
|
||||
const { dataFrameIndex } = this.state;
|
||||
const styles = getPanelInspectorStyles();
|
||||
|
||||
if (isLoading) {
|
||||
@ -213,83 +269,20 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
return <div>No Data</div>;
|
||||
}
|
||||
|
||||
const choices = dataFrames.map((frame, index) => {
|
||||
return {
|
||||
value: index,
|
||||
label: `${getFrameDisplayName(frame)} (${index})`,
|
||||
};
|
||||
});
|
||||
|
||||
const panelTransformations = this.props.panel.getTransformations();
|
||||
|
||||
return (
|
||||
<div className={styles.dataTabContent} aria-label={selectors.components.PanelInspector.Data.content}>
|
||||
<Container>
|
||||
<VerticalGroup spacing={'md'}>
|
||||
<HorizontalGroup justify={'space-between'} align={'flex-end'} wrap>
|
||||
<HorizontalGroup>
|
||||
{data.length > 1 && (
|
||||
<Container grow={1}>
|
||||
<Field
|
||||
label="Transformer"
|
||||
className={css`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Select
|
||||
options={transformationOptions}
|
||||
value={transformId}
|
||||
onChange={this.onTransformationChange}
|
||||
width={15}
|
||||
/>
|
||||
</Field>
|
||||
</Container>
|
||||
)}
|
||||
{choices.length > 1 && (
|
||||
<Container grow={1}>
|
||||
<Field
|
||||
label="Select result"
|
||||
className={css`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Select options={choices} value={dataFrameIndex} onChange={this.onSelectedFrameChanged} />
|
||||
</Field>
|
||||
</Container>
|
||||
)}
|
||||
</HorizontalGroup>
|
||||
|
||||
<Button variant="primary" onClick={() => this.exportCsv(dataFrames[dataFrameIndex])}>
|
||||
Download CSV
|
||||
</Button>
|
||||
</HorizontalGroup>
|
||||
<Container grow={1}>
|
||||
<QueryOperationRow title={'Data display options'} isOpen={false}>
|
||||
{panelTransformations && panelTransformations.length > 0 && (
|
||||
<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 title"
|
||||
label="Apply field configuration"
|
||||
labelClass="width-12"
|
||||
checked={!!options.withFieldConfig}
|
||||
onChange={() => onOptionsChange({ ...options, withFieldConfig: !options.withFieldConfig })}
|
||||
/>
|
||||
</div>
|
||||
</QueryOperationRow>
|
||||
</Container>
|
||||
</VerticalGroup>
|
||||
</Container>
|
||||
|
||||
<div className={styles.actionsWrapper}>
|
||||
<div className={styles.dataDisplayOptions}>{this.renderDataOptions(dataFrames)}</div>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={() => this.exportCsv(dataFrames[dataFrameIndex])}
|
||||
className={css`
|
||||
margin-bottom: 10px;
|
||||
`}
|
||||
>
|
||||
Download CSV
|
||||
</Button>
|
||||
</div>
|
||||
<Container grow={1}>
|
||||
<AutoSizer>
|
||||
{({ width, height }) => {
|
||||
@ -311,17 +304,10 @@ export class InspectDataTab extends PureComponent<Props, State> {
|
||||
}
|
||||
|
||||
function buildTransformationOptions() {
|
||||
const transformations: Array<SelectableValue<string>> = [
|
||||
const transformations: Array<SelectableValue<DataTransformerID>> = [
|
||||
{
|
||||
value: 'Do nothing',
|
||||
label: 'None',
|
||||
transformer: {
|
||||
id: DataTransformerID.noop,
|
||||
},
|
||||
},
|
||||
{
|
||||
value: 'join by time',
|
||||
label: 'Join by time',
|
||||
value: DataTransformerID.seriesToColumns,
|
||||
label: 'Series joined by time',
|
||||
transformer: {
|
||||
id: DataTransformerID.seriesToColumns,
|
||||
options: { byField: 'Time' },
|
||||
|
@ -54,14 +54,18 @@ export const getPanelInspectorStyles = stylesFactory(() => {
|
||||
`,
|
||||
actionsWrapper: css`
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
`,
|
||||
leftActions: css`
|
||||
display: flex;
|
||||
flex-grow: 1;
|
||||
|
||||
max-width: 85%;
|
||||
@media (max-width: 1345px) {
|
||||
max-width: 75%;
|
||||
}
|
||||
`,
|
||||
options: css`
|
||||
margin-top: 19px;
|
||||
padding-top: ${config.theme.spacing.sm};
|
||||
`,
|
||||
dataDisplayOptions: css`
|
||||
flex-grow: 1;
|
||||
|
Loading…
Reference in New Issue
Block a user