Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Dashboard variables set default value on import #3399

Merged
merged 1 commit into from
Dec 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
76 changes: 76 additions & 0 deletions cmd/tools/grafana/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@ type options struct {
customizeDir string
customAllValue string
customCluster string
varDefaults string
defaultDropdownMap map[string][]string
}

type Folder struct {
Expand Down Expand Up @@ -437,6 +439,30 @@ func initImportVars() {
}
opts.dirGrafanaFolderMap[k] = v
}

// Parse default dropdown values
opts.defaultDropdownMap = make(map[string][]string)
if opts.varDefaults != "" {
if !validateVarDefaults(opts.varDefaults) {
fmt.Println("Error: Invalid format for --var-defaults. Expected format is 'variable1=value1,value2;variable2=value3'")
os.Exit(1)
}
pairs := strings.Split(opts.varDefaults, ";")
for _, pair := range pairs {
parts := strings.SplitN(pair, "=", 2)
if len(parts) == 2 {
values := strings.Split(parts[1], ",")
opts.defaultDropdownMap[parts[0]] = values
}
}
}
}

// validateVarDefaults validates the format of the --var-defaults input string.
// The expected format is 'variable1=value1,value2;variable2=value3'.
func validateVarDefaults(input string) bool {
re := regexp.MustCompile(`^([^=;,]+=[^=;,]+(,[^=;,]+)*)(;[^=;,]+=[^=;,]+(,[^=;,]+)*)*$`)
return re.MatchString(input)
}

func checkAndCreateServerFolder(folder *Folder) error {
Expand Down Expand Up @@ -519,6 +545,11 @@ func importFiles(dir string, folder *Folder) {
data = changeClusterLabel(data, opts.customCluster)
}

// Set default dropdown values if provided
if len(opts.defaultDropdownMap) > 0 {
data = setDefaultDropdownValues(data, opts.defaultDropdownMap)
}

// labelMap is used to ensure we don't modify the query of one of the new labels we're adding
labelMap := make(map[string]string)
caser := cases.Title(language.Und)
Expand Down Expand Up @@ -590,6 +621,24 @@ func importFiles(dir string, folder *Folder) {
}
}

// setDefaultDropdownValues sets the default values for specified dropdown variables in the dashboard JSON data.
// It takes a map of variable names to their default values and updates the JSON data accordingly.
func setDefaultDropdownValues(data []byte, defaultValues map[string][]string) []byte {
for variable, defaultValues := range defaultValues {
variablePath := fmt.Sprintf("templating.list.#(name=%q)", variable)
variableData := gjson.GetBytes(data, variablePath)
if variableData.Exists() {
current := map[string]any{
"selected": true,
"text": defaultValues,
"value": defaultValues,
}
data, _ = sjson.SetBytes(data, variablePath+".current", current)
}
}
return data
}

// This function will rewrite all panel expressions in the dashboard to use the new cluster label.
// Example:
// sum(write_data{datacenter=~"$Datacenter",cluster=~"$Cluster",svm=~"$SVM"})
Expand Down Expand Up @@ -1293,13 +1342,40 @@ func init() {
addCommonFlags(importCmd, exportCmd, customizeCmd)
addImportExportFlags(importCmd, exportCmd)
addImportCustomizeFlags(importCmd, customizeCmd)
addImportFlags(importCmd)

customizeCmd.PersistentFlags().StringVarP(&opts.customizeDir, "output-dir", "o", "", "Write customized dashboards to the local directory. The directory must not exist")

metricsCmd.PersistentFlags().StringVarP(&opts.dir, "directory", "d",
"", "local directory that contains dashboards (searched recursively).")
}

func addImportFlags(cmd *cobra.Command) {
cmd.PersistentFlags().StringVar(&opts.varDefaults, "var-defaults", "", `
Default values for dropdown variables in the format 'variable1=value1,value2;variable2=value3'.
Examples:
1. Set a single variable:
To set the default value for the 'Datacenter' variable to 'DC1':
--var-defaults "Datacenter=DC1"
2. Set multiple values for a single variable:
To set the default values for the 'Datacenter' variable to 'DC1' and 'DC2':
--var-defaults "Datacenter=DC1,DC2"
3. Set multiple variables in one command:
To set the default values for 'Datacenter' to 'DC1' and 'DC2', and 'Cluster' to 'Cluster1':
--var-defaults "Datacenter=DC1,DC2;Cluster=Cluster1"
4. Set multiple values for multiple variables:
To set the default values for 'Datacenter' to 'DC1' and 'DC2', and 'Cluster' to 'Cluster1' and 'Cluster2':
--var-defaults "Datacenter=DC1,DC2;Cluster=Cluster1,Cluster2"
Note: Ensure that variable names and values do not contain the characters '=', ',', or ';' as these are used as delimiters.
`)
}

func addImportCustomizeFlags(commands ...*cobra.Command) {
for _, cmd := range commands {
cmd.PersistentFlags().StringSliceVar(&opts.labels, "labels", nil,
Expand Down
30 changes: 30 additions & 0 deletions cmd/tools/grafana/grafana_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -675,3 +675,33 @@ func TestClusterRewrite(t *testing.T) {
})
}
}

func TestValidateVarDefaults(t *testing.T) {
tests := []struct {
input string
want bool
}{
{input: "Datacenter=DC1,DC2;Cluster=Cluster1", want: true},
{input: "Datacenter=DC1;Cluster=Cluster1,Cluster2", want: true},
{input: "Datacenter=DC1,DC2;Cluster=Cluster1;Region=US,EU", want: true},
{input: "Datacenter=DC1,DC2;Cluster=Cluster1;Region=US,EU;SVM=SAN,NAS", want: true},
{input: "Datacenter=DC1,DC2", want: true},
{input: "Datacenter=nane,rtp;Cluster=Cluster2,A250-15-28-29", want: true},
{input: "Datacenter=nane,rtp;Cluster=Cluster2,A250-15-28#29", want: true},
{input: "Datacenter=DC1,DC2;", want: false}, // trailing semicolon
{input: "=DC1,DC2;Cluster=Cluster1", want: false}, // missing variable name
{input: "Datacenter=;Cluster=Cluster1", want: false}, // missing value
{input: "Datacenter=DC1,DC2;Cluster=", want: false}, // missing value
{input: "Datacenter=DC1,DC2;Cluster", want: false}, // missing equals sign
{input: "Datacenter=DC1,DC2;Cluster=Cluster1;", want: false}, // trailing semicolon
}

for _, tt := range tests {
t.Run(tt.input, func(t *testing.T) {
got := validateVarDefaults(tt.input)
if got != tt.want {
t.Errorf("validateVarDefaults(%q) = %v, want %v", tt.input, got, tt.want)
}
})
}
}
Loading