mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 02:10:45 -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 './fieldReducer';
|
||||
export { FilterFieldsByNameTransformerOptions } from './transformers/filterByName';
|
||||
export { FilterFramesByRefIdTransformerOptions } from './transformers/filterByRefId';
|
||||
export { ReduceTransformerOptions } from './transformers/reduce';
|
||||
|
@ -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<string> = {
|
||||
@ -10,8 +11,9 @@ const refIdMacher: FrameMatcherInfo<string> = {
|
||||
defaultOptions: 'A',
|
||||
|
||||
get: (pattern: string) => {
|
||||
const regex = stringToJsRegex(pattern);
|
||||
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 { 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,
|
||||
]);
|
||||
|
@ -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)
|
||||
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
|
||||
}
|
||||
|
@ -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 { reduceTransformRegistryItem } from './ReduceTransformerEditor';
|
||||
import { filterFieldsByNameTransformRegistryItem } from './FilterByNameTransformerEditor';
|
||||
import { filterFramesByRefIdTransformRegistryItem } from './FilterByRefIdTransformerEditor';
|
||||
import { TransformerUIRegistyItem } from './types';
|
||||
|
||||
export const transformersUIRegistry = new Registry<TransformerUIRegistyItem<any>>(() => {
|
||||
return [reduceTransformRegistryItem, filterFieldsByNameTransformRegistryItem];
|
||||
return [
|
||||
reduceTransformRegistryItem,
|
||||
filterFieldsByNameTransformRegistryItem,
|
||||
filterFramesByRefIdTransformRegistryItem,
|
||||
];
|
||||
});
|
||||
|
@ -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",
|
||||
},
|
||||
]
|
||||
`;
|
||||
|
@ -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')),
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -37,7 +37,6 @@ export function runSharedRequest(options: QueryRunnerOptions): Observable<PanelD
|
||||
const listenToRunner = listenToPanel.getQueryRunner();
|
||||
const subscription = listenToRunner.getData(false).subscribe({
|
||||
next: (data: PanelData) => {
|
||||
console.log('got data from other panel', data);
|
||||
subscriber.next(data);
|
||||
},
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user