mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Transformations: Selectively apply transformation to queries (#61735)
This commit is contained in:
@@ -35,7 +35,6 @@ export enum FrameMatcherID {
|
||||
byName = 'byName',
|
||||
byRefId = 'byRefId',
|
||||
byIndex = 'byIndex',
|
||||
byLabel = 'byLabel',
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -3,10 +3,11 @@ import { FieldType } from '../types';
|
||||
import { mockTransformationsRegistry } from '../utils/tests/mockTransformationsRegistry';
|
||||
|
||||
import { ReducerID } from './fieldReducer';
|
||||
import { FrameMatcherID } from './matchers/ids';
|
||||
import { transformDataFrame } from './transformDataFrame';
|
||||
import { filterFieldsByNameTransformer } from './transformers/filterByName';
|
||||
import { DataTransformerID } from './transformers/ids';
|
||||
import { reduceTransformer } from './transformers/reduce';
|
||||
import { reduceTransformer, ReduceTransformerMode } from './transformers/reduce';
|
||||
|
||||
const seriesAWithSingleField = toDataFrame({
|
||||
name: 'A',
|
||||
@@ -73,4 +74,44 @@ describe('transformDataFrame', () => {
|
||||
expect(processed[0].fields[0].values.get(0)).toEqual('temperature');
|
||||
});
|
||||
});
|
||||
|
||||
it('Support filtering', async () => {
|
||||
const frameA = toDataFrame({
|
||||
refId: 'A',
|
||||
fields: [{ name: 'value', type: FieldType.number, values: [5, 6] }],
|
||||
});
|
||||
const frameB = toDataFrame({
|
||||
refId: 'B',
|
||||
fields: [{ name: 'value', type: FieldType.number, values: [7, 8] }],
|
||||
});
|
||||
|
||||
const cfg = [
|
||||
{
|
||||
id: DataTransformerID.reduce,
|
||||
filter: {
|
||||
id: FrameMatcherID.byRefId,
|
||||
options: 'A', // Only apply to A
|
||||
},
|
||||
options: {
|
||||
reducers: [ReducerID.first],
|
||||
mode: ReduceTransformerMode.ReduceFields,
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
// Only apply A
|
||||
await expect(transformDataFrame(cfg, [frameA, frameB])).toEmitValuesWith((received) => {
|
||||
const processed = received[0].map((v) => v.fields[0].values.toArray());
|
||||
expect(processed).toBeTruthy();
|
||||
expect(processed).toMatchObject([[5], [7, 8]]);
|
||||
});
|
||||
|
||||
// Only apply to B
|
||||
cfg[0].filter.options = 'B';
|
||||
await expect(transformDataFrame(cfg, [frameA, frameB])).toEmitValuesWith((received) => {
|
||||
const processed = received[0].map((v) => v.fields[0].values.toArray());
|
||||
expect(processed).toBeTruthy();
|
||||
expect(processed).toMatchObject([[5, 6], [7]]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { MonoTypeOperatorFunction, Observable, of } from 'rxjs';
|
||||
import { map, mergeMap } from 'rxjs/operators';
|
||||
|
||||
import { DataFrame, DataTransformContext, DataTransformerConfig } from '../types';
|
||||
import { DataFrame, DataTransformContext, DataTransformerConfig, FrameMatcher } from '../types';
|
||||
|
||||
import { getFrameMatchers } from './matchers';
|
||||
import { standardTransformersRegistry, TransformerRegistryItem } from './standardTransformersRegistry';
|
||||
|
||||
const getOperator =
|
||||
@@ -17,15 +18,30 @@ const getOperator =
|
||||
const defaultOptions = info.transformation.defaultOptions ?? {};
|
||||
const options = { ...defaultOptions, ...config.options };
|
||||
|
||||
const matcher = config.filter?.options ? getFrameMatchers(config.filter) : undefined;
|
||||
return source.pipe(
|
||||
mergeMap((before) =>
|
||||
of(before).pipe(info.transformation.operator(options, ctx), postProcessTransform(before, info))
|
||||
of(filterInput(before, matcher)).pipe(
|
||||
info.transformation.operator(options, ctx),
|
||||
postProcessTransform(before, info, matcher)
|
||||
)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
function filterInput(data: DataFrame[], matcher?: FrameMatcher) {
|
||||
if (matcher) {
|
||||
return data.filter((v) => matcher(v));
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
const postProcessTransform =
|
||||
(before: DataFrame[], info: TransformerRegistryItem<any>): MonoTypeOperatorFunction<DataFrame[]> =>
|
||||
(
|
||||
before: DataFrame[],
|
||||
info: TransformerRegistryItem<any>,
|
||||
matcher?: FrameMatcher
|
||||
): MonoTypeOperatorFunction<DataFrame[]> =>
|
||||
(source) =>
|
||||
source.pipe(
|
||||
map((after) => {
|
||||
@@ -46,6 +62,21 @@ const postProcessTransform =
|
||||
}
|
||||
}
|
||||
|
||||
// Add back the filtered out frames
|
||||
if (matcher) {
|
||||
// keep the frame order the same
|
||||
let insert = 0;
|
||||
const append = before.filter((v, idx) => {
|
||||
const keep = !matcher(v);
|
||||
if (keep && !insert) {
|
||||
insert = idx;
|
||||
}
|
||||
return keep;
|
||||
});
|
||||
if (append.length) {
|
||||
after.splice(insert, 0, ...append);
|
||||
}
|
||||
}
|
||||
return after;
|
||||
})
|
||||
);
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
export type { MatcherConfig } from '@grafana/schema';
|
||||
import { MonoTypeOperatorFunction } from 'rxjs';
|
||||
|
||||
import { MatcherConfig } from '@grafana/schema';
|
||||
|
||||
import { RegistryItemWithOptions } from '../utils/Registry';
|
||||
|
||||
import { DataFrame, Field } from './dataFrame';
|
||||
import { InterpolateFunction } from './panel';
|
||||
|
||||
/** deprecated, use it from schema */
|
||||
export type { MatcherConfig };
|
||||
|
||||
/**
|
||||
* Context passed to transformDataFrame and to each transform operator
|
||||
*/
|
||||
@@ -44,10 +48,15 @@ export interface DataTransformerConfig<TOptions = any> {
|
||||
* Unique identifier of transformer
|
||||
*/
|
||||
id: string;
|
||||
|
||||
/**
|
||||
* Disabled transformations are skipped
|
||||
*/
|
||||
disabled?: boolean;
|
||||
|
||||
/** Optional frame matcher. When missing it will be applied to all results */
|
||||
filter?: MatcherConfig;
|
||||
|
||||
/**
|
||||
* Options to be passed to the transformer
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user