Silence preview (#7)

* Wip

* Add placeholder preview html

* Show affected alerts on silence show page
This commit is contained in:
stuart nelson 2017-03-17 17:14:49 +01:00 committed by GitHub
parent 080f850270
commit d229abac97
15 changed files with 204 additions and 56 deletions

View File

@ -3,18 +3,29 @@ module Alerts.Api exposing (..)
import Json.Decode as Json exposing (..)
import Utils.Api exposing (baseUrl, iso8601Time)
import Alerts.Types exposing (..)
import Utils.Types exposing (Filter)
import Utils.Types exposing (ApiData, Filter)
import Utils.Filter exposing (generateQueryString)
getAlertGroups : Filter -> Cmd Msg
getAlertGroups filter =
alertGroups filter
|> Cmd.map (AlertGroupsFetch >> ForSelf)
alertPreview : Filter -> Cmd Msg
alertPreview filter =
alertGroups filter
|> Cmd.map (AlertGroupsPreview >> ForParent)
alertGroups : Filter -> Cmd (ApiData (List AlertGroup))
alertGroups filter =
let
url =
String.join "/" [ baseUrl, "alerts", "groups" ++ (generateQueryString filter) ]
in
Utils.Api.send (Utils.Api.get url alertGroupsDecoder)
|> Cmd.map (AlertGroupsFetch >> ForSelf)

View File

@ -1,7 +1,7 @@
module Alerts.Translator exposing (translator)
import Alerts.Types exposing (Msg(..), Alert, AlertsMsg, OutMsg(..))
import Utils.Types exposing (Filter)
import Alerts.Types exposing (Msg(..), AlertGroup, Alert, AlertsMsg, OutMsg(..))
import Utils.Types exposing (ApiData, Filter)
type alias TranslationDictionary msg =
@ -9,11 +9,12 @@ type alias TranslationDictionary msg =
, onSilenceFromAlert : Alert -> msg
, onUpdateFilter : Filter -> String -> msg
, onNewUrl : String -> msg
, onAlertGroupsPreview : ApiData (List AlertGroup) -> msg
}
translator : TranslationDictionary parentMsg -> Msg -> parentMsg
translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl } msg =
translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl, onAlertGroupsPreview } msg =
case msg of
ForSelf internal ->
onInternalMessage internal
@ -26,3 +27,6 @@ translator { onInternalMessage, onSilenceFromAlert, onUpdateFilter, onNewUrl } m
ForParent (NewUrl string) ->
onNewUrl string
ForParent (AlertGroupsPreview groups) ->
onAlertGroupsPreview groups

View File

@ -16,6 +16,7 @@ type OutMsg
= SilenceFromAlert Alert
| UpdateFilter Filter String
| NewUrl String
| AlertGroupsPreview (ApiData (List AlertGroup))
type AlertsMsg

View File

@ -1,4 +1,4 @@
module Alerts.Views exposing (view)
module Alerts.Views exposing (view, compact)
import Alerts.Types exposing (Alert, AlertGroup, Block, Route(..))
import Alerts.Types exposing (AlertsMsg(..), Msg(..), OutMsg(..))
@ -43,6 +43,30 @@ view route alertGroups filter errorHtml =
]
compact : AlertGroup -> Html msg
compact ag =
let
alerts =
List.concatMap
(\b ->
b.alerts
)
ag.blocks
in
ul
[ class "list pa0"
]
(List.indexedMap alertCompact alerts)
alertCompact : Int -> Alert -> Html msg
alertCompact idx alert =
li [ class "mb2 w-80-l w-100-m" ]
[ span [] [ text <| toString (idx + 1) ++ ". " ]
, div [] (List.map labelButton alert.labels)
]
alertGroupView : AlertGroup -> Html Msg
alertGroupView alertGroup =
li [ class "pa3 pa4-ns bb b--black-10" ]

View File

@ -6,8 +6,10 @@ import Time
import Parsing
import Views
import Alerts.Update
import Alerts.Api
import Types exposing (..)
import Utils.Types exposing (..)
import Utils.List
import Silences.Types exposing (Silence, nullTime, nullSilence)
import Silences.Update
import Translators exposing (alertTranslator, silenceTranslator)
@ -41,7 +43,7 @@ init location =
Silences.Update.updateFilter silencesRoute
_ ->
{ text = Nothing, receiver = Nothing, showSilenced = Nothing }
nullFilter
( model, msg ) =
update (urlUpdate location) (Model Loading Loading Loading route filter 0 (StatusModel Nothing))
@ -59,6 +61,31 @@ update msg model =
in
( { model | silence = Success silence }, Cmd.none )
PreviewSilence silence ->
let
s =
{ silence | silencedAlertGroups = Loading }
filter =
{ nullFilter | text = Just <| Utils.List.mjoin s.matchers }
in
( { model | silence = Success silence }, Cmd.map alertTranslator (Alerts.Api.alertPreview filter) )
AlertGroupsPreview alertGroups ->
let
silence =
case model.silence of
Success sil ->
Success { sil | silencedAlertGroups = alertGroups }
Failure e ->
Failure e
Loading ->
Loading
in
( { model | silence = silence }, Cmd.none )
NavigateToAlerts alertsRoute ->
let
( alertsMsg, filter ) =

View File

@ -1,9 +1,9 @@
module Silences.Decoders exposing (..)
import Json.Decode as Json exposing (field)
import Utils.Api exposing (iso8601Time, duration)
import Utils.Api exposing (iso8601Time, duration, (|:))
import Silences.Types exposing (Silence)
import Utils.Types exposing (Matcher, Time)
import Utils.Types exposing (Matcher, Time, ApiResponse(Success))
show : Json.Decoder Silence
@ -32,19 +32,21 @@ destroy =
silence : Json.Decoder Silence
silence =
Json.map8 Silence
(field "id" Json.string)
(field "createdBy" Json.string)
Json.succeed Silence
|: (field "id" Json.string)
|: (field "createdBy" Json.string)
-- Remove this maybe once the api either disallows empty comments on
-- creation, or returns an empty string.
((Json.maybe (field "comment" Json.string))
|> Json.andThen (\x -> Json.succeed <| Maybe.withDefault "" x)
)
(field "startsAt" iso8601Time)
(field "endsAt" iso8601Time)
(duration "startsAt" "endsAt")
(field "updatedAt" iso8601Time)
(field "matchers" (Json.list matcher))
|:
((Json.maybe (field "comment" Json.string))
|> Json.andThen (\x -> Json.succeed <| Maybe.withDefault "" x)
)
|: (field "startsAt" iso8601Time)
|: (field "endsAt" iso8601Time)
|: (duration "startsAt" "endsAt")
|: (field "updatedAt" iso8601Time)
|: (field "matchers" (Json.list matcher))
|: (Json.succeed <| Success [])
matcher : Json.Decoder Matcher

View File

@ -1,6 +1,6 @@
module Silences.Translator exposing (translator)
import Silences.Types exposing (Msg(..), SilencesMsg, OutMsg(NewUrl, UpdateFilter, UpdateCurrentTime))
import Silences.Types exposing (Silence, Msg(..), SilencesMsg, OutMsg(..))
import Utils.Types exposing (Filter)
import Time
@ -10,11 +10,12 @@ type alias TranslationDictionary msg =
, onNewUrl : String -> msg
, onUpdateFilter : Filter -> String -> msg
, onUpdateCurrentTime : Time.Time -> msg
, onPreviewSilence : Silence -> msg
}
translator : TranslationDictionary parentMsg -> Msg -> parentMsg
translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime } msg =
translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime, onPreviewSilence } msg =
case msg of
ForSelf internal ->
onInternalMessage internal
@ -27,3 +28,6 @@ translator { onInternalMessage, onNewUrl, onUpdateFilter, onUpdateCurrentTime }
ForParent (UpdateCurrentTime time) ->
onUpdateCurrentTime time
ForParent (PreviewSilence silence) ->
onPreviewSilence silence

View File

@ -1,8 +1,9 @@
module Silences.Types exposing (..)
import Utils.Types exposing (Time, Duration, Matcher, Filter, ApiData)
import Utils.Types exposing (Time, Duration, Matcher, Filter, ApiData, ApiResponse(..))
import Utils.Date
import Time
import Alerts.Types exposing (AlertGroup)
type alias Silence =
@ -14,6 +15,7 @@ type alias Silence =
, duration : Duration
, updatedAt : Time
, matchers : List Matcher
, silencedAlertGroups : ApiData (List AlertGroup)
}
@ -33,6 +35,7 @@ type OutMsg
= NewUrl String
| UpdateFilter Filter String
| UpdateCurrentTime Time.Time
| PreviewSilence Silence
type SilencesMsg
@ -62,7 +65,16 @@ type SilencesMsg
nullSilence : Silence
nullSilence =
Silence "" "" "" nullTime nullTime nullDuration nullTime [ nullMatcher ]
{ id = ""
, createdBy = ""
, comment = ""
, startsAt = nullTime
, endsAt = nullTime
, duration = nullDuration
, updatedAt = nullTime
, matchers = [ nullMatcher ]
, silencedAlertGroups = Success []
}
nullMatcher : Matcher

View File

@ -18,7 +18,16 @@ update msg silences silence filter =
( sils, silence, Cmd.map ForParent (Task.perform UpdateCurrentTime Time.now) )
SilenceFetch sil ->
( silences, sil, Cmd.none )
let
cmd =
case sil of
Success sil ->
Task.perform ForParent <| Task.succeed (PreviewSilence sil)
_ ->
Cmd.none
in
( silences, sil, cmd )
FetchSilences ->
( silences, silence, Api.getSilences filter )

View File

@ -5,11 +5,13 @@ module Silences.Views exposing (..)
import Html exposing (..)
import Html.Attributes exposing (..)
import Html.Events exposing (onClick, onInput)
import Silences.Types exposing (Silence, SilencesMsg(..), Msg(..), OutMsg(UpdateFilter), Route(..))
import Silences.Types exposing (Silence, SilencesMsg(..), Msg(..), OutMsg(UpdateFilter, PreviewSilence), Route(..))
import Utils.Types exposing (Matcher, ApiResponse(..), Filter, ApiData)
import Utils.Views exposing (iconButtonMsg, checkbox, textField, formInput, formField, buttonLink, error, loading)
import Utils.Date
import Utils.List
import Time
import Alerts.Views
view : Route -> ApiData (List Silence) -> ApiData Silence -> Time.Time -> Filter -> Html Msg
@ -101,6 +103,8 @@ silence silence currentTime =
div []
[ silenceBase silence
, silenceExtra silence currentTime
, h2 [ class "h6 dark-red" ] [ text "Affected alerts" ]
, preview silence
]
@ -158,6 +162,20 @@ silenceExtra silence currentTime =
]
preview : Silence -> Html msg
preview s =
case s.silencedAlertGroups of
Success alertGroups ->
div []
(List.map Alerts.Views.compact alertGroups)
Loading ->
loading
Failure e ->
error e
status : Silence -> Time.Time -> String
status silence currentTime =
let
@ -196,32 +214,38 @@ silenceForm kind silence =
_ ->
"/#/silences"
in
div [ class "pa4 black-80" ]
[ fieldset [ class "ba b--transparent ph0 mh0" ]
[ legend [ class "ph0 mh0 fw6" ] [ text <| kind ++ " Silence" ]
, (formField "Start" silence.startsAt.s (UpdateStartsAt silence))
, div [ class "dib mb2 mr2 w-40" ] [ formField "End" silence.endsAt.s (UpdateEndsAt silence) ]
, div [ class "dib mb2 mr2 w-40" ] [ formField "Duration" silence.duration.s (UpdateDuration silence) ]
, div [ class "mt3" ]
[ label [ class "f6 b db mb2" ]
[ text "Matchers "
, span [ class "normal black-60" ] [ text "Alerts affected by this silence. Format: name=value" ]
div []
[ div [ class "pa4 black-80" ]
[ fieldset [ class "ba b--transparent ph0 mh0" ]
[ legend [ class "ph0 mh0 fw6" ] [ text <| kind ++ " Silence" ]
, (formField "Start" silence.startsAt.s (UpdateStartsAt silence))
, div [ class "dib mb2 mr2 w-40" ] [ formField "End" silence.endsAt.s (UpdateEndsAt silence) ]
, div [ class "dib mb2 mr2 w-40" ] [ formField "Duration" silence.duration.s (UpdateDuration silence) ]
, div [ class "mt3" ]
[ label [ class "f6 b db mb2" ]
[ text "Matchers "
, span [ class "normal black-60" ] [ text "Alerts affected by this silence. Format: name=value" ]
]
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Name" ]
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Value" ]
]
, div [] (List.map boundMatcherForm silence.matchers)
, iconButtonMsg "blue" "fa-plus" (AddMatcher silence)
, formField "Creator" silence.createdBy (UpdateCreatedBy silence)
, textField "Comment" silence.comment (UpdateComment silence)
, div [ class "mt3" ]
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (CreateSilence silence) ] [ text "Create" ]
-- Reset isn't working for "New" -- just updates time.
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib dark-red", href url ] [ text "Reset" ]
]
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Name" ]
, label [ class "f6 dib mb2 mr2 w-40" ] [ text "Value" ]
]
, div [] (List.map boundMatcherForm silence.matchers)
, iconButtonMsg "blue" "fa-plus" (AddMatcher silence)
, formField "Creator" silence.createdBy (UpdateCreatedBy silence)
, textField "Comment" silence.comment (UpdateComment silence)
|> (Html.map ForSelf)
, div [ class "mt3" ]
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (CreateSilence silence) ] [ text "Create" ]
-- Reset isn't working for "New" -- just updates time.
, a [ class "f6 link br2 ba ph3 pv2 mb2 dib dark-red", href url ] [ text "Reset" ]
[ a [ class "f6 link br2 ba ph3 pv2 mr2 dib dark-green", onClick (ForParent (PreviewSilence silence)) ] [ text "Show Affected Alerts" ]
]
, preview silence
]
]
|> (Html.map ForSelf)
matcherForm : Silence -> Matcher -> Html SilencesMsg
@ -236,11 +260,4 @@ matcherForm silence matcher =
matcherButton : Matcher -> Html msg
matcherButton matcher =
let
join =
if matcher.isRegex then
"=~"
else
"="
in
Utils.Views.button "light-silver hover-black ph3 pv2" <| String.join join [ matcher.name, matcher.value ]
Utils.Views.button "light-silver hover-black ph3 pv2" <| Utils.List.mstring matcher

View File

@ -4,7 +4,7 @@ import Alerts.Types
import Alerts.Translator
import Silences.Types
import Silences.Translator
import Types exposing (Msg(Alerts, CreateSilenceFromAlert, Silences, UpdateFilter, NewUrl, UpdateCurrentTime))
import Types exposing (Msg(Alerts, CreateSilenceFromAlert, Silences, UpdateFilter, NewUrl, UpdateCurrentTime, PreviewSilence, AlertGroupsPreview))
alertTranslator : Alerts.Types.Msg -> Msg
@ -14,6 +14,7 @@ alertTranslator =
, onSilenceFromAlert = CreateSilenceFromAlert
, onUpdateFilter = UpdateFilter
, onNewUrl = NewUrl
, onAlertGroupsPreview = AlertGroupsPreview
}
@ -24,4 +25,5 @@ silenceTranslator =
, onNewUrl = NewUrl
, onUpdateFilter = UpdateFilter
, onUpdateCurrentTime = UpdateCurrentTime
, onPreviewSilence = PreviewSilence
}

View File

@ -20,6 +20,8 @@ type alias Model =
type Msg
= CreateSilenceFromAlert Alert
| PreviewSilence Silence
| AlertGroupsPreview (ApiData (List AlertGroup))
| UpdateFilter Filter String
| NavigateToAlerts Alerts.Types.Route
| NavigateToSilences Silences.Types.Route

View File

@ -72,7 +72,7 @@ iso8601Time =
baseUrl : String
baseUrl =
"http://alertmanager-experimental.int.s-cloud.net/api/v1"
"http://alertmanager.int.s-cloud.net/api/v1"
defaultTimeout : Time.Time
@ -80,5 +80,11 @@ defaultTimeout =
1000 * Time.millisecond
(|:) : Json.Decoder (a -> b) -> Json.Decoder a -> Json.Decoder b
(|:) =
-- Taken from elm-community/json-extra
flip (Json.map2 (|>))
-- "http://localhost:9093/api/v1"

View File

@ -1,5 +1,7 @@
module Utils.List exposing (..)
import Utils.Types exposing (Matchers, Matcher)
replaceIf : (a -> Bool) -> a -> List a -> List a
replaceIf predicate replacement list =
@ -11,3 +13,20 @@ replaceIf predicate replacement list =
item
)
list
mjoin : Matchers -> String
mjoin m =
String.join "," (List.map mstring m)
mstring : Matcher -> String
mstring m =
let
sep =
if m.isRegex then
"=~"
else
"="
in
String.join sep [ m.name, toString m.value ]

View File

@ -17,6 +17,14 @@ type alias Filter =
}
nullFilter : Filter
nullFilter =
{ text = Nothing
, receiver = Nothing
, showSilenced = Nothing
}
type alias Matcher =
{ name : String
, value : String