Negative matchers on the Silence form page (#2488)
* Support negative matchers in silence form Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Extract url manipulation from filterBar This is needed for silence form, where we don't have to manipulate the url. Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Only show the silence button in the alert list Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Validate matchers Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Improve silence form layout Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Fix for editing existing silence Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Fix for resetting the form Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com> * Update assets_vfsdata.go Signed-off-by: Andrey Kuzmin <unsoundscapes@gmail.com>
This commit is contained in:
parent
d57b2dcdec
commit
b0083ec55d
File diff suppressed because one or more lines are too long
|
@ -5,9 +5,9 @@ module Utils.Filter exposing
|
|||
, SilenceFormGetParams
|
||||
, convertFilterMatcher
|
||||
, emptySilenceFormGetParams
|
||||
, fromApiMatcher
|
||||
, generateAPIQueryString
|
||||
, generateQueryParam
|
||||
, generateQueryString
|
||||
, nullFilter
|
||||
, parseFilter
|
||||
, parseGroup
|
||||
|
@ -16,6 +16,9 @@ module Utils.Filter exposing
|
|||
, stringifyFilter
|
||||
, stringifyGroup
|
||||
, stringifyMatcher
|
||||
, toApiMatcher
|
||||
, toUrl
|
||||
, withMatchers
|
||||
)
|
||||
|
||||
import Char
|
||||
|
@ -53,8 +56,8 @@ generateQueryParam name =
|
|||
Maybe.map (percentEncode >> (++) (name ++ "="))
|
||||
|
||||
|
||||
generateQueryString : Filter -> String
|
||||
generateQueryString { receiver, customGrouping, showSilenced, showInhibited, showActive, text, group } =
|
||||
toUrl : String -> Filter -> String
|
||||
toUrl baseUrl { receiver, customGrouping, showSilenced, showInhibited, showActive, text, group } =
|
||||
let
|
||||
parts =
|
||||
[ ( "silenced", Maybe.withDefault False showSilenced |> boolToString |> Just )
|
||||
|
@ -68,12 +71,14 @@ generateQueryString { receiver, customGrouping, showSilenced, showInhibited, sho
|
|||
|> List.filterMap (\( a, b ) -> generateQueryParam a b)
|
||||
in
|
||||
if List.length parts > 0 then
|
||||
parts
|
||||
|> String.join "&"
|
||||
|> (++) "?"
|
||||
baseUrl
|
||||
++ (parts
|
||||
|> String.join "&"
|
||||
|> (++) "?"
|
||||
)
|
||||
|
||||
else
|
||||
""
|
||||
baseUrl
|
||||
|
||||
|
||||
generateAPIQueryString : Filter -> String
|
||||
|
@ -141,8 +146,32 @@ type alias Matcher =
|
|||
}
|
||||
|
||||
|
||||
convertAPIMatcher : Data.Matcher.Matcher -> Matcher
|
||||
convertAPIMatcher { name, value, isRegex, isEqual } =
|
||||
toApiMatcher : Matcher -> Data.Matcher.Matcher
|
||||
toApiMatcher { key, op, value } =
|
||||
let
|
||||
( isRegex, isEqual ) =
|
||||
case op of
|
||||
Eq ->
|
||||
( False, True )
|
||||
|
||||
NotEq ->
|
||||
( False, False )
|
||||
|
||||
RegexMatch ->
|
||||
( True, True )
|
||||
|
||||
NotRegexMatch ->
|
||||
( True, False )
|
||||
in
|
||||
{ name = key
|
||||
, isRegex = isRegex
|
||||
, isEqual = Just isEqual
|
||||
, value = value
|
||||
}
|
||||
|
||||
|
||||
fromApiMatcher : Data.Matcher.Matcher -> Matcher
|
||||
fromApiMatcher { name, value, isRegex, isEqual } =
|
||||
let
|
||||
isEqualValue =
|
||||
case isEqual of
|
||||
|
@ -333,11 +362,16 @@ isVarChar char =
|
|||
|| Char.isDigit char
|
||||
|
||||
|
||||
withMatchers : List Matcher -> Filter -> Filter
|
||||
withMatchers matchers_ filter_ =
|
||||
{ filter_ | text = Just (stringifyFilter matchers_) }
|
||||
|
||||
|
||||
silencePreviewFilter : List Data.Matcher.Matcher -> Filter
|
||||
silencePreviewFilter apiMatchers =
|
||||
{ nullFilter
|
||||
| text =
|
||||
List.map convertAPIMatcher apiMatchers
|
||||
List.map fromApiMatcher apiMatchers
|
||||
|> stringifyFilter
|
||||
|> Just
|
||||
, showSilenced = Just True
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
module Views.AlertList.Filter exposing (matchers)
|
||||
|
||||
import Alerts.Types exposing (Alert, AlertGroup, Block)
|
||||
import Regex exposing (contains, regex)
|
||||
import Utils.Types exposing (Matchers)
|
||||
|
||||
|
||||
matchers : Maybe Utils.Types.Matchers -> List AlertGroup -> List AlertGroup
|
||||
matchers matchers alerts =
|
||||
case matchers of
|
||||
Just ms ->
|
||||
by (filterAlertGroupLabels ms) alerts
|
||||
|
||||
Nothing ->
|
||||
alerts
|
||||
|
||||
|
||||
alertsFromBlock : (Alert -> Bool) -> Block -> Maybe Block
|
||||
alertsFromBlock fn block =
|
||||
let
|
||||
alerts =
|
||||
List.filter fn block.alerts
|
||||
in
|
||||
if not <| List.isEmpty alerts then
|
||||
Just { block | alerts = alerts }
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
byLabel : Utils.Types.Matchers -> Block -> Maybe Block
|
||||
byLabel matchers block =
|
||||
alertsFromBlock
|
||||
(\a ->
|
||||
-- Check that all labels are present within the alert's label set.
|
||||
List.all
|
||||
(\m ->
|
||||
-- Check for regex or direct match
|
||||
if m.isRegex then
|
||||
-- Check if key is present, then regex match value.
|
||||
let
|
||||
x =
|
||||
List.head <| List.filter (\( key, value ) -> key == m.name) a.labels
|
||||
|
||||
regex =
|
||||
Regex.regex m.value
|
||||
in
|
||||
-- No regex match
|
||||
case x of
|
||||
Just ( _, value ) ->
|
||||
Regex.contains regex value
|
||||
|
||||
Nothing ->
|
||||
False
|
||||
|
||||
else
|
||||
List.member ( m.name, m.value ) a.labels
|
||||
)
|
||||
matchers
|
||||
)
|
||||
block
|
||||
|
||||
|
||||
filterAlertGroupLabels : Utils.Types.Matchers -> AlertGroup -> Maybe AlertGroup
|
||||
filterAlertGroupLabels matchers alertGroup =
|
||||
let
|
||||
blocks =
|
||||
List.filterMap (byLabel matchers) alertGroup.blocks
|
||||
in
|
||||
if not <| List.isEmpty blocks then
|
||||
Just { alertGroup | blocks = blocks }
|
||||
|
||||
else
|
||||
Nothing
|
||||
|
||||
|
||||
by : (a -> Maybe a) -> List a -> List a
|
||||
by fn groups =
|
||||
List.filterMap fn groups
|
|
@ -55,7 +55,7 @@ initAlertList key expandAll =
|
|||
, alertGroups = Initial
|
||||
, receiverBar = ReceiverBar.initReceiverBar key
|
||||
, groupBar = GroupBar.initGroupBar key
|
||||
, filterBar = FilterBar.initFilterBar key
|
||||
, filterBar = FilterBar.initFilterBar []
|
||||
, tab = FilterTab
|
||||
, activeId = Nothing
|
||||
, activeGroups = Set.empty
|
||||
|
|
|
@ -7,7 +7,7 @@ import Dict
|
|||
import Set
|
||||
import Task
|
||||
import Types exposing (Msg(..))
|
||||
import Utils.Filter exposing (Filter, generateQueryString, parseFilter)
|
||||
import Utils.Filter exposing (Filter)
|
||||
import Utils.List
|
||||
import Utils.Types exposing (ApiData(..))
|
||||
import Views.AlertList.Types exposing (AlertListMsg(..), Model, Tab(..))
|
||||
|
@ -21,6 +21,9 @@ update msg ({ groupBar, alerts, filterBar, receiverBar, alertGroups } as model)
|
|||
let
|
||||
alertsUrl =
|
||||
basePath ++ "#/alerts"
|
||||
|
||||
filteredUrl =
|
||||
Utils.Filter.toUrl alertsUrl
|
||||
in
|
||||
case msg of
|
||||
AlertGroupsFetched listOfAlertGroups ->
|
||||
|
@ -109,12 +112,12 @@ update msg ({ groupBar, alerts, filterBar, receiverBar, alertGroups } as model)
|
|||
|
||||
ToggleSilenced showSilenced ->
|
||||
( model
|
||||
, Navigation.pushUrl model.key (alertsUrl ++ generateQueryString { filter | showSilenced = Just showSilenced })
|
||||
, Navigation.pushUrl model.key (filteredUrl { filter | showSilenced = Just showSilenced })
|
||||
)
|
||||
|
||||
ToggleInhibited showInhibited ->
|
||||
( model
|
||||
, Navigation.pushUrl model.key (alertsUrl ++ generateQueryString { filter | showInhibited = Just showInhibited })
|
||||
, Navigation.pushUrl model.key (filteredUrl { filter | showInhibited = Just showInhibited })
|
||||
)
|
||||
|
||||
SetTab tab ->
|
||||
|
@ -122,10 +125,26 @@ update msg ({ groupBar, alerts, filterBar, receiverBar, alertGroups } as model)
|
|||
|
||||
MsgForFilterBar subMsg ->
|
||||
let
|
||||
( newFilterBar, cmd ) =
|
||||
FilterBar.update alertsUrl filter subMsg filterBar
|
||||
( newFilterBar, shouldFilter, cmd ) =
|
||||
FilterBar.update subMsg filterBar
|
||||
|
||||
filterBarCmd =
|
||||
Cmd.map (MsgForFilterBar >> MsgForAlertList) cmd
|
||||
|
||||
newUrl =
|
||||
filteredUrl (Utils.Filter.withMatchers newFilterBar.matchers filter)
|
||||
|
||||
alertsCmd =
|
||||
if shouldFilter then
|
||||
Cmd.batch
|
||||
[ Navigation.pushUrl model.key newUrl
|
||||
, filterBarCmd
|
||||
]
|
||||
|
||||
else
|
||||
filterBarCmd
|
||||
in
|
||||
( { model | filterBar = newFilterBar, tab = FilterTab }, Cmd.map (MsgForFilterBar >> MsgForAlertList) cmd )
|
||||
( { model | filterBar = newFilterBar, tab = FilterTab }, alertsCmd )
|
||||
|
||||
MsgForGroupBar subMsg ->
|
||||
let
|
||||
|
|
|
@ -65,7 +65,7 @@ view { alerts, alertGroups, groupBar, filterBar, receiverBar, tab, activeId, act
|
|||
, div [ class "card-block" ]
|
||||
[ case tab of
|
||||
FilterTab ->
|
||||
Html.map (MsgForFilterBar >> MsgForAlertList) (FilterBar.view filterBar)
|
||||
Html.map (MsgForFilterBar >> MsgForAlertList) (FilterBar.view { showSilenceButton = True } filterBar)
|
||||
|
||||
GroupTab ->
|
||||
Html.map (MsgForGroupBar >> MsgForAlertList) (GroupBar.view groupBar filter.customGrouping)
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
module Views.FilterBar.Types exposing (Model, Msg(..), initFilterBar)
|
||||
|
||||
import Browser.Navigation exposing (Key)
|
||||
import Utils.Filter
|
||||
|
||||
|
||||
|
@ -8,7 +7,6 @@ type alias Model =
|
|||
{ matchers : List Utils.Filter.Matcher
|
||||
, backspacePressed : Bool
|
||||
, matcherText : String
|
||||
, key : Key
|
||||
}
|
||||
|
||||
|
||||
|
@ -30,10 +28,9 @@ backspace to clear an input, they have to then lift up the key and press it agai
|
|||
proceed to deleting the next matcher.
|
||||
|
||||
-}
|
||||
initFilterBar : Key -> Model
|
||||
initFilterBar key =
|
||||
{ matchers = []
|
||||
initFilterBar : List Utils.Filter.Matcher -> Model
|
||||
initFilterBar matchers =
|
||||
{ matchers = matchers
|
||||
, backspacePressed = False
|
||||
, matcherText = ""
|
||||
, key = key
|
||||
}
|
||||
|
|
|
@ -1,68 +1,59 @@
|
|||
module Views.FilterBar.Updates exposing (setMatchers, update)
|
||||
|
||||
import Browser.Dom as Dom
|
||||
import Browser.Navigation as Navigation
|
||||
import Task
|
||||
import Utils.Filter exposing (Filter, generateQueryString, parseFilter, stringifyFilter)
|
||||
import Utils.Filter exposing (Filter, parseFilter)
|
||||
import Views.FilterBar.Types exposing (Model, Msg(..))
|
||||
|
||||
|
||||
update : String -> Filter -> Msg -> Model -> ( Model, Cmd Msg )
|
||||
update url filter msg model =
|
||||
{-| Returns a triple where the Bool component notifies whether the matchers have changed.
|
||||
-}
|
||||
update : Msg -> Model -> ( Model, Bool, Cmd Msg )
|
||||
update msg model =
|
||||
case msg of
|
||||
AddFilterMatcher emptyMatcherText matcher ->
|
||||
immediatelyFilter url
|
||||
filter
|
||||
{ model
|
||||
| matchers =
|
||||
if List.member matcher model.matchers then
|
||||
model.matchers
|
||||
( { model
|
||||
| matchers =
|
||||
if List.member matcher model.matchers then
|
||||
model.matchers
|
||||
|
||||
else
|
||||
model.matchers ++ [ matcher ]
|
||||
, matcherText =
|
||||
if emptyMatcherText then
|
||||
""
|
||||
else
|
||||
model.matchers ++ [ matcher ]
|
||||
, matcherText =
|
||||
if emptyMatcherText then
|
||||
""
|
||||
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
, True
|
||||
, Dom.focus "filter-bar-matcher"
|
||||
|> Task.attempt (always Noop)
|
||||
)
|
||||
|
||||
DeleteFilterMatcher setMatcherText matcher ->
|
||||
immediatelyFilter url
|
||||
filter
|
||||
{ model
|
||||
| matchers = List.filter ((/=) matcher) model.matchers
|
||||
, matcherText =
|
||||
if setMatcherText then
|
||||
Utils.Filter.stringifyMatcher matcher
|
||||
( { model
|
||||
| matchers = List.filter ((/=) matcher) model.matchers
|
||||
, matcherText =
|
||||
if setMatcherText then
|
||||
Utils.Filter.stringifyMatcher matcher
|
||||
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
else
|
||||
model.matcherText
|
||||
}
|
||||
, True
|
||||
, Dom.focus "filter-bar-matcher"
|
||||
|> Task.attempt (always Noop)
|
||||
)
|
||||
|
||||
UpdateMatcherText value ->
|
||||
( { model | matcherText = value }, Cmd.none )
|
||||
( { model | matcherText = value }, False, Cmd.none )
|
||||
|
||||
PressingBackspace isPressed ->
|
||||
( { model | backspacePressed = isPressed }, Cmd.none )
|
||||
( { model | backspacePressed = isPressed }, False, 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 | matchers = [] }
|
||||
, Cmd.batch
|
||||
[ Navigation.pushUrl model.key (url ++ generateQueryString newFilter)
|
||||
, Dom.focus "filter-bar-matcher" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
( model, False, Cmd.none )
|
||||
|
||||
|
||||
setMatchers : Filter -> Model -> Model
|
||||
|
|
|
@ -45,8 +45,8 @@ viewMatchers matchers =
|
|||
|> List.map viewMatcher
|
||||
|
||||
|
||||
view : Model -> Html Msg
|
||||
view { matchers, matcherText, backspacePressed } =
|
||||
view : { showSilenceButton : Bool } -> Model -> Html Msg
|
||||
view { showSilenceButton } { matchers, matcherText, backspacePressed } =
|
||||
let
|
||||
maybeMatcher =
|
||||
Utils.Filter.parseMatcher matcherText
|
||||
|
@ -112,28 +112,44 @@ view { matchers, matcherText, backspacePressed } =
|
|||
(viewMatchers matchers
|
||||
++ [ div
|
||||
[ class ("col " ++ className)
|
||||
, style "min-width" "200px"
|
||||
, style "min-width"
|
||||
(if showSilenceButton then
|
||||
"300px"
|
||||
|
||||
else
|
||||
"200px"
|
||||
)
|
||||
]
|
||||
[ div [ class "input-group" ]
|
||||
[ input
|
||||
[ id "filter-bar-matcher"
|
||||
, class "form-control"
|
||||
, value matcherText
|
||||
, onKeyDown keyDown
|
||||
, onKeyUp keyUp
|
||||
, onInput UpdateMatcherText
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class "input-group-btn" ]
|
||||
[ button [ class "btn btn-primary", disabled isDisabled, onClickAttr ] [ text "+" ] ]
|
||||
, a
|
||||
[ class "btn btn-outline-info border-0"
|
||||
, href (newSilenceFromMatchers dataMatchers)
|
||||
]
|
||||
[ i [ class "fa fa-bell-slash-o mr-2" ] []
|
||||
, text "Silence"
|
||||
[ div [ class "row no-gutters align-content-stretch" ]
|
||||
[ div [ class "col input-group" ]
|
||||
[ input
|
||||
[ id "filter-bar-matcher"
|
||||
, class "form-control"
|
||||
, value matcherText
|
||||
, onKeyDown keyDown
|
||||
, onKeyUp keyUp
|
||||
, onInput UpdateMatcherText
|
||||
]
|
||||
[]
|
||||
, span
|
||||
[ class "input-group-btn" ]
|
||||
[ button [ class "btn btn-primary", disabled isDisabled, onClickAttr ] [ text "+" ] ]
|
||||
]
|
||||
, if showSilenceButton then
|
||||
div [ class "col col-auto input-group-btn ml-2" ]
|
||||
[ div [ class "input-group" ]
|
||||
[ a
|
||||
[ class "btn btn-outline-info"
|
||||
, href (newSilenceFromMatchers dataMatchers)
|
||||
]
|
||||
[ i [ class "fa fa-bell-slash-o mr-2" ] []
|
||||
, text "Silence"
|
||||
]
|
||||
]
|
||||
]
|
||||
|
||||
else
|
||||
text ""
|
||||
]
|
||||
, small [ class "form-text text-muted" ]
|
||||
[ text "Custom matcher, e.g."
|
||||
|
|
|
@ -4,7 +4,7 @@ import Browser.Dom as Dom
|
|||
import Browser.Navigation as Navigation
|
||||
import Set
|
||||
import Task
|
||||
import Utils.Filter exposing (Filter, generateQueryString, parseGroup, stringifyGroup)
|
||||
import Utils.Filter exposing (Filter, parseGroup, stringifyGroup)
|
||||
import Utils.Match exposing (jaroWinkler)
|
||||
import Views.GroupBar.Types exposing (Model, Msg(..))
|
||||
|
||||
|
@ -15,7 +15,7 @@ update url filter msg model =
|
|||
CustomGrouping customGrouping ->
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Navigation.pushUrl model.key (url ++ generateQueryString { filter | customGrouping = customGrouping })
|
||||
[ Navigation.pushUrl model.key (Utils.Filter.toUrl url { filter | customGrouping = customGrouping })
|
||||
, Dom.focus "group-by-field" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
|
@ -87,7 +87,7 @@ immediatelyFilter url filter model =
|
|||
in
|
||||
( model
|
||||
, Cmd.batch
|
||||
[ Navigation.pushUrl model.key (url ++ generateQueryString newFilter)
|
||||
[ Navigation.pushUrl model.key (Utils.Filter.toUrl url newFilter)
|
||||
, Dom.focus "group-by-field" |> Task.attempt (always Noop)
|
||||
]
|
||||
)
|
||||
|
|
|
@ -4,7 +4,7 @@ import Alerts.Api as Api
|
|||
import Browser.Dom as Dom
|
||||
import Browser.Navigation as Navigation
|
||||
import Task
|
||||
import Utils.Filter exposing (Filter, generateQueryString, parseGroup, stringifyGroup)
|
||||
import Utils.Filter exposing (Filter)
|
||||
import Utils.Match exposing (jaroWinkler)
|
||||
import Utils.Types exposing (ApiData(..))
|
||||
import Views.ReceiverBar.Types exposing (Model, Msg(..), apiReceiverToReceiver)
|
||||
|
@ -60,16 +60,15 @@ update url filter msg model =
|
|||
FilterByReceiver regex ->
|
||||
( { model | showReceivers = False, resultsHovered = False }
|
||||
, Navigation.pushUrl model.key
|
||||
(url
|
||||
++ generateQueryString
|
||||
{ filter
|
||||
| receiver =
|
||||
if regex == "" then
|
||||
Nothing
|
||||
(Utils.Filter.toUrl url
|
||||
{ filter
|
||||
| receiver =
|
||||
if regex == "" then
|
||||
Nothing
|
||||
|
||||
else
|
||||
Just regex
|
||||
}
|
||||
else
|
||||
Just regex
|
||||
}
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -1,17 +1,17 @@
|
|||
module Views.SilenceForm.Types exposing
|
||||
( MatcherForm
|
||||
, Model
|
||||
( Model
|
||||
, SilenceForm
|
||||
, SilenceFormFieldMsg(..)
|
||||
, SilenceFormMsg(..)
|
||||
, emptyMatcher
|
||||
, fromDateTimePicker
|
||||
, fromMatchersAndCommentAndTime
|
||||
, fromSilence
|
||||
, initSilenceForm
|
||||
, parseEndsAt
|
||||
, toSilence
|
||||
, validMatchers
|
||||
, validateForm
|
||||
, validateMatchers
|
||||
)
|
||||
|
||||
import Browser.Navigation exposing (Key)
|
||||
|
@ -34,10 +34,13 @@ import Utils.FormValidation
|
|||
, validate
|
||||
)
|
||||
import Utils.Types exposing (ApiData(..), Duration)
|
||||
import Views.FilterBar.Types as FilterBar
|
||||
|
||||
|
||||
type alias Model =
|
||||
{ form : SilenceForm
|
||||
, filterBar : FilterBar.Model
|
||||
, filterBarValid : ValidationState
|
||||
, silenceId : ApiData String
|
||||
, alerts : ApiData (List GettableAlert)
|
||||
, activeAlertId : Maybe String
|
||||
|
@ -52,20 +55,11 @@ type alias SilenceForm =
|
|||
, startsAt : ValidatedField
|
||||
, endsAt : ValidatedField
|
||||
, duration : ValidatedField
|
||||
, matchers : List MatcherForm
|
||||
, dateTimePicker : DateTimePicker
|
||||
, viewDateTimePicker : Bool
|
||||
}
|
||||
|
||||
|
||||
type alias MatcherForm =
|
||||
{ name : ValidatedField
|
||||
, value : ValidatedField
|
||||
, isRegex : Bool
|
||||
, isEqual : Maybe Bool
|
||||
}
|
||||
|
||||
|
||||
type SilenceFormMsg
|
||||
= UpdateField SilenceFormFieldMsg
|
||||
| CreateSilence
|
||||
|
@ -78,12 +72,11 @@ type SilenceFormMsg
|
|||
| SilenceFetch (ApiData GettableSilence)
|
||||
| SilenceCreate (ApiData String)
|
||||
| UpdateDateTimePicker Utils.DateTimePicker.Types.Msg
|
||||
| MsgForFilterBar FilterBar.Msg
|
||||
|
||||
|
||||
type SilenceFormFieldMsg
|
||||
= AddMatcher
|
||||
| DeleteMatcher Int
|
||||
| UpdateStartsAt String
|
||||
= UpdateStartsAt String
|
||||
| UpdateEndsAt String
|
||||
| UpdateDuration String
|
||||
| ValidateTime
|
||||
|
@ -91,11 +84,6 @@ type SilenceFormFieldMsg
|
|||
| ValidateCreatedBy
|
||||
| UpdateComment String
|
||||
| ValidateComment
|
||||
| UpdateMatcherName Int String
|
||||
| ValidateMatcherName Int
|
||||
| UpdateMatcherValue Int String
|
||||
| ValidateMatcherValue Int
|
||||
| UpdateMatcherRegex Int Bool
|
||||
| UpdateTimesFromPicker
|
||||
| OpenDateTimePicker
|
||||
| CloseDateTimePicker
|
||||
|
@ -104,6 +92,8 @@ type SilenceFormFieldMsg
|
|||
initSilenceForm : Key -> Model
|
||||
initSilenceForm key =
|
||||
{ form = empty
|
||||
, filterBar = FilterBar.initFilterBar []
|
||||
, filterBarValid = Utils.FormValidation.Initial
|
||||
, silenceId = Utils.Types.Initial
|
||||
, alerts = Utils.Types.Initial
|
||||
, activeAlertId = Nothing
|
||||
|
@ -111,29 +101,43 @@ initSilenceForm key =
|
|||
}
|
||||
|
||||
|
||||
toSilence : SilenceForm -> Maybe PostableSilence
|
||||
toSilence { id, comment, matchers, createdBy, startsAt, endsAt } =
|
||||
toSilence : FilterBar.Model -> SilenceForm -> Maybe PostableSilence
|
||||
toSilence filterBar { id, comment, createdBy, startsAt, endsAt } =
|
||||
Result.map5
|
||||
(\nonEmptyComment validMatchers nonEmptyCreatedBy parsedStartsAt parsedEndsAt ->
|
||||
(\nonEmptyMatchers nonEmptyComment nonEmptyCreatedBy parsedStartsAt parsedEndsAt ->
|
||||
{ nullSilence
|
||||
| id = id
|
||||
, comment = nonEmptyComment
|
||||
, matchers = validMatchers
|
||||
, matchers = nonEmptyMatchers
|
||||
, createdBy = nonEmptyCreatedBy
|
||||
, startsAt = parsedStartsAt
|
||||
, endsAt = parsedEndsAt
|
||||
}
|
||||
)
|
||||
(validMatchers filterBar)
|
||||
(stringNotEmpty comment.value)
|
||||
(List.foldr appendMatcher (Ok []) matchers)
|
||||
(stringNotEmpty createdBy.value)
|
||||
(timeFromString startsAt.value)
|
||||
(parseEndsAt startsAt.value endsAt.value)
|
||||
|> Result.toMaybe
|
||||
|
||||
|
||||
validMatchers : FilterBar.Model -> Result String (List Data.Matcher.Matcher)
|
||||
validMatchers { matchers, matcherText } =
|
||||
if matcherText /= "" then
|
||||
Err "Please complete adding the matcher"
|
||||
|
||||
else
|
||||
case matchers of
|
||||
[] ->
|
||||
Err "Matchers are required"
|
||||
|
||||
nonEmptyMatchers ->
|
||||
Ok (List.map Utils.Filter.toApiMatcher nonEmptyMatchers)
|
||||
|
||||
|
||||
fromSilence : GettableSilence -> SilenceForm
|
||||
fromSilence { id, createdBy, comment, startsAt, endsAt, matchers } =
|
||||
fromSilence { id, createdBy, comment, startsAt, endsAt } =
|
||||
let
|
||||
startsPosix =
|
||||
Utils.Date.timeFromString (DateTime.toString startsAt)
|
||||
|
@ -149,26 +153,34 @@ fromSilence { id, createdBy, comment, startsAt, endsAt, matchers } =
|
|||
, startsAt = initialField (timeToString startsAt)
|
||||
, endsAt = initialField (timeToString endsAt)
|
||||
, duration = initialField (durationFormat (timeDifference startsAt endsAt) |> Maybe.withDefault "")
|
||||
, matchers = List.map fromMatcher matchers
|
||||
, dateTimePicker = initFromStartAndEndTime startsPosix endsPosix
|
||||
, viewDateTimePicker = False
|
||||
}
|
||||
|
||||
|
||||
validateForm : SilenceForm -> SilenceForm
|
||||
validateForm { id, createdBy, comment, startsAt, endsAt, duration, matchers, dateTimePicker } =
|
||||
validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker } =
|
||||
{ id = id
|
||||
, createdBy = validate stringNotEmpty createdBy
|
||||
, comment = validate stringNotEmpty comment
|
||||
, startsAt = validate timeFromString startsAt
|
||||
, endsAt = validate (parseEndsAt startsAt.value) endsAt
|
||||
, duration = validate parseDuration duration
|
||||
, matchers = List.map validateMatcherForm matchers
|
||||
, dateTimePicker = dateTimePicker
|
||||
, viewDateTimePicker = False
|
||||
}
|
||||
|
||||
|
||||
validateMatchers : FilterBar.Model -> ValidationState
|
||||
validateMatchers filter =
|
||||
case validMatchers filter of
|
||||
Err error ->
|
||||
Utils.FormValidation.Invalid error
|
||||
|
||||
Ok _ ->
|
||||
Utils.FormValidation.Valid
|
||||
|
||||
|
||||
parseEndsAt : String -> String -> Result String Posix
|
||||
parseEndsAt startsAt endsAt =
|
||||
case ( timeFromString startsAt, timeFromString endsAt ) of
|
||||
|
@ -183,15 +195,6 @@ parseEndsAt startsAt endsAt =
|
|||
endsResult
|
||||
|
||||
|
||||
validateMatcherForm : MatcherForm -> MatcherForm
|
||||
validateMatcherForm { name, value, isRegex, isEqual } =
|
||||
{ name = validate stringNotEmpty name
|
||||
, value = value
|
||||
, isRegex = isRegex
|
||||
, isEqual = isEqual
|
||||
}
|
||||
|
||||
|
||||
empty : SilenceForm
|
||||
empty =
|
||||
{ id = Nothing
|
||||
|
@ -200,87 +203,38 @@ empty =
|
|||
, startsAt = initialField ""
|
||||
, endsAt = initialField ""
|
||||
, duration = initialField ""
|
||||
, matchers = []
|
||||
, dateTimePicker = initDateTimePicker
|
||||
, viewDateTimePicker = False
|
||||
}
|
||||
|
||||
|
||||
emptyMatcher : MatcherForm
|
||||
emptyMatcher =
|
||||
{ isRegex = False
|
||||
, isEqual = Just True
|
||||
, name = initialField ""
|
||||
, value = initialField ""
|
||||
}
|
||||
|
||||
|
||||
defaultDuration : Float
|
||||
defaultDuration =
|
||||
-- 2 hours
|
||||
2 * 60 * 60 * 1000
|
||||
|
||||
|
||||
fromMatchersAndCommentAndTime : String -> List Utils.Filter.Matcher -> String -> Posix -> SilenceForm
|
||||
fromMatchersAndCommentAndTime defaultCreator matchers comment now =
|
||||
fromMatchersAndCommentAndTime : String -> String -> Posix -> SilenceForm
|
||||
fromMatchersAndCommentAndTime defaultCreator comment now =
|
||||
{ empty
|
||||
| startsAt = initialField (timeToString now)
|
||||
, endsAt = initialField (timeToString (addDuration defaultDuration now))
|
||||
, duration = initialField (durationFormat defaultDuration |> Maybe.withDefault "")
|
||||
, createdBy = initialField defaultCreator
|
||||
, matchers =
|
||||
-- If no matchers were specified, add an empty row
|
||||
if List.isEmpty matchers then
|
||||
[ emptyMatcher ]
|
||||
|
||||
else
|
||||
List.filterMap (filterMatcherToMatcher >> Maybe.map fromMatcher) matchers
|
||||
, comment = initialField comment
|
||||
, dateTimePicker = initFromStartAndEndTime (Just now) (Just (addDuration defaultDuration now))
|
||||
, viewDateTimePicker = False
|
||||
}
|
||||
|
||||
|
||||
appendMatcher : MatcherForm -> Result String (List Matcher) -> Result String (List Matcher)
|
||||
appendMatcher { isRegex, isEqual, name, value } =
|
||||
Result.map2 (::)
|
||||
(Result.map2 (\k v -> Matcher k v isRegex isEqual) (stringNotEmpty name.value) (Ok value.value))
|
||||
|
||||
|
||||
filterMatcherToMatcher : Utils.Filter.Matcher -> Maybe Matcher
|
||||
filterMatcherToMatcher { key, op, value } =
|
||||
case op of
|
||||
Utils.Filter.Eq ->
|
||||
Maybe.map2 (\isRegex isEqual -> Matcher key value isRegex isEqual) (Just False) (Just (Just True))
|
||||
|
||||
Utils.Filter.RegexMatch ->
|
||||
Maybe.map2 (\isRegex isEqual -> Matcher key value isRegex isEqual) (Just True) (Just (Just True))
|
||||
|
||||
Utils.Filter.NotRegexMatch ->
|
||||
Maybe.map2 (\isRegex isEqual -> Matcher key value isRegex isEqual) (Just True) (Just (Just False))
|
||||
|
||||
Utils.Filter.NotEq ->
|
||||
Maybe.map2 (\isRegex isEqual -> Matcher key value isRegex isEqual) (Just False) (Just (Just False))
|
||||
|
||||
|
||||
fromMatcher : Matcher -> MatcherForm
|
||||
fromMatcher { name, value, isRegex, isEqual } =
|
||||
{ name = initialField name
|
||||
, value = initialField value
|
||||
, isRegex = isRegex
|
||||
, isEqual = isEqual
|
||||
}
|
||||
|
||||
|
||||
fromDateTimePicker : SilenceForm -> DateTimePicker -> SilenceForm
|
||||
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration, matchers, dateTimePicker } newPicker =
|
||||
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration } newPicker =
|
||||
{ id = id
|
||||
, createdBy = createdBy
|
||||
, comment = comment
|
||||
, startsAt = startsAt
|
||||
, endsAt = endsAt
|
||||
, duration = duration
|
||||
, matchers = matchers
|
||||
, dateTimePicker = newPicker
|
||||
, viewDateTimePicker = True
|
||||
}
|
||||
|
|
|
@ -13,28 +13,27 @@ import Utils.Filter exposing (silencePreviewFilter)
|
|||
import Utils.FormValidation exposing (fromResult, initialField, stringNotEmpty, updateValue, validate)
|
||||
import Utils.List
|
||||
import Utils.Types exposing (ApiData(..))
|
||||
import Views.FilterBar.Types as FilterBar
|
||||
import Views.FilterBar.Updates as FilterBar
|
||||
import Views.SilenceForm.Types
|
||||
exposing
|
||||
( Model
|
||||
, SilenceForm
|
||||
, SilenceFormFieldMsg(..)
|
||||
, SilenceFormMsg(..)
|
||||
, emptyMatcher
|
||||
, fromDateTimePicker
|
||||
, fromMatchersAndCommentAndTime
|
||||
, fromSilence
|
||||
, parseEndsAt
|
||||
, toSilence
|
||||
, validateForm
|
||||
, validateMatchers
|
||||
)
|
||||
|
||||
|
||||
updateForm : SilenceFormFieldMsg -> SilenceForm -> SilenceForm
|
||||
updateForm msg form =
|
||||
case msg of
|
||||
AddMatcher ->
|
||||
{ form | matchers = form.matchers ++ [ emptyMatcher ] }
|
||||
|
||||
UpdateStartsAt time ->
|
||||
let
|
||||
startsAt =
|
||||
|
@ -127,54 +126,6 @@ updateForm msg form =
|
|||
ValidateComment ->
|
||||
{ form | comment = validate stringNotEmpty form.comment }
|
||||
|
||||
DeleteMatcher index ->
|
||||
{ form | matchers = List.take index form.matchers ++ List.drop (index + 1) form.matchers }
|
||||
|
||||
UpdateMatcherName index name ->
|
||||
let
|
||||
matchers =
|
||||
Utils.List.replaceIndex index
|
||||
(\matcher -> { matcher | name = updateValue name matcher.name })
|
||||
form.matchers
|
||||
in
|
||||
{ form | matchers = matchers }
|
||||
|
||||
ValidateMatcherName index ->
|
||||
let
|
||||
matchers =
|
||||
Utils.List.replaceIndex index
|
||||
(\matcher -> { matcher | name = validate stringNotEmpty matcher.name })
|
||||
form.matchers
|
||||
in
|
||||
{ form | matchers = matchers }
|
||||
|
||||
UpdateMatcherValue index value ->
|
||||
let
|
||||
matchers =
|
||||
Utils.List.replaceIndex index
|
||||
(\matcher -> { matcher | value = updateValue value matcher.value })
|
||||
form.matchers
|
||||
in
|
||||
{ form | matchers = matchers }
|
||||
|
||||
ValidateMatcherValue index ->
|
||||
let
|
||||
matchers =
|
||||
Utils.List.replaceIndex index
|
||||
(\matcher -> { matcher | value = matcher.value })
|
||||
form.matchers
|
||||
in
|
||||
{ form | matchers = matchers }
|
||||
|
||||
UpdateMatcherRegex index isRegex ->
|
||||
let
|
||||
matchers =
|
||||
Utils.List.replaceIndex index
|
||||
(\matcher -> { matcher | isRegex = isRegex })
|
||||
form.matchers
|
||||
in
|
||||
{ form | matchers = matchers }
|
||||
|
||||
UpdateTimesFromPicker ->
|
||||
let
|
||||
( startsAt, endsAt, duration ) =
|
||||
|
@ -224,7 +175,7 @@ update : SilenceFormMsg -> Model -> String -> String -> ( Model, Cmd Msg )
|
|||
update msg model basePath apiUrl =
|
||||
case msg of
|
||||
CreateSilence ->
|
||||
case toSilence model.form of
|
||||
case toSilence model.filterBar model.form of
|
||||
Just silence ->
|
||||
( { model | silenceId = Loading }
|
||||
, Cmd.batch
|
||||
|
@ -238,6 +189,7 @@ update msg model basePath apiUrl =
|
|||
( { model
|
||||
| silenceId = Failure "Could not submit the form, Silence is not yet valid."
|
||||
, form = validateForm model.form
|
||||
, filterBarValid = validateMatchers model.filterBar
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
@ -258,10 +210,12 @@ update msg model basePath apiUrl =
|
|||
( model, Task.perform (NewSilenceFromMatchersAndCommentAndTime defaultCreator params.matchers params.comment >> MsgForSilenceForm) Time.now )
|
||||
|
||||
NewSilenceFromMatchersAndCommentAndTime defaultCreator matchers comment time ->
|
||||
( { form = fromMatchersAndCommentAndTime defaultCreator matchers comment time
|
||||
( { form = fromMatchersAndCommentAndTime defaultCreator comment time
|
||||
, alerts = Initial
|
||||
, activeAlertId = Nothing
|
||||
, silenceId = Initial
|
||||
, filterBar = FilterBar.initFilterBar matchers
|
||||
, filterBarValid = Utils.FormValidation.Initial
|
||||
, key = model.key
|
||||
}
|
||||
, Cmd.none
|
||||
|
@ -271,7 +225,14 @@ update msg model basePath apiUrl =
|
|||
( model, Silences.Api.getSilence apiUrl silenceId (SilenceFetch >> MsgForSilenceForm) )
|
||||
|
||||
SilenceFetch (Success silence) ->
|
||||
( { model | form = fromSilence silence }
|
||||
( { form = fromSilence silence
|
||||
, filterBar = FilterBar.initFilterBar (List.map Utils.Filter.fromApiMatcher silence.matchers)
|
||||
, filterBarValid = Utils.FormValidation.Initial
|
||||
, silenceId = model.silenceId
|
||||
, alerts = Initial
|
||||
, activeAlertId = Nothing
|
||||
, key = model.key
|
||||
}
|
||||
, Task.perform identity (Task.succeed (MsgForSilenceForm PreviewSilence))
|
||||
)
|
||||
|
||||
|
@ -279,7 +240,7 @@ update msg model basePath apiUrl =
|
|||
( model, Cmd.none )
|
||||
|
||||
PreviewSilence ->
|
||||
case toSilence model.form of
|
||||
case toSilence model.filterBar model.form of
|
||||
Just silence ->
|
||||
( { model | alerts = Loading }
|
||||
, Alerts.Api.fetchAlerts
|
||||
|
@ -292,6 +253,7 @@ update msg model basePath apiUrl =
|
|||
( { model
|
||||
| alerts = Failure "Can not display affected Alerts, Silence is not yet valid."
|
||||
, form = validateForm model.form
|
||||
, filterBarValid = validateMatchers model.filterBar
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
@ -307,11 +269,10 @@ update msg model basePath apiUrl =
|
|||
)
|
||||
|
||||
UpdateField fieldMsg ->
|
||||
( { form = updateForm fieldMsg model.form
|
||||
, alerts = Initial
|
||||
, silenceId = Initial
|
||||
, key = model.key
|
||||
, activeAlertId = model.activeAlertId
|
||||
( { model
|
||||
| form = updateForm fieldMsg model.form
|
||||
, alerts = Initial
|
||||
, silenceId = Initial
|
||||
}
|
||||
, Cmd.none
|
||||
)
|
||||
|
@ -327,5 +288,14 @@ update msg model basePath apiUrl =
|
|||
, Cmd.none
|
||||
)
|
||||
|
||||
MsgForFilterBar subMsg ->
|
||||
let
|
||||
( newFilterBar, _, subCmd ) =
|
||||
FilterBar.update subMsg model.filterBar
|
||||
in
|
||||
( { model | filterBar = newFilterBar, filterBarValid = Utils.FormValidation.Initial }
|
||||
, Cmd.map (MsgForFilterBar >> MsgForSilenceForm) subCmd
|
||||
)
|
||||
|
||||
|
||||
port persistDefaultCreator : String -> Cmd msg
|
||||
|
|
|
@ -5,17 +5,19 @@ import Html exposing (Html, a, button, div, fieldset, h1, h5, i, input, label, l
|
|||
import Html.Attributes exposing (class, href, style)
|
||||
import Html.Events exposing (onClick)
|
||||
import Utils.DateTimePicker.Views exposing (viewDateTimePicker)
|
||||
import Utils.Filter exposing (SilenceFormGetParams, emptySilenceFormGetParams)
|
||||
import Utils.Filter exposing (SilenceFormGetParams)
|
||||
import Utils.FormValidation exposing (ValidatedField, ValidationState(..))
|
||||
import Utils.Types exposing (ApiData)
|
||||
import Utils.Views exposing (checkbox, iconButtonMsg, loading, validatedField, validatedTextareaField)
|
||||
import Views.FilterBar.Types as FilterBar
|
||||
import Views.FilterBar.Views as FilterBar
|
||||
import Views.Shared.SilencePreview
|
||||
import Views.Shared.Types exposing (Msg)
|
||||
import Views.SilenceForm.Types exposing (MatcherForm, Model, SilenceForm, SilenceFormFieldMsg(..), SilenceFormMsg(..))
|
||||
import Views.SilenceForm.Types exposing (Model, SilenceForm, SilenceFormFieldMsg(..), SilenceFormMsg(..), validMatchers)
|
||||
|
||||
|
||||
view : Maybe String -> SilenceFormGetParams -> String -> Model -> Html SilenceFormMsg
|
||||
view maybeId { matchers, comment } defaultCreator { form, silenceId, alerts, activeAlertId } =
|
||||
view maybeId silenceFormGetParams defaultCreator { form, filterBar, filterBarValid, silenceId, alerts, activeAlertId } =
|
||||
let
|
||||
( title, resetClick ) =
|
||||
case maybeId of
|
||||
|
@ -23,12 +25,12 @@ view maybeId { matchers, comment } defaultCreator { form, silenceId, alerts, act
|
|||
( "Edit Silence", FetchSilence silenceId_ )
|
||||
|
||||
Nothing ->
|
||||
( "New Silence", NewSilenceFromMatchersAndComment defaultCreator emptySilenceFormGetParams )
|
||||
( "New Silence", NewSilenceFromMatchersAndComment defaultCreator silenceFormGetParams )
|
||||
in
|
||||
div []
|
||||
[ h1 [] [ text title ]
|
||||
, timeInput form.startsAt form.endsAt form.duration
|
||||
, matcherInput form.matchers
|
||||
, matchersInput filterBarValid filterBar
|
||||
, validatedField input
|
||||
"Creator"
|
||||
inputSectionPadding
|
||||
|
@ -101,30 +103,29 @@ timeInput startsAt endsAt duration =
|
|||
div [ class <| "row " ++ inputSectionPadding ]
|
||||
[ validatedField input
|
||||
"Start"
|
||||
"col-4"
|
||||
"col-lg-4 col-6"
|
||||
(UpdateStartsAt >> UpdateField)
|
||||
(ValidateTime |> UpdateField)
|
||||
startsAt
|
||||
, validatedField input
|
||||
"Duration"
|
||||
"col-2"
|
||||
"col-lg-3 col-6"
|
||||
(UpdateDuration >> UpdateField)
|
||||
(ValidateTime |> UpdateField)
|
||||
duration
|
||||
, validatedField input
|
||||
"End"
|
||||
"col-4 pr-0"
|
||||
"col-lg-4 col-6"
|
||||
(UpdateEndsAt >> UpdateField)
|
||||
(ValidateTime |> UpdateField)
|
||||
endsAt
|
||||
, div
|
||||
[ class "flex-column form-group"
|
||||
]
|
||||
[ class "form-group col-lg-1 col-6" ]
|
||||
[ label
|
||||
[]
|
||||
[ text "\u{00A0}" ]
|
||||
, button
|
||||
[ class "form-control cursor-pointer"
|
||||
[ class "form-control btn btn-outline-primary cursor-pointer"
|
||||
, onClick (OpenDateTimePicker |> UpdateField)
|
||||
]
|
||||
[ i
|
||||
|
@ -136,21 +137,29 @@ timeInput startsAt endsAt duration =
|
|||
]
|
||||
|
||||
|
||||
matcherInput : List MatcherForm -> Html SilenceFormMsg
|
||||
matcherInput matchers =
|
||||
div [ class inputSectionPadding ]
|
||||
[ div []
|
||||
[ label []
|
||||
[ strong [] [ text "Matchers " ]
|
||||
, span [ class "" ] [ text "Alerts affected by this silence." ]
|
||||
]
|
||||
, div [ class "row" ]
|
||||
[ label [ class "col-5" ] [ text "Name" ]
|
||||
, label [ class "col-5" ] [ text "Value" ]
|
||||
]
|
||||
matchersInput : Utils.FormValidation.ValidationState -> FilterBar.Model -> Html SilenceFormMsg
|
||||
matchersInput filterBarValid filterBar =
|
||||
let
|
||||
errorClass =
|
||||
case filterBarValid of
|
||||
Invalid _ ->
|
||||
" has-danger"
|
||||
|
||||
_ ->
|
||||
""
|
||||
in
|
||||
div [ class (inputSectionPadding ++ errorClass) ]
|
||||
[ label [ Html.Attributes.for "filter-bar-matcher" ]
|
||||
[ strong [] [ text "Matchers " ]
|
||||
, text "Alerts affected by this silence"
|
||||
]
|
||||
, div [] (List.indexedMap (matcherForm (List.length matchers > 1)) matchers)
|
||||
, iconButtonMsg "btn btn-secondary" "fa-plus" (AddMatcher |> UpdateField)
|
||||
, FilterBar.view { showSilenceButton = False } filterBar |> Html.map MsgForFilterBar
|
||||
, case filterBarValid of
|
||||
Invalid error ->
|
||||
div [ class "form-control-feedback" ] [ text error ]
|
||||
|
||||
_ ->
|
||||
text ""
|
||||
]
|
||||
|
||||
|
||||
|
@ -207,20 +216,3 @@ previewSilenceBtn =
|
|||
, onClick PreviewSilence
|
||||
]
|
||||
[ text "Preview Alerts" ]
|
||||
|
||||
|
||||
matcherForm : Bool -> Int -> MatcherForm -> Html SilenceFormMsg
|
||||
matcherForm showDeleteButton index { name, value, isRegex } =
|
||||
div [ class "row" ]
|
||||
[ div [ class "col-5" ] [ validatedField input "" "" (UpdateMatcherName index) (ValidateMatcherName index) name ]
|
||||
, div [ class "col-5" ] [ validatedField input "" "" (UpdateMatcherValue index) (ValidateMatcherValue index) value ]
|
||||
, div [ class "col-2 d-flex align-items-center" ]
|
||||
[ checkbox "Regex" isRegex (UpdateMatcherRegex index)
|
||||
, if showDeleteButton then
|
||||
iconButtonMsg "btn btn-secondary ml-auto" "fa-trash-o" (DeleteMatcher index)
|
||||
|
||||
else
|
||||
text ""
|
||||
]
|
||||
]
|
||||
|> Html.map UpdateField
|
||||
|
|
|
@ -35,7 +35,7 @@ type alias Model =
|
|||
initSilenceList : Key -> Model
|
||||
initSilenceList key =
|
||||
{ silences = Initial
|
||||
, filterBar = FilterBar.initFilterBar key
|
||||
, filterBar = FilterBar.initFilterBar []
|
||||
, tab = Active
|
||||
, showConfirmationDialog = Nothing
|
||||
, key = key
|
||||
|
|
|
@ -5,7 +5,7 @@ import Data.GettableSilence exposing (GettableSilence)
|
|||
import Data.SilenceStatus exposing (State(..))
|
||||
import Silences.Api as Api
|
||||
import Utils.Api as ApiData
|
||||
import Utils.Filter exposing (Filter, generateQueryString)
|
||||
import Utils.Filter exposing (Filter)
|
||||
import Utils.Types as Types exposing (ApiData(..), Matchers, Time)
|
||||
import Views.FilterBar.Updates as FilterBar
|
||||
import Views.SilenceList.Types exposing (Model, SilenceListMsg(..), SilenceTab)
|
||||
|
@ -54,10 +54,27 @@ update msg model filter basePath apiUrl =
|
|||
|
||||
MsgForFilterBar subMsg ->
|
||||
let
|
||||
( filterBar, cmd ) =
|
||||
FilterBar.update (basePath ++ "#/silences") filter subMsg model.filterBar
|
||||
( newFilterBar, shouldFilter, cmd ) =
|
||||
FilterBar.update subMsg model.filterBar
|
||||
|
||||
filterBarCmd =
|
||||
Cmd.map MsgForFilterBar cmd
|
||||
|
||||
newUrl =
|
||||
Utils.Filter.toUrl (basePath ++ "#/silences")
|
||||
(Utils.Filter.withMatchers newFilterBar.matchers filter)
|
||||
|
||||
silencesCmd =
|
||||
if shouldFilter then
|
||||
Cmd.batch
|
||||
[ Navigation.pushUrl model.key newUrl
|
||||
, filterBarCmd
|
||||
]
|
||||
|
||||
else
|
||||
filterBarCmd
|
||||
in
|
||||
( { model | filterBar = filterBar }, Cmd.map MsgForFilterBar cmd )
|
||||
( { model | filterBar = newFilterBar }, silencesCmd )
|
||||
|
||||
SetTab tab ->
|
||||
( { model | tab = tab }, Cmd.none )
|
||||
|
|
|
@ -21,7 +21,7 @@ view { filterBar, tab, silences, showConfirmationDialog } =
|
|||
div []
|
||||
[ div [ class "mb-4" ]
|
||||
[ label [ class "mb-2", for "filter-bar-matcher" ] [ text "Filter" ]
|
||||
, Html.map (MsgForFilterBar >> MsgForSilenceList) (FilterBar.view filterBar)
|
||||
, Html.map (MsgForFilterBar >> MsgForSilenceList) (FilterBar.view { showSilenceButton = False } filterBar)
|
||||
]
|
||||
, lazy2 tabsView tab silences
|
||||
, lazy3 silencesView showConfirmationDialog tab silences
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
module Filter exposing (generateQueryString, parseMatcher, stringifyFilter)
|
||||
module Filter exposing (parseMatcher, stringifyFilter, toUrl)
|
||||
|
||||
import Expect
|
||||
import Fuzz exposing (int, list, string, tuple)
|
||||
|
@ -30,37 +30,37 @@ parseMatcher =
|
|||
]
|
||||
|
||||
|
||||
generateQueryString : Test
|
||||
generateQueryString =
|
||||
describe "generateQueryString"
|
||||
toUrl : Test
|
||||
toUrl =
|
||||
describe "toUrl"
|
||||
[ test "should not render keys with Nothing value except the silenced, inhibited, and active parameters, which default to false, false, true, respectively." <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=false&active=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=false&active=true"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
, test "should not render filter key with empty value" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=false&active=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Just "", showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=false&active=true"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Just "", showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
, test "should render filter key with values" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=false&active=true&filter=%7Bfoo%3D%22bar%22%2C%20baz%3D~%22quux.*%22%7D"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Just "{foo=\"bar\", baz=~\"quux.*\"}", showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=false&active=true&filter=%7Bfoo%3D%22bar%22%2C%20baz%3D~%22quux.*%22%7D"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Just "{foo=\"bar\", baz=~\"quux.*\"}", showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
, test "should render silenced key with bool" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=true&inhibited=false&active=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Just True, showInhibited = Nothing, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=true&inhibited=false&active=true"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Just True, showInhibited = Nothing, showActive = Nothing })
|
||||
, test "should render inhibited key with bool" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=true&active=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Just True, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=true&active=true"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Just True, showActive = Nothing })
|
||||
, test "should render active key with bool" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=false&active=false"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Just False })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=false&active=false"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = False, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Just False })
|
||||
, test "should add customGrouping key" <|
|
||||
\() ->
|
||||
Expect.equal "?silenced=false&inhibited=false&active=true&customGrouping=true"
|
||||
(Utils.Filter.generateQueryString { receiver = Nothing, group = Nothing, customGrouping = True, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
Expect.equal "/alerts?silenced=false&inhibited=false&active=true&customGrouping=true"
|
||||
(Utils.Filter.toUrl "/alerts" { receiver = Nothing, group = Nothing, customGrouping = True, text = Nothing, showSilenced = Nothing, showInhibited = Nothing, showActive = Nothing })
|
||||
]
|
||||
|
||||
|
||||
|
|
Loading…
Reference in New Issue