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 Json.Decode as Json exposing (..)
import Utils.Api exposing (baseUrl, iso8601Time) import Utils.Api exposing (baseUrl, iso8601Time)
import Alerts.Types exposing (..) import Alerts.Types exposing (..)
import Utils.Types exposing (Filter) import Utils.Types exposing (ApiData, Filter)
import Utils.Filter exposing (generateQueryString) import Utils.Filter exposing (generateQueryString)
getAlertGroups : Filter -> Cmd Msg getAlertGroups : Filter -> Cmd Msg
getAlertGroups filter = 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 let
url = url =
String.join "/" [ baseUrl, "alerts", "groups" ++ (generateQueryString filter) ] String.join "/" [ baseUrl, "alerts", "groups" ++ (generateQueryString filter) ]
in in
Utils.Api.send (Utils.Api.get url alertGroupsDecoder) Utils.Api.send (Utils.Api.get url alertGroupsDecoder)
|> Cmd.map (AlertGroupsFetch >> ForSelf)

View File

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

View File

@ -16,6 +16,7 @@ type OutMsg
= SilenceFromAlert Alert = SilenceFromAlert Alert
| UpdateFilter Filter String | UpdateFilter Filter String
| NewUrl String | NewUrl String
| AlertGroupsPreview (ApiData (List AlertGroup))
type AlertsMsg 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 (Alert, AlertGroup, Block, Route(..))
import Alerts.Types exposing (AlertsMsg(..), Msg(..), OutMsg(..)) 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 -> Html Msg
alertGroupView alertGroup = alertGroupView alertGroup =
li [ class "pa3 pa4-ns bb b--black-10" ] li [ class "pa3 pa4-ns bb b--black-10" ]

View File

@ -6,8 +6,10 @@ import Time
import Parsing import Parsing
import Views import Views
import Alerts.Update import Alerts.Update
import Alerts.Api
import Types exposing (..) import Types exposing (..)
import Utils.Types exposing (..) import Utils.Types exposing (..)
import Utils.List
import Silences.Types exposing (Silence, nullTime, nullSilence) import Silences.Types exposing (Silence, nullTime, nullSilence)
import Silences.Update import Silences.Update
import Translators exposing (alertTranslator, silenceTranslator) import Translators exposing (alertTranslator, silenceTranslator)
@ -41,7 +43,7 @@ init location =
Silences.Update.updateFilter silencesRoute Silences.Update.updateFilter silencesRoute
_ -> _ ->
{ text = Nothing, receiver = Nothing, showSilenced = Nothing } nullFilter
( model, msg ) = ( model, msg ) =
update (urlUpdate location) (Model Loading Loading Loading route filter 0 (StatusModel Nothing)) update (urlUpdate location) (Model Loading Loading Loading route filter 0 (StatusModel Nothing))
@ -59,6 +61,31 @@ update msg model =
in in
( { model | silence = Success silence }, Cmd.none ) ( { 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 -> NavigateToAlerts alertsRoute ->
let let
( alertsMsg, filter ) = ( alertsMsg, filter ) =

View File

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

View File

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

View File

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

View File

@ -18,7 +18,16 @@ update msg silences silence filter =
( sils, silence, Cmd.map ForParent (Task.perform UpdateCurrentTime Time.now) ) ( sils, silence, Cmd.map ForParent (Task.perform UpdateCurrentTime Time.now) )
SilenceFetch sil -> 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 -> FetchSilences ->
( silences, silence, Api.getSilences filter ) ( silences, silence, Api.getSilences filter )

View File

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

View File

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

View File

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

View File

@ -72,7 +72,7 @@ iso8601Time =
baseUrl : String baseUrl : String
baseUrl = baseUrl =
"http://alertmanager-experimental.int.s-cloud.net/api/v1" "http://alertmanager.int.s-cloud.net/api/v1"
defaultTimeout : Time.Time defaultTimeout : Time.Time
@ -80,5 +80,11 @@ defaultTimeout =
1000 * Time.millisecond 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" -- "http://localhost:9093/api/v1"

View File

@ -1,5 +1,7 @@
module Utils.List exposing (..) module Utils.List exposing (..)
import Utils.Types exposing (Matchers, Matcher)
replaceIf : (a -> Bool) -> a -> List a -> List a replaceIf : (a -> Bool) -> a -> List a -> List a
replaceIf predicate replacement list = replaceIf predicate replacement list =
@ -11,3 +13,20 @@ replaceIf predicate replacement list =
item item
) )
list 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 = type alias Matcher =
{ name : String { name : String
, value : String , value : String