mirror of
https://github.com/grafana/grafana.git
synced 2024-12-01 21:19:28 -06:00
Transformations: Improve UI and add some love to filter by name (#23751)
* Change filterByName options to accept arrays instead of strings * Improve transformations UI * Minor updates * Minor UI changes * Review
This commit is contained in:
parent
bcf5d4b25c
commit
6715cf22a3
@ -35,7 +35,7 @@ describe('filterByName transformer', () => {
|
||||
const cfg = {
|
||||
id: DataTransformerID.filterFieldsByName,
|
||||
options: {
|
||||
include: '/^(startsWith)/',
|
||||
include: ['^(startsWith)'],
|
||||
},
|
||||
};
|
||||
|
||||
@ -48,7 +48,7 @@ describe('filterByName transformer', () => {
|
||||
const cfg = {
|
||||
id: DataTransformerID.filterFieldsByName,
|
||||
options: {
|
||||
exclude: '/^(startsWith)/',
|
||||
exclude: ['^(startsWith)'],
|
||||
},
|
||||
};
|
||||
|
||||
@ -61,8 +61,8 @@ describe('filterByName transformer', () => {
|
||||
const cfg = {
|
||||
id: DataTransformerID.filterFieldsByName,
|
||||
options: {
|
||||
exclude: '/^(startsWith)/',
|
||||
include: `/^(B)$/`,
|
||||
exclude: ['^(startsWith)'],
|
||||
include: [`^(B)$`],
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -4,8 +4,8 @@ import { DataTransformerInfo } from '../../types/transformations';
|
||||
import { FieldMatcherID } from '../matchers/ids';
|
||||
|
||||
export interface FilterFieldsByNameTransformerOptions {
|
||||
include?: string;
|
||||
exclude?: string;
|
||||
include?: string[];
|
||||
exclude?: string[];
|
||||
}
|
||||
|
||||
export const filterFieldsByNameTransformer: DataTransformerInfo<FilterFieldsByNameTransformerOptions> = {
|
||||
@ -23,16 +23,21 @@ export const filterFieldsByNameTransformer: DataTransformerInfo<FilterFieldsByNa
|
||||
if (options.include) {
|
||||
filterOptions.include = {
|
||||
id: FieldMatcherID.byName,
|
||||
options: options.include,
|
||||
options: options.include.length > 0 ? buildRegex(options.include) : '',
|
||||
};
|
||||
}
|
||||
if (options.exclude) {
|
||||
filterOptions.exclude = {
|
||||
id: FieldMatcherID.byName,
|
||||
options: options.exclude,
|
||||
options: options.exclude.length > 0 ? buildRegex(options.exclude) : '',
|
||||
};
|
||||
}
|
||||
|
||||
return filterFieldsTransformer.transformer(filterOptions);
|
||||
},
|
||||
};
|
||||
|
||||
const buildRegex = (regexs: string[]) => {
|
||||
const include = regexs.map(s => `(${s})`).join('|');
|
||||
return `/${include}/`;
|
||||
};
|
||||
|
@ -29,25 +29,17 @@ export const organizeFieldsTransformer: DataTransformerInfo<OrganizeFieldsTransf
|
||||
const rename = renameFieldsTransformer.transformer(options);
|
||||
const order = orderFieldsTransformer.transformer(options);
|
||||
const filter = filterFieldsByNameTransformer.transformer({
|
||||
exclude: mapToExcludeRegexp(options.excludeByName),
|
||||
exclude: mapToExcludeArray(options.excludeByName),
|
||||
});
|
||||
|
||||
return (data: DataFrame[]) => rename(order(filter(data)));
|
||||
},
|
||||
};
|
||||
|
||||
const mapToExcludeRegexp = (excludeByName: Record<string, boolean>): string | undefined => {
|
||||
const mapToExcludeArray = (excludeByName: Record<string, boolean>): string[] => {
|
||||
if (!excludeByName) {
|
||||
return undefined;
|
||||
return [];
|
||||
}
|
||||
|
||||
const fieldsToExclude = Object.keys(excludeByName)
|
||||
.filter(name => excludeByName[name])
|
||||
.join('|');
|
||||
|
||||
if (fieldsToExclude.length === 0) {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
return `^(${fieldsToExclude})$`;
|
||||
return Object.keys(excludeByName).filter(name => excludeByName[name]);
|
||||
};
|
||||
|
@ -16,16 +16,20 @@ export const seriesToColumnsTransformer: DataTransformerInfo<SeriesToColumnsOpti
|
||||
byField: 'Time',
|
||||
},
|
||||
transformer: options => (data: DataFrame[]) => {
|
||||
const regex = `/^(${options.byField})$/`;
|
||||
const optionsArray = options.byField ? [options.byField] : [];
|
||||
// not sure if I should use filterFieldsByNameTransformer to get the key field
|
||||
const keyDataFrames = filterFieldsByNameTransformer.transformer({ include: regex })(data);
|
||||
const keyDataFrames = filterFieldsByNameTransformer.transformer({
|
||||
include: optionsArray,
|
||||
})(data);
|
||||
if (!keyDataFrames.length) {
|
||||
// for now we only parse data frames with 2 fields
|
||||
return data;
|
||||
}
|
||||
|
||||
// not sure if I should use filterFieldsByNameTransformer to get the other fields
|
||||
const otherDataFrames = filterFieldsByNameTransformer.transformer({ exclude: regex })(data);
|
||||
const otherDataFrames = filterFieldsByNameTransformer.transformer({
|
||||
exclude: optionsArray,
|
||||
})(data);
|
||||
if (!otherDataFrames.length) {
|
||||
// for now we only parse data frames with 2 fields
|
||||
return data;
|
||||
|
61
packages/grafana-ui/src/components/FilterPill/FilterPill.tsx
Normal file
61
packages/grafana-ui/src/components/FilterPill/FilterPill.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import React, { useContext } from 'react';
|
||||
import { stylesFactory, ThemeContext } from '../../themes';
|
||||
import { GrafanaTheme } from '@grafana/data';
|
||||
import { css } from 'emotion';
|
||||
import { IconButton } from '../IconButton/IconButton';
|
||||
import { IconName } from '../../types';
|
||||
|
||||
interface FilterPillProps {
|
||||
selected: boolean;
|
||||
label: string;
|
||||
onClick: React.MouseEventHandler<HTMLElement>;
|
||||
icon?: IconName;
|
||||
}
|
||||
|
||||
export const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick, icon = 'check' }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
const styles = getFilterPillStyles(theme, selected);
|
||||
return (
|
||||
<div className={styles.wrapper} onClick={onClick}>
|
||||
<IconButton
|
||||
name={icon}
|
||||
onClick={e => {
|
||||
e.stopPropagation();
|
||||
onClick(e);
|
||||
}}
|
||||
className={styles.icon}
|
||||
surface="header"
|
||||
/>
|
||||
<span className={styles.label}>{label}</span>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getFilterPillStyles = stylesFactory((theme: GrafanaTheme, isSelected: boolean) => {
|
||||
const labelColor = isSelected ? theme.colors.text : theme.colors.textWeak;
|
||||
|
||||
return {
|
||||
wrapper: css`
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
background: ${theme.colors.bg2};
|
||||
border-radius: ${theme.border.radius.sm};
|
||||
display: inline-block;
|
||||
padding: 0 ${theme.spacing.md} 0 ${theme.spacing.xs};
|
||||
font-weight: ${theme.typography.weight.semibold};
|
||||
font-size: ${theme.typography.size.sm};
|
||||
color: ${theme.colors.text};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 32px;
|
||||
cursor: pointer;
|
||||
`,
|
||||
icon: css`
|
||||
margin-right: ${theme.spacing.sm};
|
||||
margin-left: ${theme.spacing.xs};
|
||||
color: ${labelColor};
|
||||
`,
|
||||
label: css`
|
||||
color: ${labelColor};
|
||||
`,
|
||||
};
|
||||
});
|
@ -337,7 +337,7 @@ export function SelectBase<T>({
|
||||
width: width ? `${8 * width}px` : '100%',
|
||||
}),
|
||||
}}
|
||||
className={cx('select-container', className)}
|
||||
className={className}
|
||||
{...commonSelectProps}
|
||||
{...creatableProps}
|
||||
{...asyncSelectProps}
|
||||
|
@ -40,8 +40,6 @@ interface ReactTableInternalState extends UseResizeColumnsState<{}>, UseSortBySt
|
||||
function useTableStateReducer(props: Props) {
|
||||
return useCallback(
|
||||
(newState: ReactTableInternalState, action: any) => {
|
||||
console.log(action, newState);
|
||||
|
||||
switch (action.type) {
|
||||
case 'columnDoneResizing':
|
||||
if (props.onColumnResize) {
|
||||
|
@ -1,23 +1,20 @@
|
||||
import React, { useContext, ChangeEvent } from 'react';
|
||||
import React, { ChangeEvent } from 'react';
|
||||
import {
|
||||
DataTransformerID,
|
||||
CalculateFieldTransformerOptions,
|
||||
DataTransformerID,
|
||||
fieldReducers,
|
||||
FieldType,
|
||||
KeyValue,
|
||||
ReducerID,
|
||||
standardTransformers,
|
||||
TransformerRegistyItem,
|
||||
TransformerUIProps,
|
||||
FieldType,
|
||||
ReducerID,
|
||||
fieldReducers,
|
||||
} from '@grafana/data';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { css } from 'emotion';
|
||||
import { InlineList } from '../List/InlineList';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { Label } from '../Forms/Label';
|
||||
import { StatsPicker } from '../StatsPicker/StatsPicker';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
import { Switch } from '../Forms/Legacy/Switch/Switch';
|
||||
import { Input } from '../Input/Input';
|
||||
import { FilterPill } from '../FilterPill/FilterPill';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
|
||||
interface CalculateFieldTransformerEditorProps extends TransformerUIProps<CalculateFieldTransformerOptions> {}
|
||||
|
||||
@ -98,7 +95,7 @@ export class CalculateFieldTransformerEditor extends React.PureComponent<
|
||||
});
|
||||
};
|
||||
|
||||
onToggleReplaceFields = (evt: ChangeEvent<HTMLInputElement>) => {
|
||||
onToggleReplaceFields = () => {
|
||||
const { options } = this.props;
|
||||
this.props.onChange({
|
||||
...options,
|
||||
@ -125,75 +122,55 @@ export class CalculateFieldTransformerEditor extends React.PureComponent<
|
||||
const { options } = this.props;
|
||||
const { names, selected } = this.state;
|
||||
const reducer = fieldReducers.get(options.reducer);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Label>Numeric Fields</Label>
|
||||
<InlineList
|
||||
items={names}
|
||||
renderItem={(o, i) => {
|
||||
return (
|
||||
<span
|
||||
className={css`
|
||||
margin-right: ${i === names.length - 1 ? '0' : '10px'};
|
||||
`}
|
||||
>
|
||||
<FilterPill
|
||||
onClick={() => {
|
||||
this.onFieldToggle(o);
|
||||
}}
|
||||
label={o}
|
||||
selected={selected.indexOf(o) > -1}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
<Label>Calculation</Label>
|
||||
<StatsPicker stats={[options.reducer]} onChange={this.onStatsChange} defaultStat={ReducerID.sum} />
|
||||
<Label>Alias</Label>
|
||||
<Input value={options.alias} placeholder={reducer.name} onChange={this.onAliasChanged} />
|
||||
|
||||
<Label>Replace all fields</Label>
|
||||
<Switch checked={options.replaceFields} onChange={this.onToggleReplaceFields} />
|
||||
|
||||
{/* nullValueMode?: NullValueMode; */}
|
||||
<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">
|
||||
{names.map((o, i) => {
|
||||
return (
|
||||
<FilterPill
|
||||
key={`${o}/${i}`}
|
||||
onClick={() => {
|
||||
this.onFieldToggle(o);
|
||||
}}
|
||||
label={o}
|
||||
selected={selected.indexOf(o) > -1}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Calculation</div>
|
||||
<StatsPicker stats={[options.reducer]} onChange={this.onStatsChange} defaultStat={ReducerID.sum} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Alias</div>
|
||||
<Input value={options.alias} placeholder={reducer.name} onChange={this.onAliasChanged} />
|
||||
</div>
|
||||
</div>
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form gf-form--grow">
|
||||
<Switch
|
||||
label="Replace all fields"
|
||||
labelClass="width-8"
|
||||
checked={!!options.replaceFields}
|
||||
onChange={this.onToggleReplaceFields}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface FilterPillProps {
|
||||
selected: boolean;
|
||||
label: string;
|
||||
onClick: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
color: white;
|
||||
background: ${selected ? theme.palette.blue95 : theme.palette.blue77};
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{selected && (
|
||||
<Icon
|
||||
className={css`
|
||||
margin-right: 4px;
|
||||
`}
|
||||
name="check"
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const calculateFieldTransformRegistryItem: TransformerRegistyItem<CalculateFieldTransformerOptions> = {
|
||||
id: DataTransformerID.calculateField,
|
||||
editor: CalculateFieldTransformerEditor,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
DataTransformerID,
|
||||
FilterFieldsByNameTransformerOptions,
|
||||
@ -7,17 +7,17 @@ import {
|
||||
TransformerRegistyItem,
|
||||
TransformerUIProps,
|
||||
} from '@grafana/data';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { css } from 'emotion';
|
||||
import { InlineList } from '../List/InlineList';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { Input } from '../Input/Input';
|
||||
import { FilterPill } from '../FilterPill/FilterPill';
|
||||
|
||||
interface FilterByNameTransformerEditorProps extends TransformerUIProps<FilterFieldsByNameTransformerOptions> {}
|
||||
|
||||
interface FilterByNameTransformerEditorState {
|
||||
include: string;
|
||||
include: string[];
|
||||
options: FieldNameInfo[];
|
||||
selected: string[];
|
||||
regex?: string;
|
||||
}
|
||||
|
||||
interface FieldNameInfo {
|
||||
@ -31,7 +31,7 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
constructor(props: FilterByNameTransformerEditorProps) {
|
||||
super(props);
|
||||
this.state = {
|
||||
include: props.options.include || '',
|
||||
include: props.options.include || [],
|
||||
options: [],
|
||||
selected: [],
|
||||
};
|
||||
@ -43,10 +43,11 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
|
||||
private initOptions() {
|
||||
const { input, options } = this.props;
|
||||
const configuredOptions = options.include ? options.include.split('|') : [];
|
||||
const configuredOptions = options.include ? options.include : [];
|
||||
|
||||
const allNames: FieldNameInfo[] = [];
|
||||
const byName: KeyValue<FieldNameInfo> = {};
|
||||
|
||||
for (const frame of input) {
|
||||
for (const field of frame.fields) {
|
||||
let v = byName[field.name];
|
||||
@ -61,22 +62,28 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
}
|
||||
}
|
||||
|
||||
let regexOption;
|
||||
|
||||
if (configuredOptions.length) {
|
||||
const options: FieldNameInfo[] = [];
|
||||
const selected: FieldNameInfo[] = [];
|
||||
for (const v of allNames) {
|
||||
if (configuredOptions.includes(v.name)) {
|
||||
selected.push(v);
|
||||
let selected: FieldNameInfo[] = [];
|
||||
|
||||
for (const o of configuredOptions) {
|
||||
const selectedFields = allNames.filter(n => n.name === o);
|
||||
if (selectedFields.length > 0) {
|
||||
selected = selected.concat(selectedFields);
|
||||
} else {
|
||||
// there can be only one regex in the options
|
||||
regexOption = o;
|
||||
}
|
||||
options.push(v);
|
||||
}
|
||||
|
||||
this.setState({
|
||||
options,
|
||||
options: allNames,
|
||||
selected: selected.map(s => s.name),
|
||||
regex: regexOption,
|
||||
});
|
||||
} else {
|
||||
this.setState({ options: allNames, selected: [] });
|
||||
this.setState({ options: allNames, selected: allNames.map(n => n.name) });
|
||||
}
|
||||
}
|
||||
|
||||
@ -90,75 +97,57 @@ export class FilterByNameTransformerEditor extends React.PureComponent<
|
||||
};
|
||||
|
||||
onChange = (selected: string[]) => {
|
||||
this.setState({ selected });
|
||||
this.setState({ selected }, () => {
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: this.state.regex ? [...selected, this.state.regex] : selected,
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
onInputBlur = (e: React.FocusEvent<HTMLInputElement>) => {
|
||||
const { selected, regex } = this.state;
|
||||
this.props.onChange({
|
||||
...this.props.options,
|
||||
include: selected.join('|'),
|
||||
include: regex ? [...selected, regex] : selected,
|
||||
});
|
||||
};
|
||||
|
||||
render() {
|
||||
const { options, selected } = this.state;
|
||||
return (
|
||||
<>
|
||||
<InlineList
|
||||
items={options}
|
||||
renderItem={(o, i) => {
|
||||
const label = `${o.name}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
return (
|
||||
<span
|
||||
className={css`
|
||||
margin-right: ${i === options.length - 1 ? '0' : '10px'};
|
||||
`}
|
||||
>
|
||||
<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}
|
||||
/>
|
||||
{options.map((o, i) => {
|
||||
const label = `${o.name}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
const isSelected = selected.indexOf(o.name) > -1;
|
||||
return (
|
||||
<FilterPill
|
||||
key={`${o.name}/${i}`}
|
||||
onClick={() => {
|
||||
this.onFieldToggle(o.name);
|
||||
}}
|
||||
label={label}
|
||||
selected={selected.indexOf(o.name) > -1}
|
||||
selected={isSelected}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface FilterPillProps {
|
||||
selected: boolean;
|
||||
label: string;
|
||||
onClick: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
color: white;
|
||||
background: ${selected ? theme.palette.blue95 : theme.palette.blue77};
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{selected && (
|
||||
<Icon
|
||||
className={css`
|
||||
margin-right: 4px;
|
||||
`}
|
||||
name="check"
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const filterFieldsByNameTransformRegistryItem: TransformerRegistyItem<FilterFieldsByNameTransformerOptions> = {
|
||||
id: DataTransformerID.filterFieldsByName,
|
||||
editor: FilterByNameTransformerEditor,
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { useContext } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
DataTransformerID,
|
||||
FilterFramesByRefIdTransformerOptions,
|
||||
@ -7,10 +7,8 @@ import {
|
||||
TransformerRegistyItem,
|
||||
TransformerUIProps,
|
||||
} from '@grafana/data';
|
||||
import { ThemeContext } from '../../themes/ThemeContext';
|
||||
import { css } from 'emotion';
|
||||
import { InlineList } from '../List/InlineList';
|
||||
import { Icon } from '../Icon/Icon';
|
||||
import { HorizontalGroup } from '../Layout/Layout';
|
||||
import { FilterPill } from '../FilterPill/FilterPill';
|
||||
|
||||
interface FilterByRefIdTransformerEditorProps extends TransformerUIProps<FilterFramesByRefIdTransformerOptions> {}
|
||||
|
||||
@ -100,65 +98,31 @@ export class FilterByRefIdTransformerEditor extends React.PureComponent<
|
||||
render() {
|
||||
const { options, selected } = this.state;
|
||||
return (
|
||||
<>
|
||||
<InlineList
|
||||
items={options}
|
||||
renderItem={(o, i) => {
|
||||
const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
return (
|
||||
<span
|
||||
className={css`
|
||||
margin-right: ${i === options.length - 1 ? '0' : '10px'};
|
||||
`}
|
||||
>
|
||||
<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">
|
||||
{options.map((o, i) => {
|
||||
const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`;
|
||||
const isSelected = selected.indexOf(o.refId) > -1;
|
||||
return (
|
||||
<FilterPill
|
||||
key={`${o.refId}/${i}`}
|
||||
onClick={() => {
|
||||
this.onFieldToggle(o.refId);
|
||||
}}
|
||||
label={label}
|
||||
selected={selected.indexOf(o.refId) > -1}
|
||||
selected={isSelected}
|
||||
/>
|
||||
</span>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
})}
|
||||
</HorizontalGroup>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
interface FilterPillProps {
|
||||
selected: boolean;
|
||||
label: string;
|
||||
onClick: React.MouseEventHandler<HTMLElement>;
|
||||
}
|
||||
const FilterPill: React.FC<FilterPillProps> = ({ label, selected, onClick }) => {
|
||||
const theme = useContext(ThemeContext);
|
||||
return (
|
||||
<div
|
||||
className={css`
|
||||
padding: ${theme.spacing.xxs} ${theme.spacing.sm};
|
||||
color: white;
|
||||
background: ${selected ? theme.palette.blue95 : theme.palette.blue77};
|
||||
border-radius: 16px;
|
||||
display: inline-block;
|
||||
cursor: pointer;
|
||||
`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{selected && (
|
||||
<Icon
|
||||
className={css`
|
||||
margin-right: 4px;
|
||||
`}
|
||||
name="check"
|
||||
/>
|
||||
)}
|
||||
{label}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export const filterFramesByRefIdTransformRegistryItem: TransformerRegistyItem<FilterFramesByRefIdTransformerOptions> = {
|
||||
id: DataTransformerID.filterByRefId,
|
||||
editor: FilterByRefIdTransformerEditor,
|
||||
|
@ -1,11 +1,11 @@
|
||||
import React, { useMemo, useCallback } from 'react';
|
||||
import React, { useCallback, useMemo } from 'react';
|
||||
import {
|
||||
DataTransformerID,
|
||||
SelectableValue,
|
||||
SeriesToColumnsOptions,
|
||||
standardTransformers,
|
||||
TransformerRegistyItem,
|
||||
TransformerUIProps,
|
||||
SeriesToColumnsOptions,
|
||||
SelectableValue,
|
||||
} from '@grafana/data';
|
||||
import { getAllFieldNamesFromDataFrames } from './OrganizeFieldsTransformerEditor';
|
||||
import { Select } from '../Select/Select';
|
||||
@ -30,9 +30,9 @@ export const SeriesToFieldsTransformerEditor: React.FC<TransformerUIProps<Series
|
||||
|
||||
return (
|
||||
<div className="gf-form-inline">
|
||||
<div className="gf-form">
|
||||
<div className="gf-form-label">Field</div>
|
||||
<Select options={fieldNameOptions} value={options.byField} onChange={onSelectField} />
|
||||
<div className="gf-form gf-form--grow">
|
||||
<div className="gf-form-label width-8">Field name</div>
|
||||
<Select options={fieldNameOptions} value={options.byField} onChange={onSelectField} isClearable />
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
Loading…
Reference in New Issue
Block a user