Template Variables: Fix conversion from non standard data to dataFrame (#73486)

This commit is contained in:
Andrej Ocenas 2023-08-30 10:01:32 +02:00 committed by GitHub
parent 61fd34ca2b
commit dc6675cade
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 90 additions and 88 deletions

View File

@ -76,6 +76,14 @@ export abstract class CustomVariableSupport<
}
abstract editor: ComponentType<QueryEditorProps<DSType, TQuery, TOptions, VariableQuery>>;
/**
* This can return data in various formats as DataQueryResponse allows multiple types. In general though the
* assumption is that there will be a string Field or value in an Array of objects that will be taken as the possible
* variable values. You can also use this type directly MetricFindValue or just use text/value/expendable fields/keys
* in the response.
* @param request
*/
abstract query(request: DataQueryRequest<VariableQuery>): Observable<DataQueryResponse>;
}

View File

@ -22,7 +22,7 @@ import { KeyedVariableIdentifier } from '../state/types';
import { QueryVariableModel, VariableRefresh } from '../types';
import { getTemplatedRegex } from '../utils';
import { toMetricFindValues, updateOptionsState, validateVariableSelection } from './operators';
import { toMetricFindValuesOperator, updateOptionsState, validateVariableSelection } from './operators';
import { QueryRunners } from './queryRunners';
interface UpdateOptionsArgs {
@ -134,7 +134,7 @@ export class VariableQueryRunner {
return of(data);
}),
toMetricFindValues(),
toMetricFindValuesOperator(),
updateOptionsState({ variable, dispatch, getTemplatedRegexFunc }),
validateVariableSelection({ variable, dispatch, searchFilter }),
takeUntil(

View File

@ -80,8 +80,7 @@ describe('operators', () => {
],
});
// it.each wouldn't work here as we need the done callback
[
it.each([
{ series: null, expected: [] },
{ series: undefined, expected: [] },
{ series: [], expected: [] },
@ -127,32 +126,28 @@ describe('operators', () => {
{ text: 'C', value: 'C', expandable: true },
],
},
].map((scenario) => {
it(`when called with series:${JSON.stringify(scenario.series, null, 0)}`, async () => {
const { series, expected } = scenario;
const panelData = { series } as PanelData;
const observable = of(panelData).pipe(toMetricFindValues());
await expect(observable).toEmitValuesWith((received) => {
const value = received[0];
expect(value).toEqual(expected);
});
});
{
series: [[{ id: 'foo' }, { id: 'bar' }]],
expected: [
{ text: 'foo', value: 'foo' },
{ text: 'bar', value: 'bar' },
],
},
])('%# when called with: %j', ({ series, expected }) => {
const panelData = { series } as PanelData;
expect(toMetricFindValues(panelData)).toEqual(expected);
});
describe('when called without metric find values and string fields', () => {
it('then the observable throws', async () => {
it('then the observable throws', () => {
const frameWithTimeField = toDataFrame({
fields: [{ name: 'time', type: FieldType.time, values: [1, 2, 3] }],
});
const panelData = { series: [frameWithTimeField] } as PanelData;
const observable = of(panelData).pipe(toMetricFindValues());
await expect(observable).toEmitValuesWith((received) => {
const value = received[0];
expect(value).toEqual(new Error("Couldn't find any field of type string in the results."));
});
expect(() => toMetricFindValues(panelData)).toThrow(
new Error("Couldn't find any field of type string in the results.")
);
});
});
});

View File

@ -18,87 +18,86 @@ import { getTemplatedRegex, toKeyedVariableIdentifier, toVariablePayload } from
import { updateVariableOptions } from './reducer';
export function toMetricFindValues(): OperatorFunction<PanelData, MetricFindValue[]> {
return (source) =>
source.pipe(
map((panelData) => {
const frames = panelData.series;
if (!frames || !frames.length) {
return [];
}
export function toMetricFindValuesOperator(): OperatorFunction<PanelData, MetricFindValue[]> {
return (source) => source.pipe(map(toMetricFindValues));
}
if (areMetricFindValues(frames)) {
return frames;
}
export function toMetricFindValues(panelData: PanelData): MetricFindValue[] {
const frames = panelData.series;
if (!frames || !frames.length) {
return [];
}
const processedDataFrames = getProcessedDataFrames(frames);
const metrics: MetricFindValue[] = [];
if (areMetricFindValues(frames)) {
return frames;
}
let valueIndex = -1;
let textIndex = -1;
let stringIndex = -1;
let expandableIndex = -1;
const processedDataFrames = getProcessedDataFrames(frames);
const metrics: MetricFindValue[] = [];
for (const frame of processedDataFrames) {
for (let index = 0; index < frame.fields.length; index++) {
const field = frame.fields[index];
const fieldName = getFieldDisplayName(field, frame, frames).toLowerCase();
let valueIndex = -1;
let textIndex = -1;
let stringIndex = -1;
let expandableIndex = -1;
if (field.type === FieldType.string && stringIndex === -1) {
stringIndex = index;
}
for (const frame of processedDataFrames) {
for (let index = 0; index < frame.fields.length; index++) {
const field = frame.fields[index];
const fieldName = getFieldDisplayName(field, frame, frames).toLowerCase();
if (fieldName === 'text' && field.type === FieldType.string && textIndex === -1) {
textIndex = index;
}
if (field.type === FieldType.string && stringIndex === -1) {
stringIndex = index;
}
if (fieldName === 'value' && field.type === FieldType.string && valueIndex === -1) {
valueIndex = index;
}
if (fieldName === 'text' && field.type === FieldType.string && textIndex === -1) {
textIndex = index;
}
if (
fieldName === 'expandable' &&
(field.type === FieldType.boolean || field.type === FieldType.number) &&
expandableIndex === -1
) {
expandableIndex = index;
}
}
}
if (fieldName === 'value' && field.type === FieldType.string && valueIndex === -1) {
valueIndex = index;
}
if (stringIndex === -1) {
throw new Error("Couldn't find any field of type string in the results.");
}
if (
fieldName === 'expandable' &&
(field.type === FieldType.boolean || field.type === FieldType.number) &&
expandableIndex === -1
) {
expandableIndex = index;
}
}
}
for (const frame of frames) {
for (let index = 0; index < frame.length; index++) {
const expandable = expandableIndex !== -1 ? frame.fields[expandableIndex].values[index] : undefined;
const string = frame.fields[stringIndex].values[index];
const text = textIndex !== -1 ? frame.fields[textIndex].values[index] : null;
const value = valueIndex !== -1 ? frame.fields[valueIndex].values[index] : null;
if (stringIndex === -1) {
throw new Error("Couldn't find any field of type string in the results.");
}
if (valueIndex === -1 && textIndex === -1) {
metrics.push({ text: string, value: string, expandable });
continue;
}
for (const frame of processedDataFrames) {
for (let index = 0; index < frame.length; index++) {
const expandable = expandableIndex !== -1 ? frame.fields[expandableIndex].values[index] : undefined;
const string = frame.fields[stringIndex].values[index];
const text = textIndex !== -1 ? frame.fields[textIndex].values[index] : null;
const value = valueIndex !== -1 ? frame.fields[valueIndex].values[index] : null;
if (valueIndex === -1 && textIndex !== -1) {
metrics.push({ text, value: text, expandable });
continue;
}
if (valueIndex === -1 && textIndex === -1) {
metrics.push({ text: string, value: string, expandable });
continue;
}
if (valueIndex !== -1 && textIndex === -1) {
metrics.push({ text: value, value, expandable });
continue;
}
if (valueIndex === -1 && textIndex !== -1) {
metrics.push({ text, value: text, expandable });
continue;
}
metrics.push({ text, value, expandable });
}
}
if (valueIndex !== -1 && textIndex === -1) {
metrics.push({ text: value, value, expandable });
continue;
}
return metrics;
})
);
metrics.push({ text, value, expandable });
}
}
return metrics;
}
export function updateOptionsState(args: {