Extract Alert Filter Bar (#755)
* Extract Alert Filter Bar * Add FilterBar to silenceList page * Prevent empty key= in query string
This commit is contained in:
parent
952543b6e8
commit
91e802c4f3
|
@ -26,6 +26,7 @@ import Utils.Filter exposing (nullFilter)
|
|||
import Views.SilenceForm.Types exposing (initSilenceForm)
|
||||
import Views.Status.Types exposing (StatusModel, initStatusModel)
|
||||
import Views.AlertList.Types exposing (initAlertList)
|
||||
import Views.SilenceList.Types exposing (initSilenceList)
|
||||
import Updates exposing (update)
|
||||
|
||||
|
||||
|
@ -57,7 +58,7 @@ init location =
|
|||
nullFilter
|
||||
|
||||
( model, msg ) =
|
||||
update (urlUpdate location) (Model Loading Loading initSilenceForm initAlertList route filter 0 initStatusModel)
|
||||
update (urlUpdate location) (Model initSilenceList Loading initSilenceForm initAlertList route filter 0 initStatusModel)
|
||||
in
|
||||
model ! [ msg, Task.perform UpdateCurrentTime Time.now ]
|
||||
|
||||
|
|
|
@ -2,7 +2,7 @@ module Types exposing (Model, Msg(..), Route(..))
|
|||
|
||||
import Alerts.Types exposing (AlertGroup, Alert)
|
||||
import Views.AlertList.Types as AlertList exposing (AlertListMsg)
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg)
|
||||
import Views.SilenceList.Types as SilenceList exposing (SilenceListMsg)
|
||||
import Views.Silence.Types exposing (SilenceMsg)
|
||||
import Views.SilenceForm.Types as SilenceForm exposing (SilenceFormMsg)
|
||||
import Views.Status.Types exposing (StatusModel, StatusMsg)
|
||||
|
@ -13,7 +13,7 @@ import Time
|
|||
|
||||
|
||||
type alias Model =
|
||||
{ silences : ApiData (List Silence)
|
||||
{ silenceList : SilenceList.Model
|
||||
, silence : ApiData Silence
|
||||
, silenceForm : SilenceForm.Model
|
||||
, alertList : AlertList.Model
|
||||
|
|
|
@ -48,10 +48,10 @@ update msg model =
|
|||
|
||||
NavigateToSilenceList filter ->
|
||||
let
|
||||
( silences, silence, cmd ) =
|
||||
Views.SilenceList.Updates.update FetchSilences model.silences model.silence filter
|
||||
( silenceList, silence, cmd ) =
|
||||
Views.SilenceList.Updates.update FetchSilences model.silenceList model.silence filter
|
||||
in
|
||||
( { model | silence = silence, silences = silences, route = SilenceListRoute filter, filter = filter }
|
||||
( { model | silence = silence, silenceList = silenceList, route = SilenceListRoute filter, filter = filter }
|
||||
, cmd
|
||||
)
|
||||
|
||||
|
@ -116,10 +116,10 @@ update msg model =
|
|||
|
||||
MsgForSilenceList msg ->
|
||||
let
|
||||
( silences, silence, cmd ) =
|
||||
Views.SilenceList.Updates.update msg model.silences model.silence model.filter
|
||||
( silenceList, silence, cmd ) =
|
||||
Views.SilenceList.Updates.update msg model.silenceList model.silence model.filter
|
||||
in
|
||||
( { model | silences = silences, silence = silence }, cmd )
|
||||
( { model | silenceList = silenceList, silence = silence }, cmd )
|
||||
|
||||
MsgForSilence msg ->
|
||||
Views.Silence.Updates.update msg model
|
||||
|
|
|
@ -41,13 +41,30 @@ generateQueryParam name =
|
|||
|
||||
generateQueryString : Filter -> String
|
||||
generateQueryString { receiver, showSilenced, text } =
|
||||
-- TODO: Re-add receiver once it is parsed on the server side.
|
||||
[ ( "silenced", Maybe.map (toString >> String.toLower) showSilenced )
|
||||
, ( "filter", text )
|
||||
]
|
||||
|> List.filterMap (uncurry generateQueryParam)
|
||||
|> String.join "&"
|
||||
|> (++) "?"
|
||||
let
|
||||
-- TODO: Re-add receiver once it is parsed on the server side.
|
||||
parts =
|
||||
[ ( "silenced", Maybe.map (toString >> String.toLower) showSilenced )
|
||||
, ( "filter", emptyToNothing text )
|
||||
]
|
||||
|> List.filterMap (uncurry generateQueryParam)
|
||||
in
|
||||
if List.length parts > 0 then
|
||||
parts
|
||||
|> String.join "&"
|
||||
|> (++) "?"
|
||||
else
|
||||
""
|
||||
|
||||
|
||||
emptyToNothing : Maybe String -> Maybe String
|
||||
emptyToNothing str =
|
||||
case str of
|
||||
Just "" ->
|
||||
Nothing
|
||||
|
||||
_ ->
|
||||
str
|
||||
|
||||
|
||||
type alias Matcher =
|
||||
|
|
|
@ -6,12 +6,12 @@ import Html.Events exposing (onCheck, onInput, onClick)
|
|||
import Http exposing (Error(..))
|
||||
|
||||
|
||||
labelButton : Maybe msg -> ( String, String ) -> Html msg
|
||||
labelButton maybeMsg ( key, value ) =
|
||||
labelButton : Maybe msg -> String -> Html msg
|
||||
labelButton maybeMsg labelText =
|
||||
let
|
||||
label =
|
||||
[ span [ class " badge badge-warning" ]
|
||||
[ i [] [], text (key ++ "=" ++ value) ]
|
||||
[ i [] [], text labelText ]
|
||||
]
|
||||
in
|
||||
case maybeMsg of
|
||||
|
|
|
@ -34,8 +34,8 @@ currentView model =
|
|||
AlertsRoute filter ->
|
||||
AlertList.view model.alertList filter
|
||||
|
||||
SilenceListRoute route ->
|
||||
SilenceList.view model.silences model.silence model.currentTime model.filter
|
||||
SilenceListRoute _ ->
|
||||
SilenceList.view model.silenceList model.currentTime
|
||||
|
||||
SilenceFormNewRoute keep ->
|
||||
SilenceForm.view Nothing model.silenceForm |> Html.map MsgForSilenceForm
|
||||
|
|
|
@ -6,7 +6,8 @@ import Html.Attributes exposing (class, style, href)
|
|||
import Html.Events exposing (onClick)
|
||||
import Types exposing (Msg(CreateSilenceFromAlert, Noop, MsgForAlertList))
|
||||
import Utils.Date
|
||||
import Views.AlertList.Types exposing (AlertListMsg(AddFilterMatcher))
|
||||
import Views.FilterBar.Types as FilterBarTypes
|
||||
import Views.AlertList.Types exposing (AlertListMsg(MsgForFilterBar))
|
||||
import Utils.Views exposing (buttonLink)
|
||||
import Utils.Filter
|
||||
import Time exposing (Time)
|
||||
|
@ -51,12 +52,14 @@ labelButton : ( String, String ) -> Html Msg
|
|||
labelButton ( key, value ) =
|
||||
let
|
||||
msg =
|
||||
AddFilterMatcher False
|
||||
(FilterBarTypes.AddFilterMatcher False
|
||||
{ key = key
|
||||
, op = Utils.Filter.Eq
|
||||
, value = value
|
||||
}
|
||||
|> MsgForFilterBar
|
||||
|> MsgForAlertList
|
||||
)
|
||||
in
|
||||
-- Hide "alertname" key if label is the alertname label
|
||||
if key == "alertname" then
|
||||
|
@ -65,7 +68,7 @@ labelButton ( key, value ) =
|
|||
[ i [] [], text value ]
|
||||
]
|
||||
else
|
||||
Utils.Views.labelButton (Just msg) ( key, value )
|
||||
Utils.Views.labelButton (Just msg) (key ++ "=" ++ value)
|
||||
|
||||
|
||||
silenceButton : Alert -> Html Msg
|
||||
|
|
|
@ -2,40 +2,24 @@ module Views.AlertList.Types exposing (AlertListMsg(..), Model, initAlertList)
|
|||
|
||||
import Utils.Types exposing (ApiData, ApiResponse(Loading))
|
||||
import Alerts.Types exposing (Alert)
|
||||
import Views.FilterBar.Types as FilterBar
|
||||
import Utils.Filter exposing (Filter)
|
||||
|
||||
|
||||
type AlertListMsg
|
||||
= AlertsFetched (ApiData (List Alert))
|
||||
| FetchAlerts
|
||||
| AddFilterMatcher Bool Utils.Filter.Matcher
|
||||
| DeleteFilterMatcher Bool Utils.Filter.Matcher
|
||||
| PressingBackspace Bool
|
||||
| UpdateMatcherText String
|
||||
| MsgForFilterBar FilterBar.Msg
|
||||
|
||||
|
||||
{-| A note about the `backspacePressed` attribute:
|
||||
|
||||
Holding down the backspace removes (one by one) each last character in the input,
|
||||
and the whole time sends multiple keyDown events. This is a guard so that if a user
|
||||
holds down backspace to remove the text in the input, they won't accidentally hold
|
||||
backspace too long and then delete the preceding matcher as well. So, once a user holds
|
||||
backspace to clear an input, they have to then lift up the key and press it again to
|
||||
proceed to deleting the next matcher.
|
||||
|
||||
-}
|
||||
type alias Model =
|
||||
{ alerts : ApiData (List Alert)
|
||||
, matchers : List Utils.Filter.Matcher
|
||||
, backspacePressed : Bool
|
||||
, matcherText : String
|
||||
, filterBar : FilterBar.Model
|
||||
}
|
||||
|
||||
|
||||
initAlertList : Model
|
||||
initAlertList =
|
||||
{ alerts = Loading
|
||||
, matchers = []
|
||||
, backspacePressed = False
|
||||
, matcherText = ""
|
||||
, filterBar = FilterBar.initFilterBar
|
||||
}
|
||||
|
|
|
@ -2,28 +2,15 @@ module Views.AlertList.Updates exposing (..)
|
|||
|
||||
import Alerts.Api as Api
|
||||
import Views.AlertList.Types exposing (AlertListMsg(..), Model)
|
||||
import Views.FilterBar.Updates as FilterBar
|
||||
import Navigation
|
||||
import Utils.Filter exposing (Filter, parseFilter)
|
||||
import Utils.Types exposing (ApiData, ApiResponse(Loading))
|
||||
import Utils.Filter exposing (Filter, generateQueryString, stringifyFilter, parseFilter)
|
||||
import Types exposing (Msg(MsgForAlertList, Noop))
|
||||
import Dom
|
||||
import Task
|
||||
|
||||
|
||||
immediatelyFilter : Filter -> Model -> ( Model, Cmd Types.Msg )
|
||||
immediatelyFilter filter model =
|
||||
let
|
||||
newFilter =
|
||||
{ filter | text = Just (stringifyFilter model.matchers) }
|
||||
in
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Navigation.newUrl ("/#/alerts" ++ generateQueryString newFilter)
|
||||
, Dom.focus "custom-matcher" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
update : AlertListMsg -> Model -> Filter -> ( Model, Cmd Types.Msg )
|
||||
update msg model filter =
|
||||
case msg of
|
||||
|
@ -32,43 +19,15 @@ update msg model filter =
|
|||
|
||||
FetchAlerts ->
|
||||
( { model
|
||||
| matchers =
|
||||
filter.text
|
||||
|> Maybe.andThen parseFilter
|
||||
|> Maybe.withDefault []
|
||||
| filterBar = FilterBar.setMatchers filter model.filterBar
|
||||
, alerts = Loading
|
||||
}
|
||||
, Api.fetchAlerts filter |> Cmd.map (AlertsFetched >> MsgForAlertList)
|
||||
)
|
||||
|
||||
AddFilterMatcher emptyMatcherText matcher ->
|
||||
immediatelyFilter filter
|
||||
{ model
|
||||
| matchers =
|
||||
if List.member matcher model.matchers then
|
||||
model.matchers
|
||||
else
|
||||
model.matchers ++ [ matcher ]
|
||||
, matcherText =
|
||||
if emptyMatcherText then
|
||||
""
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
DeleteFilterMatcher setMatcherText matcher ->
|
||||
immediatelyFilter filter
|
||||
{ model
|
||||
| matchers = List.filter ((/=) matcher) model.matchers
|
||||
, matcherText =
|
||||
if setMatcherText then
|
||||
Utils.Filter.stringifyMatcher matcher
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
UpdateMatcherText value ->
|
||||
( { model | matcherText = value }, Cmd.none )
|
||||
|
||||
PressingBackspace isPressed ->
|
||||
( { model | backspacePressed = isPressed }, Cmd.none )
|
||||
MsgForFilterBar msg ->
|
||||
let
|
||||
( filterBar, cmd ) =
|
||||
FilterBar.update "/#/alerts" filter msg model.filterBar
|
||||
in
|
||||
( { model | filterBar = filterBar }, Cmd.map (MsgForFilterBar >> MsgForAlertList) cmd )
|
||||
|
|
|
@ -5,18 +5,21 @@ import Html exposing (..)
|
|||
import Html.Attributes exposing (..)
|
||||
import Types exposing (Msg(Noop, CreateSilenceFromAlert, MsgForAlertList))
|
||||
import Utils.Filter exposing (Filter)
|
||||
import Views.FilterBar.Views as FilterBar
|
||||
import Views.FilterBar.Types as FilterBarTypes
|
||||
import Utils.Types exposing (ApiResponse(Success, Loading, Failure))
|
||||
import Utils.Views exposing (buttonLink, listButton)
|
||||
import Views.AlertList.AlertView as AlertView
|
||||
import Views.AlertList.Filter exposing (silenced, matchers)
|
||||
import Views.AlertList.FilterBar
|
||||
import Views.AlertList.Types exposing (AlertListMsg(AddFilterMatcher), Model)
|
||||
import Utils.Views exposing (buttonLink, listButton)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(MsgForFilterBar), Model)
|
||||
import Types exposing (Msg(Noop, CreateSilenceFromAlert, MsgForAlertList))
|
||||
|
||||
|
||||
view : Model -> Filter -> Html Msg
|
||||
view { alerts, matchers, matcherText, backspacePressed } filter =
|
||||
view { alerts, filterBar } filter =
|
||||
div []
|
||||
[ Views.AlertList.FilterBar.view matchers matcherText backspacePressed
|
||||
[ Html.map (MsgForFilterBar >> MsgForAlertList) (FilterBar.view filterBar)
|
||||
, case alerts of
|
||||
Success groups ->
|
||||
alertList groups filter
|
||||
|
|
|
@ -0,0 +1,36 @@
|
|||
module Views.FilterBar.Types exposing (Model, Msg(..), initFilterBar)
|
||||
|
||||
import Utils.Filter
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ matchers : List Utils.Filter.Matcher
|
||||
, backspacePressed : Bool
|
||||
, matcherText : String
|
||||
}
|
||||
|
||||
|
||||
type Msg
|
||||
= AddFilterMatcher Bool Utils.Filter.Matcher
|
||||
| DeleteFilterMatcher Bool Utils.Filter.Matcher
|
||||
| PressingBackspace Bool
|
||||
| UpdateMatcherText String
|
||||
| Noop
|
||||
|
||||
|
||||
{-| A note about the `backspacePressed` attribute:
|
||||
|
||||
Holding down the backspace removes (one by one) each last character in the input,
|
||||
and the whole time sends multiple keyDown events. This is a guard so that if a user
|
||||
holds down backspace to remove the text in the input, they won't accidentally hold
|
||||
backspace too long and then delete the preceding matcher as well. So, once a user holds
|
||||
backspace to clear an input, they have to then lift up the key and press it again to
|
||||
proceed to deleting the next matcher.
|
||||
|
||||
-}
|
||||
initFilterBar : Model
|
||||
initFilterBar =
|
||||
{ matchers = []
|
||||
, backspacePressed = False
|
||||
, matcherText = ""
|
||||
}
|
|
@ -0,0 +1,72 @@
|
|||
module Views.FilterBar.Updates exposing (update, setMatchers)
|
||||
|
||||
import Views.FilterBar.Types exposing (Msg(..), Model)
|
||||
import Task
|
||||
import Dom
|
||||
import Navigation
|
||||
import Utils.Filter exposing (Filter, generateQueryString, stringifyFilter, parseFilter)
|
||||
|
||||
|
||||
update : String -> Filter -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update url filter msg model =
|
||||
case msg of
|
||||
AddFilterMatcher emptyMatcherText matcher ->
|
||||
immediatelyFilter url
|
||||
filter
|
||||
{ model
|
||||
| matchers =
|
||||
if List.member matcher model.matchers then
|
||||
model.matchers
|
||||
else
|
||||
model.matchers ++ [ matcher ]
|
||||
, matcherText =
|
||||
if emptyMatcherText then
|
||||
""
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
DeleteFilterMatcher setMatcherText matcher ->
|
||||
immediatelyFilter url
|
||||
filter
|
||||
{ model
|
||||
| matchers = List.filter ((/=) matcher) model.matchers
|
||||
, matcherText =
|
||||
if setMatcherText then
|
||||
Utils.Filter.stringifyMatcher matcher
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
|
||||
UpdateMatcherText value ->
|
||||
( { model | matcherText = value }, Cmd.none )
|
||||
|
||||
PressingBackspace isPressed ->
|
||||
( { model | backspacePressed = isPressed }, Cmd.none )
|
||||
|
||||
Noop ->
|
||||
( model, Cmd.none )
|
||||
|
||||
|
||||
immediatelyFilter : String -> Filter -> Model -> ( Model, Cmd Msg )
|
||||
immediatelyFilter url filter model =
|
||||
let
|
||||
newFilter =
|
||||
{ filter | text = Just (stringifyFilter model.matchers) }
|
||||
in
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Navigation.newUrl (url ++ generateQueryString newFilter)
|
||||
, Dom.focus "custom-matcher" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
setMatchers : Filter -> Model -> Model
|
||||
setMatchers filter model =
|
||||
{ model
|
||||
| matchers =
|
||||
filter.text
|
||||
|> Maybe.andThen parseFilter
|
||||
|> Maybe.withDefault []
|
||||
}
|
|
@ -1,11 +1,10 @@
|
|||
module Views.AlertList.FilterBar exposing (view)
|
||||
module Views.FilterBar.Views exposing (view)
|
||||
|
||||
import Html exposing (Html, Attribute, div, span, input, text, button, i, small)
|
||||
import Html.Attributes exposing (value, class, style, disabled, id)
|
||||
import Html.Events exposing (onClick, onInput, on, keyCode)
|
||||
import Utils.Filter exposing (Matcher)
|
||||
import Views.AlertList.Types exposing (AlertListMsg(AddFilterMatcher, DeleteFilterMatcher, PressingBackspace, UpdateMatcherText))
|
||||
import Types exposing (Msg(MsgForAlertList, Noop))
|
||||
import Views.FilterBar.Types exposing (Msg(..), Model)
|
||||
import Json.Decode as Json
|
||||
|
||||
|
||||
|
@ -25,13 +24,13 @@ viewMatcher matcher =
|
|||
[ div [ class "btn-group" ]
|
||||
[ button
|
||||
[ class "btn btn-outline-info"
|
||||
, onClick (DeleteFilterMatcher True matcher |> MsgForAlertList)
|
||||
, onClick (DeleteFilterMatcher True matcher)
|
||||
]
|
||||
[ text <| Utils.Filter.stringifyMatcher matcher
|
||||
]
|
||||
, button
|
||||
[ class "btn btn-outline-danger"
|
||||
, onClick (DeleteFilterMatcher False matcher |> MsgForAlertList)
|
||||
, onClick (DeleteFilterMatcher False matcher)
|
||||
]
|
||||
[ text "×" ]
|
||||
]
|
||||
|
@ -63,8 +62,8 @@ onKey event key msg =
|
|||
)
|
||||
|
||||
|
||||
view : List Matcher -> String -> Bool -> Html Msg
|
||||
view matchers matcherText backspacePressed =
|
||||
view : Model -> Html Msg
|
||||
view { matchers, matcherText, backspacePressed } =
|
||||
let
|
||||
className =
|
||||
if matcherText == "" then
|
||||
|
@ -88,24 +87,24 @@ view matchers matcherText backspacePressed =
|
|||
|
||||
( "", False ) ->
|
||||
lastElem matchers
|
||||
|> Maybe.map (DeleteFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.map (DeleteFilterMatcher True)
|
||||
|> Maybe.withDefault Noop
|
||||
|
||||
_ ->
|
||||
PressingBackspace True |> MsgForAlertList
|
||||
PressingBackspace True
|
||||
|
||||
onKeypress =
|
||||
maybeMatcher
|
||||
|> Maybe.map (AddFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.map (AddFilterMatcher True)
|
||||
|> Maybe.withDefault Noop
|
||||
|> onKey "keypress" keys.enter
|
||||
|
||||
onKeyup =
|
||||
onKey "keyup" keys.backspace (PressingBackspace False |> MsgForAlertList)
|
||||
onKey "keyup" keys.backspace (PressingBackspace False)
|
||||
|
||||
onClickAttr =
|
||||
maybeMatcher
|
||||
|> Maybe.map (AddFilterMatcher True >> MsgForAlertList)
|
||||
|> Maybe.map (AddFilterMatcher True)
|
||||
|> Maybe.withDefault Noop
|
||||
|> onClick
|
||||
|
||||
|
@ -131,7 +130,7 @@ view matchers matcherText backspacePressed =
|
|||
, onKeydown
|
||||
, onKeyup
|
||||
, onKeypress
|
||||
, onInput (UpdateMatcherText >> MsgForAlertList)
|
||||
, onInput (UpdateMatcherText)
|
||||
]
|
||||
[]
|
||||
, span
|
||||
|
@ -142,10 +141,15 @@ view matchers matcherText backspacePressed =
|
|||
[ text "Custom matcher, e.g."
|
||||
, button
|
||||
[ class "btn btn-link btn-sm align-baseline"
|
||||
, onClick (UpdateMatcherText "env=\"production\"" |> MsgForAlertList)
|
||||
, onClick (UpdateMatcherText exampleMatcher)
|
||||
]
|
||||
[ text "env=\"production\"" ]
|
||||
[ text exampleMatcher ]
|
||||
]
|
||||
]
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
exampleMatcher : String
|
||||
exampleMatcher =
|
||||
"env=\"production\""
|
|
@ -9,4 +9,8 @@ import Utils.Views exposing (labelButton)
|
|||
view : Alert -> Html msg
|
||||
view alert =
|
||||
li [ class "mb2 w-80-l w-100-m" ] <|
|
||||
List.map (labelButton Nothing) alert.labels
|
||||
List.map
|
||||
(\( key, value ) ->
|
||||
labelButton Nothing (key ++ "=" ++ value)
|
||||
)
|
||||
alert.labels
|
||||
|
|
|
@ -4,11 +4,13 @@ import Html exposing (Html, div, a, p, text, b)
|
|||
import Html.Attributes exposing (class, href)
|
||||
import Silences.Types exposing (Silence)
|
||||
import Types exposing (Msg(Noop, MsgForSilenceList))
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(DestroySilence))
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(DestroySilence, MsgForFilterBar))
|
||||
import Utils.Date
|
||||
import Utils.Views exposing (buttonLink)
|
||||
import Utils.Types exposing (Matcher)
|
||||
import Utils.Filter
|
||||
import Utils.List
|
||||
import Views.FilterBar.Types as FilterBarTypes
|
||||
|
||||
|
||||
view : Silence -> Html Msg
|
||||
|
@ -41,6 +43,23 @@ view silence =
|
|||
]
|
||||
|
||||
|
||||
matcherButton : Matcher -> Html msg
|
||||
matcherButton : Matcher -> Html Msg
|
||||
matcherButton matcher =
|
||||
Utils.Views.button "light-silver hover-black ph3 pv2" <| Utils.List.mstring matcher
|
||||
let
|
||||
op =
|
||||
if matcher.isRegex then
|
||||
Utils.Filter.RegexMatch
|
||||
else
|
||||
Utils.Filter.Eq
|
||||
|
||||
msg =
|
||||
(FilterBarTypes.AddFilterMatcher False
|
||||
{ key = matcher.name
|
||||
, op = op
|
||||
, value = matcher.value
|
||||
}
|
||||
|> MsgForFilterBar
|
||||
|> MsgForSilenceList
|
||||
)
|
||||
in
|
||||
Utils.Views.labelButton (Just msg) (Utils.List.mstring matcher)
|
||||
|
|
|
@ -1,12 +1,27 @@
|
|||
module Views.SilenceList.Types exposing (SilenceListMsg(..))
|
||||
module Views.SilenceList.Types exposing (SilenceListMsg(..), Model, initSilenceList)
|
||||
|
||||
import Utils.Types exposing (ApiData, ApiResponse(Loading))
|
||||
import Silences.Types exposing (Silence)
|
||||
import Utils.Types exposing (ApiData)
|
||||
import Views.FilterBar.Types as FilterBar
|
||||
|
||||
|
||||
type SilenceListMsg
|
||||
= SilenceDestroy (ApiData String)
|
||||
| DestroySilence Silence
|
||||
| FilterSilences
|
||||
| SilencesFetch (ApiData (List Silence))
|
||||
| FetchSilences
|
||||
| MsgForFilterBar FilterBar.Msg
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ silences : ApiData (List Silence)
|
||||
, filterBar : FilterBar.Model
|
||||
}
|
||||
|
||||
|
||||
initSilenceList : Model
|
||||
initSilenceList =
|
||||
{ silences = Loading
|
||||
, filterBar = FilterBar.initFilterBar
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
module Views.SilenceList.Updates exposing (..)
|
||||
|
||||
import Silences.Api as Api
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(..))
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(..), Model)
|
||||
import Silences.Types exposing (Silence, nullSilence, nullMatcher)
|
||||
import Navigation
|
||||
import Task
|
||||
|
@ -9,28 +9,39 @@ import Utils.Types as Types exposing (ApiData, ApiResponse(Failure, Loading, Suc
|
|||
import Time
|
||||
import Types exposing (Msg(UpdateCurrentTime, MsgForSilenceList), Route(SilenceListRoute))
|
||||
import Utils.Filter exposing (Filter, generateQueryString)
|
||||
import Views.FilterBar.Updates as FilterBar
|
||||
|
||||
|
||||
update : SilenceListMsg -> ApiData (List Silence) -> ApiData Silence -> Filter -> ( ApiData (List Silence), ApiData Silence, Cmd Types.Msg )
|
||||
update msg silences silence filter =
|
||||
update : SilenceListMsg -> Model -> ApiData Silence -> Filter -> ( Model, ApiData Silence, Cmd Types.Msg )
|
||||
update msg model silence filter =
|
||||
case msg of
|
||||
SilencesFetch sils ->
|
||||
( sils, silence, Task.perform UpdateCurrentTime Time.now )
|
||||
( { model | silences = sils }, silence, Task.perform UpdateCurrentTime Time.now )
|
||||
|
||||
FetchSilences ->
|
||||
( silences, silence, Api.getSilences filter (SilencesFetch >> MsgForSilenceList) )
|
||||
( { model
|
||||
| filterBar = FilterBar.setMatchers filter model.filterBar
|
||||
, silences = Loading
|
||||
}
|
||||
, silence
|
||||
, Api.getSilences filter (SilencesFetch >> MsgForSilenceList)
|
||||
)
|
||||
|
||||
DestroySilence silence ->
|
||||
( silences, Loading, Api.destroy silence (SilenceDestroy >> MsgForSilenceList) )
|
||||
( model, Loading, Api.destroy silence (SilenceDestroy >> MsgForSilenceList) )
|
||||
|
||||
SilenceDestroy silence ->
|
||||
-- TODO: "Deleted id: ID" growl
|
||||
-- TODO: Add DELETE to accepted CORS methods in alertmanager
|
||||
-- TODO: Check why POST isn't there but is accepted
|
||||
( silences, Loading, Navigation.newUrl "/#/silences" )
|
||||
( model, Loading, Navigation.newUrl "/#/silences" )
|
||||
|
||||
FilterSilences ->
|
||||
( silences, silence, Navigation.newUrl ("/#/silences" ++ generateQueryString filter) )
|
||||
MsgForFilterBar msg ->
|
||||
let
|
||||
( filterBar, cmd ) =
|
||||
FilterBar.update "/#/silences" filter msg model.filterBar
|
||||
in
|
||||
( { model | filterBar = filterBar }, silence, Cmd.map (MsgForFilterBar >> MsgForSilenceList) cmd )
|
||||
|
||||
|
||||
urlUpdate : Maybe String -> ( SilenceListMsg, Filter )
|
||||
|
|
|
@ -5,7 +5,7 @@ module Views.SilenceList.Views exposing (..)
|
|||
import Html exposing (..)
|
||||
import Html.Attributes exposing (..)
|
||||
import Html.Events exposing (onClick, onInput)
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(..))
|
||||
import Views.SilenceList.Types exposing (SilenceListMsg(..), Model)
|
||||
import Views.Shared.SilenceBase
|
||||
import Silences.Types exposing (Silence)
|
||||
import Utils.Types exposing (Matcher, ApiResponse(..), ApiData)
|
||||
|
@ -13,28 +13,27 @@ import Utils.Filter exposing (Filter)
|
|||
import Utils.Views exposing (iconButtonMsg, checkbox, textField, formInput, formField, buttonLink, error, loading)
|
||||
import Time
|
||||
import Types exposing (Msg(UpdateFilter, MsgForSilenceList, Noop))
|
||||
import Views.FilterBar.Views as FilterBar
|
||||
import Views.FilterBar.Types as FilterBarTypes
|
||||
|
||||
|
||||
view : ApiData (List Silence) -> ApiData Silence -> Time.Time -> Filter -> Html Msg
|
||||
view apiSilences apiSilence currentTime filter =
|
||||
case apiSilences of
|
||||
view : Model -> Time.Time -> Html Msg
|
||||
view model currentTime =
|
||||
case model.silences of
|
||||
Success sils ->
|
||||
-- Add buttons at the top to filter Active/Pending/Expired
|
||||
silences sils filter (text "")
|
||||
silences sils model.filterBar (text "")
|
||||
|
||||
Loading ->
|
||||
loading
|
||||
|
||||
Failure msg ->
|
||||
silences [] filter (error msg)
|
||||
silences [] model.filterBar (error msg)
|
||||
|
||||
|
||||
silences : List Silence -> Filter -> Html Msg -> Html Msg
|
||||
silences silences filter errorHtml =
|
||||
silences : List Silence -> FilterBarTypes.Model -> Html Msg -> Html Msg
|
||||
silences silences filterBar errorHtml =
|
||||
let
|
||||
filterText =
|
||||
Maybe.withDefault "" filter.text
|
||||
|
||||
html =
|
||||
if List.isEmpty silences then
|
||||
div [ class "mt2" ] [ text "no silences found" ]
|
||||
|
@ -48,8 +47,7 @@ silences silences filter errorHtml =
|
|||
(List.map silenceList silences)
|
||||
in
|
||||
div []
|
||||
[ textField "Filter" filterText UpdateFilter
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", onClick (MsgForSilenceList FilterSilences) ] [ text "Filter Silences" ]
|
||||
[ Html.map (MsgForFilterBar >> MsgForSilenceList) (FilterBar.view filterBar)
|
||||
, a [ class "f6 link br2 ba ph3 pv2 mr2 dib blue", href "#/silences/new" ] [ text "New Silence" ]
|
||||
, errorHtml
|
||||
, html
|
||||
|
|
|
@ -0,0 +1,75 @@
|
|||
module Filter exposing (all)
|
||||
|
||||
import Test exposing (..)
|
||||
import Expect
|
||||
import Fuzz exposing (list, int, tuple, string)
|
||||
import Utils.Filter exposing (Matcher, MatchOperator(Eq, RegexMatch))
|
||||
import Helpers exposing (isNotEmptyTrimmedAlphabetWord)
|
||||
|
||||
|
||||
all : Test
|
||||
all =
|
||||
describe "Filter"
|
||||
[ parseMatcher
|
||||
, generateQueryString
|
||||
, stringifyFilter
|
||||
]
|
||||
|
||||
|
||||
parseMatcher : Test
|
||||
parseMatcher =
|
||||
describe "parseMatcher"
|
||||
[ test "should parse empty matcher string" <|
|
||||
\() ->
|
||||
Expect.equal Nothing (Utils.Filter.parseMatcher "")
|
||||
, fuzz (tuple ( string, string )) "should parse random matcher string" <|
|
||||
\( key, value ) ->
|
||||
if List.map isNotEmptyTrimmedAlphabetWord [ key, value ] /= [ True, True ] then
|
||||
Expect.equal
|
||||
Nothing
|
||||
(Utils.Filter.parseMatcher <| String.join "" [ key, "=", value ])
|
||||
else
|
||||
Expect.equal
|
||||
(Just (Matcher key Eq value))
|
||||
(Utils.Filter.parseMatcher <| String.join "" [ key, "=", "\"", value, "\"" ])
|
||||
]
|
||||
|
||||
|
||||
generateQueryString : Test
|
||||
generateQueryString =
|
||||
describe "generateQueryString"
|
||||
[ test "should not render keys with Nothing value" <|
|
||||
\() ->
|
||||
Expect.equal ""
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, text = Nothing, showSilenced = Nothing })
|
||||
, test "should not render filter key with empty value" <|
|
||||
\() ->
|
||||
Expect.equal ""
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, text = Just "", showSilenced = Nothing })
|
||||
, test "should render filter key with values" <|
|
||||
\() ->
|
||||
Expect.equal "?filter=%7Bfoo%3D%22bar%22%2C%20baz%3D~%22quux.*%22%7D"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, text = Just "{foo=\"bar\", baz=~\"quux.*\"}", showSilenced = Nothing })
|
||||
, test "should render silenced key with bool" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, text = Nothing, showSilenced = Just True })
|
||||
]
|
||||
|
||||
|
||||
stringifyFilter : Test
|
||||
stringifyFilter =
|
||||
describe "stringifyFilter"
|
||||
[ test "empty" <|
|
||||
\() ->
|
||||
Expect.equal ""
|
||||
(Utils.Filter.stringifyFilter [])
|
||||
, test "non-empty" <|
|
||||
\() ->
|
||||
Expect.equal "{foo=\"bar\", baz=~\"quux.*\"}"
|
||||
(Utils.Filter.stringifyFilter
|
||||
[ { key = "foo", op = Eq, value = "bar" }
|
||||
, { key = "baz", op = RegexMatch, value = "quux.*" }
|
||||
]
|
||||
)
|
||||
]
|
|
@ -1,10 +1,7 @@
|
|||
module Tests exposing (..)
|
||||
|
||||
import Test exposing (..)
|
||||
import Expect
|
||||
import Fuzz exposing (list, int, tuple, string)
|
||||
import Utils.Filter exposing (parseMatcher, Matcher, MatchOperator(Eq))
|
||||
import Helpers exposing (isNotEmptyTrimmedAlphabetWord)
|
||||
import Filter
|
||||
|
||||
|
||||
all : Test
|
||||
|
@ -17,26 +14,5 @@ all =
|
|||
utils : Test
|
||||
utils =
|
||||
describe "Utils"
|
||||
[ filter
|
||||
]
|
||||
|
||||
|
||||
filter : Test
|
||||
filter =
|
||||
describe "Filter"
|
||||
[ describe "parseMatcher"
|
||||
[ test "should parse empty matcher string" <|
|
||||
\() ->
|
||||
Expect.equal Nothing <| parseMatcher ""
|
||||
, fuzz (tuple ( string, string )) "should parse random matcher string" <|
|
||||
\( key, value ) ->
|
||||
if List.map isNotEmptyTrimmedAlphabetWord [ key, value ] /= [ True, True ] then
|
||||
Expect.equal
|
||||
Nothing
|
||||
(parseMatcher <| String.join "" [ key, "=", value ])
|
||||
else
|
||||
Expect.equal
|
||||
(Just (Matcher key Eq value))
|
||||
(parseMatcher <| String.join "" [ key, "=", "\"", value, "\"" ])
|
||||
]
|
||||
[ Filter.all
|
||||
]
|
||||
|
|
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue