Dashboard : Allow applying variable regex to display text (#114426)
* Ability to apply regex to display text * Frontend tests * scenes-react version * lock file * adjust tests input * adjust inputs * unused variable * change data type * unit tests * bump scenes * bump scenes * Update docs * V2->V1 conversion * re-generate files * update openai snapshots
This commit is contained in:
@@ -768,6 +768,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
VariableRegexApplyTo: *"value" | "text"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -803,6 +807,7 @@ QueryVariableSpec: {
|
||||
datasource?: DataSourceRef
|
||||
query: DataQueryKind
|
||||
regex: string | *""
|
||||
regexApplyTo?: VariableRegexApplyTo
|
||||
sort: VariableSort
|
||||
definition?: string
|
||||
options: [...VariableOption] | *[]
|
||||
|
||||
@@ -772,6 +772,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
VariableRegexApplyTo: *"value" | "text"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -806,6 +810,7 @@ QueryVariableSpec: {
|
||||
description?: string
|
||||
query: DataQueryKind
|
||||
regex: string | *""
|
||||
regexApplyTo?: VariableRegexApplyTo
|
||||
sort: VariableSort
|
||||
definition?: string
|
||||
options: [...VariableOption] | *[]
|
||||
|
||||
@@ -222,6 +222,8 @@ 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
|
||||
// Determine whether regex applies to variable value or display text
|
||||
regexApplyTo?: #VariableRegexApplyTo
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
@@ -249,6 +251,10 @@ lineage: schemas: [{
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are "value" (apply to value used in queries) or "text" (apply to display text shown to users)
|
||||
#VariableRegexApplyTo: "value" | "text" @cuetsy(kind="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `0`: No sorting
|
||||
|
||||
@@ -222,6 +222,8 @@ 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
|
||||
// Determine whether regex applies to variable value or display text
|
||||
regexApplyTo?: #VariableRegexApplyTo
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
@@ -249,6 +251,10 @@ lineage: schemas: [{
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are "value" (apply to value used in queries) or "text" (apply to display text shown to users)
|
||||
#VariableRegexApplyTo: "value" | "text" @cuetsy(kind="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `0`: No sorting
|
||||
|
||||
@@ -772,6 +772,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable"
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
VariableRegexApplyTo: *"value" | "text"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -807,6 +811,7 @@ QueryVariableSpec: {
|
||||
datasource?: DataSourceRef
|
||||
query: DataQueryKind
|
||||
regex: string | *""
|
||||
regexApplyTo?: VariableRegexApplyTo
|
||||
sort: VariableSort
|
||||
definition?: string
|
||||
options: [...VariableOption] | *[]
|
||||
|
||||
@@ -1364,6 +1364,7 @@ type DashboardQueryVariableSpec struct {
|
||||
Datasource *DashboardDataSourceRef `json:"datasource,omitempty"`
|
||||
Query DashboardDataQueryKind `json:"query"`
|
||||
Regex string `json:"regex"`
|
||||
RegexApplyTo *DashboardVariableRegexApplyTo `json:"regexApplyTo,omitempty"`
|
||||
Sort DashboardVariableSort `json:"sort"`
|
||||
Definition *string `json:"definition,omitempty"`
|
||||
Options []DashboardVariableOption `json:"options"`
|
||||
@@ -1393,6 +1394,7 @@ func NewDashboardQueryVariableSpec() *DashboardQueryVariableSpec {
|
||||
SkipUrlSync: false,
|
||||
Query: *NewDashboardDataQueryKind(),
|
||||
Regex: "",
|
||||
RegexApplyTo: (func(input DashboardVariableRegexApplyTo) *DashboardVariableRegexApplyTo { return &input })(DashboardVariableRegexApplyToValue),
|
||||
Options: []DashboardVariableOption{},
|
||||
Multi: false,
|
||||
IncludeAll: false,
|
||||
@@ -1443,6 +1445,16 @@ const (
|
||||
DashboardVariableRefreshOnTimeRangeChanged DashboardVariableRefresh = "onTimeRangeChanged"
|
||||
)
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardVariableRegexApplyTo string
|
||||
|
||||
const (
|
||||
DashboardVariableRegexApplyToValue DashboardVariableRegexApplyTo = "value"
|
||||
DashboardVariableRegexApplyToText DashboardVariableRegexApplyTo = "text"
|
||||
)
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `disabled`: No sorting
|
||||
|
||||
@@ -3646,6 +3646,12 @@ func schema_pkg_apis_dashboard_v2alpha1_DashboardQueryVariableSpec(ref common.Re
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"regexApplyTo": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sort": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
|
||||
@@ -776,6 +776,10 @@ VariableRefresh: *"never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
// Accepted values are `dontHide` (show label and value), `hideLabel` (show value only), `hideVariable` (show nothing), `inControlsMenu` (show in a drop-down menu).
|
||||
VariableHide: *"dontHide" | "hideLabel" | "hideVariable" | "inControlsMenu"
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
VariableRegexApplyTo: *"value" | "text"
|
||||
|
||||
// Determine the origin of the adhoc variable filter
|
||||
FilterOrigin: "dashboard"
|
||||
|
||||
@@ -810,6 +814,7 @@ QueryVariableSpec: {
|
||||
description?: string
|
||||
query: DataQueryKind
|
||||
regex: string | *""
|
||||
regexApplyTo?: VariableRegexApplyTo
|
||||
sort: VariableSort
|
||||
definition?: string
|
||||
options: [...VariableOption] | *[]
|
||||
|
||||
@@ -1367,6 +1367,7 @@ type DashboardQueryVariableSpec struct {
|
||||
Description *string `json:"description,omitempty"`
|
||||
Query DashboardDataQueryKind `json:"query"`
|
||||
Regex string `json:"regex"`
|
||||
RegexApplyTo *DashboardVariableRegexApplyTo `json:"regexApplyTo,omitempty"`
|
||||
Sort DashboardVariableSort `json:"sort"`
|
||||
Definition *string `json:"definition,omitempty"`
|
||||
Options []DashboardVariableOption `json:"options"`
|
||||
@@ -1396,6 +1397,7 @@ func NewDashboardQueryVariableSpec() *DashboardQueryVariableSpec {
|
||||
SkipUrlSync: false,
|
||||
Query: *NewDashboardDataQueryKind(),
|
||||
Regex: "",
|
||||
RegexApplyTo: (func(input DashboardVariableRegexApplyTo) *DashboardVariableRegexApplyTo { return &input })(DashboardVariableRegexApplyToValue),
|
||||
Options: []DashboardVariableOption{},
|
||||
Multi: false,
|
||||
IncludeAll: false,
|
||||
@@ -1447,6 +1449,16 @@ const (
|
||||
DashboardVariableRefreshOnTimeRangeChanged DashboardVariableRefresh = "onTimeRangeChanged"
|
||||
)
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
// +k8s:openapi-gen=true
|
||||
type DashboardVariableRegexApplyTo string
|
||||
|
||||
const (
|
||||
DashboardVariableRegexApplyToValue DashboardVariableRegexApplyTo = "value"
|
||||
DashboardVariableRegexApplyToText DashboardVariableRegexApplyTo = "text"
|
||||
)
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `disabled`: No sorting
|
||||
|
||||
@@ -3656,6 +3656,12 @@ func schema_pkg_apis_dashboard_v2beta1_DashboardQueryVariableSpec(ref common.Ref
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"regexApplyTo": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"sort": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
|
||||
+2
-2
File diff suppressed because one or more lines are too long
+5
-3
@@ -42,7 +42,7 @@
|
||||
"regex": "",
|
||||
"skipUrlSync": false,
|
||||
"refresh": 1
|
||||
},
|
||||
},
|
||||
{
|
||||
"name": "query_var",
|
||||
"type": "query",
|
||||
@@ -81,6 +81,7 @@
|
||||
"allValue": ".*",
|
||||
"multi": true,
|
||||
"regex": "/.*9090.*/",
|
||||
"regexApplyTo": "text",
|
||||
"skipUrlSync": false,
|
||||
"refresh": 2,
|
||||
"sort": 1,
|
||||
@@ -107,7 +108,7 @@
|
||||
},
|
||||
{
|
||||
"selected": false,
|
||||
"text": "staging",
|
||||
"text": "staging",
|
||||
"value": "staging"
|
||||
},
|
||||
{
|
||||
@@ -335,6 +336,7 @@
|
||||
"allValue": "*",
|
||||
"multi": true,
|
||||
"regex": "/host[0-9]+/",
|
||||
"regexApplyTo": "value",
|
||||
"skipUrlSync": false,
|
||||
"refresh": 1,
|
||||
"sort": 2,
|
||||
@@ -354,4 +356,4 @@
|
||||
},
|
||||
"links": []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Vendored
+2
@@ -94,6 +94,7 @@
|
||||
"query": "label_values(up, instance)",
|
||||
"refresh": 2,
|
||||
"regex": "/.*9090.*/",
|
||||
"regexApplyTo": "text",
|
||||
"skipUrlSync": false,
|
||||
"sort": 1,
|
||||
"tagValuesQuery": "",
|
||||
@@ -362,6 +363,7 @@
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "/host[0-9]+/",
|
||||
"regexApplyTo": "value",
|
||||
"skipUrlSync": false,
|
||||
"sort": 2,
|
||||
"tagValuesQuery": "",
|
||||
|
||||
Vendored
+2
@@ -110,6 +110,7 @@
|
||||
}
|
||||
},
|
||||
"regex": "/.*9090.*/",
|
||||
"regexApplyTo": "text",
|
||||
"sort": "alphabeticalAsc",
|
||||
"definition": "label_values(up, instance)",
|
||||
"options": [
|
||||
@@ -401,6 +402,7 @@
|
||||
}
|
||||
},
|
||||
"regex": "/host[0-9]+/",
|
||||
"regexApplyTo": "value",
|
||||
"sort": "alphabeticalDesc",
|
||||
"definition": "terms field:@host size:100",
|
||||
"options": [],
|
||||
|
||||
Vendored
+2
@@ -111,6 +111,7 @@
|
||||
}
|
||||
},
|
||||
"regex": "/.*9090.*/",
|
||||
"regexApplyTo": "text",
|
||||
"sort": "alphabeticalAsc",
|
||||
"definition": "label_values(up, instance)",
|
||||
"options": [
|
||||
@@ -404,6 +405,7 @@
|
||||
}
|
||||
},
|
||||
"regex": "/host[0-9]+/",
|
||||
"regexApplyTo": "value",
|
||||
"sort": "alphabeticalDesc",
|
||||
"definition": "terms field:@host size:100",
|
||||
"options": [],
|
||||
|
||||
@@ -229,6 +229,16 @@ func getBoolField(m map[string]interface{}, key string, defaultValue bool) bool
|
||||
return defaultValue
|
||||
}
|
||||
|
||||
func getUnionField[T ~string](m map[string]interface{}, key string) *T {
|
||||
if val, ok := m[key]; ok {
|
||||
if str, ok := val.(string); ok && str != "" {
|
||||
result := T(str)
|
||||
return &result
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Helper function to create int64 pointer
|
||||
func int64Ptr(i int64) *int64 {
|
||||
return &i
|
||||
@@ -1195,6 +1205,7 @@ func buildQueryVariable(ctx context.Context, varMap map[string]interface{}, comm
|
||||
Refresh: transformVariableRefreshToEnum(varMap["refresh"]),
|
||||
Sort: transformVariableSortToEnum(varMap["sort"]),
|
||||
Regex: schemaversion.GetStringValue(varMap, "regex"),
|
||||
RegexApplyTo: getUnionField[dashv2alpha1.DashboardVariableRegexApplyTo](varMap, "regexApplyTo"),
|
||||
Query: buildDataQueryKindForVariable(varMap["query"], datasourceType),
|
||||
AllowCustomValue: getBoolField(varMap, "allowCustomValue", true),
|
||||
},
|
||||
|
||||
@@ -1312,6 +1312,9 @@ func convertQueryVariableToV1(variable *dashv2alpha1.DashboardQueryVariableKind)
|
||||
if spec.Definition != nil {
|
||||
varMap["definition"] = *spec.Definition
|
||||
}
|
||||
if spec.RegexApplyTo != nil {
|
||||
varMap["regexApplyTo"] = string(*spec.RegexApplyTo)
|
||||
}
|
||||
varMap["allowCustomValue"] = spec.AllowCustomValue
|
||||
|
||||
// Convert query - handle LEGACY_STRING_VALUE_KEY
|
||||
|
||||
@@ -767,6 +767,7 @@ func convertQueryVariableSpec_V2alpha1_to_V2beta1(in *dashv2alpha1.DashboardQuer
|
||||
out.SkipUrlSync = in.SkipUrlSync
|
||||
out.Description = in.Description
|
||||
out.Regex = in.Regex
|
||||
out.RegexApplyTo = (*dashv2beta1.DashboardVariableRegexApplyTo)(in.RegexApplyTo)
|
||||
out.Sort = dashv2beta1.DashboardVariableSort(in.Sort)
|
||||
out.Definition = in.Definition
|
||||
out.Options = convertVariableOptions_V2alpha1_to_V2beta1(in.Options)
|
||||
|
||||
@@ -806,6 +806,7 @@ func convertQueryVariableSpec_V2beta1_to_V2alpha1(in *dashv2beta1.DashboardQuery
|
||||
out.SkipUrlSync = in.SkipUrlSync
|
||||
out.Description = in.Description
|
||||
out.Regex = in.Regex
|
||||
out.RegexApplyTo = (*dashv2alpha1.DashboardVariableRegexApplyTo)(in.RegexApplyTo)
|
||||
out.Sort = dashv2alpha1.DashboardVariableSort(in.Sort)
|
||||
out.Definition = in.Definition
|
||||
out.Options = convertVariableOptions_V2beta1_to_V2alpha1(in.Options)
|
||||
|
||||
@@ -171,6 +171,7 @@ Query expressions are different for each data source. For more information, refe
|
||||
- If you need more room in a single input field query editor, then hover your cursor over the lines in the lower right corner of the field and drag downward to expand.
|
||||
|
||||
1. (Optional) In the **Regex** field, type a regular expression to filter or capture specific parts of the names returned by your data source query. To see examples, refer to [Filter variables with a regular expression](#filter-variables-with-regex).
|
||||
1. Under **Apply regex to**, select **Variable value** or **Display text** to choose where the regex pattern is applied. The default is **Variable value**.
|
||||
1. In the **Sort** drop-down list, select the sort order for values to be displayed in the dropdown list. The default option, **Disabled**, means that the order of options returned by your data source query is used.
|
||||
1. Under **Refresh**, select when the variable should update options:
|
||||
- **On dashboard load** - Queries the data source every time the dashboard loads. This slows down dashboard loading, because the variable query needs to be completed before dashboard can be initialized.
|
||||
|
||||
@@ -79,6 +79,16 @@ test.describe(
|
||||
await expect(regexInput).toHaveAttribute('placeholder', '/.*-(?<text>.*)-(?<value>.*)-.*/');
|
||||
await expect(regexInput).toHaveValue('');
|
||||
|
||||
// Check regex apply to field - should default to "Variable value"
|
||||
const regexApplyToField = dashboardPage.getByGrafanaSelector(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExApplyToSelectV2
|
||||
);
|
||||
await expect(regexApplyToField).toBeVisible();
|
||||
const variableValueRadio = page.getByRole('radio', { name: 'Variable value' });
|
||||
await expect(variableValueRadio).toBeChecked();
|
||||
const displayTextRadio = page.getByRole('radio', { name: 'Display text' });
|
||||
await expect(displayTextRadio).not.toBeChecked();
|
||||
|
||||
const sortSelect = dashboardPage.getByGrafanaSelector(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2
|
||||
);
|
||||
|
||||
@@ -2001,11 +2001,6 @@
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/settings/variables/components/VariableTextAreaField.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"public/app/features/dashboard-scene/settings/variables/components/VariableTextField.tsx": {
|
||||
"no-restricted-syntax": {
|
||||
"count": 1
|
||||
|
||||
@@ -218,6 +218,8 @@ 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
|
||||
// Determine whether regex applies to variable value or display text
|
||||
regexApplyTo?: #VariableRegexApplyTo
|
||||
// Additional static options for query variable
|
||||
staticOptions?: [...#VariableOption]
|
||||
// Ordering of static options in relation to options returned from data source for query variable
|
||||
@@ -245,6 +247,10 @@ lineage: schemas: [{
|
||||
// Accepted values are 0 (show label and value), 1 (show value only), 2 (show nothing), 3 (show under the controls dropdown menu).
|
||||
#VariableHide: 0 | 1 | 2 | 3 @cuetsy(kind="enum",memberNames="dontHide|hideLabel|hideVariable|inControlsMenu") @grafana(TSVeneer="type")
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are "value" (apply to value used in queries) or "text" (apply to display text shown to users)
|
||||
#VariableRegexApplyTo: "value" | "text" @cuetsy(kind="type")
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `0`: No sorting
|
||||
|
||||
+2
-2
@@ -296,8 +296,8 @@
|
||||
"@grafana/plugin-ui": "^0.11.1",
|
||||
"@grafana/prometheus": "workspace:*",
|
||||
"@grafana/runtime": "workspace:*",
|
||||
"@grafana/scenes": "6.47.1",
|
||||
"@grafana/scenes-react": "6.47.1",
|
||||
"@grafana/scenes": "6.49.0",
|
||||
"@grafana/scenes-react": "6.49.0",
|
||||
"@grafana/schema": "workspace:*",
|
||||
"@grafana/sql": "workspace:*",
|
||||
"@grafana/ui": "workspace:*",
|
||||
|
||||
@@ -521,6 +521,7 @@ export {
|
||||
VariableRefresh,
|
||||
VariableSort,
|
||||
VariableHide,
|
||||
type VariableRegexApplyTo,
|
||||
type VariableType,
|
||||
type VariableModel,
|
||||
type TypedVariableModel,
|
||||
|
||||
@@ -32,6 +32,8 @@ export enum VariableRefresh {
|
||||
onTimeRangeChanged,
|
||||
}
|
||||
|
||||
export type VariableRegexApplyTo = 'value' | 'text';
|
||||
|
||||
export enum VariableSort {
|
||||
disabled,
|
||||
alphabeticalAsc,
|
||||
@@ -117,6 +119,7 @@ export interface QueryVariableModel extends VariableWithMultiSupport {
|
||||
queryValue?: string;
|
||||
query: any;
|
||||
regex: string;
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
refresh: VariableRefresh;
|
||||
staticOptions?: VariableOption[];
|
||||
staticOptionsOrder?: 'before' | 'after' | 'sorted';
|
||||
|
||||
@@ -508,6 +508,9 @@ export const versionedPages = {
|
||||
queryOptionsRegExInputV2: {
|
||||
[MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Query RegEx field',
|
||||
},
|
||||
queryOptionsRegExApplyToSelectV2: {
|
||||
[MIN_GRAFANA_VERSION]: 'data-testid Variable editor Form Query RegExApplyTo select',
|
||||
},
|
||||
queryOptionsSortSelect: {
|
||||
[MIN_GRAFANA_VERSION]: 'Variable editor Form Query Sort select',
|
||||
},
|
||||
|
||||
Generated
+1
@@ -12,6 +12,7 @@ export type {
|
||||
AnnotationTarget,
|
||||
AnnotationPanelFilter,
|
||||
VariableOption,
|
||||
VariableRegexApplyTo,
|
||||
DashboardLink,
|
||||
DashboardLinkType,
|
||||
DashboardLinkPlacement,
|
||||
|
||||
@@ -187,6 +187,10 @@ export interface VariableModel {
|
||||
* Named capture groups can be used to separate the display text and value.
|
||||
*/
|
||||
regex?: string;
|
||||
/**
|
||||
* Determine whether regex applies to variable value or display text
|
||||
*/
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
/**
|
||||
* Whether the variable value should be managed by URL query params or not
|
||||
*/
|
||||
@@ -259,6 +263,12 @@ export enum VariableHide {
|
||||
inControlsMenu = 3,
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether regex applies to variable value or display text
|
||||
* Accepted values are "value" (apply to value used in queries) or "text" (apply to display text shown to users)
|
||||
*/
|
||||
export type VariableRegexApplyTo = ('value' | 'text');
|
||||
|
||||
/**
|
||||
* Sort variable options
|
||||
* Accepted values are:
|
||||
|
||||
@@ -293,6 +293,7 @@ export const handyTestingSchema: Spec = {
|
||||
},
|
||||
refresh: 'onDashboardLoad',
|
||||
regex: 'regex1',
|
||||
regexApplyTo: 'value',
|
||||
skipUrlSync: false,
|
||||
sort: 'disabled',
|
||||
allowCustomValue: true,
|
||||
|
||||
@@ -1105,6 +1105,7 @@ export interface QueryVariableSpec {
|
||||
datasource?: DataSourceRef;
|
||||
query: DataQueryKind;
|
||||
regex: string;
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
sort: VariableSort;
|
||||
definition?: string;
|
||||
options: VariableOption[];
|
||||
@@ -1125,6 +1126,7 @@ export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
|
||||
skipUrlSync: false,
|
||||
query: defaultDataQueryKind(),
|
||||
regex: "",
|
||||
regexApplyTo: "value",
|
||||
sort: "disabled",
|
||||
options: [],
|
||||
multi: false,
|
||||
@@ -1161,6 +1163,12 @@ export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
|
||||
export const defaultVariableRefresh = (): VariableRefresh => ("never");
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
export type VariableRegexApplyTo = "value" | "text";
|
||||
|
||||
export const defaultVariableRegexApplyTo = (): VariableRegexApplyTo => ("value");
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `disabled`: No sorting
|
||||
|
||||
@@ -1111,6 +1111,7 @@ export interface QueryVariableSpec {
|
||||
description?: string;
|
||||
query: DataQueryKind;
|
||||
regex: string;
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
sort: VariableSort;
|
||||
definition?: string;
|
||||
options: VariableOption[];
|
||||
@@ -1131,6 +1132,7 @@ export const defaultQueryVariableSpec = (): QueryVariableSpec => ({
|
||||
skipUrlSync: false,
|
||||
query: defaultDataQueryKind(),
|
||||
regex: "",
|
||||
regexApplyTo: "value",
|
||||
sort: "disabled",
|
||||
options: [],
|
||||
multi: false,
|
||||
@@ -1167,6 +1169,12 @@ export type VariableRefresh = "never" | "onDashboardLoad" | "onTimeRangeChanged"
|
||||
|
||||
export const defaultVariableRefresh = (): VariableRefresh => ("never");
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)
|
||||
export type VariableRegexApplyTo = "value" | "text";
|
||||
|
||||
export const defaultVariableRegexApplyTo = (): VariableRegexApplyTo => ("value");
|
||||
|
||||
// Sort variable options
|
||||
// Accepted values are:
|
||||
// `disabled`: No sorting
|
||||
|
||||
+11
@@ -834,6 +834,8 @@ 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"`
|
||||
// Determine whether regex applies to variable value or display text
|
||||
RegexApplyTo *VariableRegexApplyTo `json:"regexApplyTo,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
|
||||
@@ -942,6 +944,15 @@ const (
|
||||
VariableSortNaturalDesc VariableSort = 8
|
||||
)
|
||||
|
||||
// Determine whether regex applies to variable value or display text
|
||||
// Accepted values are "value" (apply to value used in queries) or "text" (apply to display text shown to users)
|
||||
type VariableRegexApplyTo string
|
||||
|
||||
const (
|
||||
VariableRegexApplyToValue VariableRegexApplyTo = "value"
|
||||
VariableRegexApplyToText VariableRegexApplyTo = "text"
|
||||
)
|
||||
|
||||
// Contains the list of annotations that are associated with the dashboard.
|
||||
// Annotations are used to overlay event markers and overlay event tags on graphs.
|
||||
// Grafana comes with a native annotation store and the ability to add annotation events directly from the graph panel or via the HTTP API.
|
||||
|
||||
@@ -3020,6 +3020,9 @@
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"regexApplyTo": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRegexApplyTo"
|
||||
},
|
||||
"skipUrlSync": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
@@ -3930,6 +3933,14 @@
|
||||
"onTimeRangeChanged"
|
||||
]
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableRegexApplyTo": {
|
||||
"description": "Determine whether regex applies to variable value or display text\nAccepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"value",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2alpha1.DashboardVariableSort": {
|
||||
"description": "Sort variable options\nAccepted values are:\n`disabled`: No sorting\n`alphabeticalAsc`: Alphabetical ASC\n`alphabeticalDesc`: Alphabetical DESC\n`numericalAsc`: Numerical ASC\n`numericalDesc`: Numerical DESC\n`alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC\n`alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC\n`naturalAsc`: Natural ASC\n`naturalDesc`: Natural DESC\nVariableSort enum with default value",
|
||||
"type": "string",
|
||||
|
||||
@@ -3047,6 +3047,9 @@
|
||||
"type": "string",
|
||||
"default": ""
|
||||
},
|
||||
"regexApplyTo": {
|
||||
"$ref": "#/components/schemas/com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableRegexApplyTo"
|
||||
},
|
||||
"skipUrlSync": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
@@ -3957,6 +3960,14 @@
|
||||
"onTimeRangeChanged"
|
||||
]
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableRegexApplyTo": {
|
||||
"description": "Determine whether regex applies to variable value or display text\nAccepted values are `value` (apply to value used in queries) or `text` (apply to display text shown to users)",
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"value",
|
||||
"text"
|
||||
]
|
||||
},
|
||||
"com.github.grafana.grafana.apps.dashboard.pkg.apis.dashboard.v2beta1.DashboardVariableSort": {
|
||||
"description": "Sort variable options\nAccepted values are:\n`disabled`: No sorting\n`alphabeticalAsc`: Alphabetical ASC\n`alphabeticalDesc`: Alphabetical DESC\n`numericalAsc`: Numerical ASC\n`numericalDesc`: Numerical DESC\n`alphabeticalCaseInsensitiveAsc`: Alphabetical Case Insensitive ASC\n`alphabeticalCaseInsensitiveDesc`: Alphabetical Case Insensitive DESC\n`naturalAsc`: Natural ASC\n`naturalDesc`: Natural DESC\nVariableSort enum with default value",
|
||||
"type": "string",
|
||||
|
||||
+3
@@ -323,6 +323,7 @@ exports[`Given a scene with custom quick ranges should save quick ranges to save
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"type": "query",
|
||||
},
|
||||
{
|
||||
@@ -1049,6 +1050,7 @@ exports[`transformSceneToSaveModel Given a simple scene with custom settings Sho
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"type": "query",
|
||||
},
|
||||
{
|
||||
@@ -1408,6 +1410,7 @@ exports[`transformSceneToSaveModel Given a simple scene with variables Should tr
|
||||
},
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"type": "query",
|
||||
},
|
||||
{
|
||||
|
||||
+1
@@ -173,6 +173,7 @@ exports[`transformSceneToSaveModelSchemaV2 should transform scene to save model
|
||||
},
|
||||
"refresh": "onDashboardLoad",
|
||||
"regex": "regex1",
|
||||
"regexApplyTo": "value",
|
||||
"skipUrlSync": false,
|
||||
"sort": "alphabeticalDesc",
|
||||
},
|
||||
|
||||
@@ -141,6 +141,7 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
"query": "query",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"staticOptions": [
|
||||
{
|
||||
"text": "test",
|
||||
@@ -205,6 +206,7 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
"query": "query",
|
||||
"refresh": 1,
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"staticOptions": [
|
||||
{
|
||||
"text": "test",
|
||||
@@ -1084,6 +1086,7 @@ describe('sceneVariablesSetToVariables', () => {
|
||||
},
|
||||
"refresh": "onDashboardLoad",
|
||||
"regex": "",
|
||||
"regexApplyTo": "value",
|
||||
"skipUrlSync": false,
|
||||
"sort": "disabled",
|
||||
"staticOptions": [
|
||||
|
||||
@@ -84,6 +84,7 @@ export function sceneVariablesSetToVariables(set: SceneVariables, keepQueryOptio
|
||||
sort: variable.state.sort,
|
||||
refresh: variable.state.refresh,
|
||||
regex: variable.state.regex,
|
||||
regexApplyTo: variable.state.regexApplyTo,
|
||||
allValue: variable.state.allValue,
|
||||
includeAll: variable.state.includeAll,
|
||||
multi: variable.state.isMulti,
|
||||
@@ -375,6 +376,7 @@ export function sceneVariablesSetToSchemaV2Variables(
|
||||
sort: transformSortVariableToEnum(variable.state.sort),
|
||||
refresh: transformVariableRefreshToEnum(variable.state.refresh),
|
||||
regex: variable.state.regex ?? '',
|
||||
regexApplyTo: variable.state.regexApplyTo ?? 'value',
|
||||
allValue: variable.state.allValue,
|
||||
includeAll: variable.state.includeAll || false,
|
||||
multi: variable.state.isMulti || false,
|
||||
|
||||
@@ -366,6 +366,7 @@ function createSceneVariableFromVariableModel(variable: TypedVariableModelV2): S
|
||||
sort: transformSortVariableToEnumV1(variable.spec.sort),
|
||||
refresh: transformVariableRefreshToEnumV1(variable.spec.refresh),
|
||||
regex: variable.spec.regex,
|
||||
regexApplyTo: variable.spec.regexApplyTo,
|
||||
allValue: variable.spec.allValue || undefined,
|
||||
includeAll: variable.spec.includeAll,
|
||||
defaultToAll: Boolean(variable.spec.includeAll),
|
||||
|
||||
+1
@@ -283,6 +283,7 @@ describe('transformSceneToSaveModelSchemaV2', () => {
|
||||
sort: VariableSortV1.alphabeticalDesc,
|
||||
refresh: VariableRefresh.onDashboardLoad,
|
||||
regex: 'regex1',
|
||||
regexApplyTo: 'value',
|
||||
allValue: '*',
|
||||
includeAll: true,
|
||||
isMulti: true,
|
||||
|
||||
+23
@@ -71,6 +71,7 @@ describe('QueryVariableEditorForm', () => {
|
||||
const mockOnQueryChange = jest.fn();
|
||||
const mockOnLegacyQueryChange = jest.fn();
|
||||
const mockOnRegExChange = jest.fn();
|
||||
const mockOnRegexApplyToChange = jest.fn();
|
||||
const mockOnSortChange = jest.fn();
|
||||
const mockOnRefreshChange = jest.fn();
|
||||
const mockOnMultiChange = jest.fn();
|
||||
@@ -89,6 +90,8 @@ describe('QueryVariableEditorForm', () => {
|
||||
timeRange: getDefaultTimeRange(),
|
||||
regex: '.*',
|
||||
onRegExChange: mockOnRegExChange,
|
||||
regexApplyTo: 'value',
|
||||
onRegexApplyToChange: mockOnRegexApplyToChange,
|
||||
sort: VariableSort.alphabeticalAsc,
|
||||
onSortChange: mockOnSortChange,
|
||||
refresh: VariableRefresh.onDashboardLoad,
|
||||
@@ -126,6 +129,9 @@ describe('QueryVariableEditorForm', () => {
|
||||
const regexInput = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2
|
||||
);
|
||||
const regexApplyToSelect = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExApplyToSelectV2
|
||||
);
|
||||
const sortSelect = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsSortSelectV2
|
||||
);
|
||||
@@ -154,6 +160,8 @@ describe('QueryVariableEditorForm', () => {
|
||||
expect(dataSourcePicker.getAttribute('placeholder')).toBe('Default Test Data Source');
|
||||
expect(regexInput).toBeInTheDocument();
|
||||
expect(regexInput).toHaveValue('.*');
|
||||
expect(regexApplyToSelect).toBeInTheDocument();
|
||||
expect(getByRole('radio', { name: 'Variable value' })).toBeChecked();
|
||||
expect(sortSelect).toBeInTheDocument();
|
||||
expect(sortSelect).toHaveTextContent('Alphabetical (asc)');
|
||||
expect(refreshSelect).toBeInTheDocument();
|
||||
@@ -213,6 +221,21 @@ describe('QueryVariableEditorForm', () => {
|
||||
).toBe('.?');
|
||||
});
|
||||
|
||||
it('should call onRegexApplyToChange when selecting the regex apply to option', async () => {
|
||||
const {
|
||||
renderer: { getByTestId },
|
||||
} = await setup();
|
||||
const regexApplyToSelect = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExApplyToSelectV2
|
||||
);
|
||||
await userEvent.click(regexApplyToSelect);
|
||||
const anotherOption = screen.getByText('Display text');
|
||||
await userEvent.click(anotherOption);
|
||||
|
||||
expect(mockOnRegexApplyToChange).toHaveBeenCalledTimes(1);
|
||||
expect(mockOnRegexApplyToChange).toHaveBeenCalledWith('text');
|
||||
});
|
||||
|
||||
it('should call onSortChange when changing the sort', async () => {
|
||||
const {
|
||||
renderer: { getByTestId },
|
||||
|
||||
+12
-29
@@ -1,14 +1,15 @@
|
||||
import { FormEvent, useCallback } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { DataSourceInstanceSettings, SelectableValue, TimeRange } from '@grafana/data';
|
||||
import { DataSourceInstanceSettings, SelectableValue, TimeRange, VariableRegexApplyTo } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { QueryVariable } from '@grafana/scenes';
|
||||
import { DataSourceRef, VariableRefresh, VariableSort } from '@grafana/schema';
|
||||
import { Field, TextLink } from '@grafana/ui';
|
||||
import { Field } from '@grafana/ui';
|
||||
import { QueryEditor } from 'app/features/dashboard-scene/settings/variables/components/QueryEditor';
|
||||
import { QueryVariableRegexForm } from 'app/features/dashboard-scene/settings/variables/components/QueryVariableRegexForm';
|
||||
import { SelectionOptionsForm } from 'app/features/dashboard-scene/settings/variables/components/SelectionOptionsForm';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { getVariableQueryEditor } from 'app/features/variables/editor/getVariableQueryEditor';
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
} from 'app/features/variables/query/QueryVariableStaticOptions';
|
||||
|
||||
import { VariableLegend } from './VariableLegend';
|
||||
import { VariableTextAreaField } from './VariableTextAreaField';
|
||||
|
||||
type VariableQueryType = QueryVariable['state']['query'];
|
||||
|
||||
@@ -34,6 +34,8 @@ interface QueryVariableEditorFormProps {
|
||||
timeRange: TimeRange;
|
||||
regex: string | null;
|
||||
onRegExChange: (event: FormEvent<HTMLTextAreaElement>) => void;
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
onRegexApplyToChange?: (event: VariableRegexApplyTo) => void;
|
||||
sort: VariableSort;
|
||||
onSortChange: (option: SelectableValue<VariableSort>) => void;
|
||||
refresh: VariableRefresh;
|
||||
@@ -61,6 +63,8 @@ export function QueryVariableEditorForm({
|
||||
timeRange,
|
||||
regex,
|
||||
onRegExChange,
|
||||
regexApplyTo,
|
||||
onRegexApplyToChange,
|
||||
sort,
|
||||
onSortChange,
|
||||
refresh,
|
||||
@@ -131,32 +135,11 @@ export function QueryVariableEditorForm({
|
||||
/>
|
||||
)}
|
||||
|
||||
<VariableTextAreaField
|
||||
defaultValue={regex ?? ''}
|
||||
name={t('dashboard-scene.query-variable-editor-form.name-regex', 'Regex')}
|
||||
description={
|
||||
<div>
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-optional">
|
||||
Optional, if you want to extract part of a series name or metric node segment.
|
||||
</Trans>
|
||||
<br />
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-examples">
|
||||
Named capture groups can be used to separate the display text and value (
|
||||
<TextLink
|
||||
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
|
||||
external
|
||||
>
|
||||
see examples
|
||||
</TextLink>
|
||||
).
|
||||
</Trans>
|
||||
</div>
|
||||
}
|
||||
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
|
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||
onBlur={onRegExChange}
|
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
|
||||
width={52}
|
||||
<QueryVariableRegexForm
|
||||
regex={regex}
|
||||
regexApplyTo={regexApplyTo}
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
|
||||
<QueryVariableSortSelect
|
||||
|
||||
+96
@@ -0,0 +1,96 @@
|
||||
import { render, fireEvent } from '@testing-library/react';
|
||||
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
|
||||
import { QueryVariableRegexForm } from './QueryVariableRegexForm';
|
||||
|
||||
describe('QueryVariableRegexForm', () => {
|
||||
const onRegExChange = jest.fn();
|
||||
const onRegexApplyToChange = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('should render the form fields correctly', () => {
|
||||
const { getByTestId, getByRole } = render(
|
||||
<QueryVariableRegexForm
|
||||
regex=".*test.*"
|
||||
regexApplyTo="value"
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const regexInput = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2
|
||||
);
|
||||
const regexApplyToField = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExApplyToSelectV2
|
||||
);
|
||||
|
||||
expect(regexInput).toBeInTheDocument();
|
||||
expect(regexInput).toHaveValue('.*test.*');
|
||||
expect(regexApplyToField).toBeInTheDocument();
|
||||
expect(getByRole('radio', { name: 'Variable value' })).toBeChecked();
|
||||
expect(getByRole('radio', { name: 'Display text' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should render with "Display text" option selected', () => {
|
||||
const { getByRole } = render(
|
||||
<QueryVariableRegexForm
|
||||
regex=""
|
||||
regexApplyTo="text"
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
);
|
||||
|
||||
expect(getByRole('radio', { name: 'Display text' })).toBeChecked();
|
||||
expect(getByRole('radio', { name: 'Variable value' })).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('should default to "Variable value" when regexApplyTo is not provided', () => {
|
||||
const { getByRole } = render(
|
||||
<QueryVariableRegexForm regex="" onRegExChange={onRegExChange} onRegexApplyToChange={onRegexApplyToChange} />
|
||||
);
|
||||
|
||||
expect(getByRole('radio', { name: 'Variable value' })).toBeChecked();
|
||||
});
|
||||
|
||||
it('should call onRegExChange when regex input is blurred', () => {
|
||||
const { getByTestId } = render(
|
||||
<QueryVariableRegexForm
|
||||
regex=".*"
|
||||
regexApplyTo="value"
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const regexInput = getByTestId(
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2
|
||||
);
|
||||
|
||||
fireEvent.blur(regexInput);
|
||||
|
||||
expect(onRegExChange).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
it('should call onRegexApplyToChange when radio option is changed', () => {
|
||||
const { getByRole } = render(
|
||||
<QueryVariableRegexForm
|
||||
regex=""
|
||||
regexApplyTo="value"
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
);
|
||||
|
||||
const displayTextOption = getByRole('radio', { name: 'Display text' });
|
||||
fireEvent.click(displayTextOption);
|
||||
|
||||
expect(onRegexApplyToChange).toHaveBeenCalledTimes(1);
|
||||
expect(onRegexApplyToChange).toHaveBeenCalledWith('text');
|
||||
});
|
||||
});
|
||||
+91
@@ -0,0 +1,91 @@
|
||||
import { useMemo, FormEvent } from 'react';
|
||||
|
||||
import { VariableRegexApplyTo, SelectableValue } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { Field, Stack, TextLink, RadioButtonGroup, Box } from '@grafana/ui';
|
||||
|
||||
import { VariableTextAreaField } from '../components/VariableTextAreaField';
|
||||
|
||||
interface Props {
|
||||
regex: string | null;
|
||||
onRegExChange: (event: FormEvent<HTMLTextAreaElement>) => void;
|
||||
regexApplyTo?: VariableRegexApplyTo;
|
||||
onRegexApplyToChange?: (option: VariableRegexApplyTo) => void;
|
||||
}
|
||||
|
||||
export function QueryVariableRegexForm({ regex, regexApplyTo, onRegExChange, onRegexApplyToChange }: Props) {
|
||||
const APPLY_REGEX_TO_OPTIONS: Array<SelectableValue<VariableRegexApplyTo>> = useMemo(
|
||||
() => [
|
||||
{
|
||||
label: t('dashboard-scene.query-variable-editor-form.regex-apply-to-options.label.value', 'Variable value'),
|
||||
value: 'value',
|
||||
},
|
||||
{
|
||||
label: t('dashboard-scene.query-variable-editor-form.regex-apply-to-options.label.text', 'Display text'),
|
||||
value: 'text',
|
||||
},
|
||||
],
|
||||
[]
|
||||
);
|
||||
|
||||
const regexApplyToValue = useMemo(
|
||||
() => APPLY_REGEX_TO_OPTIONS.find((o) => o.value === regexApplyTo)?.value ?? APPLY_REGEX_TO_OPTIONS[0].value,
|
||||
[regexApplyTo, APPLY_REGEX_TO_OPTIONS]
|
||||
);
|
||||
|
||||
return (
|
||||
<Box marginBottom={2}>
|
||||
<Stack direction="column" gap={2}>
|
||||
<VariableTextAreaField
|
||||
defaultValue={regex ?? ''}
|
||||
name={t('dashboard-scene.query-variable-editor-form.name-regex', 'Regex')}
|
||||
description={
|
||||
<div>
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-optional">
|
||||
Optional, if you want to extract part of a series name or metric node segment.
|
||||
</Trans>
|
||||
<br />
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-examples">
|
||||
Named capture groups can be used to separate the display text and value (
|
||||
<TextLink
|
||||
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
|
||||
external
|
||||
>
|
||||
see examples
|
||||
</TextLink>
|
||||
).
|
||||
</Trans>
|
||||
</div>
|
||||
}
|
||||
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
|
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||
onBlur={onRegExChange}
|
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
|
||||
width={52}
|
||||
noMargin
|
||||
/>
|
||||
|
||||
{onRegexApplyToChange && (
|
||||
<Field
|
||||
label={t('dashboard-scene.query-variable-editor-form.label-regex-apply-to', 'Apply regex to')}
|
||||
description={t(
|
||||
'dashboard-scene.query-variable-editor-form.description-regex-apply-to',
|
||||
'Choose whether to apply the regex pattern to the variable value or display text'
|
||||
)}
|
||||
data-testid={
|
||||
selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExApplyToSelectV2
|
||||
}
|
||||
noMargin
|
||||
>
|
||||
<RadioButtonGroup
|
||||
options={APPLY_REGEX_TO_OPTIONS}
|
||||
onChange={onRegexApplyToChange}
|
||||
value={regexApplyToValue}
|
||||
/>
|
||||
</Field>
|
||||
)}
|
||||
</Stack>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
+5
-4
@@ -1,7 +1,6 @@
|
||||
import { css } from '@emotion/css';
|
||||
import { useId } from '@react-aria/utils';
|
||||
import { FormEvent, PropsWithChildren, ReactElement } from 'react';
|
||||
import * as React from 'react';
|
||||
import { FormEvent, PropsWithChildren, ReactElement, ReactNode } from 'react';
|
||||
|
||||
import { GrafanaTheme2 } from '@grafana/data';
|
||||
import { Field, TextArea, useStyles2 } from '@grafana/ui';
|
||||
@@ -17,7 +16,8 @@ interface VariableTextAreaFieldProps {
|
||||
required?: boolean;
|
||||
testId?: string;
|
||||
onBlur?: (event: FormEvent<HTMLTextAreaElement>) => void;
|
||||
description?: React.ReactNode;
|
||||
description?: ReactNode;
|
||||
noMargin?: boolean;
|
||||
}
|
||||
|
||||
export function VariableTextAreaField({
|
||||
@@ -31,13 +31,14 @@ export function VariableTextAreaField({
|
||||
ariaLabel,
|
||||
required,
|
||||
width,
|
||||
noMargin,
|
||||
testId,
|
||||
}: PropsWithChildren<VariableTextAreaFieldProps>): ReactElement {
|
||||
const styles = useStyles2(getStyles);
|
||||
const id = useId();
|
||||
|
||||
return (
|
||||
<Field label={name} description={description} htmlFor={id}>
|
||||
<Field label={name} description={description} htmlFor={id} noMargin={noMargin}>
|
||||
<TextArea
|
||||
id={id}
|
||||
rows={2}
|
||||
|
||||
+1
@@ -73,6 +73,7 @@ describe('QueryVariableEditor', () => {
|
||||
},
|
||||
query: 'my-query',
|
||||
regex: '.*',
|
||||
regexApplyTo: 'value',
|
||||
sort: VariableSort.alphabeticalAsc,
|
||||
refresh: VariableRefresh.onDashboardLoad,
|
||||
isMulti: true,
|
||||
|
||||
+18
-31
@@ -1,15 +1,16 @@
|
||||
import { useState, FormEvent } from 'react';
|
||||
import { useAsync } from 'react-use';
|
||||
|
||||
import { SelectableValue, DataSourceInstanceSettings, getDataSourceRef } from '@grafana/data';
|
||||
import { SelectableValue, DataSourceInstanceSettings, getDataSourceRef, VariableRegexApplyTo } from '@grafana/data';
|
||||
import { selectors } from '@grafana/e2e-selectors';
|
||||
import { Trans, t } from '@grafana/i18n';
|
||||
import { getDataSourceSrv } from '@grafana/runtime';
|
||||
import { QueryVariable, sceneGraph, SceneVariable } from '@grafana/scenes';
|
||||
import { VariableRefresh, VariableSort } from '@grafana/schema';
|
||||
import { Box, Button, Field, Modal, TextLink } from '@grafana/ui';
|
||||
import { Box, Button, Field, Modal } from '@grafana/ui';
|
||||
import { OptionsPaneItemDescriptor } from 'app/features/dashboard/components/PanelEditor/OptionsPaneItemDescriptor';
|
||||
import { QueryEditor } from 'app/features/dashboard-scene/settings/variables/components/QueryEditor';
|
||||
import { QueryVariableRegexForm } from 'app/features/dashboard-scene/settings/variables/components/QueryVariableRegexForm';
|
||||
import { DataSourcePicker } from 'app/features/datasources/components/picker/DataSourcePicker';
|
||||
import { getVariableQueryEditor } from 'app/features/variables/editor/getVariableQueryEditor';
|
||||
import { QueryVariableRefreshSelect } from 'app/features/variables/query/QueryVariableRefreshSelect';
|
||||
@@ -21,7 +22,6 @@ import {
|
||||
} from 'app/features/variables/query/QueryVariableStaticOptions';
|
||||
|
||||
import { QueryVariableEditorForm } from '../components/QueryVariableForm';
|
||||
import { VariableTextAreaField } from '../components/VariableTextAreaField';
|
||||
import { VariableValuesPreview } from '../components/VariableValuesPreview';
|
||||
import { hasVariableOptions } from '../utils';
|
||||
|
||||
@@ -35,6 +35,7 @@ export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEdito
|
||||
const {
|
||||
datasource,
|
||||
regex,
|
||||
regexApplyTo,
|
||||
sort,
|
||||
refresh,
|
||||
isMulti,
|
||||
@@ -50,6 +51,9 @@ export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEdito
|
||||
const onRegExChange = (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
variable.setState({ regex: event.currentTarget.value });
|
||||
};
|
||||
const onRegexApplyToChange = (event: VariableRegexApplyTo) => {
|
||||
variable.setState({ regexApplyTo: event });
|
||||
};
|
||||
const onSortChange = (sort: SelectableValue<VariableSort>) => {
|
||||
variable.setState({ sort: sort.value });
|
||||
};
|
||||
@@ -102,7 +106,9 @@ export function QueryVariableEditor({ variable, onRunQuery }: QueryVariableEdito
|
||||
onLegacyQueryChange={onQueryChange}
|
||||
timeRange={timeRange}
|
||||
regex={regex}
|
||||
regexApplyTo={regexApplyTo}
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
sort={sort}
|
||||
onSortChange={onSortChange}
|
||||
refresh={refresh}
|
||||
@@ -196,6 +202,7 @@ export function Editor({ variable }: { variable: QueryVariable }) {
|
||||
refresh,
|
||||
query,
|
||||
regex,
|
||||
regexApplyTo,
|
||||
staticOptions,
|
||||
staticOptionsOrder,
|
||||
} = variable.useState();
|
||||
@@ -230,11 +237,12 @@ export function Editor({ variable }: { variable: QueryVariable }) {
|
||||
const onQueryChange = (query: VariableQueryType) => {
|
||||
variable.setState({ query, definition: getQueryDef(query) });
|
||||
};
|
||||
|
||||
const onRegExChange = (event: React.FormEvent<HTMLTextAreaElement>) => {
|
||||
variable.setState({ regex: event.currentTarget.value });
|
||||
};
|
||||
|
||||
const onRegexApplyToChange = (event: VariableRegexApplyTo) => {
|
||||
variable.setState({ regexApplyTo: event });
|
||||
};
|
||||
const onSortChange = (sort: SelectableValue<VariableSort>) => {
|
||||
variable.setState({ sort: sort.value });
|
||||
};
|
||||
@@ -271,32 +279,11 @@ export function Editor({ variable }: { variable: QueryVariable }) {
|
||||
/>
|
||||
)}
|
||||
|
||||
<VariableTextAreaField
|
||||
defaultValue={regex ?? ''}
|
||||
name={t('dashboard-scene.query-variable-editor-form.name-regex', 'Regex')}
|
||||
description={
|
||||
<div>
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-optional">
|
||||
Optional, if you want to extract part of a series name or metric node segment.
|
||||
</Trans>
|
||||
<br />
|
||||
<Trans i18nKey="dashboard-scene.query-variable-editor-form.description-examples">
|
||||
Named capture groups can be used to separate the display text and value (
|
||||
<TextLink
|
||||
href="https://grafana.com/docs/grafana/latest/variables/filter-variables-with-regex#filter-and-modify-using-named-text-and-value-capture-groups"
|
||||
external
|
||||
>
|
||||
see examples
|
||||
</TextLink>
|
||||
).
|
||||
</Trans>
|
||||
</div>
|
||||
}
|
||||
// eslint-disable-next-line @grafana/i18n/no-untranslated-strings
|
||||
placeholder="/.*-(?<text>.*)-(?<value>.*)-.*/"
|
||||
onBlur={onRegExChange}
|
||||
testId={selectors.pages.Dashboard.Settings.Variables.Edit.QueryVariable.queryOptionsRegExInputV2}
|
||||
width={52}
|
||||
<QueryVariableRegexForm
|
||||
regex={regex}
|
||||
regexApplyTo={regexApplyTo}
|
||||
onRegExChange={onRegExChange}
|
||||
onRegexApplyToChange={onRegexApplyToChange}
|
||||
/>
|
||||
|
||||
<QueryVariableSortSelect
|
||||
|
||||
@@ -104,6 +104,7 @@ describe('when creating variables objects', () => {
|
||||
type: 'custom',
|
||||
value: 'a',
|
||||
hide: 0,
|
||||
valuesFormat: 'csv',
|
||||
});
|
||||
});
|
||||
|
||||
@@ -184,6 +185,7 @@ describe('when creating variables objects', () => {
|
||||
query: 'SHOW TAG VALUES WITH KEY = "datacenter" ',
|
||||
refresh: 1,
|
||||
regex: '',
|
||||
regexApplyTo: 'value',
|
||||
skipUrlSync: false,
|
||||
sort: 0,
|
||||
text: 'America',
|
||||
|
||||
@@ -189,12 +189,12 @@ export function createSceneVariableFromVariableModel(variable: TypedVariableMode
|
||||
...commonProperties,
|
||||
value: variable.current?.value ?? '',
|
||||
text: variable.current?.text ?? '',
|
||||
|
||||
query: variable.query ?? {},
|
||||
datasource: variable.datasource,
|
||||
sort: variable.sort,
|
||||
refresh: variable.refresh,
|
||||
regex: variable.regex,
|
||||
...(variable.regexApplyTo && { regexApplyTo: variable.regexApplyTo }),
|
||||
allValue: variable.allValue || undefined,
|
||||
includeAll: variable.includeAll,
|
||||
defaultToAll: Boolean(variable.includeAll),
|
||||
|
||||
@@ -654,6 +654,7 @@ function getVariables(vars: TypedVariableModel[]): DashboardV2Spec['variables']
|
||||
...(v.definition && { definition: v.definition }),
|
||||
refresh: transformVariableRefreshToEnum(v.refresh),
|
||||
regex: v.regex ?? '',
|
||||
...(v.regexApplyTo && { regexApplyTo: v.regexApplyTo }),
|
||||
sort: v.sort ? transformSortVariableToEnum(v.sort) : 'disabled',
|
||||
query: {
|
||||
kind: 'DataQuery',
|
||||
@@ -919,6 +920,7 @@ function getVariablesV1(vars: DashboardV2Spec['variables']): VariableModel[] {
|
||||
sort: transformSortVariableToEnumV1(v.spec.sort),
|
||||
refresh: transformVariableRefreshToEnumV1(v.spec.refresh),
|
||||
regex: v.spec.regex,
|
||||
regexApplyTo: v.spec.regexApplyTo,
|
||||
allValue: v.spec.allValue,
|
||||
includeAll: v.spec.includeAll,
|
||||
multi: v.spec.multi,
|
||||
|
||||
@@ -6271,12 +6271,20 @@
|
||||
"query-variable-editor-form": {
|
||||
"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.",
|
||||
"description-regex-apply-to": "Choose whether to apply the regex pattern to the variable value or display text",
|
||||
"label-data-source": "Data source",
|
||||
"label-regex-apply-to": "Apply regex to",
|
||||
"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",
|
||||
"regex-apply-to-options": {
|
||||
"label": {
|
||||
"text": "Display text",
|
||||
"value": "Variable value"
|
||||
}
|
||||
},
|
||||
"selection-options": "Selection options",
|
||||
"static-options-legend": "Static options"
|
||||
},
|
||||
|
||||
@@ -3603,11 +3603,11 @@ __metadata:
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
"@grafana/scenes-react@npm:6.47.1":
|
||||
version: 6.47.1
|
||||
resolution: "@grafana/scenes-react@npm:6.47.1"
|
||||
"@grafana/scenes-react@npm:6.49.0":
|
||||
version: 6.49.0
|
||||
resolution: "@grafana/scenes-react@npm:6.49.0"
|
||||
dependencies:
|
||||
"@grafana/scenes": "npm:6.47.1"
|
||||
"@grafana/scenes": "npm:6.49.0"
|
||||
lru-cache: "npm:^10.2.2"
|
||||
react-use: "npm:^17.4.0"
|
||||
peerDependencies:
|
||||
@@ -3619,7 +3619,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/dc20f9ee80eaf648665f7449e3ccb3b640a931f8f4a1be89599dce17eb0f52e763e3a603a4d491d9886b3e6cdf2ad3634124252c223315917206200f7cd6da16
|
||||
checksum: 10/0f9ba2ccaf2c8a703f3c4867320852c07f640aea85491e6c1a35d7fd25b48e69740f7782b52280ab4d423a6e87659de41cf66904acc865c9ea1f934ded6c7e64
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -3649,9 +3649,9 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@grafana/scenes@npm:6.47.1":
|
||||
version: 6.47.1
|
||||
resolution: "@grafana/scenes@npm:6.47.1"
|
||||
"@grafana/scenes@npm:6.49.0":
|
||||
version: 6.49.0
|
||||
resolution: "@grafana/scenes@npm:6.49.0"
|
||||
dependencies:
|
||||
"@floating-ui/react": "npm:^0.26.16"
|
||||
"@leeoniya/ufuzzy": "npm:^1.0.16"
|
||||
@@ -3671,7 +3671,7 @@ __metadata:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
react-router-dom: ^6.28.0
|
||||
checksum: 10/bc0c76258955058e7493b04e7cdd5d59dcc4159adf06da0837e992716ea15700b54f8403614df04326350363dc3344fb2602a2e8f7807724571659b4bd95aded
|
||||
checksum: 10/0e873ceac0834879ade41df56ced5a3e8f6a10e5aba15a6f271327aa804729402bcf44845e54614dd0fb1541a28efbb9a5499acceae04cb8be3b11bcdc230347
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -19441,8 +19441,8 @@ __metadata:
|
||||
"@grafana/plugin-ui": "npm:^0.11.1"
|
||||
"@grafana/prometheus": "workspace:*"
|
||||
"@grafana/runtime": "workspace:*"
|
||||
"@grafana/scenes": "npm:6.47.1"
|
||||
"@grafana/scenes-react": "npm:6.47.1"
|
||||
"@grafana/scenes": "npm:6.49.0"
|
||||
"@grafana/scenes-react": "npm:6.49.0"
|
||||
"@grafana/schema": "workspace:*"
|
||||
"@grafana/sql": "workspace:*"
|
||||
"@grafana/test-utils": "workspace:*"
|
||||
|
||||
Reference in New Issue
Block a user