Skip to content

Commit

Permalink
feat: linking dashboard part-1 (#2931)
Browse files Browse the repository at this point in the history
* feat: linking dashboard volume, aggregate, svm part-1

* feat: linking dashboards - Volume

* feat: linking dashboards - import/export tested, generic folder names

* feat: folder name correction

* feat: handling review comments

* feat: linking dashboard: volume, aggr and svm with test (#2949)

* feat: linking dashboard: volume, aggr and svm with test

* feat: handling review comments
  • Loading branch information
Hardikl authored Jun 4, 2024
1 parent 70a1550 commit 3b920f6
Show file tree
Hide file tree
Showing 42 changed files with 740 additions and 144 deletions.
64 changes: 34 additions & 30 deletions cmd/tools/grafana/dashboard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -762,7 +762,7 @@ func TestIDIsBlank(t *testing.T) {
VisitDashboards(
dashboards,
func(path string, data []byte) {
checkUIDIsBlank(t, path, data)
checkUIDNotEmpty(t, path, data)
checkIDIsNull(t, path, data)
})
}
Expand All @@ -781,19 +781,11 @@ func checkExemplarIsFalse(t *testing.T, path string, data []byte) {
}
}

var uidExceptions = map[string]bool{
"cmode-details/volumeBySVM.json": true,
"cmode-details/volumeDeepDive.json": true,
}

func checkUIDIsBlank(t *testing.T, path string, data []byte) {
func checkUIDNotEmpty(t *testing.T, path string, data []byte) {
path = ShortPath(path)
if uidExceptions[path] {
return
}
uid := gjson.GetBytes(data, "uid").String()
if uid != "" {
t.Errorf(`dashboard=%s uid should be "" but is %s`, path, uid)
if uid == "" {
t.Errorf(`dashboard=%s uid is "", but should not be empty`, path)
}
}

Expand Down Expand Up @@ -1626,17 +1618,17 @@ func checkVariablesAreFSxFriendly(t *testing.T, path string, data []byte) {
}

var linkPath = regexp.MustCompile(`/d/(.*?)/`)
var supportedLinkedObjects = []string{"cluster", "datacenter", "aggr", "svm", "volume", "node", "home_node"}

func TestLinks(t *testing.T) {
hasLinks := map[string][]string{}
uids := map[string]string{}

VisitDashboards(dashboards, func(path string, data []byte) {
checkLinks(path, data, hasLinks, uids)
checkLinks(t, path, data, hasLinks, uids)
})

// Check that the links are valid URLs and that the link points to an existing dashboard

for path, list := range hasLinks {
hasOrgID := false
for _, link := range list {
Expand Down Expand Up @@ -1692,31 +1684,43 @@ func TestLinks(t *testing.T) {
}
}

func checkLinks(path string, data []byte, hasLinks map[string][]string, uids map[string]string) {
func checkLinks(t *testing.T, path string, data []byte, hasLinks map[string][]string, uids map[string]string) {
dashPath := ShortPath(path)

VisitAllPanels(data, func(_ string, _, value gjson.Result) {
checkPanelLinks(value, dashPath, hasLinks)
checkPanelLinks(t, value, dashPath, hasLinks)
})

uid := gjson.GetBytes(data, "uid").String()
if uid != "" {
uids[uid] = dashPath
}
uids[uid] = dashPath
}

func checkPanelLinks(value gjson.Result, path string, hasLinks map[string][]string) {
value.Get("fieldConfig.overrides").ForEach(func(_, anOverride gjson.Result) bool {
anOverride.Get("properties").ForEach(func(_, propValue gjson.Result) bool {
propValue.Get("value").ForEach(func(_, value gjson.Result) bool {
link := value.Get("url").String()
if link != "" {
hasLinks[path] = append(hasLinks[path], link)
func checkPanelLinks(t *testing.T, value gjson.Result, path string, hasLinks map[string][]string) {
linkFound := false

// Testing only for volume/aggregate/svm for now, it will be covered for all later
supportedDashboards := []string{"cmode/volume.json", "cmode/aggregate.json", "cmode/svm.json"}

if slices.Contains(supportedDashboards, path) && value.Get("type").String() == "table" {
value.Get("fieldConfig.overrides").ForEach(func(_, anOverride gjson.Result) bool {
if name := anOverride.Get("matcher.options").String(); slices.Contains(supportedLinkedObjects, name) {
linkFound = false
anOverride.Get("properties").ForEach(func(_, propValue gjson.Result) bool {
propValue.Get("value").ForEach(func(_, value gjson.Result) bool {
link := value.Get("url").String()
if link != "" {
linkFound = true
hasLinks[path] = append(hasLinks[path], link)
}
return true
})
return true
})
if !linkFound {
t.Errorf(`dashboard=%s panel="%s" column=%s is missing the links`, path, value.Get("title").String(), name)
}
return true
})
}
return true
})
return true
})
}
}
47 changes: 10 additions & 37 deletions cmd/tools/grafana/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"encoding/json"
"errors"
"fmt"
"github.com/netapp/harvest/v2/cmd/harvest/version"
"github.com/netapp/harvest/v2/pkg/conf"
"github.com/netapp/harvest/v2/pkg/requests"
"github.com/netapp/harvest/v2/pkg/util"
Expand Down Expand Up @@ -39,7 +38,6 @@ const (
var (
grafanaMinVers = "7.1.0" // lowest grafana version we require
homePath string
harvestRelease = version.VERSION
)

type options struct {
Expand Down Expand Up @@ -422,10 +420,10 @@ func initImportVars() {
// default behaviour
switch {
case opts.dir == "grafana/dashboards" && opts.serverfolder.name == "":
m[filepath.Join(opts.dir, "cmode")] = &Folder{name: "Harvest-" + harvestRelease + "-cDOT"}
m[filepath.Join(opts.dir, "cmode-details")] = &Folder{name: "Harvest-" + harvestRelease + "-cDOT Details"}
m[filepath.Join(opts.dir, "7mode")] = &Folder{name: "Harvest-" + harvestRelease + "-7mode"}
m[filepath.Join(opts.dir, "storagegrid")] = &Folder{name: "Harvest-" + harvestRelease + "-StorageGrid"}
m[filepath.Join(opts.dir, "cmode")] = &Folder{name: "Harvest-main-cDOT"}
m[filepath.Join(opts.dir, "cmode", "details")] = &Folder{name: "Harvest-main-cDOT Details"}
m[filepath.Join(opts.dir, "7mode")] = &Folder{name: "Harvest-main-7mode"}
m[filepath.Join(opts.dir, "storagegrid")] = &Folder{name: "Harvest-main-StorageGrid"}
case opts.dir != "" && opts.serverfolder.name != "":
m[opts.dir] = &Folder{name: opts.serverfolder.name}
case opts.dir != "" && opts.customizeDir != "":
Expand Down Expand Up @@ -475,6 +473,12 @@ func exitIfExist(fp string, s string) {
}

func importDashboards(opts *options) {
if opts.overwrite {
fmt.Printf("warning: The overwrite flag is no longer used and will be removed in a future release. Please remove this flag from your command line invocation. When importing, dashboards are always overwritten.\n")
}
// Set overwrite flag to true, dashboards are always overwritten.
opts.overwrite = true

for k, v := range opts.dirGrafanaFolderMap {
importFiles(k, v)
}
Expand Down Expand Up @@ -507,37 +511,6 @@ func importFiles(dir string, folder *Folder) {

data = bytes.ReplaceAll(data, []byte("${DS_PROMETHEUS}"), []byte(opts.datasource))

// If the dashboard has an uid defined, change the uid to the empty string, unless overwrite is true.
// We do comparison for dashboard create/update based on title
if !opts.overwrite {

// Don't change the uid of linked dashboards since that will break the links
isLinkedDashboard := file.Name() == "volumeBySVM.json" || file.Name() == "volumeDeepDive.json"

if !isLinkedDashboard {
dashboardID := gjson.GetBytes(data, "uid").String()
if dashboardID != "" {
data, err = sjson.SetBytes(data, "uid", []byte(""))
if err != nil {
fmt.Printf("error while updating the uid %s into dashboard %s, err: %+v", dashboardID, file.Name(), err)
continue
}
}
}
}

// If the dashboard has an id defined, change the id to empty string, unless overwrite was passed,
// so Grafana treats this as a new dashboard instead of an update to an existing one
if !opts.overwrite {
if dashboardID := gjson.GetBytes(data, "id").String(); dashboardID != "" {
data, err = sjson.SetBytes(data, "id", []byte(""))
if err != nil {
fmt.Printf("error while updating the id %s into dashboard %s, err: %+v", dashboardID, file.Name(), err)
continue
}
}
}

// add svm regex
if opts.svmRegex != "" {
data = addSvmRegex(data, file.Name(), opts.svmRegex)
Expand Down
4 changes: 2 additions & 2 deletions grafana/dashboards/cmode-details/volumeBySVM.json
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@
{
"targetBlank": true,
"title": "Drill Down",
"url": "/d/IkXhW11Iz/ontap-volume-deep-dive?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${SVM:queryparam}&${__url_time_range}&var-Volume=${__value.raw}"
"url": "/d/cdot-volume-deep-dive/ontap-volume-deep-dive?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${SVM:queryparam}&${__url_time_range}&var-Volume=${__value.raw}"
}
]
}
Expand Down Expand Up @@ -478,6 +478,6 @@
"timepicker": {},
"timezone": "",
"title": "ONTAP: Volume by SVM",
"uid": "bb326bd1",
"uid": "cdot-volume-by-svm",
"version": 1
}
2 changes: 1 addition & 1 deletion grafana/dashboards/cmode-details/volumeDeepDive.json
Original file line number Diff line number Diff line change
Expand Up @@ -3586,6 +3586,6 @@
"timepicker": {},
"timezone": "",
"title": "ONTAP: Volume Deep Dive",
"uid": "IkXhW11Iz",
"uid": "cdot-volume-deep-dive",
"version": 2
}
102 changes: 93 additions & 9 deletions grafana/dashboards/cmode/aggregate.json
Original file line number Diff line number Diff line change
Expand Up @@ -734,36 +734,104 @@
{
"matcher": {
"id": "byName",
"options": "Datacenter"
"options": "datacenter"
},
"properties": [
{
"id": "displayName",
"value": "Datacenter"
},
{
"id": "custom.width",
"value": 114
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-datacenter/ontap-datacenter?orgId=1&${__url_time_range}&var-Datacenter=${__value.raw}"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "Node"
"options": "node"
},
"properties": [
{
"id": "displayName",
"value": "Node"
},
{
"id": "custom.width",
"value": 126
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-node/ontap-node?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-Node=${__value.raw}"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "Cluster"
"options": "cluster"
},
"properties": [
{
"id": "displayName",
"value": "Cluster"
},
{
"id": "custom.width",
"value": 121
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-cluster/ontap-cluster?orgId=1&${Datacenter:queryparam}&${__url_time_range}&var-Cluster=${__value.raw}"
}
]
}
]
},
{
"matcher": {
"id": "byName",
"options": "aggr"
},
"properties": [
{
"id": "displayName",
"value": "Aggregate"
},
{
"id": "custom.width",
"value": 260
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-aggregate/ontap-aggregate?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-Aggregate=${__value.raw}"
}
]
}
]
},
Expand Down Expand Up @@ -1022,11 +1090,7 @@
"Value #E": "Used",
"Value #F": "Used %",
"Value #H": "Available",
"aggr": "Aggregate",
"cluster": "Cluster",
"datacenter": "Datacenter",
"is_encrypted": "Encrypted",
"node": "Node"
"is_encrypted": "Encrypted"
}
}
}
Expand Down Expand Up @@ -3237,6 +3301,16 @@
{
"id": "custom.width",
"value": 300
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-node/ontap-node?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-Node=${__value.raw}"
}
]
}
]
},
Expand All @@ -3253,6 +3327,16 @@
{
"id": "custom.width",
"value": 300
},
{
"id": "links",
"value": [
{
"targetBlank": true,
"title": "",
"url": "/d/cdot-aggregate/ontap-aggregate?orgId=1&${Datacenter:queryparam}&${Cluster:queryparam}&${__url_time_range}&var-Aggregate=${__value.raw}"
}
]
}
]
}
Expand Down Expand Up @@ -6119,6 +6203,6 @@
"timepicker": {},
"timezone": "",
"title": "ONTAP: Aggregate",
"uid": "",
"uid": "cdot-aggregate",
"version": 11
}
2 changes: 1 addition & 1 deletion grafana/dashboards/cmode/cdot.json
Original file line number Diff line number Diff line change
Expand Up @@ -2192,6 +2192,6 @@
},
"timezone": "",
"title": "ONTAP: cDOT",
"uid": "",
"uid": "cdot-cdot",
"version": 4
}
2 changes: 1 addition & 1 deletion grafana/dashboards/cmode/changelogMonitor.json
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,6 @@
},
"timezone": "",
"title": "ONTAP: Changelog Monitor",
"uid": "",
"uid": "cdot-changelog-monitor",
"version": 1
}
Loading

0 comments on commit 3b920f6

Please sign in to comment.