Show transformations when editing scene dashboard
This commit is contained in:
@@ -16,7 +16,7 @@ import { VizPanelManager } from '../VizPanelManager';
|
||||
|
||||
import { PanelDataAlertingTab } from './PanelDataAlertingTab';
|
||||
import { PanelDataQueriesTab } from './PanelDataQueriesTab';
|
||||
import { PanelDataTransformationsTab } from './PanelDataTransformationsTab';
|
||||
import { PanelDataTransformationsTab } from './PanelDataTransformations/PanelDataTransformationsTab';
|
||||
import { PanelDataPaneTab } from './types';
|
||||
|
||||
export interface PanelDataPaneState extends SceneObjectState {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import React from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Box, Button, Stack, Text } from '@grafana/ui';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
|
||||
interface EmptyTransformationsProps {
|
||||
onShowPicker: () => void;
|
||||
}
|
||||
export function EmptyTransformationsMessage(props: EmptyTransformationsProps) {
|
||||
return (
|
||||
<Box alignItems="center" padding={4}>
|
||||
<Stack direction="column" alignItems="center" gap={2}>
|
||||
<Text element="h3" textAlignment="center">
|
||||
<Trans key="transformations.empty.add-transformation-header">Start transforming data</Trans>
|
||||
</Text>
|
||||
<Text element="p" textAlignment="center" data-testid={selectors.components.Transforms.noTransformationsMessage}>
|
||||
<Trans key="transformations.empty.add-transformation-body">
|
||||
Transformations allow data to be changed in various ways before your visualization is shown.
|
||||
<br />
|
||||
This includes joining data together, renaming fields, making calculations, formatting data for display, and
|
||||
more.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={props.onShowPicker}
|
||||
data-testid={selectors.components.Transforms.addTransformationButton}
|
||||
>
|
||||
Add transformation
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
import { render, screen } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
|
||||
import { FieldType, LoadingState, TimeRange, standardTransformersRegistry, toDataFrame } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { SceneDataTransformer } from '@grafana/scenes';
|
||||
import { getStandardTransformers } from 'app/features/transformers/standardTransformers';
|
||||
|
||||
import { PanelDataTransformationsTab, PanelDataTransformationsTabRendered } from './PanelDataTransformationsTab';
|
||||
|
||||
function createPanelManagerMock(sceneDataTransformer: SceneDataTransformer) {
|
||||
return {
|
||||
panelManager: {
|
||||
useState: () => {
|
||||
return {
|
||||
panel: {
|
||||
useState: () => {
|
||||
return { $data: sceneDataTransformer };
|
||||
},
|
||||
},
|
||||
};
|
||||
},
|
||||
},
|
||||
} as unknown as PanelDataTransformationsTab;
|
||||
}
|
||||
|
||||
describe('PanelDataTransformationsTab', () => {
|
||||
it('renders empty message when there are no transformations', async () => {
|
||||
const modelMock = createPanelManagerMock(new SceneDataTransformer({ transformations: [] }));
|
||||
render(<PanelDataTransformationsTabRendered model={modelMock}></PanelDataTransformationsTabRendered>);
|
||||
|
||||
await screen.findByTestId(selectors.components.Transforms.noTransformationsMessage);
|
||||
});
|
||||
|
||||
it('renders transformations when there are transformations', async () => {
|
||||
standardTransformersRegistry.setInit(getStandardTransformers);
|
||||
const modelMock = createPanelManagerMock(
|
||||
new SceneDataTransformer({
|
||||
data: {
|
||||
timeRange: {} as unknown as TimeRange,
|
||||
state: {} as unknown as LoadingState,
|
||||
series: [
|
||||
toDataFrame({
|
||||
name: 'A',
|
||||
fields: [
|
||||
{ name: 'time', type: FieldType.time, values: [100, 200, 300] },
|
||||
{ name: 'values', type: FieldType.number, values: [1, 2, 3] },
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
transformations: [
|
||||
{
|
||||
id: 'calculateField',
|
||||
options: {},
|
||||
},
|
||||
],
|
||||
})
|
||||
);
|
||||
render(<PanelDataTransformationsTabRendered model={modelMock}></PanelDataTransformationsTabRendered>);
|
||||
|
||||
await screen.findByText('1 - Add field from calculation');
|
||||
});
|
||||
});
|
||||
+106
@@ -0,0 +1,106 @@
|
||||
import React from 'react';
|
||||
import { DragDropContext, Droppable } from 'react-beautiful-dnd';
|
||||
|
||||
import { IconName } from '@grafana/data';
|
||||
import { SceneObjectBase, SceneComponentProps, SceneDataTransformer } from '@grafana/scenes';
|
||||
import { Container, CustomScrollbar } from '@grafana/ui';
|
||||
import { TransformationOperationRows } from 'app/features/dashboard/components/TransformationsEditor/TransformationOperationRows';
|
||||
import { TransformationsEditorTransformation } from 'app/features/dashboard/components/TransformationsEditor/types';
|
||||
|
||||
import { VizPanelManager } from '../../VizPanelManager';
|
||||
import { PanelDataPaneTabState, PanelDataPaneTab } from '../types';
|
||||
|
||||
import { EmptyTransformationsMessage } from './EmptyTransformationsMessage';
|
||||
|
||||
interface PanelDataTransformationsTabState extends PanelDataPaneTabState {}
|
||||
|
||||
export class PanelDataTransformationsTab
|
||||
extends SceneObjectBase<PanelDataTransformationsTabState>
|
||||
implements PanelDataPaneTab
|
||||
{
|
||||
static Component = PanelDataTransformationsTabRendered;
|
||||
tabId = 'transformations';
|
||||
icon: IconName = 'process';
|
||||
private _panelManager: VizPanelManager;
|
||||
|
||||
getTabLabel() {
|
||||
return 'Transformations';
|
||||
}
|
||||
|
||||
getItemsCount() {
|
||||
return null;
|
||||
}
|
||||
|
||||
constructor(panelManager: VizPanelManager) {
|
||||
super({});
|
||||
|
||||
this._panelManager = panelManager;
|
||||
}
|
||||
|
||||
get panelManager() {
|
||||
return this._panelManager;
|
||||
}
|
||||
}
|
||||
|
||||
interface TransformationEditorProps {
|
||||
sceneDataTransformer: SceneDataTransformer;
|
||||
}
|
||||
|
||||
function TransformationsEditor(props: TransformationEditorProps) {
|
||||
const dataState = props.sceneDataTransformer.useState();
|
||||
const transformationEditorRows: TransformationsEditorTransformation[] = [];
|
||||
|
||||
let i = 0; // Ids need to be unique for drag to change order to work
|
||||
for (const t of dataState.transformations) {
|
||||
if ('id' in t) {
|
||||
transformationEditorRows.push({ id: `${i} - ${t.id}`, transformation: t });
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<DragDropContext onDragEnd={() => {}}>
|
||||
<Droppable droppableId="transformations-list" direction="vertical">
|
||||
{(provided) => {
|
||||
return (
|
||||
<div ref={provided.innerRef} {...provided.droppableProps}>
|
||||
<TransformationOperationRows
|
||||
onChange={() => {}}
|
||||
onRemove={() => {}}
|
||||
configs={transformationEditorRows}
|
||||
data={{
|
||||
series: dataState.data?.series || [],
|
||||
annotations: dataState.data?.annotations || [],
|
||||
}}
|
||||
></TransformationOperationRows>
|
||||
{provided.placeholder}
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
</Droppable>
|
||||
</DragDropContext>
|
||||
);
|
||||
}
|
||||
|
||||
export function PanelDataTransformationsTabRendered({ model }: SceneComponentProps<PanelDataTransformationsTab>) {
|
||||
const panelManagerState = model.panelManager.useState();
|
||||
const panelState = panelManagerState.panel.useState();
|
||||
|
||||
if (!(panelState.$data instanceof SceneDataTransformer)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dataState = panelState.$data.useState();
|
||||
|
||||
return (
|
||||
<CustomScrollbar autoHeightMin="100%">
|
||||
<Container>
|
||||
{dataState.transformations.length < 1 ? (
|
||||
<EmptyTransformationsMessage onShowPicker={() => {}}></EmptyTransformationsMessage>
|
||||
) : (
|
||||
<TransformationsEditor sceneDataTransformer={panelState.$data}></TransformationsEditor>
|
||||
)}
|
||||
</Container>
|
||||
</CustomScrollbar>
|
||||
);
|
||||
}
|
||||
-46
@@ -1,46 +0,0 @@
|
||||
import React from 'react';
|
||||
|
||||
import { IconName } from '@grafana/data';
|
||||
import { SceneObjectBase, SceneComponentProps } from '@grafana/scenes';
|
||||
|
||||
import { VizPanelManager } from '../VizPanelManager';
|
||||
|
||||
import { PanelDataPaneTabState, PanelDataPaneTab } from './types';
|
||||
|
||||
interface PanelDataTransformationsTabState extends PanelDataPaneTabState {}
|
||||
|
||||
export class PanelDataTransformationsTab
|
||||
extends SceneObjectBase<PanelDataTransformationsTabState>
|
||||
implements PanelDataPaneTab
|
||||
{
|
||||
static Component = PanelDataTransformationsTabRendered;
|
||||
tabId = 'transformations';
|
||||
icon: IconName = 'process';
|
||||
private _panelManager: VizPanelManager;
|
||||
|
||||
getTabLabel() {
|
||||
return 'Transformations';
|
||||
}
|
||||
|
||||
getItemsCount() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
constructor(panelManager: VizPanelManager) {
|
||||
super({});
|
||||
|
||||
this._panelManager = panelManager;
|
||||
}
|
||||
|
||||
get panelManager() {
|
||||
return this._panelManager;
|
||||
}
|
||||
}
|
||||
|
||||
function PanelDataTransformationsTabRendered({ model }: SceneComponentProps<PanelDataTransformationsTab>) {
|
||||
// const { dataRef } = model.useState();
|
||||
// const dataObj = dataRef.resolve();
|
||||
// // const { transformations } = dataObj.useState();
|
||||
|
||||
return <div>TODO Transformations</div>;
|
||||
}
|
||||
+6
-34
@@ -21,12 +21,9 @@ import {
|
||||
withTheme,
|
||||
IconButton,
|
||||
ButtonGroup,
|
||||
Box,
|
||||
Text,
|
||||
Stack,
|
||||
} from '@grafana/ui';
|
||||
import config from 'app/core/config';
|
||||
import { Trans } from 'app/core/internationalization';
|
||||
import { EmptyTransformationsMessage } from 'app/features/dashboard-scene/panel-edit/PanelDataPane/PanelDataTransformations/EmptyTransformationsMessage';
|
||||
|
||||
import { PanelModel } from '../../state';
|
||||
import { PanelNotSupported } from '../PanelEditor/PanelNotSupported';
|
||||
@@ -258,36 +255,11 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
|
||||
renderEmptyMessage = () => {
|
||||
return (
|
||||
<Box alignItems="center" padding={4}>
|
||||
<Stack direction="column" alignItems="center" gap={2}>
|
||||
<Text element="h3" textAlignment="center">
|
||||
<Trans key="transformations.empty.add-transformation-header">Start transforming data</Trans>
|
||||
</Text>
|
||||
<Text
|
||||
element="p"
|
||||
textAlignment="center"
|
||||
data-testid={selectors.components.Transforms.noTransformationsMessage}
|
||||
>
|
||||
<Trans key="transformations.empty.add-transformation-body">
|
||||
Transformations allow data to be changed in various ways before your visualization is shown.
|
||||
<br />
|
||||
This includes joining data together, renaming fields, making calculations, formatting data for display,
|
||||
and more.
|
||||
</Trans>
|
||||
</Text>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="primary"
|
||||
size="md"
|
||||
onClick={() => {
|
||||
this.setState({ showPicker: true });
|
||||
}}
|
||||
data-testid={selectors.components.Transforms.addTransformationButton}
|
||||
>
|
||||
Add transformation
|
||||
</Button>
|
||||
</Stack>
|
||||
</Box>
|
||||
<EmptyTransformationsMessage
|
||||
onShowPicker={() => {
|
||||
this.setState({ showPicker: true });
|
||||
}}
|
||||
></EmptyTransformationsMessage>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user