From 9e4abbd42b7f2ebf6564a9fe0acfe816a855289e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Mierzwa?= Date: Mon, 4 Feb 2019 13:18:47 +0000 Subject: [PATCH] feat(backend): allow setting custom colors for labels This allows to have a user defined color (rgb or hex) for label values configured in the config file --- go.mod | 1 + go.sum | 2 ++ internal/config/config.go | 6 +++++ internal/config/config_test.go | 1 + internal/config/models.go | 1 + internal/transform/color_test.go | 46 +++++++++++++++++++++++--------- internal/transform/colors.go | 37 ++++++++++++++++++++++++- 7 files changed, 81 insertions(+), 13 deletions(-) diff --git a/go.mod b/go.mod index 2e8e30168..1614ea7ac 100644 --- a/go.mod +++ b/go.mod @@ -25,6 +25,7 @@ require ( github.com/spf13/pflag v1.0.3 github.com/spf13/viper v1.3.1 github.com/terinjokes/bakelite v0.2.0 // indirect + gopkg.in/go-playground/colors.v1 v1.2.0 gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 gopkg.in/yaml.v2 v2.2.2 ) diff --git a/go.sum b/go.sum index aafe40a7f..7c10fccf8 100644 --- a/go.sum +++ b/go.sum @@ -275,6 +275,8 @@ gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMy gopkg.in/gemnasium/logrus-airbrake-hook.v2 v2.1.2/go.mod h1:Xk6kEKp8OKb+X14hQBKWaSkCsqBpgog8nAV2xsGOxlo= gopkg.in/go-playground/assert.v1 v1.2.1 h1:xoYuJVE7KT85PYWrN730RguIQO0ePzVRfFMXadIrXTM= gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/colors.v1 v1.2.0 h1:SPweMUve+ywPrfwao+UvfD5Ah78aOLUkT5RlJiZn52c= +gopkg.in/go-playground/colors.v1 v1.2.0/go.mod h1:AvbqcMpNXVl5gBrM20jBm3VjjKBbH/kI5UnqjU7lxFI= gopkg.in/go-playground/validator.v8 v8.18.2 h1:lFB4DoMU6B626w8ny76MV7VX6W2VHct2GVOI3xgiMrQ= gopkg.in/go-playground/validator.v8 v8.18.2/go.mod h1:RX2a/7Ha8BgOhfk7j780h4/u/RRjR0eouCJSH80/M2Y= gopkg.in/jarcoal/httpmock.v1 v1.0.0-20181117152235-275e9df93516 h1:H6trpavCIuipdInWrab8l34Mf+GGVfphniHostMdMaQ= diff --git a/internal/config/config.go b/internal/config/config.go index d381fafcc..0c1a221c0 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -137,6 +137,7 @@ func (config *configSchema) Read() { config.Custom.JS = v.GetString("custom.js") config.Debug = v.GetBool("debug") config.Filters.Default = v.GetStringSlice("filters.default") + config.Labels.Color.Custom = map[string]map[string]string{} config.Labels.Color.Static = v.GetStringSlice("labels.color.static") config.Labels.Color.Unique = v.GetStringSlice("labels.color.unique") config.Labels.Keep = v.GetStringSlice("labels.keep") @@ -161,6 +162,11 @@ func (config *configSchema) Read() { log.Fatal(err) } + err = v.UnmarshalKey("labels.color.custom", &config.Labels.Color.Custom) + if err != nil { + log.Fatal(err) + } + // accept single Alertmanager server from flag/env if nothing is set yet if len(config.Alertmanager.Servers) == 0 && v.GetString("alertmanager.uri") != "" { log.Info("Using simple config with a single Alertmanager server") diff --git a/internal/config/config_test.go b/internal/config/config_test.go index 0f5c095ec..2c33995f8 100644 --- a/internal/config/config_test.go +++ b/internal/config/config_test.go @@ -90,6 +90,7 @@ labels: - abc - def color: + custom: {} static: - a - bb diff --git a/internal/config/models.go b/internal/config/models.go index b38e7299b..eefd6b66a 100644 --- a/internal/config/models.go +++ b/internal/config/models.go @@ -47,6 +47,7 @@ type configSchema struct { Keep []string Strip []string Color struct { + Custom map[string]map[string]string Static []string Unique []string } diff --git a/internal/transform/color_test.go b/internal/transform/color_test.go index 1116224bb..d0a993891 100644 --- a/internal/transform/color_test.go +++ b/internal/transform/color_test.go @@ -9,22 +9,23 @@ import ( ) type colorTest struct { - config []string - labels map[string]string - colors map[string]string + uniqueLabels []string + customLabels map[string]map[string]string + labels map[string]string + colors map[string]string } var colorTests = []colorTest{ - colorTest{ + { labels: map[string]string{}, }, - colorTest{ + { labels: map[string]string{ "node": "localhost", }, }, - colorTest{ - config: []string{"node"}, + { + uniqueLabels: []string{"node"}, labels: map[string]string{ "node": "localhost", }, @@ -32,8 +33,8 @@ var colorTests = []colorTest{ "node": "localhost", }, }, - colorTest{ - config: []string{"node", "instance"}, + { + uniqueLabels: []string{"node", "instance"}, labels: map[string]string{ "node": "instance", "env": "instance", @@ -45,8 +46,8 @@ var colorTests = []colorTest{ "instance": "server1", }, }, - colorTest{ - config: []string{"job", "node", "instance"}, + { + uniqueLabels: []string{"job", "node", "instance"}, labels: map[string]string{ "job": "node_ping", }, @@ -54,11 +55,32 @@ var colorTests = []colorTest{ "job": "node_ping", }, }, + { + customLabels: map[string]map[string]string{ + "node": map[string]string{"localhost": "#fff"}, + }, + labels: map[string]string{ + "node": "localhost", + }, + colors: map[string]string{ + "node": "localhost", + }, + }, + { + customLabels: map[string]map[string]string{ + "node": map[string]string{"localhost": "not a color"}, + }, + labels: map[string]string{ + "node": "localhost", + }, + colors: map[string]string{}, + }, } func TestColorLabel(t *testing.T) { for _, testCase := range colorTests { - config.Config.Labels.Color.Unique = testCase.config + config.Config.Labels.Color.Unique = testCase.uniqueLabels + config.Config.Labels.Color.Custom = testCase.customLabels colorStore := models.LabelsColorMap{} for key, value := range testCase.labels { transform.ColorLabel(colorStore, key, value) diff --git a/internal/transform/colors.go b/internal/transform/colors.go index 3087f0d4c..9800a1f22 100644 --- a/internal/transform/colors.go +++ b/internal/transform/colors.go @@ -10,6 +10,7 @@ import ( "github.com/prymitive/karma/internal/slices" "github.com/hansrodtang/randomcolor" + plcolors "gopkg.in/go-playground/colors.v1" log "github.com/sirupsen/logrus" ) @@ -34,10 +35,44 @@ func labelToSeed(key string, val string) int64 { return seed } +func rgbToBrightness(r, g, b uint8) int32 { + return ((int32(r) * 299) + (int32(g) * 587) + (int32(b) * 114)) / 1000 +} + // ColorLabel update karmaColorMap object with a color object generated // from label key and value passed here // It's used to generate unique colors for configured labels func ColorLabel(colorStore models.LabelsColorMap, key string, val string) { + // first handle custom colors + _, ok := config.Config.Labels.Color.Custom[key] + if ok { + c, ol := config.Config.Labels.Color.Custom[key][val] + if ol { + color, err := plcolors.Parse(c) + if err != nil { + log.Warningf("Failed to parse custom color for %s=%s: %s", key, val, err) + return + } + rgb := color.ToRGB() + bc := models.Color{ + Red: rgb.R, + Green: rgb.G, + Blue: rgb.B, + Alpha: 255, + } + brightness := rgbToBrightness(bc.Red, bc.Green, bc.Blue) + if _, found := colorStore[key]; !found { + colorStore[key] = make(map[string]models.LabelColors) + } + colorStore[key][val] = models.LabelColors{ + Brightness: brightness, + Background: bc, + } + } + return + } + + // if no custom color is found then generate unique colors if needed if slices.StringInSlice(config.Config.Labels.Color.Unique, key) { if _, found := colorStore[key]; !found { colorStore[key] = make(map[string]models.LabelColors) @@ -54,7 +89,7 @@ func ColorLabel(colorStore models.LabelsColorMap, key string, val string) { } // check if color is bright or dark and pick the right background // uses https://www.w3.org/WAI/ER/WD-AERT/#color-contrast method - brightness := ((int32(bc.Red) * 299) + (int32(bc.Green) * 587) + (int32(bc.Blue) * 114)) / 1000 + brightness := rgbToBrightness(bc.Red, bc.Green, bc.Blue) colorStore[key][val] = models.LabelColors{ Brightness: brightness, Background: bc,