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:
stuart nelson 2017-05-08 04:42:42 -04:00 committed by Max Inden
parent 952543b6e8
commit 91e802c4f3
21 changed files with 346 additions and 169 deletions

View File

@ -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 ]

View File

@ -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

View File

@ -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

View File

@ -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 =

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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 )

View File

@ -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

View File

@ -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 = ""
}

View File

@ -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 []
}

View File

@ -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\""

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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 )

View File

@ -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

75
ui/app/tests/Filter.elm Normal file
View File

@ -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.*" }
]
)
]

View File

@ -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