diff --git a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx index 9123c31e243..c124a0d6f0b 100644 --- a/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx +++ b/public/app/core/components/QueryOperationRow/QueryOperationRow.tsx @@ -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; diff --git a/public/app/features/dashboard/components/Inspector/DetailText.tsx b/public/app/features/dashboard/components/Inspector/DetailText.tsx new file mode 100644 index 00000000000..26b55fc5974 --- /dev/null +++ b/public/app/features/dashboard/components/Inspector/DetailText.tsx @@ -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

{children}

; +}; diff --git a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx index c0e097e1cb6..e7bc72afbcc 100644 --- a/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx +++ b/public/app/features/dashboard/components/Inspector/InspectDataTab.tsx @@ -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>; + transformationOptions: Array>; } export class InspectDataTab extends PureComponent { @@ -56,8 +48,9 @@ export class InspectDataTab extends PureComponent { 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 { saveAs(blob, fileName); }; - onSelectedFrameChanged = (item: SelectableValue) => { - this.setState({ dataFrameIndex: item.value || 0 }); - }; - - onTransformationChange = (value: SelectableValue) => { - this.setState({ transformId: value.value, dataFrameIndex: 0 }); + onDataFrameChange = (item: SelectableValue) => { + 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 { }); } - 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 { 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; + }); + + const selectableOptions = [...transformationOptions, ...choices]; + if (!showDataOptions) { return null; } return ( -
- - {showPanelTransformationsOption && ( -
- onOptionsChange({ ...options, withTransforms: !options.withTransforms })} - /> -
- )} - {showFieldConfigsOption && ( -
- onOptionsChange({ ...options, withFieldConfig: !options.withFieldConfig })} - /> -
- )} -
-
+ {this.getActiveString()}} + isOpen={false} + > +
+ + {data.length > 1 && ( + + - - - )} - {choices.length > 1 && ( - - -