mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: UI tweaks, filter by name regex validation (#23800)
* Add validation to filter by name regex, minor layout tweaks * Use cards uin for non configured transformations
This commit is contained in:
@@ -11,7 +11,12 @@ const fieldNameMacher: FieldMatcherInfo<string> = {
|
||||
defaultOptions: '/.*/',
|
||||
|
||||
get: (pattern: string) => {
|
||||
const regex = stringToJsRegex(pattern);
|
||||
let regex = new RegExp('');
|
||||
try {
|
||||
regex = stringToJsRegex(pattern);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
return (field: Field) => {
|
||||
return regex.test(field.name);
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@ enum Orientation {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
}
|
||||
type Spacing = 'xs' | 'sm' | 'md' | 'lg';
|
||||
type Spacing = 'none' | 'xs' | 'sm' | 'md' | 'lg';
|
||||
type Justify = 'flex-start' | 'flex-end' | 'space-between' | 'center';
|
||||
type Align = 'normal' | 'flex-start' | 'flex-end' | 'center';
|
||||
|
||||
@@ -18,6 +18,7 @@ export interface LayoutProps {
|
||||
justify?: Justify;
|
||||
align?: Align;
|
||||
width?: string;
|
||||
wrap?: boolean;
|
||||
}
|
||||
|
||||
export interface ContainerProps {
|
||||
@@ -31,10 +32,11 @@ export const Layout: React.FC<LayoutProps> = ({
|
||||
spacing = 'sm',
|
||||
justify = 'flex-start',
|
||||
align = 'normal',
|
||||
wrap = false,
|
||||
width = 'auto',
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyles(theme, orientation, spacing, justify, align);
|
||||
const styles = getStyles(theme, orientation, spacing, justify, align, wrap);
|
||||
return (
|
||||
<div className={styles.layout} style={{ width }}>
|
||||
{React.Children.toArray(children)
|
||||
@@ -55,13 +57,26 @@ export const HorizontalGroup: React.FC<Omit<LayoutProps, 'orientation'>> = ({
|
||||
spacing,
|
||||
justify,
|
||||
align = 'center',
|
||||
wrap,
|
||||
width,
|
||||
}) => (
|
||||
<Layout spacing={spacing} justify={justify} orientation={Orientation.Horizontal} align={align} width={width}>
|
||||
<Layout
|
||||
spacing={spacing}
|
||||
justify={justify}
|
||||
orientation={Orientation.Horizontal}
|
||||
align={align}
|
||||
width={width}
|
||||
wrap={wrap}
|
||||
>
|
||||
{children}
|
||||
</Layout>
|
||||
);
|
||||
export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation'>> = ({ children, spacing, justify, width }) => (
|
||||
export const VerticalGroup: React.FC<Omit<LayoutProps, 'orientation' | 'wrap'>> = ({
|
||||
children,
|
||||
spacing,
|
||||
justify,
|
||||
width,
|
||||
}) => (
|
||||
<Layout spacing={spacing} justify={justify} orientation={Orientation.Vertical} width={width}>
|
||||
{children}
|
||||
</Layout>
|
||||
@@ -74,22 +89,28 @@ export const Container: React.FC<ContainerProps> = ({ children, padding, margin
|
||||
};
|
||||
|
||||
const getStyles = stylesFactory(
|
||||
(theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align) => {
|
||||
(theme: GrafanaTheme, orientation: Orientation, spacing: Spacing, justify: Justify, align, wrap) => {
|
||||
const finalSpacing = spacing !== 'none' ? theme.spacing[spacing] : 0;
|
||||
const marginCompensation = orientation === Orientation.Horizontal && !wrap ? 0 : `-${finalSpacing}`;
|
||||
|
||||
return {
|
||||
layout: css`
|
||||
display: flex;
|
||||
flex-direction: ${orientation === Orientation.Vertical ? 'column' : 'row'};
|
||||
flex-wrap: ${wrap ? 'wrap' : 'nowrap'};
|
||||
justify-content: ${justify};
|
||||
align-items: ${align};
|
||||
height: 100%;
|
||||
max-width: 100%;
|
||||
// compensate for last row margin when wrapped, horizontal layout
|
||||
margin-bottom: ${marginCompensation};
|
||||
`,
|
||||
childWrapper: css`
|
||||
margin-bottom: ${orientation === Orientation.Horizontal ? 0 : theme.spacing[spacing]};
|
||||
margin-right: ${orientation === Orientation.Horizontal ? theme.spacing[spacing] : 0};
|
||||
margin-bottom: ${orientation === Orientation.Horizontal && !wrap ? 0 : finalSpacing};
|
||||
margin-right: ${orientation === Orientation.Horizontal ? finalSpacing : 0};
|
||||
display: flex;
|
||||
align-items: ${align};
|
||||
height: 100%;
|
||||
// height: 100%;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
@@ -101,8 +122,8 @@ const getStyles = stylesFactory(
|
||||
);
|
||||
|
||||
const getContainerStyles = stylesFactory((theme: GrafanaTheme, padding?: Spacing, margin?: Spacing) => {
|
||||
const paddingSize = (padding && theme.spacing[padding]) || 0;
|
||||
const marginSize = (margin && theme.spacing[margin]) || 0;
|
||||
const paddingSize = (padding && padding !== 'none' && theme.spacing[padding]) || 0;
|
||||
const marginSize = (margin && margin !== 'none' && theme.spacing[margin]) || 0;
|
||||
return {
|
||||
wrapper: css`
|
||||
margin: ${marginSize};
|
||||
|
||||
@@ -128,7 +128,7 @@ export class CalculateFieldTransformerEditor extends React.PureComponent<
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Field name</div>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<HorizontalGroup spacing="xs" align="flex-start" wrap>
|
||||
{names.map((o, i) => {
|
||||
return (
|
||||
<FilterPill
|
||||
|
||||
@@ -10,6 +10,8 @@ import {
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { Input } from '../Input/Input';
|
||||
import { FilterPill } from '../FilterPill/FilterPill';
|
||||
import { Field } from '../Forms/Field';
|
||||
import { css } from 'emotion';
|
||||
|
||||
interface FilterByNameTransformerEditorProps extends TransformerUIProps<FilterFieldsByNameTransformerOptions> {}
|
||||
|
||||
@@ -18,6 +20,7 @@ interface FilterByNameTransformerEditorState {
|
||||
options: FieldNameInfo[];
|
||||
selected: string[];
|
||||
regex?: string;
|
||||
isRegexValid?: boolean;
|
||||
}
|
||||
|
||||
interface FieldNameInfo {
|
||||
@@ -34,6 +37,7 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
include: props.options.include || [],
|
||||
options: [],
|
||||
selected: [],
|
||||
isRegexValid: true,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -97,36 +101,69 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
};
|
||||
|
||||
onChange = (selected: string[]) => {
|
||||
const { regex, isRegexValid } = this.state;
|
||||
let include = selected;
|
||||
|
||||
if (regex && isRegexValid) {
|
||||
include = include.concat([regex]);
|
||||
}
|
||||
|
||||
this.setState({ selected }, () => {
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: this.state.regex ? [...selected, this.state.regex] : selected,
|
||||
include,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
const { selected, regex } = this.state;
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: regex ? [...selected, regex] : selected,
|
||||
let isRegexValid = true;
|
||||
try {
|
||||
if (regex) {
|
||||
new RegExp(regex);
|
||||
}
|
||||
} catch (e) {
|
||||
isRegexValid = false;
|
||||
}
|
||||
if (isRegexValid) {
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: regex ? [...selected, regex] : selected,
|
||||
});
|
||||
} else {
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: selected,
|
||||
});
|
||||
}
|
||||
this.setState({
|
||||
isRegexValid,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options, selected } = this.state;
|
||||
const { options, selected, isRegexValid } = this.state;
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Field name</div>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<Input
|
||||
placeholder="Regular expression pattern"
|
||||
value={this.state.regex || ''}
|
||||
onChange={e => this.setState({ regex: e.currentTarget.value })}
|
||||
onBlur={this.onInputBlur}
|
||||
width={25}
|
||||
/>
|
||||
<HorizontalGroup spacing="xs" align="flex-start" wrap>
|
||||
<Field
|
||||
invalid={!isRegexValid}
|
||||
error={!isRegexValid ? 'Invalid pattern' : undefined}
|
||||
className={css`
|
||||
margin-bottom: 0;
|
||||
`}
|
||||
>
|
||||
<Input
|
||||
placeholder="Regular expression pattern"
|
||||
value={this.state.regex || ''}
|
||||
onChange={e => this.setState({ regex: e.currentTarget.value })}
|
||||
onBlur={this.onInputBlur}
|
||||
width={25}
|
||||
/>
|
||||
</Field>
|
||||
{options.map((o, i) => {
|
||||
const label = `${o.name}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
const isSelected = selected.indexOf(o.name) > -1;
|
||||
|
||||
@@ -101,7 +101,7 @@ export class FilterByRefIdTransformerEditor extends React.PureComponent<
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Series refId</div>
|
||||
<HorizontalGroup spacing="xs">
|
||||
<HorizontalGroup spacing="xs" align="flex-start" wrap>
|
||||
{options.map((o, i) => {
|
||||
const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
const isSelected = selected.indexOf(o.refId) > -1;
|
||||
|
||||
Reference in New Issue
Block a user