diff --git a/packages/grafana-data/src/types/variables.ts b/packages/grafana-data/src/types/variables.ts index 43978fe69a8..8fbc7507db1 100644 --- a/packages/grafana-data/src/types/variables.ts +++ b/packages/grafana-data/src/types/variables.ts @@ -76,6 +76,14 @@ export abstract class CustomVariableSupport< } abstract editor: ComponentType>; + + /** + * 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): Observable; } diff --git a/public/app/features/variables/query/VariableQueryRunner.ts b/public/app/features/variables/query/VariableQueryRunner.ts index 0e613a04bc3..0c44da90e83 100644 --- a/public/app/features/variables/query/VariableQueryRunner.ts +++ b/public/app/features/variables/query/VariableQueryRunner.ts @@ -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( diff --git a/public/app/features/variables/query/operators.test.ts b/public/app/features/variables/query/operators.test.ts index a76cae269b2..0ead4dc4a96 100644 --- a/public/app/features/variables/query/operators.test.ts +++ b/public/app/features/variables/query/operators.test.ts @@ -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.") + ); }); }); }); diff --git a/public/app/features/variables/query/operators.ts b/public/app/features/variables/query/operators.ts index 16ccb1a73fd..cd390b7c95a 100644 --- a/public/app/features/variables/query/operators.ts +++ b/public/app/features/variables/query/operators.ts @@ -18,87 +18,86 @@ import { getTemplatedRegex, toKeyedVariableIdentifier, toVariablePayload } from import { updateVariableOptions } from './reducer'; -export function toMetricFindValues(): OperatorFunction { - return (source) => - source.pipe( - map((panelData) => { - const frames = panelData.series; - if (!frames || !frames.length) { - return []; - } +export function toMetricFindValuesOperator(): OperatorFunction { + 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: {