Skip to content

Commit

Permalink
fix: Set a viewport to browser when fetching dashboard model
Browse files Browse the repository at this point in the history
* Without a fixed viewport, seems like chromium is choosing one at runtime based on server and ending up with wierd panel sizes. By setting a viewport, we should be able to control the panel sizes better.

Signed-off-by: Mahendra Paipuri <mahendra.paipuri@gmail.com>
  • Loading branch information
mahendrapaipuri committed Sep 17, 2024
1 parent d06dfc4 commit 82710dd
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 25 deletions.
19 changes: 19 additions & 0 deletions pkg/plugin/client/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,23 @@ var (
dashboardDataJS = `[...document.getElementsByClassName('react-grid-item')].map((e) => ({"width": e.style.width, "height": e.style.height, "transform": e.style.transform, "id": e.getAttribute("data-panelid")}))`
)

// Browser vars.
var (
// We must set a view port to browser to ensure chromedp (or chromium)
// does not choose one randomly based on the current host.
//
// The idea here is to use a "regular" viewport of 1920x1080. However
// seems like Grafana uses a 16px margin on either side and hence the
// "effective" width of panels is only 1888px which is not a multiple of
// 24 (which is column measure of Grafana panels). So we add that additional
// 32px + 1920px = 1952px so that "effective" width becomes 1920px which is
// multiple of 24. This should give us nicer panels without overlaps.
//
// This can be flaky though! Need to make it better in the future?!
viewportWidth int64 = 1952
viewportHeight int64 = 1080
)

var getPanelRetrySleepTime = time.Duration(10) * time.Second

// Grafana is a Grafana API httpClient.
Expand Down Expand Up @@ -215,7 +232,9 @@ func (g GrafanaClient) dashboardFromBrowser(dashUID string) ([]interface{}, erro
var dashboardData []interface{}

// JS that will fetch dashboard model
tasks = append(tasks, chromedp.EmulateViewport(viewportWidth, viewportHeight))
tasks = append(tasks, chromedp.Evaluate(dashboardDataJS, &dashboardData))

if err := tab.Run(tasks); err != nil {
return nil, fmt.Errorf("error fetching dashboard URL from browser %s: %w", dashURL, err)
}
Expand Down
50 changes: 25 additions & 25 deletions pkg/plugin/dashboard/dashboard.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math"
"net/url"
"regexp"
"slices"
Expand All @@ -16,10 +17,15 @@ import (

// Regex for parsing X and Y co-ordinates from CSS
// Scales for converting width and height to Grafana units.
//
// This is based on viewportWidth that we used in client.go which
// is 1952px. Stripping margin 32px we get 1920px / 24 = 80px
// height scale should be fine with 36px as width and aspect ratio
// should choose a height appropriately.
var (
translateRegex = regexp.MustCompile(`translate\((?P<X>\d+)px, (?P<Y>\d+)px\)`)
scales = map[string]int{
"width": 30,
scales = map[string]float64{
"width": 80,
"height": 36,
}
)
Expand Down Expand Up @@ -170,10 +176,12 @@ func panelsFromBrowser(dashData []interface{}) ([]Panel, error) {
)

// Iterate over the slice of interfaces and build each panel
for _, p := range dashData {
var id, x, y, w, h, vInt, xInt, yInt int
for _, panelData := range dashData {
var vInt, xInt, yInt float64

pMap, ok := p.(map[string]interface{})
var p Panel

pMap, ok := panelData.(map[string]interface{})
if !ok {
continue
}
Expand All @@ -186,58 +194,50 @@ func panelsFromBrowser(dashData []interface{}) ([]Panel, error) {

switch k {
case "width":
if vInt, err = strconv.Atoi(strings.TrimSuffix(vString, "px")); err != nil {
if vInt, err = strconv.ParseFloat(strings.TrimSuffix(vString, "px"), 64); err != nil {
allErrs = errors.Join(err, allErrs)
}

w = vInt / scales[k]
p.GridPos.W = math.Round(vInt / scales[k])
case "height":
if vInt, err = strconv.Atoi(strings.TrimSuffix(vString, "px")); err != nil {
if vInt, err = strconv.ParseFloat(strings.TrimSuffix(vString, "px"), 64); err != nil {
allErrs = errors.Join(err, allErrs)
}

h = vInt / scales[k]
p.GridPos.H = math.Round(vInt / scales[k])
case "transform":
matches := translateRegex.FindStringSubmatch(vString)
if len(matches) == 3 {
xCoord := matches[translateRegex.SubexpIndex("X")]
if xInt, err = strconv.Atoi(xCoord); err != nil {
if xInt, err = strconv.ParseFloat(xCoord, 64); err != nil {
allErrs = errors.Join(err, allErrs)
} else {
x = xInt / scales["width"]
p.GridPos.X = math.Round(xInt / scales["width"])
}

yCoord := matches[translateRegex.SubexpIndex("Y")]
if yInt, err = strconv.Atoi(yCoord); err != nil {
if yInt, err = strconv.ParseFloat(yCoord, 64); err != nil {
allErrs = errors.Join(err, allErrs)
} else {
y = yInt / scales["height"]
p.GridPos.Y = math.Round(yInt / scales["height"])
}
} else {
allErrs = errors.Join(errors.New("failed to capture X and Y coordinate regex groups"), allErrs)
}
case "id":
if id, err = strconv.Atoi(vString); err != nil {
if p.ID, err = strconv.Atoi(vString); err != nil {
allErrs = errors.Join(err, allErrs)
}
}
}

// If height comes to zero, it is row panel and ignore it
if h == 0 {
// If height comes to 1 or less, it is row panel and ignore it
if p.GridPos.H <= 1 {
continue
}

// Create panel model and append to panels
panels = append(panels, Panel{
ID: id,
GridPos: GridPos{
X: float64(x),
Y: float64(y),
H: float64(h),
W: float64(w),
},
})
panels = append(panels, p)
}

// Check if we fetched any panels
Expand Down

0 comments on commit 82710dd

Please sign in to comment.