diff --git a/packages/grafana-data/src/transformations/index.ts b/packages/grafana-data/src/transformations/index.ts index 798dabce10b..17c0ff92d8a 100644 --- a/packages/grafana-data/src/transformations/index.ts +++ b/packages/grafana-data/src/transformations/index.ts @@ -4,4 +4,5 @@ export * from './matchers'; export * from './transformers'; export * from './fieldReducer'; export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName'; +export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId'; export { ReduceTransformerOptions } from './transformers/reduce'; diff --git a/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts b/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts index 0d525d53dd9..a8e0aa99ede 100644 --- a/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts +++ b/packages/grafana-data/src/transformations/matchers/refIdMatcher.ts @@ -1,6 +1,7 @@ import { DataFrame } from '../../types/dataFrame'; import { FrameMatcherID } from './ids'; import { FrameMatcherInfo } from '../../types/transformations'; +import { stringToJsRegex } from '../../text'; // General Field matcher const refIdMacher: FrameMatcherInfo = { @@ -10,8 +11,9 @@ const refIdMacher: FrameMatcherInfo = { defaultOptions: 'A', get: (pattern: string) => { + const regex = stringToJsRegex(pattern); return (frame: DataFrame) => { - return pattern === frame.refId; + return regex.test(frame.refId || ''); }; }, diff --git a/packages/grafana-data/src/transformations/transformers.ts b/packages/grafana-data/src/transformations/transformers.ts index 189a76d03ab..dc2e1489ae1 100644 --- a/packages/grafana-data/src/transformations/transformers.ts +++ b/packages/grafana-data/src/transformations/transformers.ts @@ -8,6 +8,7 @@ import { filterFieldsTransformer, filterFramesTransformer } from './transformers import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName'; import { noopTransformer } from './transformers/noop'; import { DataTransformerInfo, DataTransformerConfig } from '../types/transformations'; +import { filterFramesByRefIdTransformer } from './transformers/filterByRefId'; /** * Apply configured transformations to the input data @@ -64,6 +65,7 @@ export const transformersRegistry = new TransformerRegistry(() => [ filterFieldsTransformer, filterFieldsByNameTransformer, filterFramesTransformer, + filterFramesByRefIdTransformer, appendTransformer, reduceTransformer, ]); diff --git a/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts new file mode 100644 index 00000000000..81e8d4a4766 --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/filterByRefId.test.ts @@ -0,0 +1,44 @@ +import { DataTransformerID } from './ids'; +import { transformDataFrame } from '../transformers'; +import { toDataFrame } from '../../dataframe/processDataFrame'; + +export const allSeries = [ + toDataFrame({ + refId: 'A', + fields: [], + }), + toDataFrame({ + refId: 'B', + fields: [], + }), + toDataFrame({ + refId: 'C', + fields: [], + }), +]; + +describe('filterByRefId transformer', () => { + it('returns all series if no options provided', () => { + const cfg = { + id: DataTransformerID.filterByRefId, + options: {}, + }; + + const filtered = transformDataFrame([cfg], allSeries); + expect(filtered.length).toBe(3); + }); + + describe('respects', () => { + it('inclusion', () => { + const cfg = { + id: DataTransformerID.filterByRefId, + options: { + include: 'A|B', + }, + }; + + const filtered = transformDataFrame([cfg], allSeries); + expect(filtered.map(f => f.refId)).toEqual(['A', 'B']); + }); + }); +}); diff --git a/packages/grafana-data/src/transformations/transformers/filterByRefId.ts b/packages/grafana-data/src/transformations/transformers/filterByRefId.ts new file mode 100644 index 00000000000..59aedb08122 --- /dev/null +++ b/packages/grafana-data/src/transformations/transformers/filterByRefId.ts @@ -0,0 +1,38 @@ +import { DataTransformerID } from './ids'; +import { FilterOptions, filterFramesTransformer } from './filter'; +import { DataTransformerInfo } from '../../types/transformations'; +import { FrameMatcherID } from '../matchers/ids'; + +export interface FilterFramesByRefIdTransformerOptions { + include?: string; + exclude?: string; +} + +export const filterFramesByRefIdTransformer: DataTransformerInfo = { + id: DataTransformerID.filterByRefId, + name: 'Filter data by query refId', + description: 'select a subset of results', + defaultOptions: {}, + + /** + * Return a modified copy of the series. If the transform is not or should not + * be applied, just return the input series + */ + transformer: (options: FilterFramesByRefIdTransformerOptions) => { + const filterOptions: FilterOptions = {}; + if (options.include) { + filterOptions.include = { + id: FrameMatcherID.byRefId, + options: options.include, + }; + } + if (options.exclude) { + filterOptions.exclude = { + id: FrameMatcherID.byRefId, + options: options.exclude, + }; + } + + return filterFramesTransformer.transformer(filterOptions); + }, +}; diff --git a/packages/grafana-data/src/transformations/transformers/ids.ts b/packages/grafana-data/src/transformations/transformers/ids.ts index c088186fc0a..79bcb63ab81 100644 --- a/packages/grafana-data/src/transformations/transformers/ids.ts +++ b/packages/grafana-data/src/transformations/transformers/ids.ts @@ -7,5 +7,6 @@ export enum DataTransformerID { filterFields = 'filterFields', // Pick some fields (keep all frames) filterFieldsByName = 'filterFieldsByName', // Pick fields with name matching regex (keep all frames) filterFrames = 'filterFrames', // Pick some frames (keep all fields) + filterByRefId = 'filterByRefId', // Pick some frames by RefId noop = 'noop', // Does nothing to the dataframe } diff --git a/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx b/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx new file mode 100644 index 00000000000..04450ff149b --- /dev/null +++ b/packages/grafana-ui/src/components/TransformersUI/FilterByRefIdTransformerEditor.tsx @@ -0,0 +1,170 @@ +import React, { useContext } from 'react'; +import { + FilterFramesByRefIdTransformerOptions, + DataTransformerID, + transformersRegistry, + KeyValue, +} from '@grafana/data'; +import { TransformerUIProps, TransformerUIRegistyItem } from './types'; +import { ThemeContext } from '../../themes/ThemeContext'; +import { css, cx } from 'emotion'; +import { InlineList } from '../List/InlineList'; + +interface FilterByRefIdTransformerEditorProps extends TransformerUIProps {} + +interface FilterByRefIdTransformerEditorState { + include: string; + options: RefIdInfo[]; + selected: string[]; +} + +interface RefIdInfo { + refId: string; + count: number; +} +export class FilterByRefIdTransformerEditor extends React.PureComponent< + FilterByRefIdTransformerEditorProps, + FilterByRefIdTransformerEditorState +> { + constructor(props: FilterByRefIdTransformerEditorProps) { + super(props); + this.state = { + include: props.options.include || '', + options: [], + selected: [], + }; + } + + componentDidMount() { + this.initOptions(); + } + + private initOptions() { + const { input, options } = this.props; + const configuredOptions = options.include ? options.include.split('|') : []; + + const allNames: RefIdInfo[] = []; + const byName: KeyValue = {}; + for (const frame of input) { + if (frame.refId) { + let v = byName[frame.refId]; + if (!v) { + v = byName[frame.refId] = { + refId: frame.refId, + count: 0, + }; + allNames.push(v); + } + v.count++; + } + } + + if (configuredOptions.length) { + const options: RefIdInfo[] = []; + const selected: RefIdInfo[] = []; + for (const v of allNames) { + if (configuredOptions.includes(v.refId)) { + selected.push(v); + } + options.push(v); + } + + this.setState({ + options, + selected: selected.map(s => s.refId), + }); + } else { + this.setState({ options: allNames, selected: [] }); + } + } + + onFieldToggle = (fieldName: string) => { + const { selected } = this.state; + if (selected.indexOf(fieldName) > -1) { + this.onChange(selected.filter(s => s !== fieldName)); + } else { + this.onChange([...selected, fieldName]); + } + }; + + onChange = (selected: string[]) => { + this.setState({ selected }); + this.props.onChange({ + ...this.props.options, + include: selected.join('|'), + }); + }; + + render() { + const { options, selected } = this.state; + return ( + <> + { + const label = `${o.refId}${o.count > 1 ? ' (' + o.count + ')' : ''}`; + return ( + + { + this.onFieldToggle(o.refId); + }} + label={label} + selected={selected.indexOf(o.refId) > -1} + /> + + ); + }} + /> + + ); + } +} + +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: TransformerUIRegistyItem< + FilterFramesByRefIdTransformerOptions +> = { + id: DataTransformerID.filterByRefId, + component: FilterByRefIdTransformerEditor, + transformer: transformersRegistry.get(DataTransformerID.filterByRefId), + name: 'Filter by refId', + description: 'Filter results by refId', +}; diff --git a/packages/grafana-ui/src/components/TransformersUI/transformers.ts b/packages/grafana-ui/src/components/TransformersUI/transformers.ts index 2a721ec093a..55e906d9af8 100644 --- a/packages/grafana-ui/src/components/TransformersUI/transformers.ts +++ b/packages/grafana-ui/src/components/TransformersUI/transformers.ts @@ -1,8 +1,13 @@ import { Registry } from '@grafana/data'; import { reduceTransformRegistryItem } from './ReduceTransformerEditor'; import { filterFieldsByNameTransformRegistryItem } from './FilterByNameTransformerEditor'; +import { filterFramesByRefIdTransformRegistryItem } from './FilterByRefIdTransformerEditor'; import { TransformerUIRegistyItem } from './types'; export const transformersUIRegistry = new Registry>(() => { - return [reduceTransformRegistryItem, filterFieldsByNameTransformRegistryItem]; + return [ + reduceTransformRegistryItem, + filterFieldsByNameTransformRegistryItem, + filterFramesByRefIdTransformRegistryItem, + ]; }); diff --git a/public/app/features/expressions/__snapshots__/util.test.ts.snap b/public/app/features/expressions/__snapshots__/util.test.ts.snap index f95b3fd883c..c0eb2137761 100644 --- a/public/app/features/expressions/__snapshots__/util.test.ts.snap +++ b/public/app/features/expressions/__snapshots__/util.test.ts.snap @@ -62,7 +62,7 @@ Array [ ], "meta": undefined, "name": undefined, - "refId": undefined, + "refId": "GC", }, Object { "fields": Array [ @@ -124,7 +124,7 @@ Array [ ], "meta": undefined, "name": undefined, - "refId": undefined, + "refId": "GB", }, ] `; diff --git a/public/app/features/expressions/util.ts b/public/app/features/expressions/util.ts index 4beb0feadc3..1ad86aaa9f5 100644 --- a/public/app/features/expressions/util.ts +++ b/public/app/features/expressions/util.ts @@ -9,8 +9,13 @@ export function base64StringToArrowTable(text: string): Table { return Table.from(arr); } +function valueOrUndefined(val?: string) { + return val ? val : undefined; +} + export function arrowTableToDataFrame(table: Table): DataFrame { const fields: Field[] = []; + for (let i = 0; i < table.numCols; i++) { const col = table.getColumnAt(i); if (col) { @@ -35,6 +40,7 @@ export function arrowTableToDataFrame(table: Table): DataFrame { default: console.log('UNKNOWN Type:', schema); } + // console.log(' field>', schema.metadata); fields.push({ name: col.name, @@ -44,9 +50,12 @@ export function arrowTableToDataFrame(table: Table): DataFrame { }); } } + const meta = table.schema.metadata; return { fields, length: table.length, + refId: valueOrUndefined(meta.get('refId')), + name: valueOrUndefined(meta.get('name')), }; } diff --git a/public/app/plugins/datasource/dashboard/runSharedRequest.ts b/public/app/plugins/datasource/dashboard/runSharedRequest.ts index d63b2600c55..50d9c09a5ac 100644 --- a/public/app/plugins/datasource/dashboard/runSharedRequest.ts +++ b/public/app/plugins/datasource/dashboard/runSharedRequest.ts @@ -37,7 +37,6 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable { - console.log('got data from other panel', data); subscriber.next(data); }, });