diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts index 890c9e6a8b9..d6dc8d19599 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.test.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.test.ts @@ -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)$`], }, }; diff --git a/packages/grafana-data/src/transformations/transformers/filterByName.ts b/packages/grafana-data/src/transformations/transformers/filterByName.ts index 7ece827065c..80374a8e15b 100644 --- a/packages/grafana-data/src/transformations/transformers/filterByName.ts +++ b/packages/grafana-data/src/transformations/transformers/filterByName.ts @@ -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 = { @@ -23,16 +23,21 @@ export const filterFieldsByNameTransformer: DataTransformerInfo 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}/`; +}; diff --git a/packages/grafana-data/src/transformations/transformers/organize.ts b/packages/grafana-data/src/transformations/transformers/organize.ts index 457ef214f37..5c06d5cc36c 100644 --- a/packages/grafana-data/src/transformations/transformers/organize.ts +++ b/packages/grafana-data/src/transformations/transformers/organize.ts @@ -29,25 +29,17 @@ export const organizeFieldsTransformer: DataTransformerInfo rename(order(filter(data))); }, }; -const mapToExcludeRegexp = (excludeByName: Record): string | undefined => { +const mapToExcludeArray = (excludeByName: Record): 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]); }; diff --git a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts index eb6e0093e26..ca4b8c09748 100644 --- a/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts +++ b/packages/grafana-data/src/transformations/transformers/seriesToColumns.ts @@ -16,16 +16,20 @@ export const seriesToColumnsTransformer: DataTransformerInfo (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; diff --git a/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx new file mode 100644 index 00000000000..1f0b6202efa --- /dev/null +++ b/packages/grafana-ui/src/components/FilterPill/FilterPill.tsx @@ -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; + icon?: IconName; +} + +export const FilterPill: React.FC = ({ label, selected, onClick, icon = 'check' }) => { + const theme = useContext(ThemeContext); + const styles = getFilterPillStyles(theme, selected); + return ( +
+ { + e.stopPropagation(); + onClick(e); + }} + className={styles.icon} + surface="header" + /> + {label} +
+ ); +}; + +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}; + `, + }; +}); diff --git a/packages/grafana-ui/src/components/Select/SelectBase.tsx b/packages/grafana-ui/src/components/Select/SelectBase.tsx index 67ab53f34fa..ccceedeac9a 100644 --- a/packages/grafana-ui/src/components/Select/SelectBase.tsx +++ b/packages/grafana-ui/src/components/Select/SelectBase.tsx @@ -337,7 +337,7 @@ export function SelectBase({ width: width ? `${8 * width}px` : '100%', }), }} - className={cx('select-container', className)} + className={className} {...commonSelectProps} {...creatableProps} {...asyncSelectProps} diff --git a/packages/grafana-ui/src/components/Table/Table.tsx b/packages/grafana-ui/src/components/Table/Table.tsx index 04980907f75..ca2b7d8d366 100644 --- a/packages/grafana-ui/src/components/Table/Table.tsx +++ b/packages/grafana-ui/src/components/Table/Table.tsx @@ -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) { diff --git a/packages/grafana-ui/src/components/TransformersUI/CalculateFieldTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/CalculateFieldTransformerEditor.tsx index 3afa5885ab8..7d9942cf4e4 100644 --- a/packages/grafana-ui/src/components/TransformersUI/CalculateFieldTransformerEditor.tsx +++ b/packages/grafana-ui/src/components/TransformersUI/CalculateFieldTransformerEditor.tsx @@ -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 {} @@ -98,7 +95,7 @@ export class CalculateFieldTransformerEditor extends React.PureComponent< }); }; - onToggleReplaceFields = (evt: ChangeEvent) => { + 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 (
- - { - return ( - - { - this.onFieldToggle(o); - }} - label={o} - selected={selected.indexOf(o) > -1} - /> - - ); - }} - /> - - - - - - - - - {/* nullValueMode?: NullValueMode; */} +
+
+
Field name
+ + {names.map((o, i) => { + return ( + { + this.onFieldToggle(o); + }} + label={o} + selected={selected.indexOf(o) > -1} + /> + ); + })} + +
+
+
+
+
Calculation
+ +
+
+
+
+
Alias
+ +
+
+
+
+ +
+
); } } -interface FilterPillProps { - selected: boolean; - label: string; - onClick: React.MouseEventHandler; -} -const FilterPill: React.FC = ({ label, selected, onClick }) => { - const theme = useContext(ThemeContext); - return ( -
- {selected && ( - - )} - {label} -
- ); -}; - export const calculateFieldTransformRegistryItem: TransformerRegistyItem = { id: DataTransformerID.calculateField, editor: CalculateFieldTransformerEditor, diff --git a/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx index e08fa6b8937..c1de3057818 100644 --- a/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx +++ b/packages/grafana-ui/src/components/TransformersUI/FilterByNameTransformerEditor.tsx @@ -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 {} 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 = {}; + 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) => { + 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 ( - <> - { - const label = `${o.name}${o.count > 1 ? ' (' + o.count + ')' : ''}`; - return ( - +
+
+
Field name
+ + 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 ( { this.onFieldToggle(o.name); }} label={label} - selected={selected.indexOf(o.name) > -1} + selected={isSelected} /> - - ); - }} - /> - + ); + })} + +
+
); } } -interface FilterPillProps { - selected: boolean; - label: string; - onClick: React.MouseEventHandler; -} -const FilterPill: React.FC = ({ label, selected, onClick }) => { - const theme = useContext(ThemeContext); - return ( -
- {selected && ( - - )} - {label} -
- ); -}; - export const filterFieldsByNameTransformRegistryItem: TransformerRegistyItem = { id: DataTransformerID.filterFieldsByName, editor: FilterByNameTransformerEditor, diff --git a/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx index a3056288e1e..51108458a00 100644 --- a/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx +++ b/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx @@ -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 {} @@ -100,65 +98,31 @@ export class FilterByRefIdTransformerEditor extends React.PureComponent< render() { const { options, selected } = this.state; return ( - <> - { - const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`; - return ( - +
+
+
Series refId
+ + {options.map((o, i) => { + const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`; + const isSelected = selected.indexOf(o.refId) > -1; + return ( { this.onFieldToggle(o.refId); }} label={label} - selected={selected.indexOf(o.refId) > -1} + selected={isSelected} /> - - ); - }} - /> - + ); + })} + +
+
); } } -interface FilterPillProps { - selected: boolean; - label: string; - onClick: React.MouseEventHandler; -} -const FilterPill: React.FC = ({ label, selected, onClick }) => { - const theme = useContext(ThemeContext); - return ( -
- {selected && ( - - )} - {label} -
- ); -}; - export const filterFramesByRefIdTransformRegistryItem: TransformerRegistyItem = { id: DataTransformerID.filterByRefId, editor: FilterByRefIdTransformerEditor, diff --git a/packages/grafana-ui/src/components/TransformersUI/SeriesToFieldsTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/SeriesToFieldsTransformerEditor.tsx index 3206d35c924..c55c6be7670 100644 --- a/packages/grafana-ui/src/components/TransformersUI/SeriesToFieldsTransformerEditor.tsx +++ b/packages/grafana-ui/src/components/TransformersUI/SeriesToFieldsTransformerEditor.tsx @@ -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 -
-
Field
-
);