QueryVariable: Add static options input (#107514)
* static options for query variable * add toggle * fix and add tests * run the hack codegen thing * more test fixes * make betterer happier * also make typecheck happy * make betterer happier * fix i18n key * tranalte static variables sort label * gen translations * update snapshot
This commit is contained in:
@@ -734,6 +734,8 @@ QueryVariableSpec: {
|
||||
allValue?: string
|
||||
placeholder?: string
|
||||
allowCustomValue: bool | *true
|
||||
staticOptions?: [...VariableOption]
|
||||
staticOptionsOrder?: "before" | "after" | "sorted"
|
||||
}
|
||||
|
||||
// Query variable kind
|
||||
|
||||
@@ -219,6 +219,10 @@ lineage: schemas: [{
|
||||
// Optional field, if you want to extract part of a series name or metric node segment.
|
||||
// Named capture groups can be used to separate the display text and value.
|
||||
regex?: string
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
staticOptionsOrder?: "before" | "after" | "sorted"
|
||||
...
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
|
||||
@@ -219,6 +219,10 @@ lineage: schemas: [{
|
||||
// Optional field, if you want to extract part of a series name or metric node segment.
|
||||
// Named capture groups can be used to separate the display text and value.
|
||||
regex?: string
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
staticOptionsOrder?: "before" | "after" | "sorted"
|
||||
...
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
|
||||
@@ -738,6 +738,8 @@ QueryVariableSpec: {
|
||||
allValue?: string
|
||||
placeholder?: string
|
||||
allowCustomValue: bool | *true
|
||||
staticOptions?: [...VariableOption]
|
||||
staticOptionsOrder?: "before" | "after" | "sorted"
|
||||
}
|
||||
|
||||
// Query variable kind
|
||||
|
||||
@@ -1214,24 +1214,26 @@ func NewDashboardQueryVariableKind() *DashboardQueryVariableKind {
|
||||
// Query variable specification
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardQueryVariableSpec struct {
|
||||
Name string `json:"name"`
|
||||
Current DashboardVariableOption `json:"current"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
Hide DashboardVariableHide `json:"hide"`
|
||||
Refresh DashboardVariableRefresh `json:"refresh"`
|
||||
SkipUrlSync bool `json:"skipUrlSync"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
|
||||
Query DashboardDataQueryKind `json:"query"`
|
||||
Regex string `json:"regex"`
|
||||
Sort DashboardVariableSort `json:"sort"`
|
||||
Definition *string `json:"definition,omitempty"`
|
||||
Options []DashboardVariableOption `json:"options"`
|
||||
Multi bool `json:"multi"`
|
||||
IncludeAll bool `json:"includeAll"`
|
||||
AllValue *string `json:"allValue,omitempty"`
|
||||
Placeholder *string `json:"placeholder,omitempty"`
|
||||
AllowCustomValue bool `json:"allowCustomValue"`
|
||||
Name string `json:"name"`
|
||||
Current DashboardVariableOption `json:"current"`
|
||||
Label *string `json:"label,omitempty"`
|
||||
Hide DashboardVariableHide `json:"hide"`
|
||||
Refresh DashboardVariableRefresh `json:"refresh"`
|
||||
SkipUrlSync bool `json:"skipUrlSync"`
|
||||
Description *string `json:"description,omitempty"`
|
||||
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
|
||||
Query DashboardDataQueryKind `json:"query"`
|
||||
Regex string `json:"regex"`
|
||||
Sort DashboardVariableSort `json:"sort"`
|
||||
Definition *string `json:"definition,omitempty"`
|
||||
Options []DashboardVariableOption `json:"options"`
|
||||
Multi bool `json:"multi"`
|
||||
IncludeAll bool `json:"includeAll"`
|
||||
AllValue *string `json:"allValue,omitempty"`
|
||||
Placeholder *string `json:"placeholder,omitempty"`
|
||||
AllowCustomValue bool `json:"allowCustomValue"`
|
||||
StaticOptions []DashboardVariableOption `json:"staticOptions,omitempty"`
|
||||
StaticOptionsOrder *DashboardQueryVariableSpecStaticOptionsOrder `json:"staticOptionsOrder,omitempty"`
|
||||
}
|
||||
|
||||
// NewDashboardQueryVariableSpec creates a new DashboardQueryVariableSpec object.
|
||||
@@ -1880,6 +1882,15 @@ const (
|
||||
DashboardTimeSettingsSpecWeekStartSunday DashboardTimeSettingsSpecWeekStart = "sunday"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardQueryVariableSpecStaticOptionsOrder string
|
||||
|
||||
const (
|
||||
DashboardQueryVariableSpecStaticOptionsOrderBefore DashboardQueryVariableSpecStaticOptionsOrder = "before"
|
||||
DashboardQueryVariableSpecStaticOptionsOrderAfter DashboardQueryVariableSpecStaticOptionsOrder = "after"
|
||||
DashboardQueryVariableSpecStaticOptionsOrderSorted DashboardQueryVariableSpecStaticOptionsOrder = "sorted"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardPanelKindOrLibraryPanelKind struct {
|
||||
PanelKind *DashboardPanelKind `json:"PanelKind,omitempty"`
|
||||
|
||||
@@ -3327,6 +3327,25 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"staticOptions": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1.DashboardVariableOption"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"staticOptionsOrder": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "current", "hide", "refresh", "skipUrlSync", "query", "regex", "sort", "options", "multi", "includeAll", "allowCustomValue"},
|
||||
},
|
||||
|
||||
@@ -20,6 +20,7 @@ API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardQueryGroupSpec,Queries
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardQueryGroupSpec,Transformations
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardQueryVariableSpec,Options
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardQueryVariableSpec,StaticOptions
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardRowsLayoutSpec,Rows
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardSpec,Annotations
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v2alpha1,DashboardSpec,Links
|
||||
|
||||
@@ -215,6 +215,10 @@ lineage: schemas: [{
|
||||
// Optional field, if you want to extract part of a series name or metric node segment.
|
||||
// Named capture groups can be used to separate the display text and value.
|
||||
regex?: string
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
staticOptionsOrder?: "before" | "after" | "sorted"
|
||||
...
|
||||
} @cuetsy(kind="interface") @grafana(TSVeneer="type") @grafanamaturity(NeedsExpertReview)
|
||||
|
||||
|
||||
@@ -289,8 +289,8 @@
|
||||
"@grafana/plugin-ui": "0.10.7",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "^6.27.0",
|
||||
"@grafana/scenes-react": "^6.27.0",
|
||||
"@grafana/scenes": "^6.27.1",
|
||||
"@grafana/scenes-react": "^6.27.1",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -116,6 +116,8 @@ export interface QueryVariableModel extends VariableWithMultiSupport {
|
||||
query: any;
|
||||
regex: string;
|
||||
refresh: VariableRefresh;
|
||||
staticOptions?: VariableOption[];
|
||||
staticOptionsOrder?: 'before' | 'after' | 'sorted';
|
||||
}
|
||||
|
||||
export interface TextBoxVariableModel extends VariableWithOptions {
|
||||
|
||||
@@ -489,6 +489,27 @@ export const versionedPages = {
|
||||
queryOptionsQueryInput: {
|
||||
'10.4.0': 'data-testid Variable editor Form Default Variable Query Editor textarea',
|
||||
},
|
||||
queryOptionsStaticOptionsRow: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options row',
|
||||
},
|
||||
queryOptionsStaticOptionsToggle: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options toggle',
|
||||
},
|
||||
queryOptionsStaticOptionsLabelInput: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Label input',
|
||||
},
|
||||
queryOptionsStaticOptionsValueInput: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Value input',
|
||||
},
|
||||
queryOptionsStaticOptionsDeleteButton: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Delete button',
|
||||
},
|
||||
queryOptionsStaticOptionsAddButton: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Add button',
|
||||
},
|
||||
queryOptionsStaticOptionsOrderDropdown: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Static Options Order dropdown',
|
||||
},
|
||||
valueGroupsTagsEnabledSwitch: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query UseTags switch',
|
||||
},
|
||||
|
||||
@@ -191,6 +191,14 @@ export interface VariableModel {
|
||||
* Options sort order
|
||||
*/
|
||||
sort?: VariableSort;
|
||||
/**
|
||||
* Additional static options for query variable
|
||||
*/
|
||||
staticOptions?: Array<VariableOption>;
|
||||
/**
|
||||
* Ordering of static options in relation to options returned from data source for query variable
|
||||
*/
|
||||
staticOptionsOrder?: ('before' | 'after' | 'sorted');
|
||||
/**
|
||||
* Type of variable
|
||||
*/
|
||||
@@ -203,6 +211,7 @@ export const defaultVariableModel: Partial<VariableModel> = {
|
||||
multi: false,
|
||||
options: [],
|
||||
skipUrlSync: false,
|
||||
staticOptions: [],
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -1005,6 +1005,8 @@ export interface QueryVariableSpec {
|
||||
allValue?: string;
|
||||
placeholder?: string;
|
||||
allowCustomValue: boolean;
|
||||
staticOptions?: VariableOption[];
|
||||
staticOptionsOrder?: "before" | "after" | "sorted";
|
||||
}
|
||||
|
||||
export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
|
||||
|
||||
@@ -725,6 +725,10 @@ type VariableModel struct {
|
||||
// Optional field, if you want to extract part of a series name or metric node segment.
|
||||
// Named capture groups can be used to separate the display text and value.
|
||||
Regex *string `json:"regex,omitempty"`
|
||||
// Additional static options for query variable
|
||||
StaticOptions []VariableOption `json:"staticOptions,omitempty"`
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
StaticOptionsOrder *VariableModelStaticOptionsOrder `json:"staticOptionsOrder,omitempty"`
|
||||
}
|
||||
|
||||
// NewVariableModel creates a new VariableModel object.
|
||||
@@ -1045,6 +1049,14 @@ const (
|
||||
DataTransformerConfigTopicAlertStates DataTransformerConfigTopic = "alertStates"
|
||||
)
|
||||
|
||||
type VariableModelStaticOptionsOrder string
|
||||
|
||||
const (
|
||||
VariableModelStaticOptionsOrderBefore VariableModelStaticOptionsOrder = "before"
|
||||
VariableModelStaticOptionsOrderAfter VariableModelStaticOptionsOrder = "after"
|
||||
VariableModelStaticOptionsOrderSorted VariableModelStaticOptionsOrder = "sorted"
|
||||
)
|
||||
|
||||
type ValueMapOrRangeMapOrRegexMapOrSpecialValueMap struct {
|
||||
ValueMap *ValueMap `json:"ValueMap,omitempty"`
|
||||
RangeMap *RangeMap `json:"RangeMap,omitempty"`
|
||||
|
||||
@@ -3058,6 +3058,20 @@
|
||||
"sort": {
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"staticOptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"default": {},
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableOption"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"staticOptionsOrder": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -101,6 +101,8 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
allowCustomValue: true,
|
||||
allValue: 'test-all',
|
||||
isMulti: true,
|
||||
staticOptions: [{ label: 'test', value: 'test' }],
|
||||
staticOptionsOrder: 'after',
|
||||
});
|
||||
|
||||
const set = new SceneVariableSet({
|
||||
@@ -136,6 +138,13 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
"query": "query",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"staticOptions": [
|
||||
{
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"staticOptionsOrder": "after",
|
||||
"type": "query",
|
||||
}
|
||||
`);
|
||||
@@ -155,6 +164,8 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
allValue: 'test-all',
|
||||
allowCustomValue: false,
|
||||
isMulti: true,
|
||||
staticOptions: [{ label: 'test', value: 'test' }],
|
||||
staticOptionsOrder: 'after',
|
||||
});
|
||||
const set = new SceneVariableSet({
|
||||
variables: [variable],
|
||||
@@ -189,6 +200,13 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
"query": "query",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"staticOptions": [
|
||||
{
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"staticOptionsOrder": "after",
|
||||
"type": "query",
|
||||
}
|
||||
`);
|
||||
@@ -221,7 +239,7 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
expect(result[0].options).toEqual([]);
|
||||
});
|
||||
|
||||
it('should handle Query variable when sceneVariablesSetToVariables shoudl keep options', () => {
|
||||
it('should handle Query variable when sceneVariablesSetToVariables should keep options', () => {
|
||||
const variable = new QueryVariable({
|
||||
name: 'test',
|
||||
label: 'test-label',
|
||||
@@ -846,6 +864,8 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
includeAll: true,
|
||||
allValue: 'test-all',
|
||||
isMulti: true,
|
||||
staticOptions: [{ label: 'test', value: 'test' }],
|
||||
staticOptionsOrder: 'after',
|
||||
});
|
||||
|
||||
const set = new SceneVariableSet({
|
||||
@@ -891,6 +911,13 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"sort": "disabled",
|
||||
"staticOptions": [
|
||||
{
|
||||
"text": "test",
|
||||
"value": "test",
|
||||
},
|
||||
],
|
||||
"staticOptionsOrder": "after",
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
@@ -86,6 +86,11 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
multi: variable.state.isMulti,
|
||||
allowCustomValue: variable.state.allowCustomValue,
|
||||
skipUrlSync: variable.state.skipUrlSync,
|
||||
staticOptions: variable.state.staticOptions?.map((option) => ({
|
||||
text: option.label,
|
||||
value: String(option.value),
|
||||
})),
|
||||
staticOptionsOrder: variable.state.staticOptionsOrder,
|
||||
});
|
||||
} else if (sceneUtils.isCustomVariable(variable)) {
|
||||
variables.push({
|
||||
@@ -324,6 +329,11 @@ export function sceneVariablesSetToSchemaV2Variables(
|
||||
multi: variable.state.isMulti || false,
|
||||
skipUrlSync: variable.state.skipUrlSync || false,
|
||||
allowCustomValue: variable.state.allowCustomValue ?? true,
|
||||
staticOptions: variable.state.staticOptions?.map((option) => ({
|
||||
text: option.label,
|
||||
value: String(option.value),
|
||||
})),
|
||||
staticOptionsOrder: variable.state.staticOptionsOrder,
|
||||
},
|
||||
};
|
||||
variables.push(queryVariable);
|
||||
|
||||
@@ -77,6 +77,8 @@ describe('QueryVariableEditorForm', () => {
|
||||
const mockOnIncludeAllChange = jest.fn();
|
||||
const mockOnAllValueChange = jest.fn();
|
||||
const mockOnAllowCustomValueChange = jest.fn();
|
||||
const mockOnStaticOptionsChange = jest.fn();
|
||||
const mockOnStaticOptionsOrderChange = jest.fn();
|
||||
|
||||
const defaultProps: React.ComponentProps<typeof QueryVariableEditorForm> = {
|
||||
datasource: { uid: defaultDatasource.uid, type: defaultDatasource.type },
|
||||
@@ -99,6 +101,8 @@ describe('QueryVariableEditorForm', () => {
|
||||
allValue: 'custom all value',
|
||||
onAllValueChange: mockOnAllValueChange,
|
||||
onAllowCustomValueChange: mockOnAllowCustomValueChange,
|
||||
onStaticOptionsChange: mockOnStaticOptionsChange,
|
||||
onStaticOptionsOrderChange: mockOnStaticOptionsOrderChange,
|
||||
};
|
||||
|
||||
async function setup(props?: React.ComponentProps<typeof QueryVariableEditorForm>) {
|
||||
@@ -142,6 +146,10 @@ describe('QueryVariableEditorForm', () => {
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
const staticOptionsToggle = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(regexInput).toBeInTheDocument();
|
||||
@@ -158,6 +166,7 @@ describe('QueryVariableEditorForm', () => {
|
||||
expect(includeAllSwitch).toBeChecked();
|
||||
expect(allValueInput).toBeInTheDocument();
|
||||
expect(allValueInput).toHaveValue('custom all value');
|
||||
expect(staticOptionsToggle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should call onDataSourceChange when changing the datasource', async () => {
|
||||
@@ -293,4 +302,169 @@ describe('QueryVariableEditorForm', () => {
|
||||
((mockOnAllValueChange.mock.calls[0][0] as FormEvent<HTMLInputElement>).target as HTMLInputElement).value
|
||||
).toBe('custom all value and another value');
|
||||
});
|
||||
|
||||
it('should call onStaticOptionsOrderChange when changing the static options order', async () => {
|
||||
const {
|
||||
renderer: { getByTestId },
|
||||
} = await setup();
|
||||
|
||||
// First enable static options
|
||||
const staticOptionsToggle = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
await userEvent.click(staticOptionsToggle);
|
||||
|
||||
// Then access the dropdown
|
||||
const staticOptionsOrderDropdown = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsOrderDropdown
|
||||
);
|
||||
await userEvent.click(staticOptionsOrderDropdown); // open the select
|
||||
const anotherOption = await screen.getByText('After query values');
|
||||
await userEvent.click(anotherOption);
|
||||
|
||||
expect(mockOnStaticOptionsOrderChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnStaticOptionsOrderChange.mock.calls[0][0]).toBe('after');
|
||||
});
|
||||
|
||||
it('should call onStaticOptionsChange when adding a static option', async () => {
|
||||
const {
|
||||
renderer: { getByTestId, getAllByTestId },
|
||||
} = await setup();
|
||||
|
||||
// First enable static options
|
||||
const staticOptionsToggle = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
await userEvent.click(staticOptionsToggle);
|
||||
|
||||
const addButton = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsAddButton
|
||||
);
|
||||
await userEvent.click(addButton);
|
||||
|
||||
// Now enter label and value for the new option
|
||||
const labelInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsLabelInput
|
||||
);
|
||||
const valueInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsValueInput
|
||||
);
|
||||
|
||||
// Enter label for the new option (second input)
|
||||
await userEvent.type(labelInputs[1], 'New Option Label');
|
||||
await userEvent.type(valueInputs[1], 'new-option-value');
|
||||
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalled();
|
||||
expect(mockOnStaticOptionsChange.mock.lastCall[0]).toEqual([
|
||||
{ value: 'new-option-value', label: 'New Option Label' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should call onStaticOptionsChange when removing a static option', async () => {
|
||||
const {
|
||||
renderer: { getAllByTestId },
|
||||
} = await setup({
|
||||
...defaultProps,
|
||||
staticOptions: [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
],
|
||||
});
|
||||
|
||||
const deleteButtons = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsDeleteButton
|
||||
);
|
||||
|
||||
// Remove the first option
|
||||
await userEvent.click(deleteButtons[0]);
|
||||
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalledTimes(1);
|
||||
// Should call with only the second option remaining
|
||||
expect(mockOnStaticOptionsChange.mock.calls[0][0]).toEqual([{ value: 'option2', label: 'Option 2' }]);
|
||||
});
|
||||
|
||||
it('should call onStaticOptionsChange when editing a static option label', async () => {
|
||||
const {
|
||||
renderer: { getAllByTestId },
|
||||
} = await setup({
|
||||
...defaultProps,
|
||||
staticOptions: [{ value: 'test', label: 'Test Label' }],
|
||||
});
|
||||
|
||||
const labelInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsLabelInput
|
||||
);
|
||||
|
||||
await userEvent.clear(labelInputs[0]);
|
||||
await userEvent.type(labelInputs[0], 'Updated Label');
|
||||
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalled();
|
||||
expect(mockOnStaticOptionsChange.mock.lastCall[0]).toEqual([{ value: 'test', label: 'Updated Label' }]);
|
||||
});
|
||||
|
||||
it('should call onStaticOptionsChange when editing a static option value', async () => {
|
||||
const {
|
||||
renderer: { getAllByTestId },
|
||||
} = await setup({
|
||||
...defaultProps,
|
||||
staticOptions: [{ value: 'old-value', label: 'Test Label' }],
|
||||
});
|
||||
|
||||
const valueInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsValueInput
|
||||
);
|
||||
|
||||
await userEvent.clear(valueInputs[0]);
|
||||
await userEvent.type(valueInputs[0], 'new-value');
|
||||
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalled();
|
||||
expect(mockOnStaticOptionsChange.mock.lastCall[0]).toEqual([{ value: 'new-value', label: 'Test Label' }]);
|
||||
});
|
||||
|
||||
it('should remove static options and hide UI elements when static options switch is unchecked', async () => {
|
||||
const {
|
||||
renderer: { getByTestId, queryByTestId, getAllByTestId },
|
||||
} = await setup({
|
||||
...defaultProps,
|
||||
staticOptions: [
|
||||
{ value: 'option1', label: 'Option 1' },
|
||||
{ value: 'option2', label: 'Option 2' },
|
||||
],
|
||||
});
|
||||
|
||||
// Static options should be visible initially
|
||||
expect(
|
||||
getByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle)
|
||||
).toBeChecked();
|
||||
expect(
|
||||
getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsOrderDropdown
|
||||
)
|
||||
).toBeInTheDocument();
|
||||
|
||||
// Option rows should be visible
|
||||
expect(
|
||||
getAllByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsRow)
|
||||
).toHaveLength(2);
|
||||
|
||||
// Uncheck the static options switch
|
||||
const staticOptionsToggle = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
await userEvent.click(staticOptionsToggle);
|
||||
|
||||
// Should call onStaticOptionsChange to remove static options
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnStaticOptionsChange).toHaveBeenCalledWith(undefined);
|
||||
|
||||
// Static options UI elements should be hidden
|
||||
expect(
|
||||
queryByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsOrderDropdown
|
||||
)
|
||||
).not.toBeInTheDocument();
|
||||
expect(
|
||||
queryByTestId(selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsRow)
|
||||
).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -14,6 +14,11 @@ import { DataSourcePicker } from 'app/features/datasources/components/picker/Dat
|
||||
import { getVariableQueryEditor } from 'app/features/variables/editor/getVariableQueryEditor';
|
||||
import { QueryVariableRefreshSelect } from 'app/features/variables/query/QueryVariableRefreshSelect';
|
||||
import { QueryVariableSortSelect } from 'app/features/variables/query/QueryVariableSortSelect';
|
||||
import {
|
||||
StaticOptionsOrderType,
|
||||
StaticOptionsType,
|
||||
QueryVariableStaticOptions,
|
||||
} from 'app/features/variables/query/QueryVariableStaticOptions';
|
||||
|
||||
import { VariableLegend } from './VariableLegend';
|
||||
import { VariableTextAreaField } from './VariableTextAreaField';
|
||||
@@ -41,6 +46,10 @@ interface QueryVariableEditorFormProps {
|
||||
onIncludeAllChange: (event: FormEvent<HTMLInputElement>) => void;
|
||||
allValue: string;
|
||||
onAllValueChange: (event: FormEvent<HTMLInputElement>) => void;
|
||||
staticOptions?: StaticOptionsType;
|
||||
staticOptionsOrder?: StaticOptionsOrderType;
|
||||
onStaticOptionsChange?: (staticOptions: StaticOptionsType) => void;
|
||||
onStaticOptionsOrderChange?: (staticOptionsOrder: StaticOptionsOrderType) => void;
|
||||
}
|
||||
|
||||
export function QueryVariableEditorForm({
|
||||
@@ -64,6 +73,10 @@ export function QueryVariableEditorForm({
|
||||
onIncludeAllChange,
|
||||
allValue,
|
||||
onAllValueChange,
|
||||
staticOptions,
|
||||
staticOptionsOrder,
|
||||
onStaticOptionsChange,
|
||||
onStaticOptionsOrderChange,
|
||||
}: QueryVariableEditorFormProps) {
|
||||
const { value: dsConfig } = useAsync(async () => {
|
||||
const datasource = await getDataSourceSrv().get(datasourceRef ?? '');
|
||||
@@ -144,6 +157,15 @@ export function QueryVariableEditorForm({
|
||||
refresh={refresh}
|
||||
/>
|
||||
|
||||
{onStaticOptionsChange && onStaticOptionsOrderChange && (
|
||||
<QueryVariableStaticOptions
|
||||
staticOptions={staticOptions}
|
||||
staticOptionsOrder={staticOptionsOrder}
|
||||
onStaticOptionsChange={onStaticOptionsChange}
|
||||
onStaticOptionsOrderChange={onStaticOptionsOrderChange}
|
||||
/>
|
||||
)}
|
||||
|
||||
<VariableLegend>
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.selection-options">Selection options</Trans>
|
||||
</VariableLegend>
|
||||
|
||||
@@ -0,0 +1,106 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { VariableValueOption } from '@grafana/scenes';
|
||||
import { Button, Input, Stack } from '@grafana/ui';
|
||||
|
||||
interface VariableOptionsFieldProps {
|
||||
options: VariableValueOption[];
|
||||
onChange: (options: VariableValueOption[]) => void;
|
||||
width?: number;
|
||||
}
|
||||
|
||||
export function VariableOptionsInput({ options, onChange, width }: VariableOptionsFieldProps) {
|
||||
const [optionsLocal, setOptionsLocal] = useState(options.length ? options : [{ value: '', label: '' }]);
|
||||
|
||||
const updateOptions = (newOptions: VariableValueOption[]) => {
|
||||
setOptionsLocal(newOptions);
|
||||
onChange(
|
||||
newOptions
|
||||
.map((option) => ({
|
||||
label: option.label.trim(),
|
||||
value: String(option.value).trim(),
|
||||
}))
|
||||
.filter((option) => !!option.label)
|
||||
);
|
||||
};
|
||||
|
||||
const handleValueChange = (index: number, value: string) => {
|
||||
if (optionsLocal[index].value !== value) {
|
||||
const newOptions = [...optionsLocal];
|
||||
newOptions[index] = { ...newOptions[index], value };
|
||||
updateOptions(newOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const handleLabelChange = (index: number, label: string) => {
|
||||
if (optionsLocal[index].label !== label) {
|
||||
const newOptions = [...optionsLocal];
|
||||
newOptions[index] = { ...newOptions[index], label };
|
||||
updateOptions(newOptions);
|
||||
}
|
||||
};
|
||||
|
||||
const addOption = () => {
|
||||
const newOption: VariableValueOption = { value: '', label: '' };
|
||||
const newOptions = [...optionsLocal, newOption];
|
||||
updateOptions(newOptions);
|
||||
};
|
||||
|
||||
const removeOption = (index: number) => {
|
||||
const newOptions = optionsLocal.filter((_, i) => i !== index);
|
||||
updateOptions(newOptions);
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack direction="column" gap={2} width={width}>
|
||||
{optionsLocal.map((option, index) => (
|
||||
<Stack
|
||||
direction="row"
|
||||
key={index}
|
||||
data-testid={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsRow}
|
||||
>
|
||||
<Input
|
||||
value={option.label}
|
||||
placeholder={t('variables.query-variable-static-options.label-placeholder', 'display label')}
|
||||
onChange={(e) => handleLabelChange(index, e.currentTarget.value)}
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsLabelInput
|
||||
}
|
||||
/>
|
||||
<Input
|
||||
value={String(option.value)}
|
||||
placeholder={t('variables.query-variable-static-options.value-placeholder', 'value, default empty string')}
|
||||
onChange={(e) => handleValueChange(index, e.currentTarget.value)}
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsValueInput
|
||||
}
|
||||
/>
|
||||
<Button
|
||||
icon="times"
|
||||
variant="secondary"
|
||||
aria-label={t('variables.query-variable-static-options.remove-option-button-label', 'Remove option')}
|
||||
onClick={() => removeOption(index)}
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsDeleteButton
|
||||
}
|
||||
/>
|
||||
</Stack>
|
||||
))}
|
||||
<div>
|
||||
<Button
|
||||
icon="plus"
|
||||
variant="secondary"
|
||||
onClick={addOption}
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsAddButton
|
||||
}
|
||||
aria-label={t('variables.query-variable-static-options.add-option-button-label', 'Add option')}
|
||||
>
|
||||
<Trans i18nKey="variables.query-variable-static-options.add-option-button-label">Add option</Trans>
|
||||
</Button>
|
||||
</div>
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
@@ -123,6 +123,10 @@ describe('QueryVariableEditor', () => {
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.General.selectionOptionsAllowCustomValueSwitch
|
||||
);
|
||||
|
||||
const staticOptionsToggle = renderer.getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
|
||||
expect(dataSourcePicker).toBeInTheDocument();
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(queryEditor).toBeInTheDocument();
|
||||
@@ -141,6 +145,7 @@ describe('QueryVariableEditor', () => {
|
||||
expect(includeAllSwitch).toBeChecked();
|
||||
expect(allValueInput).toBeInTheDocument();
|
||||
expect(allValueInput).toHaveValue('custom all value');
|
||||
expect(staticOptionsToggle).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('should update the variable with default query for the selected DS', async () => {
|
||||
@@ -362,6 +367,70 @@ describe('QueryVariableEditor', () => {
|
||||
expect(variable.state.allValue).toBe('custom all value and another value');
|
||||
});
|
||||
|
||||
it('should update the variable state when adding two static options', async () => {
|
||||
const {
|
||||
variable,
|
||||
renderer: { getByTestId, getAllByTestId },
|
||||
user,
|
||||
} = await setup();
|
||||
|
||||
// Initially no static options
|
||||
expect(variable.state.staticOptions).toBeUndefined();
|
||||
|
||||
// First enable static options
|
||||
const staticOptionsToggle = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
);
|
||||
await userEvent.click(staticOptionsToggle);
|
||||
|
||||
// Add first static option
|
||||
const addButton = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsAddButton
|
||||
);
|
||||
await user.click(addButton);
|
||||
|
||||
// Enter label and value for first option
|
||||
const labelInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsLabelInput
|
||||
);
|
||||
const valueInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsValueInput
|
||||
);
|
||||
|
||||
await user.type(labelInputs[0], 'First Option');
|
||||
await user.type(valueInputs[0], 'first-value');
|
||||
|
||||
await waitFor(async () => {
|
||||
await lastValueFrom(variable.validateAndUpdate());
|
||||
});
|
||||
|
||||
expect(variable.state.staticOptions).toEqual([{ label: 'First Option', value: 'first-value' }]);
|
||||
|
||||
// Add second static option
|
||||
await user.click(addButton);
|
||||
|
||||
// Get updated inputs (now there should be 2 sets)
|
||||
const updatedLabelInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsLabelInput
|
||||
);
|
||||
const updatedValueInputs = getAllByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsValueInput
|
||||
);
|
||||
|
||||
// Enter label and value for second option
|
||||
await user.type(updatedLabelInputs[1], 'Second Option');
|
||||
await user.type(updatedValueInputs[1], 'second-value');
|
||||
|
||||
await waitFor(async () => {
|
||||
await lastValueFrom(variable.validateAndUpdate());
|
||||
});
|
||||
|
||||
expect(variable.state.staticOptions).toEqual([
|
||||
{ label: 'First Option', value: 'first-value' },
|
||||
{ label: 'Second Option', value: 'second-value' },
|
||||
]);
|
||||
});
|
||||
|
||||
it('should return an empty array if variable is not a QueryVariable', () => {
|
||||
const consoleWarnSpy = jest.spyOn(console, 'warn').mockImplementation(() => {});
|
||||
const variable = new TextBoxVariable({ name: 'test', value: 'test value' });
|
||||
|
||||
@@ -14,6 +14,7 @@ import { DataSourcePicker } from 'app/features/datasources/components/picker/Dat
|
||||
import { getVariableQueryEditor } from 'app/features/variables/editor/getVariableQueryEditor';
|
||||
import { QueryVariableRefreshSelect } from 'app/features/variables/query/QueryVariableRefreshSelect';
|
||||
import { QueryVariableSortSelect } from 'app/features/variables/query/QueryVariableSortSelect';
|
||||
import { StaticOptionsOrderType, StaticOptionsType } from 'app/features/variables/query/QueryVariableStaticOptions';
|
||||
|
||||
import { QueryVariableEditorForm } from '../components/QueryVariableForm';
|
||||
import { VariableTextAreaField } from '../components/VariableTextAreaField';
|
||||
@@ -27,8 +28,19 @@ interface QueryVariableEditorProps {
|
||||
type VariableQueryType = QueryVariable['state']['query'];
|
||||
|
||||
export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEditorProps) {
|
||||
const { datasource, regex, sort, refresh, isMulti, includeAll, allValue, query, allowCustomValue } =
|
||||
variable.useState();
|
||||
const {
|
||||
datasource,
|
||||
regex,
|
||||
sort,
|
||||
refresh,
|
||||
isMulti,
|
||||
includeAll,
|
||||
allValue,
|
||||
query,
|
||||
allowCustomValue,
|
||||
staticOptions,
|
||||
staticOptionsOrder,
|
||||
} = variable.useState();
|
||||
const { value: timeRange } = sceneGraph.getTimeRange(variable).useState();
|
||||
|
||||
const onRegExChange = (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
@@ -67,6 +79,16 @@ export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEdito
|
||||
onRunQuery();
|
||||
};
|
||||
|
||||
const onStaticOptionsChange = (staticOptions: StaticOptionsType) => {
|
||||
onRunQuery();
|
||||
variable.setState({ staticOptions });
|
||||
};
|
||||
|
||||
const onStaticOptionsOrderChange = (staticOptionsOrder: StaticOptionsOrderType) => {
|
||||
onRunQuery();
|
||||
variable.setState({ staticOptionsOrder });
|
||||
};
|
||||
|
||||
return (
|
||||
<QueryVariableEditorForm
|
||||
datasource={datasource ?? undefined}
|
||||
@@ -89,6 +111,10 @@ export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEdito
|
||||
onAllValueChange={onAllValueChange}
|
||||
allowCustomValue={allowCustomValue}
|
||||
onAllowCustomValueChange={onAllowCustomValueChange}
|
||||
staticOptions={staticOptions}
|
||||
staticOptionsOrder={staticOptionsOrder}
|
||||
onStaticOptionsChange={onStaticOptionsChange}
|
||||
onStaticOptionsOrderChange={onStaticOptionsOrderChange}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -190,6 +190,11 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
||||
hide: variable.hide,
|
||||
definition: variable.definition,
|
||||
allowCustomValue: variable.allowCustomValue,
|
||||
staticOptions: variable.staticOptions?.map((option) => ({
|
||||
label: String(option.text),
|
||||
value: String(option.value),
|
||||
})),
|
||||
staticOptionsOrder: variable.staticOptionsOrder,
|
||||
});
|
||||
} else if (variable.type === 'datasource') {
|
||||
return new DataSourceVariable({
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
import { useState } from 'react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { t, Trans } from '@grafana/i18n';
|
||||
import { QueryVariable } from '@grafana/scenes';
|
||||
import { Field, Stack, Switch } from '@grafana/ui';
|
||||
import { VariableLegend } from 'app/features/dashboard-scene/settings/variables/components/VariableLegend';
|
||||
import { VariableOptionsInput } from 'app/features/dashboard-scene/settings/variables/components/VariableOptionsInput';
|
||||
import { VariableSelectField } from 'app/features/dashboard-scene/settings/variables/components/VariableSelectField';
|
||||
|
||||
export type StaticOptionsType = QueryVariable['state']['staticOptions'];
|
||||
export type StaticOptionsOrderType = QueryVariable['state']['staticOptionsOrder'];
|
||||
|
||||
interface QueryVariableStaticOptionsProps {
|
||||
staticOptions: StaticOptionsType;
|
||||
staticOptionsOrder: StaticOptionsOrderType;
|
||||
onStaticOptionsChange: (staticOptions: StaticOptionsType) => void;
|
||||
onStaticOptionsOrderChange: (staticOptionsOrder: StaticOptionsOrderType) => void;
|
||||
}
|
||||
|
||||
const SORT_OPTIONS = [
|
||||
{ label: 'Before query values', value: 'before' },
|
||||
{ label: 'After query values', value: 'after' },
|
||||
{ label: 'Sorted with query values', value: 'sorted' },
|
||||
];
|
||||
|
||||
export function QueryVariableStaticOptions(props: QueryVariableStaticOptionsProps) {
|
||||
const { staticOptions, onStaticOptionsChange, staticOptionsOrder, onStaticOptionsOrderChange } = props;
|
||||
|
||||
const value = SORT_OPTIONS.find((o) => o.value === staticOptionsOrder) ?? SORT_OPTIONS[0];
|
||||
|
||||
const [areStaticOptionsEnabled, setAreStaticOptionsEnabled] = useState(!!staticOptions?.length);
|
||||
|
||||
return (
|
||||
<>
|
||||
<VariableLegend>
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.static-options-legend">Static options</Trans>
|
||||
</VariableLegend>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Field
|
||||
noMargin
|
||||
label={t('dashboard-scene.query-variable-editor-form.label-use-static-options', 'Use static options')}
|
||||
description={t(
|
||||
'variables.query-variable-static-options.description',
|
||||
'Add custom options in addition to query results'
|
||||
)}
|
||||
>
|
||||
<>
|
||||
<Stack direction="column" gap={2}>
|
||||
<Switch
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsToggle
|
||||
}
|
||||
value={areStaticOptionsEnabled}
|
||||
onChange={(e) => {
|
||||
if (e.currentTarget.checked) {
|
||||
setAreStaticOptionsEnabled(true);
|
||||
} else {
|
||||
setAreStaticOptionsEnabled(false);
|
||||
if (!!staticOptions?.length) {
|
||||
onStaticOptionsChange(undefined);
|
||||
}
|
||||
}
|
||||
}}
|
||||
/>
|
||||
|
||||
{areStaticOptionsEnabled && (
|
||||
<VariableOptionsInput width={60} options={staticOptions ?? []} onChange={onStaticOptionsChange} />
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
</Field>
|
||||
|
||||
{areStaticOptionsEnabled && (
|
||||
<VariableSelectField
|
||||
name={t('dashboard-scene.query-variable-editor-form.label-static-options-sort', 'Static options sort')}
|
||||
description={t(
|
||||
'variables.query-variable-static-options-sort-select.description-values-variable',
|
||||
'How to sort static options with query results'
|
||||
)}
|
||||
value={value}
|
||||
options={SORT_OPTIONS}
|
||||
onChange={(opt) => onStaticOptionsOrderChange(opt.value)}
|
||||
testId={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsStaticOptionsOrderDropdown
|
||||
}
|
||||
width={25}
|
||||
/>
|
||||
)}
|
||||
</Stack>
|
||||
</>
|
||||
);
|
||||
}
|
||||
@@ -5837,10 +5837,13 @@
|
||||
"description-examples": "Named capture groups can be used to separate the display text and value (<1>see examples</1>).",
|
||||
"description-optional": "Optional, if you want to extract part of a series name or metric node segment.",
|
||||
"label-data-source": "Data source",
|
||||
"label-static-options-sort": "Static options sort",
|
||||
"label-target-data-source": "Target data source",
|
||||
"label-use-static-options": "Use static options",
|
||||
"name-regex": "Regex",
|
||||
"query-options": "Query options",
|
||||
"selection-options": "Selection options"
|
||||
"selection-options": "Selection options",
|
||||
"static-options-legend": "Static options"
|
||||
},
|
||||
"resource-export": {
|
||||
"label": {
|
||||
@@ -13158,6 +13161,16 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"query-variable-static-options": {
|
||||
"add-option-button-label": "Add option",
|
||||
"description": "Add custom options in addition to query results",
|
||||
"label-placeholder": "display label",
|
||||
"remove-option-button-label": "Remove option",
|
||||
"value-placeholder": "value, default empty string"
|
||||
},
|
||||
"query-variable-static-options-sort-select": {
|
||||
"description-values-variable": "How to sort static options with query results"
|
||||
},
|
||||
"text-box-variable-editor": {
|
||||
"name-default-value": "Default value",
|
||||
"placeholder-default-value-if-any": "default value, if any",
|
||||
|
||||
@@ -3549,7 +3549,7 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:^6.27.0":
|
||||
"@grafana/scenes-react@npm:^6.27.1":
|
||||
version: 6.27.2
|
||||
resolution: "@grafana/scenes-react@npm:6.27.2"
|
||||
dependencies:
|
||||
@@ -3569,7 +3569,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.27.2, @grafana/scenes@npm:^6.27.0":
|
||||
"@grafana/scenes@npm:6.27.2, @grafana/scenes@npm:^6.27.1":
|
||||
version: 6.27.2
|
||||
resolution: "@grafana/scenes@npm:6.27.2"
|
||||
dependencies:
|
||||
@@ -18159,8 +18159,8 @@ __metadata:
|
||||
"@grafana/plugin-ui": "npm:0.10.7"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:^6.27.0"
|
||||
"@grafana/scenes-react": "npm:^6.27.0"
|
||||
"@grafana/scenes": "npm:^6.27.1"
|
||||
"@grafana/scenes-react": "npm:^6.27.1"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/test-utils": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user