mirror of
https://github.com/grafana/grafana.git
synced 2025-01-02 12:17:01 -06:00
Transformers: add search to transform selection (#30854)
This commit is contained in:
parent
48334ab863
commit
11fa7c697e
@ -122,6 +122,7 @@ export const Components = {
|
||||
modeLabel: 'Transform mode label',
|
||||
calculationsLabel: 'Transform calculations label',
|
||||
},
|
||||
searchInput: 'search transformations',
|
||||
},
|
||||
PageToolbar: {
|
||||
container: () => '.page-toolbar',
|
||||
|
@ -51,8 +51,8 @@ describe('TransformationsEditor', () => {
|
||||
const addTransformationButton = screen.getByText(buttonLabel);
|
||||
userEvent.click(addTransformationButton);
|
||||
|
||||
const picker = screen.getByLabelText(selectors.components.ValuePicker.select(buttonLabel));
|
||||
expect(picker).toBeDefined();
|
||||
const search = screen.getByLabelText(selectors.components.Transforms.searchInput);
|
||||
expect(search).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React from 'react';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Button,
|
||||
@ -8,9 +8,10 @@ import {
|
||||
Themeable,
|
||||
DismissableFeatureInfoBox,
|
||||
useTheme,
|
||||
ValuePicker,
|
||||
VerticalGroup,
|
||||
withTheme,
|
||||
Input,
|
||||
IconButton,
|
||||
} from '@grafana/ui';
|
||||
import {
|
||||
DataFrame,
|
||||
@ -40,6 +41,8 @@ interface TransformationsEditorProps extends Themeable {
|
||||
interface State {
|
||||
data: DataFrame[];
|
||||
transformations: TransformationsEditorTransformation[];
|
||||
search: string;
|
||||
showPicker?: boolean;
|
||||
}
|
||||
|
||||
class UnThemedTransformationsEditor extends React.PureComponent<TransformationsEditorProps, State> {
|
||||
@ -56,9 +59,34 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
id: ids[i],
|
||||
})),
|
||||
data: [],
|
||||
search: '',
|
||||
};
|
||||
}
|
||||
|
||||
onSearchChange = (event: ChangeEvent<HTMLInputElement>) => {
|
||||
this.setState({ search: event.target.value });
|
||||
};
|
||||
|
||||
onSearchKeyDown = (event: React.KeyboardEvent<HTMLInputElement>) => {
|
||||
if (event.key === 'Enter') {
|
||||
const { search } = this.state;
|
||||
if (search) {
|
||||
const lower = search.toLowerCase();
|
||||
const filtered = standardTransformersRegistry.list().filter((t) => {
|
||||
const txt = (t.name + t.description).toLowerCase();
|
||||
return txt.indexOf(lower) >= 0;
|
||||
});
|
||||
if (filtered.length > 0) {
|
||||
this.onTransformationAdd({ value: filtered[0].id });
|
||||
}
|
||||
}
|
||||
} else if (event.keyCode === 27) {
|
||||
// Escape key
|
||||
this.setState({ search: '', showPicker: false });
|
||||
event.stopPropagation(); // don't exit the editor
|
||||
}
|
||||
};
|
||||
|
||||
buildTransformationIds(transformations: DataTransformerConfig[]) {
|
||||
const transformationCounters: Record<string, number> = {};
|
||||
const transformationIds: string[] = [];
|
||||
@ -113,6 +141,7 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
const { transformations } = this.state;
|
||||
|
||||
const nextId = this.getTransformationNextId(selectable.value!);
|
||||
this.setState({ search: '', showPicker: false });
|
||||
this.onChange([
|
||||
...transformations,
|
||||
{
|
||||
@ -139,33 +168,6 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
this.onChange(next);
|
||||
};
|
||||
|
||||
renderTransformationSelector = () => {
|
||||
const availableTransformers = standardTransformersRegistry.list().map((t) => {
|
||||
return {
|
||||
value: t.transformation.id,
|
||||
label: t.name,
|
||||
description: t.description,
|
||||
};
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
max-width: 66%;
|
||||
`}
|
||||
>
|
||||
<ValuePicker
|
||||
size="md"
|
||||
variant="secondary"
|
||||
label="Add transformation"
|
||||
options={availableTransformers}
|
||||
onChange={this.onTransformationAdd}
|
||||
isFullWidth={false}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
onDragEnd = (result: DropResult) => {
|
||||
const { transformations } = this.state;
|
||||
|
||||
@ -208,43 +210,106 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
);
|
||||
};
|
||||
|
||||
renderNoAddedTransformsState() {
|
||||
renderTransformsPicker() {
|
||||
const { transformations, search } = this.state;
|
||||
let suffix: React.ReactNode = null;
|
||||
let xforms = standardTransformersRegistry.list();
|
||||
if (search) {
|
||||
const lower = search.toLowerCase();
|
||||
const filtered = xforms.filter((t) => {
|
||||
const txt = (t.name + t.description).toLowerCase();
|
||||
return txt.indexOf(lower) >= 0;
|
||||
});
|
||||
suffix = (
|
||||
<>
|
||||
{filtered.length} / {xforms.length}
|
||||
<IconButton
|
||||
name="times"
|
||||
surface="header"
|
||||
onClick={() => {
|
||||
this.setState({ search: '' });
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
|
||||
xforms = filtered;
|
||||
}
|
||||
|
||||
const noTransforms = !transformations?.length;
|
||||
const showPicker = noTransforms || this.state.showPicker;
|
||||
if (!suffix && showPicker && !noTransforms) {
|
||||
suffix = (
|
||||
<IconButton
|
||||
name="times"
|
||||
surface="header"
|
||||
onClick={() => {
|
||||
this.setState({ showPicker: false });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Container grow={1}>
|
||||
<DismissableFeatureInfoBox
|
||||
title="Transformations"
|
||||
className={css`
|
||||
margin-bottom: ${this.props.theme.spacing.lg};
|
||||
`}
|
||||
persistenceId="transformationsFeaturesInfoBox"
|
||||
url={getDocsLink(DocsId.Transformations)}
|
||||
{noTransforms && (
|
||||
<Container grow={1}>
|
||||
<DismissableFeatureInfoBox
|
||||
title="Transformations"
|
||||
className={css`
|
||||
margin-bottom: ${this.props.theme.spacing.lg};
|
||||
`}
|
||||
persistenceId="transformationsFeaturesInfoBox"
|
||||
url={getDocsLink(DocsId.Transformations)}
|
||||
>
|
||||
<p>
|
||||
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
|
||||
visualized. <br />
|
||||
Many transforms are not suitable if you're using the Graph visualization as it currently only
|
||||
supports time series. <br />
|
||||
It can help to switch to Table visualization to understand what a transformation is doing. <br />
|
||||
</p>
|
||||
</DismissableFeatureInfoBox>
|
||||
</Container>
|
||||
)}
|
||||
{showPicker ? (
|
||||
<VerticalGroup>
|
||||
<Input
|
||||
aria-label={selectors.components.Transforms.searchInput}
|
||||
value={search ?? ''}
|
||||
autoFocus={!noTransforms}
|
||||
placeholder="Add transformation"
|
||||
onChange={this.onSearchChange}
|
||||
onKeyDown={this.onSearchKeyDown}
|
||||
suffix={suffix}
|
||||
/>
|
||||
|
||||
{xforms.map((t) => {
|
||||
return (
|
||||
<TransformationCard
|
||||
key={t.name}
|
||||
title={t.name}
|
||||
description={t.description}
|
||||
actions={<Button>Select</Button>}
|
||||
ariaLabel={selectors.components.TransformTab.newTransform(t.name)}
|
||||
onClick={() => {
|
||||
this.onTransformationAdd({ value: t.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</VerticalGroup>
|
||||
) : (
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="secondary"
|
||||
onClick={() => {
|
||||
this.setState({ showPicker: true });
|
||||
}}
|
||||
>
|
||||
<p>
|
||||
Transformations allow you to join, calculate, re-order, hide and rename your query results before being
|
||||
visualized. <br />
|
||||
Many transforms are not suitable if you're using the Graph visualization as it currently only
|
||||
supports time series. <br />
|
||||
It can help to switch to Table visualization to understand what a transformation is doing. <br />
|
||||
</p>
|
||||
</DismissableFeatureInfoBox>
|
||||
</Container>
|
||||
<VerticalGroup>
|
||||
{standardTransformersRegistry.list().map((t) => {
|
||||
return (
|
||||
<TransformationCard
|
||||
key={t.name}
|
||||
title={t.name}
|
||||
description={t.description}
|
||||
actions={<Button>Select</Button>}
|
||||
ariaLabel={selectors.components.TransformTab.newTransform(t.name)}
|
||||
onClick={() => {
|
||||
this.onTransformationAdd({ value: t.id });
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</VerticalGroup>
|
||||
Add transformation
|
||||
</Button>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
@ -271,9 +336,8 @@ class UnThemedTransformationsEditor extends React.PureComponent<TransformationsE
|
||||
title="Transformations can't be used on a panel with alerts"
|
||||
/>
|
||||
) : null}
|
||||
{!hasTransforms && this.renderNoAddedTransformsState()}
|
||||
{hasTransforms && this.renderTransformationEditors()}
|
||||
{hasTransforms && this.renderTransformationSelector()}
|
||||
{this.renderTransformsPicker()}
|
||||
</div>
|
||||
</Container>
|
||||
</CustomScrollbar>
|
||||
|
Loading…
Reference in New Issue
Block a user