diff --git a/autocomplete.go b/autocomplete.go index 9b5ed15d6..969258d64 100644 --- a/autocomplete.go +++ b/autocomplete.go @@ -61,3 +61,43 @@ func knownLabelNames(c *gin.Context) { c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte)) logAlertsView(c, "MIS", time.Since(start)) } + +func knownLabelValues(c *gin.Context) { + noCache(c) + start := time.Now() + + cacheKey := c.Request.RequestURI + if cacheKey == "" { + // FIXME c.Request.RequestURI is empty when running tests for some reason + // needs checking, below acts as a workaround + cacheKey = c.Request.URL.RawQuery + } + + data, found := apiCache.Get(cacheKey) + if found { + c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte)) + logAlertsView(c, "HIT", time.Since(start)) + return + } + + name, found := c.GetQuery("name") + if !found || name == "" { + c.JSON(http.StatusBadRequest, gin.H{"error": "missing name= parameter"}) + log.Infof("[%s] <%d> %s %s took %s", c.ClientIP(), http.StatusBadRequest, c.Request.Method, c.Request.RequestURI, time.Since(start)) + return + } + + values := alertmanager.DedupKnownLabelValues(name) + sort.Strings(values) + + data, err := json.Marshal(values) + if err != nil { + log.Error(err.Error()) + panic(err) + } + + apiCache.Set(cacheKey, data, time.Second*15) + + c.Data(http.StatusOK, gin.MIMEJSON, data.([]byte)) + logAlertsView(c, "MIS", time.Since(start)) +} diff --git a/autocomplete_test.go b/autocomplete_test.go index d512baad7..8c68ed03c 100644 --- a/autocomplete_test.go +++ b/autocomplete_test.go @@ -34,7 +34,7 @@ var labelTests = []labelTest{ }, } -func TestKnownLabels(t *testing.T) { +func TestKnownLabelNames(t *testing.T) { mockConfig() for _, version := range mock.ListAllMocks() { t.Logf("Testing known labels using mock files from Alertmanager %s", version) @@ -68,3 +68,58 @@ func TestKnownLabels(t *testing.T) { } } } + +type valueTest struct { + Name string + Results []string +} + +var valueTests = []valueTest{ + { + Name: "foobar", + Results: []string{}, + }, + { + Name: "alertname", + Results: []string{"Free_Disk_Space_Too_Low", "HTTP_Probe_Failed", "Host_Down", "Memory_Usage_Too_High"}, + }, + { + Name: "cluster", + Results: []string{"dev", "prod", "staging"}, + }, +} + +func TestKnownLabelValues(t *testing.T) { + mockConfig() + for _, version := range mock.ListAllMocks() { + t.Logf("Testing known label values using mock files from Alertmanager %s", version) + mockAlerts(version) + r := ginTestEngine() + + req, _ := http.NewRequest("GET", "/labelValues.json", nil) + resp := httptest.NewRecorder() + r.ServeHTTP(resp, req) + if resp.Code != http.StatusBadRequest { + t.Errorf("Invalid status code for request without any query: %d", resp.Code) + } + + for _, testCase := range valueTests { + url := fmt.Sprintf("/labelValues.json?name=%s", testCase.Name) + req, _ := http.NewRequest("GET", url, nil) + resp := httptest.NewRecorder() + r.ServeHTTP(resp, req) + + if resp.Code != http.StatusOK { + t.Errorf("GET %s returned status %d", url, resp.Code) + } + + ur := []string{} + json.Unmarshal(resp.Body.Bytes(), &ur) + + if len(ur) != len(testCase.Results) { + t.Errorf("Invalid number of label values for %s, got %d, expected %d", url, len(ur), len(testCase.Results)) + t.Errorf("Results: %s", ur) + } + } + } +} diff --git a/internal/alertmanager/dedup.go b/internal/alertmanager/dedup.go index 5b0d92765..404f9aa59 100644 --- a/internal/alertmanager/dedup.go +++ b/internal/alertmanager/dedup.go @@ -180,3 +180,25 @@ func DedupKnownLabels() []string { } return flatLabels } + +// DedupKnownLabelValues returns a list of all known values for label $name +func DedupKnownLabelValues(name string) []string { + dedupedValues := map[string]bool{} + upstreams := GetAlertmanagers() + + for _, am := range upstreams { + for _, ag := range am.Alerts() { + for _, alert := range ag.Alerts { + if val, found := alert.Labels[name]; found { + dedupedValues[val] = true + } + } + } + } + + flatValues := []string{} + for key := range dedupedValues { + flatValues = append(flatValues, key) + } + return flatValues +} diff --git a/main.go b/main.go index 5e7b98464..a14353753 100644 --- a/main.go +++ b/main.go @@ -69,6 +69,7 @@ func setupRouter(router *gin.Engine) { router.GET(getViewURL("/alerts.json"), alerts) router.GET(getViewURL("/autocomplete.json"), autocomplete) router.GET(getViewURL("/labelNames.json"), knownLabelNames) + router.GET(getViewURL("/labelValues.json"), knownLabelValues) router.NoRoute(notFound) }