Skip to content

Commit

Permalink
Stn/add receiver support (#872)
Browse files Browse the repository at this point in the history
Add ability to filter alerts by receiver in UI. This adds changes both in the Elm UI, as well as the Go backend.
  • Loading branch information
stuartnelson3 authored and mxinden committed Jun 26, 2017
1 parent 6ef5ca6 commit a7009a9
Show file tree
Hide file tree
Showing 13 changed files with 185 additions and 55 deletions.
71 changes: 59 additions & 12 deletions api/api.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import (
"encoding/json"
"fmt"
"net/http"
"regexp"
"sync"
"time"

Expand Down Expand Up @@ -75,6 +76,7 @@ type API struct {
alerts provider.Alerts
silences *silence.Silences
config *config.Config
route *dispatch.Route
resolveTimeout time.Duration
uptime time.Time
mrouter *mesh.Router
Expand Down Expand Up @@ -119,6 +121,7 @@ func (api *API) Register(r *route.Router) {
r = r.WithPrefix("/v1")

r.Get("/status", ihf("status", api.status))
r.Get("/receivers", ihf("receivers", api.receivers))
r.Get("/alerts/groups", ihf("alert_groups", api.alertGroups))

r.Get("/alerts", ihf("list_alerts", api.listAlerts))
Expand All @@ -137,6 +140,7 @@ func (api *API) Update(cfg *config.Config, resolveTimeout time.Duration) error {

api.resolveTimeout = resolveTimeout
api.config = cfg
api.route = dispatch.NewRoute(cfg.Route, nil)
return nil
}

Expand All @@ -157,6 +161,18 @@ func (e *apiError) Error() string {
return fmt.Sprintf("%s: %s", e.typ, e.err)
}

func (api *API) receivers(w http.ResponseWriter, req *http.Request) {
api.mtx.RLock()
defer api.mtx.RUnlock()

receivers := make([]string, 0, len(api.config.Receivers))
for _, r := range api.config.Receivers {
receivers = append(receivers, r.Name)
}

respond(w, receivers)
}

func (api *API) status(w http.ResponseWriter, req *http.Request) {
api.mtx.RLock()

Expand Down Expand Up @@ -217,10 +233,11 @@ func getMeshStatus(api *API) meshStatus {
return strippedStatus
}

func (api *API) alertGroups(w http.ResponseWriter, req *http.Request) {
func (api *API) alertGroups(w http.ResponseWriter, r *http.Request) {
var err error
matchers := []*labels.Matcher{}
if filter := req.FormValue("filter"); filter != "" {

if filter := r.FormValue("filter"); filter != "" {
matchers, err = parse.Matchers(filter)
if err != nil {
respondError(w, apiError{
Expand All @@ -236,18 +253,13 @@ func (api *API) alertGroups(w http.ResponseWriter, req *http.Request) {
respond(w, groups)
}

type APIAlert struct {
*model.Alert

Status types.AlertStatus `json:"status"`
}

func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
var (
err error
re *regexp.Regexp
// Initialize result slice to prevent api returning `null` when there
// are no alerts present
res = []*APIAlert{}
res = []*dispatch.APIAlert{}
matchers = []*labels.Matcher{}
showSilenced = true
)
Expand Down Expand Up @@ -278,6 +290,20 @@ func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
}
}

if receiverParam := r.FormValue("receiver"); receiverParam != "" {
re, err = regexp.Compile("^(?:" + receiverParam + ")$")
if err != nil {
respondError(w, apiError{
typ: errorBadData,
err: fmt.Errorf(
"failed to parse receiver param: %s",
receiverParam,
),
}, nil)
return
}
}

alerts := api.alerts.GetPending()
defer alerts.Close()

Expand All @@ -287,6 +313,16 @@ func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
break
}

routes := api.route.Match(a.Labels)
receivers := make([]string, 0, len(routes))
for _, r := range routes {
receivers = append(receivers, r.RouteOpts.Receiver)
}

if re != nil && !regexpAny(re, receivers) {
continue
}

if !alertMatchesFilterLabels(&a.Alert, matchers) {
continue
}
Expand All @@ -302,9 +338,10 @@ func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
continue
}

apiAlert := &APIAlert{
Alert: &a.Alert,
Status: status,
apiAlert := &dispatch.APIAlert{
Alert: &a.Alert,
Status: status,
Receivers: receivers,
}

res = append(res, apiAlert)
Expand All @@ -320,6 +357,16 @@ func (api *API) listAlerts(w http.ResponseWriter, r *http.Request) {
respond(w, res)
}

func regexpAny(re *regexp.Regexp, ss []string) bool {
for _, s := range ss {
if re.MatchString(s) {
return true
}
}

return false
}

func alertMatchesFilterLabels(a *model.Alert, matchers []*labels.Matcher) bool {
for _, m := range matchers {
if v, prs := a.Labels[model.LabelName(m.Name)]; !prs || !m.Matches(string(v)) {
Expand Down
3 changes: 2 additions & 1 deletion dispatch/dispatch.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ type AlertBlock struct {
// annotated with silencing and inhibition info.
type APIAlert struct {
*model.Alert
Status types.AlertStatus `json:"status"`
Status types.AlertStatus `json:"status"`
Receivers []string `json:"receivers"`
}

// AlertGroup is a list of alert blocks grouped by the same label set.
Expand Down
4 changes: 2 additions & 2 deletions examples/ha/alertmanager.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ route:
group_wait: 10s
group_interval: 10s
repeat_interval: 1h
receiver: 'webhook'
receiver: 'web.hook'
receivers:
- name: 'webhook'
- name: 'web.hook'
webhook_configs:
- url: 'http://127.0.0.1:5001/'
inhibit_rules:
Expand Down
2 changes: 1 addition & 1 deletion template/default.tmpl
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{{ define "__alertmanager" }}AlertManager{{ end }}
{{ define "__alertmanagerURL" }}{{ .ExternalURL }}/#/alerts{{ end }}
{{ define "__alertmanagerURL" }}{{ .ExternalURL }}/#/alerts?receiver={{ .Receiver }}{{ end }}

{{ define "__subject" }}[{{ .Status | toUpper }}{{ if eq .Status "firing" }}:{{ .Alerts.Firing | len }}{{ end }}] {{ .GroupLabels.SortedPairs.Values | join " " }} {{ if gt (len .CommonLabels) (len .GroupLabels) }}({{ with .CommonLabels.Remove .GroupLabels.Names }}{{ .Values | join " " }}{{ end }}){{ end }}{{ end }}
{{ define "__description" }}{{ end }}
Expand Down
4 changes: 2 additions & 2 deletions template/internal/deftmpl/bindata.go

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ func (as Alerts) Resolved() []Alert {
// Data assembles data for template expansion.
func (t *Template) Data(recv string, groupLabels model.LabelSet, alerts ...*types.Alert) *Data {
data := &Data{
Receiver: strings.SplitN(recv, "/", 2)[0],
Receiver: regexp.QuoteMeta(strings.SplitN(recv, "/", 2)[0]),
Status: string(types.Alerts(alerts...).Status()),
Alerts: make(Alerts, 0, len(alerts)),
GroupLabels: KV{},
Expand Down
15 changes: 12 additions & 3 deletions ui/app/src/Alerts/Api.elm
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
module Alerts.Api exposing (..)

import Alerts.Types exposing (Alert, RouteOpts, Block, AlertGroup)
import Alerts.Types exposing (Alert, AlertGroup, Block, RouteOpts)
import Json.Decode as Json exposing (..)
import Utils.Api exposing (iso8601Time)
import Utils.Types exposing (ApiData)
import Utils.Filter exposing (Filter, generateQueryString)
import Utils.Types exposing (ApiData)


fetchReceivers : String -> Cmd (ApiData (List String))
fetchReceivers apiUrl =
Utils.Api.send
(Utils.Api.get
(apiUrl ++ "/receivers")
(field "data" (list string))
)


fetchAlerts : String -> Filter -> Cmd (ApiData (List Alert))
fetchAlerts apiUrl filter =
let
url =
String.join "/" [ apiUrl, "alerts" ++ (generateQueryString filter) ]
String.join "/" [ apiUrl, "alerts" ++ generateQueryString filter ]
in
Utils.Api.send (Utils.Api.get url alertsDecoder)

Expand Down
4 changes: 2 additions & 2 deletions ui/app/src/Utils/Filter.elm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ import Set
type alias Filter =
{ text : Maybe String
, group : Maybe String
, receiver : Maybe Matcher
, receiver : Maybe String
, showSilenced : Maybe Bool
}

Expand All @@ -46,10 +46,10 @@ generateQueryParam name =
generateQueryString : Filter -> String
generateQueryString { receiver, showSilenced, text, group } =
let
-- TODO: Re-add receiver once it is parsed on the server side.
parts =
[ ( "silenced", Maybe.withDefault False showSilenced |> toString |> String.toLower |> Just )
, ( "filter", emptyToNothing text )
, ( "receiver", emptyToNothing receiver )
, ( "group", group )
]
|> List.filterMap (uncurry generateQueryParam)
Expand Down
31 changes: 7 additions & 24 deletions ui/app/src/Views/AlertList/Parsing.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,14 @@ import Utils.Filter exposing (Filter, parseMatcher, MatchOperator(RegexMatch))
boolParam : String -> UrlParser.QueryParser (Maybe Bool -> a) a
boolParam name =
UrlParser.customParam name
(\x ->
case x of
Nothing ->
Nothing

Just value ->
if (String.toLower value) == "false" then
Just False
else
Just True
)
(Maybe.map (String.toLower >> (/=) "false"))


alertsParser : Parser (Filter -> a) a
alertsParser =
map
(\filter group receiver silenced ->
let
parsed =
Maybe.map
(\r ->
{ key = "receiver", op = RegexMatch, value = "^(?:" ++ r ++ ")$" }
)
receiver
in
Filter filter group parsed silenced
)
(s "alerts" <?> stringParam "filter" <?> stringParam "group" <?> stringParam "receiver" <?> boolParam "silenced")
s "alerts"
<?> stringParam "filter"
<?> stringParam "group"
<?> stringParam "receiver"
<?> boolParam "silenced"
|> map Filter
9 changes: 8 additions & 1 deletion ui/app/src/Views/AlertList/Types.elm
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
module Views.AlertList.Types exposing (AlertListMsg(..), Model, Tab(..), initAlertList)

import Utils.Types exposing (ApiData(Initial))
import Alerts.Types exposing (Alert)
import Utils.Types exposing (ApiData(Initial))
import Views.FilterBar.Types as FilterBar
import Views.GroupBar.Types as GroupBar


type AlertListMsg
= AlertsFetched (ApiData (List Alert))
| ReceiversFetched (ApiData (List String))
| ToggleReceivers Bool
| SelectReceiver (Maybe String)
| FetchAlerts
| MsgForFilterBar FilterBar.Msg
| MsgForGroupBar GroupBar.Msg
Expand All @@ -23,6 +26,8 @@ type Tab

type alias Model =
{ alerts : ApiData (List Alert)
, receivers : List String
, showRecievers : Bool
, groupBar : GroupBar.Model
, filterBar : FilterBar.Model
, tab : Tab
Expand All @@ -33,6 +38,8 @@ type alias Model =
initAlertList : Model
initAlertList =
{ alerts = Initial
, receivers = []
, showRecievers = False
, groupBar = GroupBar.initGroupBar
, filterBar = FilterBar.initFilterBar
, tab = FilterTab
Expand Down
20 changes: 19 additions & 1 deletion ui/app/src/Views/AlertList/Updates.elm
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import Utils.Filter exposing (Filter, parseFilter)
import Utils.Types exposing (ApiData(Initial, Loading, Success, Failure))
import Types exposing (Msg(MsgForAlertList, Noop))
import Set
import Regex
import Navigation
import Utils.Filter exposing (generateQueryString)
import Views.GroupBar.Updates as GroupBar
Expand Down Expand Up @@ -47,9 +48,26 @@ update msg ({ groupBar, filterBar } as model) filter apiUrl basePath =
FilterBar.setMatchers filter filterBar
in
( { model | alerts = Loading, filterBar = newFilterBar, groupBar = newGroupBar, activeId = Nothing }
, Api.fetchAlerts apiUrl filter |> Cmd.map (AlertsFetched >> MsgForAlertList)
, Cmd.batch
[ Api.fetchAlerts apiUrl filter |> Cmd.map (AlertsFetched >> MsgForAlertList)
, Api.fetchReceivers apiUrl |> Cmd.map (ReceiversFetched >> MsgForAlertList)
]
)

ReceiversFetched (Success receivers) ->
( { model | receivers = receivers }, Cmd.none )

ReceiversFetched _ ->
( model, Cmd.none )

ToggleReceivers show ->
( { model | showRecievers = show }, Cmd.none )

SelectReceiver receiver ->
( { model | showRecievers = False }
, Navigation.newUrl (alertsUrl ++ generateQueryString { filter | receiver = Maybe.map Regex.escape receiver })
)

ToggleSilenced showSilenced ->
( model
, Navigation.newUrl (alertsUrl ++ generateQueryString { filter | showSilenced = Just showSilenced })
Expand Down
Loading

0 comments on commit a7009a9

Please sign in to comment.