mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Scene: Variables and All value support (#59635)
* Working on the all value * Support for custom allValue * Fixes * More progress * Progress * Updated * Fixed issue with multi and All value * Clarified tests
This commit is contained in:
parent
d2f9d7f39b
commit
ef46761b9a
@ -9,7 +9,7 @@ import { getGridWithRowLayoutTest } from './gridWithRow';
|
|||||||
import { getNestedScene } from './nested';
|
import { getNestedScene } from './nested';
|
||||||
import { getQueryVariableDemo } from './queryVariableDemo';
|
import { getQueryVariableDemo } from './queryVariableDemo';
|
||||||
import { getSceneWithRows } from './sceneWithRows';
|
import { getSceneWithRows } from './sceneWithRows';
|
||||||
import { getVariablesDemo } from './variablesDemo';
|
import { getVariablesDemo, getVariablesDemoWithAll } from './variablesDemo';
|
||||||
|
|
||||||
interface SceneDef {
|
interface SceneDef {
|
||||||
title: string;
|
title: string;
|
||||||
@ -27,6 +27,7 @@ export function getScenes(): SceneDef[] {
|
|||||||
{ title: 'Grid with rows and different queries and time ranges', getScene: getGridWithMultipleTimeRanges },
|
{ title: 'Grid with rows and different queries and time ranges', getScene: getGridWithMultipleTimeRanges },
|
||||||
{ title: 'Multiple grid layouts test', getScene: getMultipleGridLayoutTest },
|
{ title: 'Multiple grid layouts test', getScene: getMultipleGridLayoutTest },
|
||||||
{ title: 'Variables', getScene: getVariablesDemo },
|
{ title: 'Variables', getScene: getVariablesDemo },
|
||||||
|
{ title: 'Variables with All values', getScene: getVariablesDemoWithAll },
|
||||||
{ title: 'Query variable', getScene: getQueryVariableDemo },
|
{ title: 'Query variable', getScene: getQueryVariableDemo },
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -85,3 +85,74 @@ export function getVariablesDemo(standalone: boolean): Scene {
|
|||||||
|
|
||||||
return standalone ? new Scene(state) : new EmbeddedScene(state);
|
return standalone ? new Scene(state) : new EmbeddedScene(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getVariablesDemoWithAll(): Scene {
|
||||||
|
const scene = new Scene({
|
||||||
|
title: 'Variables with All values',
|
||||||
|
$variables: new SceneVariableSet({
|
||||||
|
variables: [
|
||||||
|
new TestVariable({
|
||||||
|
name: 'server',
|
||||||
|
query: 'A.*',
|
||||||
|
value: 'AA',
|
||||||
|
text: 'AA',
|
||||||
|
includeAll: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
delayMs: 1000,
|
||||||
|
options: [],
|
||||||
|
}),
|
||||||
|
new TestVariable({
|
||||||
|
name: 'pod',
|
||||||
|
query: 'A.$server.*',
|
||||||
|
value: [],
|
||||||
|
delayMs: 1000,
|
||||||
|
isMulti: true,
|
||||||
|
includeAll: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
text: '',
|
||||||
|
options: [],
|
||||||
|
}),
|
||||||
|
new TestVariable({
|
||||||
|
name: 'handler',
|
||||||
|
query: 'A.$server.$pod.*',
|
||||||
|
value: [],
|
||||||
|
delayMs: 1000,
|
||||||
|
includeAll: true,
|
||||||
|
defaultToAll: false,
|
||||||
|
isMulti: true,
|
||||||
|
text: '',
|
||||||
|
options: [],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
layout: new SceneFlexLayout({
|
||||||
|
direction: 'row',
|
||||||
|
children: [
|
||||||
|
new SceneFlexLayout({
|
||||||
|
children: [
|
||||||
|
new VizPanel({
|
||||||
|
pluginId: 'timeseries',
|
||||||
|
title: 'handler: $handler',
|
||||||
|
$data: getQueryRunnerWithRandomWalkQuery({
|
||||||
|
alias: 'handler: $handler',
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
new SceneCanvasText({
|
||||||
|
size: { width: '40%' },
|
||||||
|
text: 'server: ${server} pod:${pod}',
|
||||||
|
fontSize: 20,
|
||||||
|
align: 'center',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
$timeRange: new SceneTimeRange(),
|
||||||
|
actions: [new SceneTimePicker({})],
|
||||||
|
subMenu: new SceneSubMenu({
|
||||||
|
children: [new VariableValueSelectors({})],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
return scene;
|
||||||
|
}
|
||||||
|
@ -1,34 +1,13 @@
|
|||||||
import { isArray } from 'lodash';
|
import { isArray } from 'lodash';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
|
||||||
import { Select, MultiSelect } from '@grafana/ui';
|
import { MultiSelect, Select } from '@grafana/ui';
|
||||||
|
|
||||||
import { SceneComponentProps } from '../../core/types';
|
import { SceneComponentProps } from '../../core/types';
|
||||||
import { MultiValueVariable } from '../variants/MultiValueVariable';
|
import { MultiValueVariable } from '../variants/MultiValueVariable';
|
||||||
|
|
||||||
export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVariable>) {
|
export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVariable>) {
|
||||||
const { value, key, loading, isMulti, options } = model.useState();
|
const { value, key, loading } = model.useState();
|
||||||
|
|
||||||
if (isMulti) {
|
|
||||||
return (
|
|
||||||
<MultiSelect
|
|
||||||
id={key}
|
|
||||||
placeholder="Select value"
|
|
||||||
width="auto"
|
|
||||||
value={isArray(value) ? value : [value]}
|
|
||||||
allowCustomValue
|
|
||||||
isLoading={loading}
|
|
||||||
options={options}
|
|
||||||
closeMenuOnSelect={false}
|
|
||||||
onChange={(newValue) => {
|
|
||||||
model.changeValueTo(
|
|
||||||
newValue.map((v) => v.value!),
|
|
||||||
newValue.map((v) => v.label!)
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Select
|
<Select
|
||||||
@ -37,11 +16,47 @@ export function VariableValueSelect({ model }: SceneComponentProps<MultiValueVar
|
|||||||
width="auto"
|
width="auto"
|
||||||
value={value}
|
value={value}
|
||||||
allowCustomValue
|
allowCustomValue
|
||||||
|
tabSelectsValue={false}
|
||||||
isLoading={loading}
|
isLoading={loading}
|
||||||
options={options}
|
options={model.getOptionsForSelect()}
|
||||||
onChange={(newValue) => {
|
onChange={(newValue) => {
|
||||||
model.changeValueTo(newValue.value!, newValue.label!);
|
model.changeValueTo(newValue.value!, newValue.label!);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function VariableValueSelectMulti({ model }: SceneComponentProps<MultiValueVariable>) {
|
||||||
|
const { value, key, loading } = model.useState();
|
||||||
|
const arrayValue = isArray(value) ? value : [value];
|
||||||
|
|
||||||
|
return (
|
||||||
|
<MultiSelect
|
||||||
|
id={key}
|
||||||
|
placeholder="Select value"
|
||||||
|
width="auto"
|
||||||
|
value={arrayValue}
|
||||||
|
tabSelectsValue={false}
|
||||||
|
allowCustomValue
|
||||||
|
isLoading={loading}
|
||||||
|
options={model.getOptionsForSelect()}
|
||||||
|
closeMenuOnSelect={false}
|
||||||
|
isClearable={true}
|
||||||
|
onOpenMenu={() => {}}
|
||||||
|
onChange={(newValue) => {
|
||||||
|
model.changeValueTo(
|
||||||
|
newValue.map((v) => v.value!),
|
||||||
|
newValue.map((v) => v.label!)
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function renderSelectForVariable(model: MultiValueVariable) {
|
||||||
|
if (model.state.isMulti) {
|
||||||
|
return <VariableValueSelectMulti model={model} />;
|
||||||
|
} else {
|
||||||
|
return <VariableValueSelect model={model} />;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -326,6 +326,10 @@ function luceneEscape(value: string) {
|
|||||||
* unicode handling uses UTF-8 as in ECMA-262.
|
* unicode handling uses UTF-8 as in ECMA-262.
|
||||||
*/
|
*/
|
||||||
function encodeURIComponentStrict(str: VariableValueSingle) {
|
function encodeURIComponentStrict(str: VariableValueSingle) {
|
||||||
|
if (typeof str === 'object') {
|
||||||
|
str = String(str);
|
||||||
|
}
|
||||||
|
|
||||||
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
|
return encodeURIComponent(str).replace(/[!'()*]/g, (c) => {
|
||||||
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
|
return '%' + c.charCodeAt(0).toString(16).toUpperCase();
|
||||||
});
|
});
|
||||||
|
@ -1,3 +1,5 @@
|
|||||||
|
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||||
|
|
||||||
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
import { SceneObjectBase } from '../../core/SceneObjectBase';
|
||||||
import { SceneObjectStatePlain } from '../../core/types';
|
import { SceneObjectStatePlain } from '../../core/types';
|
||||||
import { SceneVariableSet } from '../sets/SceneVariableSet';
|
import { SceneVariableSet } from '../sets/SceneVariableSet';
|
||||||
@ -45,6 +47,25 @@ describe('sceneInterpolator', () => {
|
|||||||
expect(sceneInterpolator(scene.state.nested!, '${atRootOnly}')).toBe('RootValue');
|
expect(sceneInterpolator(scene.state.nested!, '${atRootOnly}')).toBe('RootValue');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('Given a variable with allValue', () => {
|
||||||
|
it('Should not escape it', () => {
|
||||||
|
const scene = new TestScene({
|
||||||
|
$variables: new SceneVariableSet({
|
||||||
|
variables: [
|
||||||
|
new TestVariable({
|
||||||
|
name: 'test',
|
||||||
|
value: ALL_VARIABLE_VALUE,
|
||||||
|
text: ALL_VARIABLE_TEXT,
|
||||||
|
allValue: '.*',
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
}),
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(sceneInterpolator(scene, '${test:regex}')).toBe('.*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('Given an expression with fieldPath', () => {
|
describe('Given an expression with fieldPath', () => {
|
||||||
it('Should interpolate correctly', () => {
|
it('Should interpolate correctly', () => {
|
||||||
const scene = new TestScene({
|
const scene = new TestScene({
|
||||||
|
@ -87,6 +87,12 @@ function formatValue(
|
|||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Special handling for custom values that should not be formatted / escaped
|
||||||
|
// This is used by the custom allValue that usually contain wildcards and therefore should not be escaped
|
||||||
|
if (typeof value === 'object' && 'isCustomValue' in value && formatNameOrFn !== FormatRegistryID.text) {
|
||||||
|
return value.toString();
|
||||||
|
}
|
||||||
|
|
||||||
// if (isAdHoc(variable) && format !== FormatRegistryID.queryParam) {
|
// if (isAdHoc(variable) && format !== FormatRegistryID.queryParam) {
|
||||||
// return '';
|
// return '';
|
||||||
// }
|
// }
|
||||||
|
@ -38,12 +38,21 @@ export interface SceneVariable<TState extends SceneVariableState = SceneVariable
|
|||||||
|
|
||||||
export type VariableValue = VariableValueSingle | VariableValueSingle[];
|
export type VariableValue = VariableValueSingle | VariableValueSingle[];
|
||||||
|
|
||||||
export type VariableValueSingle = string | boolean | number;
|
export type VariableValueSingle = string | boolean | number | VariableValueCustom;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is for edge case values like the custom "allValue" that should not be escaped/formatted like other values.
|
||||||
|
* The custom all value usually contain wildcards that should not be escaped.
|
||||||
|
*/
|
||||||
|
export interface VariableValueCustom {
|
||||||
|
isCustomValue: true;
|
||||||
|
toString(): string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface ValidateAndUpdateResult {}
|
export interface ValidateAndUpdateResult {}
|
||||||
export interface VariableValueOption {
|
export interface VariableValueOption {
|
||||||
label: string;
|
label: string;
|
||||||
value: string;
|
value: VariableValueSingle;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SceneVariableSetState extends SceneObjectStatePlain {
|
export interface SceneVariableSetState extends SceneObjectStatePlain {
|
||||||
|
@ -1,9 +1,8 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
import { SceneComponentProps } from '../../core/types';
|
import { SceneComponentProps } from '../../core/types';
|
||||||
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
||||||
import { VariableValueSelect } from '../components/VariableValueSelect';
|
import { renderSelectForVariable } from '../components/VariableValueSelect';
|
||||||
import { VariableValueOption } from '../types';
|
import { VariableValueOption } from '../types';
|
||||||
|
|
||||||
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
||||||
@ -43,14 +42,9 @@ export class CustomVariable extends MultiValueVariable<CustomVariableState> {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return of(options);
|
return of(options);
|
||||||
|
|
||||||
// TODO: Support 'All'
|
|
||||||
//if (this.state.includeAll) {
|
|
||||||
// options.unshift({ text: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE, selected: false });
|
|
||||||
//}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
||||||
return <VariableValueSelect model={model} />;
|
return renderSelectForVariable(model);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Observable, of } from 'rxjs';
|
import { Observable, of } from 'rxjs';
|
||||||
|
|
||||||
import { stringToJsRegex, DataSourceInstanceSettings } from '@grafana/data';
|
import { stringToJsRegex, DataSourceInstanceSettings } from '@grafana/data';
|
||||||
@ -7,7 +6,7 @@ import { getDataSourceSrv } from '@grafana/runtime';
|
|||||||
import { sceneGraph } from '../../core/sceneGraph';
|
import { sceneGraph } from '../../core/sceneGraph';
|
||||||
import { SceneComponentProps } from '../../core/types';
|
import { SceneComponentProps } from '../../core/types';
|
||||||
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
||||||
import { VariableValueSelect } from '../components/VariableValueSelect';
|
import { renderSelectForVariable } from '../components/VariableValueSelect';
|
||||||
import { VariableValueOption } from '../types';
|
import { VariableValueOption } from '../types';
|
||||||
|
|
||||||
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
||||||
@ -70,11 +69,6 @@ export class DataSourceVariable extends MultiValueVariable<DataSourceVariableSta
|
|||||||
options.push({ label: 'No data sources found', value: '' });
|
options.push({ label: 'No data sources found', value: '' });
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Add support for include All
|
|
||||||
// if (instanceState.includeAll) {
|
|
||||||
// options.unshift({ label: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE });
|
|
||||||
//}
|
|
||||||
|
|
||||||
return of(options);
|
return of(options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -83,7 +77,7 @@ export class DataSourceVariable extends MultiValueVariable<DataSourceVariableSta
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
||||||
return <VariableValueSelect model={model} />;
|
return renderSelectForVariable(model);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import { lastValueFrom, Observable, of } from 'rxjs';
|
|||||||
|
|
||||||
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
import { ALL_VARIABLE_TEXT, ALL_VARIABLE_VALUE } from 'app/features/variables/constants';
|
||||||
|
|
||||||
import { SceneVariableValueChangedEvent, VariableValueOption } from '../types';
|
import { SceneVariableValueChangedEvent, VariableValueCustom, VariableValueOption } from '../types';
|
||||||
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from '../variants/MultiValueVariable';
|
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from '../variants/MultiValueVariable';
|
||||||
|
|
||||||
export interface ExampleVariableState extends MultiValueVariableState {
|
export interface ExampleVariableState extends MultiValueVariableState {
|
||||||
@ -46,6 +46,22 @@ describe('MultiValueVariable', () => {
|
|||||||
expect(variable.state.text).toBe('B');
|
expect(variable.state.text).toBe('B');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should pick All value when defaultToAll is true', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [],
|
||||||
|
optionsToReturn: [
|
||||||
|
{ label: 'B', value: 'B' },
|
||||||
|
{ label: 'C', value: 'C' },
|
||||||
|
],
|
||||||
|
defaultToAll: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
await lastValueFrom(variable.validateAndUpdate());
|
||||||
|
|
||||||
|
expect(variable.state.value).toBe(ALL_VARIABLE_VALUE);
|
||||||
|
});
|
||||||
|
|
||||||
it('Should keep current value if current value is valid', async () => {
|
it('Should keep current value if current value is valid', async () => {
|
||||||
const variable = new ExampleVariable({
|
const variable = new ExampleVariable({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
@ -99,6 +115,26 @@ describe('MultiValueVariable', () => {
|
|||||||
expect(variable.state.text).toEqual(['A']);
|
expect(variable.state.text).toEqual(['A']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('Should select All option if none of the current values are valid', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [],
|
||||||
|
isMulti: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
optionsToReturn: [
|
||||||
|
{ label: 'A', value: 'A' },
|
||||||
|
{ label: 'C', value: 'C' },
|
||||||
|
],
|
||||||
|
value: ['D', 'E'],
|
||||||
|
text: ['E', 'E'],
|
||||||
|
});
|
||||||
|
|
||||||
|
await lastValueFrom(variable.validateAndUpdate());
|
||||||
|
|
||||||
|
expect(variable.state.value).toEqual([ALL_VARIABLE_VALUE]);
|
||||||
|
expect(variable.state.text).toEqual([ALL_VARIABLE_TEXT]);
|
||||||
|
});
|
||||||
|
|
||||||
it('Should handle $__all value and send change event even when value is still $__all', async () => {
|
it('Should handle $__all value and send change event even when value is still $__all', async () => {
|
||||||
const variable = new ExampleVariable({
|
const variable = new ExampleVariable({
|
||||||
name: 'test',
|
name: 'test',
|
||||||
@ -123,6 +159,60 @@ describe('MultiValueVariable', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('changeValueTo', () => {
|
||||||
|
it('Should set default empty state to all value if defaultToAll multi', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [],
|
||||||
|
isMulti: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
optionsToReturn: [],
|
||||||
|
value: ['1'],
|
||||||
|
text: ['A'],
|
||||||
|
});
|
||||||
|
|
||||||
|
variable.changeValueTo([]);
|
||||||
|
|
||||||
|
expect(variable.state.value).toEqual([ALL_VARIABLE_VALUE]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('When changing to all value', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [
|
||||||
|
{ label: 'A', value: '1' },
|
||||||
|
{ label: 'B', value: '2' },
|
||||||
|
],
|
||||||
|
isMulti: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
optionsToReturn: [],
|
||||||
|
value: ['1'],
|
||||||
|
text: ['A'],
|
||||||
|
});
|
||||||
|
|
||||||
|
variable.changeValueTo(['1', ALL_VARIABLE_VALUE]);
|
||||||
|
// Should clear the value so only all value is set
|
||||||
|
expect(variable.state.value).toEqual([ALL_VARIABLE_VALUE]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('When changing from all value', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [
|
||||||
|
{ label: 'A', value: '1' },
|
||||||
|
{ label: 'B', value: '2' },
|
||||||
|
],
|
||||||
|
isMulti: true,
|
||||||
|
defaultToAll: true,
|
||||||
|
optionsToReturn: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
variable.changeValueTo([ALL_VARIABLE_VALUE, '1']);
|
||||||
|
// Should remove the all value so only the new value is present
|
||||||
|
expect(variable.state.value).toEqual(['1']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('getValue and getValueText', () => {
|
describe('getValue and getValueText', () => {
|
||||||
it('GetValueText should return text', async () => {
|
it('GetValueText should return text', async () => {
|
||||||
const variable = new ExampleVariable({
|
const variable = new ExampleVariable({
|
||||||
@ -163,6 +253,63 @@ describe('MultiValueVariable', () => {
|
|||||||
|
|
||||||
expect(variable.getValue()).toEqual(['1', '2']);
|
expect(variable.getValue()).toEqual(['1', '2']);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('GetValue should return allValue when value is $__all', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [],
|
||||||
|
optionsToReturn: [],
|
||||||
|
value: ALL_VARIABLE_VALUE,
|
||||||
|
allValue: '.*',
|
||||||
|
text: 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
const value = variable.getValue() as VariableValueCustom;
|
||||||
|
expect(value.isCustomValue).toBe(true);
|
||||||
|
expect(value.toString()).toBe('.*');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getOptionsForSelect', () => {
|
||||||
|
it('Should return options', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [{ label: 'A', value: '1' }],
|
||||||
|
optionsToReturn: [],
|
||||||
|
value: '1',
|
||||||
|
text: 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(variable.getOptionsForSelect()).toEqual([{ label: 'A', value: '1' }]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should return include All option when includeAll is true', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [{ label: 'A', value: '1' }],
|
||||||
|
optionsToReturn: [],
|
||||||
|
includeAll: true,
|
||||||
|
value: '1',
|
||||||
|
text: 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(variable.getOptionsForSelect()).toEqual([
|
||||||
|
{ label: ALL_VARIABLE_TEXT, value: ALL_VARIABLE_VALUE },
|
||||||
|
{ label: 'A', value: '1' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('Should add current value if not found', async () => {
|
||||||
|
const variable = new ExampleVariable({
|
||||||
|
name: 'test',
|
||||||
|
options: [],
|
||||||
|
optionsToReturn: [],
|
||||||
|
value: '1',
|
||||||
|
text: 'A',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(variable.getOptionsForSelect()).toEqual([{ label: 'A', value: '1' }]);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Url syncing', () => {
|
describe('Url syncing', () => {
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
ValidateAndUpdateResult,
|
ValidateAndUpdateResult,
|
||||||
VariableValue,
|
VariableValue,
|
||||||
VariableValueOption,
|
VariableValueOption,
|
||||||
|
VariableValueCustom,
|
||||||
VariableValueSingle,
|
VariableValueSingle,
|
||||||
} from '../types';
|
} from '../types';
|
||||||
|
|
||||||
@ -20,6 +21,10 @@ export interface MultiValueVariableState extends SceneVariableState {
|
|||||||
text: VariableValue; // old current.value
|
text: VariableValue; // old current.value
|
||||||
options: VariableValueOption[];
|
options: VariableValueOption[];
|
||||||
isMulti?: boolean;
|
isMulti?: boolean;
|
||||||
|
includeAll?: boolean;
|
||||||
|
defaultToAll?: boolean;
|
||||||
|
allValue?: string;
|
||||||
|
placeholder?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface VariableGetOptionsArgs {
|
export interface VariableGetOptionsArgs {
|
||||||
@ -71,8 +76,9 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
|||||||
|
|
||||||
// If no valid values pick the first option
|
// If no valid values pick the first option
|
||||||
if (validValues.length === 0) {
|
if (validValues.length === 0) {
|
||||||
stateUpdate.value = [options[0].value];
|
const defaultState = this.getDefaultMultiState(options);
|
||||||
stateUpdate.text = [options[0].label];
|
stateUpdate.value = defaultState.value;
|
||||||
|
stateUpdate.text = defaultState.text;
|
||||||
}
|
}
|
||||||
// We have valid values, if it's different from current valid values update current values
|
// We have valid values, if it's different from current valid values update current values
|
||||||
else if (!isEqual(validValues, this.state.value)) {
|
else if (!isEqual(validValues, this.state.value)) {
|
||||||
@ -84,9 +90,14 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
|||||||
// Single valued variable
|
// Single valued variable
|
||||||
const foundCurrent = options.find((x) => x.value === this.state.value);
|
const foundCurrent = options.find((x) => x.value === this.state.value);
|
||||||
if (!foundCurrent) {
|
if (!foundCurrent) {
|
||||||
// Current value is not valid. Set to first of the available options
|
if (this.state.defaultToAll) {
|
||||||
stateUpdate.value = options[0].value;
|
stateUpdate.value = ALL_VARIABLE_VALUE;
|
||||||
stateUpdate.text = options[0].label;
|
stateUpdate.text = ALL_VARIABLE_TEXT;
|
||||||
|
} else {
|
||||||
|
// Current value is not valid. Set to first of the available options
|
||||||
|
stateUpdate.value = options[0].value;
|
||||||
|
stateUpdate.text = options[0].label;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +115,10 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
|||||||
|
|
||||||
public getValue(): VariableValue {
|
public getValue(): VariableValue {
|
||||||
if (this.hasAllValue()) {
|
if (this.hasAllValue()) {
|
||||||
|
if (this.state.allValue) {
|
||||||
|
return new CustomAllValue(this.state.allValue);
|
||||||
|
}
|
||||||
|
|
||||||
return this.state.options.map((x) => x.value);
|
return this.state.options.map((x) => x.value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -127,22 +142,55 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
|||||||
return value === ALL_VARIABLE_VALUE || (Array.isArray(value) && value[0] === ALL_VARIABLE_VALUE);
|
return value === ALL_VARIABLE_VALUE || (Array.isArray(value) && value[0] === ALL_VARIABLE_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private getDefaultMultiState(options: VariableValueOption[]) {
|
||||||
|
if (this.state.defaultToAll) {
|
||||||
|
return { value: [ALL_VARIABLE_VALUE], text: [ALL_VARIABLE_TEXT] };
|
||||||
|
} else {
|
||||||
|
return { value: [options[0].value], text: [options[0].label] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the value and publish SceneVariableValueChangedEvent event
|
* Change the value and publish SceneVariableValueChangedEvent event
|
||||||
*/
|
*/
|
||||||
public changeValueTo(value: VariableValue, text?: VariableValue) {
|
public changeValueTo(value: VariableValue, text?: VariableValue) {
|
||||||
if (value !== this.state.value || text !== this.state.text) {
|
// Igore if there is no change
|
||||||
if (!text) {
|
if (value === this.state.value && text === this.state.text) {
|
||||||
if (Array.isArray(value)) {
|
return;
|
||||||
text = value.map((v) => this.findLabelTextForValue(v));
|
}
|
||||||
} else {
|
|
||||||
text = this.findLabelTextForValue(value);
|
if (!text) {
|
||||||
}
|
if (Array.isArray(value)) {
|
||||||
|
text = value.map((v) => this.findLabelTextForValue(v));
|
||||||
|
} else {
|
||||||
|
text = this.findLabelTextForValue(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
// If we are a multi valued variable is cleared (empty array) we need to set the default empty state
|
||||||
|
if (value.length === 0) {
|
||||||
|
const state = this.getDefaultMultiState(this.state.options);
|
||||||
|
value = state.value;
|
||||||
|
text = state.text;
|
||||||
}
|
}
|
||||||
|
|
||||||
this.setStateHelper({ value, text, loading: false });
|
// If last value is the All value then replace all with it
|
||||||
this.publishEvent(new SceneVariableValueChangedEvent(this), true);
|
if (value[value.length - 1] === ALL_VARIABLE_VALUE) {
|
||||||
|
value = [ALL_VARIABLE_VALUE];
|
||||||
|
text = [ALL_VARIABLE_TEXT];
|
||||||
|
}
|
||||||
|
// If the first value is the ALL value and we have other values, then remove the All value
|
||||||
|
else if (value[0] === ALL_VARIABLE_VALUE && value.length > 1) {
|
||||||
|
value.shift();
|
||||||
|
if (Array.isArray(text)) {
|
||||||
|
text.shift();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.setStateHelper({ value, text, loading: false });
|
||||||
|
this.publishEvent(new SceneVariableValueChangedEvent(this), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private findLabelTextForValue(value: VariableValueSingle): VariableValueSingle {
|
private findLabelTextForValue(value: VariableValueSingle): VariableValueSingle {
|
||||||
@ -166,6 +214,36 @@ export abstract class MultiValueVariable<TState extends MultiValueVariableState
|
|||||||
const test: SceneObject<MultiValueVariableState> = this;
|
const test: SceneObject<MultiValueVariableState> = this;
|
||||||
test.setState(state);
|
test.setState(state);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public getOptionsForSelect(): VariableValueOption[] {
|
||||||
|
let options = this.state.options;
|
||||||
|
|
||||||
|
if (this.state.includeAll) {
|
||||||
|
options = [{ value: ALL_VARIABLE_VALUE, label: ALL_VARIABLE_TEXT }, ...options];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Array.isArray(this.state.value)) {
|
||||||
|
const current = options.find((x) => x.value === this.state.value);
|
||||||
|
if (!current) {
|
||||||
|
options = [{ value: this.state.value, label: String(this.state.text) }, ...options];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The custom allValue needs a special wrapping / handling to make it not be formatted / escaped like normal values
|
||||||
|
*/
|
||||||
|
class CustomAllValue implements VariableValueCustom {
|
||||||
|
public isCustomValue: true = true;
|
||||||
|
|
||||||
|
public constructor(private _value: string) {}
|
||||||
|
|
||||||
|
public toString() {
|
||||||
|
return this._value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class MultiValueUrlSyncHandler<TState extends MultiValueVariableState = MultiValueVariableState>
|
export class MultiValueUrlSyncHandler<TState extends MultiValueVariableState = MultiValueVariableState>
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
import React from 'react';
|
|
||||||
import { Observable, Subject } from 'rxjs';
|
import { Observable, Subject } from 'rxjs';
|
||||||
|
|
||||||
import { queryMetricTree } from 'app/plugins/datasource/testdata/metricTree';
|
import { queryMetricTree } from 'app/plugins/datasource/testdata/metricTree';
|
||||||
@ -6,7 +5,7 @@ import { queryMetricTree } from 'app/plugins/datasource/testdata/metricTree';
|
|||||||
import { sceneGraph } from '../../core/sceneGraph';
|
import { sceneGraph } from '../../core/sceneGraph';
|
||||||
import { SceneComponentProps } from '../../core/types';
|
import { SceneComponentProps } from '../../core/types';
|
||||||
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
import { VariableDependencyConfig } from '../VariableDependencyConfig';
|
||||||
import { VariableValueSelect } from '../components/VariableValueSelect';
|
import { renderSelectForVariable } from '../components/VariableValueSelect';
|
||||||
import { VariableValueOption } from '../types';
|
import { VariableValueOption } from '../types';
|
||||||
|
|
||||||
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
import { MultiValueVariable, MultiValueVariableState, VariableGetOptionsArgs } from './MultiValueVariable';
|
||||||
@ -89,6 +88,6 @@ export class TestVariable extends MultiValueVariable<TestVariableState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
public static Component = ({ model }: SceneComponentProps<MultiValueVariable>) => {
|
||||||
return <VariableValueSelect model={model} />;
|
return renderSelectForVariable(model);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user