mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: filter results by refId (#20261)
This commit is contained in:
parent
767c672a2f
commit
8430a182ef
@ -4,4 +4,5 @@ export * from './matchers';
|
|||||||
export * from './transformers';
|
export * from './transformers';
|
||||||
export * from './fieldReducer';
|
export * from './fieldReducer';
|
||||||
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||||
|
export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId';
|
||||||
export { ReduceTransformerOptions } from './transformers/reduce';
|
export { ReduceTransformerOptions } from './transformers/reduce';
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { DataFrame } from '../../types/dataFrame';
|
import { DataFrame } from '../../types/dataFrame';
|
||||||
import { FrameMatcherID } from './ids';
|
import { FrameMatcherID } from './ids';
|
||||||
import { FrameMatcherInfo } from '../../types/transformations';
|
import { FrameMatcherInfo } from '../../types/transformations';
|
||||||
|
import { stringToJsRegex } from '../../text';
|
||||||
|
|
||||||
// General Field matcher
|
// General Field matcher
|
||||||
const refIdMacher: FrameMatcherInfo<string> = {
|
const refIdMacher: FrameMatcherInfo<string> = {
|
||||||
@ -10,8 +11,9 @@ const refIdMacher: FrameMatcherInfo<string> = {
|
|||||||
defaultOptions: 'A',
|
defaultOptions: 'A',
|
||||||
|
|
||||||
get: (pattern: string) => {
|
get: (pattern: string) => {
|
||||||
|
const regex = stringToJsRegex(pattern);
|
||||||
return (frame: DataFrame) => {
|
return (frame: DataFrame) => {
|
||||||
return pattern === frame.refId;
|
return regex.test(frame.refId || '');
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
||||||
|
@ -8,6 +8,7 @@ import { filterFieldsTransformer, filterFramesTransformer } from './transformers
|
|||||||
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
import { filterFieldsByNameTransformer, FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||||
import { noopTransformer } from './transformers/noop';
|
import { noopTransformer } from './transformers/noop';
|
||||||
import { DataTransformerInfo, DataTransformerConfig } from '../types/transformations';
|
import { DataTransformerInfo, DataTransformerConfig } from '../types/transformations';
|
||||||
|
import { filterFramesByRefIdTransformer } from './transformers/filterByRefId';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Apply configured transformations to the input data
|
* Apply configured transformations to the input data
|
||||||
@ -64,6 +65,7 @@ export const transformersRegistry = new TransformerRegistry(() => [
|
|||||||
filterFieldsTransformer,
|
filterFieldsTransformer,
|
||||||
filterFieldsByNameTransformer,
|
filterFieldsByNameTransformer,
|
||||||
filterFramesTransformer,
|
filterFramesTransformer,
|
||||||
|
filterFramesByRefIdTransformer,
|
||||||
appendTransformer,
|
appendTransformer,
|
||||||
reduceTransformer,
|
reduceTransformer,
|
||||||
]);
|
]);
|
||||||
|
@ -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']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -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<FilterFramesByRefIdTransformerOptions> = {
|
||||||
|
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);
|
||||||
|
},
|
||||||
|
};
|
@ -7,5 +7,6 @@ export enum DataTransformerID {
|
|||||||
filterFields = 'filterFields', // Pick some fields (keep all frames)
|
filterFields = 'filterFields', // Pick some fields (keep all frames)
|
||||||
filterFieldsByName = 'filterFieldsByName', // Pick fields with name matching regex (keep all frames)
|
filterFieldsByName = 'filterFieldsByName', // Pick fields with name matching regex (keep all frames)
|
||||||
filterFrames = 'filterFrames', // Pick some frames (keep all fields)
|
filterFrames = 'filterFrames', // Pick some frames (keep all fields)
|
||||||
|
filterByRefId = 'filterByRefId', // Pick some frames by RefId
|
||||||
noop = 'noop', // Does nothing to the dataframe
|
noop = 'noop', // Does nothing to the dataframe
|
||||||
}
|
}
|
||||||
|
@ -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<FilterFramesByRefIdTransformerOptions> {}
|
||||||
|
|
||||||
|
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<RefIdInfo> = {};
|
||||||
|
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 (
|
||||||
|
<>
|
||||||
|
<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'};
|
||||||
|
`}
|
||||||
|
>
|
||||||
|
<FilterPill
|
||||||
|
onClick={() => {
|
||||||
|
this.onFieldToggle(o.refId);
|
||||||
|
}}
|
||||||
|
label={label}
|
||||||
|
selected={selected.indexOf(o.refId) > -1}
|
||||||
|
/>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.colors.blueLight : theme.colors.blueShade};
|
||||||
|
border-radius: 16px;
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
`}
|
||||||
|
onClick={onClick}
|
||||||
|
>
|
||||||
|
{selected && (
|
||||||
|
<i
|
||||||
|
className={cx(
|
||||||
|
'fa fa-check',
|
||||||
|
css`
|
||||||
|
margin-right: 4px;
|
||||||
|
`
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{label}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const filterFramesByRefIdTransformRegistryItem: TransformerUIRegistyItem<
|
||||||
|
FilterFramesByRefIdTransformerOptions
|
||||||
|
> = {
|
||||||
|
id: DataTransformerID.filterByRefId,
|
||||||
|
component: FilterByRefIdTransformerEditor,
|
||||||
|
transformer: transformersRegistry.get(DataTransformerID.filterByRefId),
|
||||||
|
name: 'Filter by refId',
|
||||||
|
description: 'Filter results by refId',
|
||||||
|
};
|
@ -1,8 +1,13 @@
|
|||||||
import { Registry } from '@grafana/data';
|
import { Registry } from '@grafana/data';
|
||||||
import { reduceTransformRegistryItem } from './ReduceTransformerEditor';
|
import { reduceTransformRegistryItem } from './ReduceTransformerEditor';
|
||||||
import { filterFieldsByNameTransformRegistryItem } from './FilterByNameTransformerEditor';
|
import { filterFieldsByNameTransformRegistryItem } from './FilterByNameTransformerEditor';
|
||||||
|
import { filterFramesByRefIdTransformRegistryItem } from './FilterByRefIdTransformerEditor';
|
||||||
import { TransformerUIRegistyItem } from './types';
|
import { TransformerUIRegistyItem } from './types';
|
||||||
|
|
||||||
export const transformersUIRegistry = new Registry<TransformerUIRegistyItem<any>>(() => {
|
export const transformersUIRegistry = new Registry<TransformerUIRegistyItem<any>>(() => {
|
||||||
return [reduceTransformRegistryItem, filterFieldsByNameTransformRegistryItem];
|
return [
|
||||||
|
reduceTransformRegistryItem,
|
||||||
|
filterFieldsByNameTransformRegistryItem,
|
||||||
|
filterFramesByRefIdTransformRegistryItem,
|
||||||
|
];
|
||||||
});
|
});
|
||||||
|
@ -62,7 +62,7 @@ Array [
|
|||||||
],
|
],
|
||||||
"meta": undefined,
|
"meta": undefined,
|
||||||
"name": undefined,
|
"name": undefined,
|
||||||
"refId": undefined,
|
"refId": "GC",
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"fields": Array [
|
"fields": Array [
|
||||||
@ -124,7 +124,7 @@ Array [
|
|||||||
],
|
],
|
||||||
"meta": undefined,
|
"meta": undefined,
|
||||||
"name": undefined,
|
"name": undefined,
|
||||||
"refId": undefined,
|
"refId": "GB",
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
`;
|
`;
|
||||||
|
@ -9,8 +9,13 @@ export function base64StringToArrowTable(text: string): Table {
|
|||||||
return Table.from(arr);
|
return Table.from(arr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function valueOrUndefined(val?: string) {
|
||||||
|
return val ? val : undefined;
|
||||||
|
}
|
||||||
|
|
||||||
export function arrowTableToDataFrame(table: Table): DataFrame {
|
export function arrowTableToDataFrame(table: Table): DataFrame {
|
||||||
const fields: Field[] = [];
|
const fields: Field[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < table.numCols; i++) {
|
for (let i = 0; i < table.numCols; i++) {
|
||||||
const col = table.getColumnAt(i);
|
const col = table.getColumnAt(i);
|
||||||
if (col) {
|
if (col) {
|
||||||
@ -35,6 +40,7 @@ export function arrowTableToDataFrame(table: Table): DataFrame {
|
|||||||
default:
|
default:
|
||||||
console.log('UNKNOWN Type:', schema);
|
console.log('UNKNOWN Type:', schema);
|
||||||
}
|
}
|
||||||
|
// console.log(' field>', schema.metadata);
|
||||||
|
|
||||||
fields.push({
|
fields.push({
|
||||||
name: col.name,
|
name: col.name,
|
||||||
@ -44,9 +50,12 @@ export function arrowTableToDataFrame(table: Table): DataFrame {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const meta = table.schema.metadata;
|
||||||
return {
|
return {
|
||||||
fields,
|
fields,
|
||||||
length: table.length,
|
length: table.length,
|
||||||
|
refId: valueOrUndefined(meta.get('refId')),
|
||||||
|
name: valueOrUndefined(meta.get('name')),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,7 +37,6 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
|
|||||||
const listenToRunner = listenToPanel.getQueryRunner();
|
const listenToRunner = listenToPanel.getQueryRunner();
|
||||||
const subscription = listenToRunner.getData(false).subscribe({
|
const subscription = listenToRunner.getData(false).subscribe({
|
||||||
next: (data: PanelData) => {
|
next: (data: PanelData) => {
|
||||||
console.log('got data from other panel', data);
|
|
||||||
subscriber.next(data);
|
subscriber.next(data);
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user