Transformations: Fixed transformation crash issue (#25152)

* Transformations: Fixed transformation crash issue

* Updated
This commit is contained in:
Torkel Ödegaard 2020-05-28 08:06:24 +02:00 committed by GitHub
parent 6a4f45625c
commit 3833aa416d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 70 additions and 31 deletions

View File

@ -0,0 +1,26 @@
import { e2e } from '@grafana/e2e';
const PANEL_UNDER_TEST = 'Random walk series';
e2e.scenario({
describeName: 'Panel edit tests - transformations',
itName: 'Tests transformations editor',
addScenarioDataSource: false,
addScenarioDashBoard: false,
skipScenario: false,
scenario: () => {
e2e.flows.openDashboard('5SdHCadmz');
e2e.flows.openPanelMenuItem(e2e.flows.PanelMenuItems.Edit, PANEL_UNDER_TEST);
e2e.components.Tab.title('Transform')
.should('be.visible')
.click();
e2e.components.TransformTab.newTransform('Reduce')
.should('be.visible')
.click();
e2e.components.Transforms.Reduce.calculationsLabel().should('be.visible');
},
});

View File

@ -97,6 +97,12 @@ export const Components = {
}, },
TransformTab: { TransformTab: {
content: 'Transform editor tab content', content: 'Transform editor tab content',
newTransform: (title: string) => `New transform ${title}`,
},
Transforms: {
Reduce: {
calculationsLabel: 'Transform calculations label',
},
}, },
QueryEditorToolbarItem: { QueryEditorToolbarItem: {
button: (title: string) => `QueryEditor toolbar item button ${title}`, button: (title: string) => `QueryEditor toolbar item button ${title}`,

View File

@ -9,6 +9,7 @@ import {
} from '@grafana/data'; } from '@grafana/data';
import { ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce'; import { ReduceTransformerOptions } from '@grafana/data/src/transformations/transformers/reduce';
import { selectors } from '@grafana/e2e-selectors';
// TODO: Minimal implementation, needs some <3 // TODO: Minimal implementation, needs some <3
export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({ export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransformerOptions>> = ({
@ -18,7 +19,9 @@ export const ReduceTransformerEditor: React.FC<TransformerUIProps<ReduceTransfor
return ( return (
<div className="gf-form-inline"> <div className="gf-form-inline">
<div className="gf-form gf-form--grow"> <div className="gf-form gf-form--grow">
<div className="gf-form-label width-8">Calculations</div> <div className="gf-form-label width-8" aria-label={selectors.components.Transforms.Reduce.calculationsLabel}>
Calculations
</div>
<StatsPicker <StatsPicker
className="flex-grow-1" className="flex-grow-1"
placeholder="Choose Stat" placeholder="Choose Stat"

View File

@ -2,7 +2,6 @@ import React, { useCallback } from 'react';
import { config } from 'app/core/config'; import { config } from 'app/core/config';
import { css } from 'emotion'; import { css } from 'emotion';
import { IconName, stylesFactory, Tab, TabContent, TabsBar } from '@grafana/ui'; import { IconName, stylesFactory, Tab, TabContent, TabsBar } from '@grafana/ui';
import { DataTransformerConfig } from '@grafana/data';
import { PanelEditorTab, PanelEditorTabId } from './types'; import { PanelEditorTab, PanelEditorTabId } from './types';
import { DashboardModel } from '../../state'; import { DashboardModel } from '../../state';
import { QueriesTab } from '../../panel_editor/QueriesTab'; import { QueriesTab } from '../../panel_editor/QueriesTab';
@ -42,10 +41,6 @@ export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboa
return null; return null;
} }
const onTransformersChange = (transformers: DataTransformerConfig[]) => {
panel.setTransformations(transformers);
};
return ( return (
<div className={styles.wrapper}> <div className={styles.wrapper}>
<TabsBar className={styles.tabBar}> <TabsBar className={styles.tabBar}>
@ -65,13 +60,7 @@ export const PanelEditorTabs: React.FC<PanelEditorTabsProps> = ({ panel, dashboa
<TabContent className={styles.tabContent}> <TabContent className={styles.tabContent}>
{activeTab.id === PanelEditorTabId.Query && <QueriesTab panel={panel} dashboard={dashboard} />} {activeTab.id === PanelEditorTabId.Query && <QueriesTab panel={panel} dashboard={dashboard} />}
{activeTab.id === PanelEditorTabId.Alert && <AlertTab panel={panel} dashboard={dashboard} />} {activeTab.id === PanelEditorTabId.Alert && <AlertTab panel={panel} dashboard={dashboard} />}
{activeTab.id === PanelEditorTabId.Transform && ( {activeTab.id === PanelEditorTabId.Transform && <TransformationsEditor panel={panel} />}
<TransformationsEditor
transformations={panel.transformations || []}
onChange={onTransformersChange}
panel={panel}
/>
)}
</TabContent> </TabContent>
</div> </div>
); );

View File

@ -28,17 +28,25 @@ import { PanelModel } from '../../state';
interface Props { interface Props {
panel: PanelModel; panel: PanelModel;
onChange: (transformations: DataTransformerConfig[]) => void;
transformations: DataTransformerConfig[];
} }
interface State { interface State {
data?: DataFrame[]; data: DataFrame[];
transformations: DataTransformerConfig[];
} }
export class TransformationsEditor extends React.PureComponent<Props, State> { export class TransformationsEditor extends React.PureComponent<Props, State> {
subscription?: Unsubscribable; subscription?: Unsubscribable;
constructor(props: Props) {
super(props);
this.state = {
transformations: props.panel.transformations || [],
data: [],
};
}
componentDidMount() { componentDidMount() {
this.subscription = this.props.panel this.subscription = this.props.panel
.getQueryRunner() .getQueryRunner()
@ -54,9 +62,15 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
} }
} }
onChange(transformations: DataTransformerConfig[]) {
this.props.panel.setTransformations(transformations);
this.setState({ transformations });
}
onTransformationAdd = (selectable: SelectableValue<string>) => { onTransformationAdd = (selectable: SelectableValue<string>) => {
const { transformations, onChange } = this.props; const { transformations } = this.state;
onChange([
this.onChange([
...transformations, ...transformations,
{ {
id: selectable.value as string, id: selectable.value as string,
@ -66,17 +80,17 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
}; };
onTransformationChange = (idx: number, config: DataTransformerConfig) => { onTransformationChange = (idx: number, config: DataTransformerConfig) => {
const { transformations, onChange } = this.props; const { transformations } = this.state;
const next = Array.from(transformations); const next = Array.from(transformations);
next[idx] = config; next[idx] = config;
onChange(next); this.onChange(next);
}; };
onTransformationRemove = (idx: number) => { onTransformationRemove = (idx: number) => {
const { transformations, onChange } = this.props; const { transformations } = this.state;
const next = Array.from(transformations); const next = Array.from(transformations);
next.splice(idx, 1); next.splice(idx, 1);
onChange(next); this.onChange(next);
}; };
renderTransformationSelector = () => { renderTransformationSelector = () => {
@ -108,10 +122,7 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
}; };
renderTransformationEditors = () => { renderTransformationEditors = () => {
const { transformations } = this.props; const { data, transformations } = this.state;
const { data } = this.state;
const preTransformData = data ?? [];
return ( return (
<> <>
@ -123,7 +134,7 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
return null; return null;
} }
const input = transformDataFrame(transformations.slice(0, i), preTransformData); const input = transformDataFrame(transformations.slice(0, i), data);
const output = transformDataFrame(transformations.slice(i), input); const output = transformDataFrame(transformations.slice(i), input);
if (transformationUI) { if (transformationUI) {
@ -182,6 +193,7 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
title={t.name} title={t.name}
description={t.description} description={t.description}
actions={<Button>Select</Button>} actions={<Button>Select</Button>}
ariaLabel={selectors.components.TransformTab.newTransform(t.name)}
onClick={() => { onClick={() => {
this.onTransformationAdd({ value: t.id }); this.onTransformationAdd({ value: t.id });
}} }}
@ -194,14 +206,17 @@ export class TransformationsEditor extends React.PureComponent<Props, State> {
} }
render() { render() {
const hasTransformationsConfigured = this.props.transformations.length > 0; const { transformations } = this.state;
const hasTransforms = transformations.length > 0;
return ( return (
<CustomScrollbar autoHeightMin="100%"> <CustomScrollbar autoHeightMin="100%">
<Container padding="md"> <Container padding="md">
<div aria-label={selectors.components.TransformTab.content}> <div aria-label={selectors.components.TransformTab.content}>
{!hasTransformationsConfigured && this.renderNoAddedTransformsState()} {!hasTransforms && this.renderNoAddedTransformsState()}
{hasTransformationsConfigured && this.renderTransformationEditors()} {hasTransforms && this.renderTransformationEditors()}
{hasTransformationsConfigured && this.renderTransformationSelector()} {hasTransforms && this.renderTransformationSelector()}
</div> </div>
</Container> </Container>
</CustomScrollbar> </CustomScrollbar>