mirror of
https://github.com/grafana/grafana.git
synced 2024-12-27 09:21:35 -06:00
Transformations: Fixed transformation crash issue (#25152)
* Transformations: Fixed transformation crash issue * Updated
This commit is contained in:
parent
6a4f45625c
commit
3833aa416d
26
e2e/suite1/specs/panelEdit_transforms.spec.ts
Normal file
26
e2e/suite1/specs/panelEdit_transforms.spec.ts
Normal 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');
|
||||||
|
},
|
||||||
|
});
|
@ -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}`,
|
||||||
|
@ -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"
|
||||||
|
@ -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>
|
||||||
);
|
);
|
||||||
|
@ -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>
|
||||||
|
Loading…
Reference in New Issue
Block a user