diff --git a/CHANGELOG.md b/CHANGELOG.md index 3d7210e3c6..45f7c13d6a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ * [CHANGE] eventrecorder: The output data format now uses the new `events/v2` schema. This is a breaking change: alert labels, alert annotations, group labels, silence annotations, and muted-alert labels are JSON maps, and protobuf consumers must use the new schema. * [FEATURE] api: Add an experimental ConnectRPC API served alongside `/api/v2/` under the version-neutral `/api/` prefix, exposing the Connect, gRPC, and gRPC-Web protocols plus the gRPC Health Checking Protocol and server reflection. The first service is `status.v3alpha.StatusService`. +* [ENHANCEMENT] ui: The silence form no longer requires a creator and a comment, matching the API. #5471 ## 0.34.0 / 2026-08-16 diff --git a/ui/app/src/Utils/FormValidation.elm b/ui/app/src/Utils/FormValidation.elm index 11a64d99a0..74189a81ac 100644 --- a/ui/app/src/Utils/FormValidation.elm +++ b/ui/app/src/Utils/FormValidation.elm @@ -2,7 +2,6 @@ module Utils.FormValidation exposing ( ValidatedField , ValidationState(..) , initialField - , stringNotEmpty , updateValue , validate ) @@ -45,12 +44,3 @@ updateValue value field = validate : (String -> Result String a) -> ValidatedField -> ValidatedField validate validator field = { field | validationState = fromResult (validator field.value) } - - -stringNotEmpty : String -> Result String String -stringNotEmpty string = - if String.isEmpty (String.trim string) then - Err "Should not be empty" - - else - Ok string diff --git a/ui/app/src/Views/SilenceForm/Types.elm b/ui/app/src/Views/SilenceForm/Types.elm index f8822f56ca..87ad5fa201 100644 --- a/ui/app/src/Views/SilenceForm/Types.elm +++ b/ui/app/src/Views/SilenceForm/Types.elm @@ -34,7 +34,6 @@ import Utils.FormValidation ( ValidatedField , ValidationState(..) , initialField - , stringNotEmpty , validate ) import Utils.Types exposing (ApiData(..)) @@ -90,9 +89,7 @@ type SilenceFormFieldMsg | UpdateDuration String | ValidateTime | UpdateCreatedBy String - | ValidateCreatedBy | UpdateComment String - | ValidateComment | UpdateTimesFromPicker | OpenDateTimePicker | CloseDateTimePicker @@ -118,13 +115,15 @@ initSilenceForm key firstDayOfWeek = toSilence : FilterBar.Model -> SilenceForm -> Maybe PostableSilence toSilence filterBar { id, comment, createdBy, startsAt, endsAt, annotations } = - Result.map5 - (\nonEmptyMatchers nonEmptyComment nonEmptyCreatedBy parsedStartsAt parsedEndsAt -> + -- The API allows an empty creator and comment, so they are not + -- validated here (see https://github.com/prometheus/alertmanager/issues/2998). + Result.map3 + (\nonEmptyMatchers parsedStartsAt parsedEndsAt -> { nullSilence | id = id - , comment = nonEmptyComment + , comment = comment.value , matchers = nonEmptyMatchers - , createdBy = nonEmptyCreatedBy + , createdBy = createdBy.value , startsAt = parsedStartsAt , endsAt = parsedEndsAt , annotations = @@ -136,8 +135,6 @@ toSilence filterBar { id, comment, createdBy, startsAt, endsAt, annotations } = } ) (validMatchers filterBar) - (stringNotEmpty comment.value) - (stringNotEmpty createdBy.value) (timeFromString startsAt.value) (parseEndsAt startsAt.value endsAt.value) |> Result.toMaybe @@ -229,8 +226,8 @@ fromSilence { id, createdBy, comment, startsAt, endsAt, annotations } firstDayOf validateForm : SilenceForm -> SilenceForm validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker, annotations, annotationText } = { id = id - , createdBy = validate stringNotEmpty createdBy - , comment = validate stringNotEmpty comment + , createdBy = createdBy + , comment = comment , startsAt = validate timeFromString startsAt , endsAt = validate (parseEndsAt startsAt.value) endsAt , duration = validate parseDuration duration diff --git a/ui/app/src/Views/SilenceForm/Updates.elm b/ui/app/src/Views/SilenceForm/Updates.elm index 075c69be42..f3e9844e1e 100644 --- a/ui/app/src/Views/SilenceForm/Updates.elm +++ b/ui/app/src/Views/SilenceForm/Updates.elm @@ -10,7 +10,7 @@ import Utils.Date exposing (timeFromString) import Utils.DateTimePicker.Types exposing (initFromStartAndEndTime) import Utils.DateTimePicker.Updates as DateTimePickerUpdates import Utils.Filter exposing (silencePreviewFilter) -import Utils.FormValidation exposing (initialField, stringNotEmpty, updateValue, validate) +import Utils.FormValidation exposing (initialField, updateValue, validate) import Utils.Types exposing (ApiData(..)) import Views.FilterBar.Types as FilterBar import Views.FilterBar.Updates as FilterBar @@ -119,15 +119,9 @@ updateForm msg form = UpdateCreatedBy createdBy -> { form | createdBy = updateValue createdBy form.createdBy } - ValidateCreatedBy -> - { form | createdBy = validate stringNotEmpty form.createdBy } - UpdateComment comment -> { form | comment = updateValue comment form.comment } - ValidateComment -> - { form | comment = validate stringNotEmpty form.comment } - UpdateTimesFromPicker -> let ( startsAt, endsAt, duration ) = diff --git a/ui/app/src/Views/SilenceForm/Views.elm b/ui/app/src/Views/SilenceForm/Views.elm index 94e8170019..d652f8ce58 100644 --- a/ui/app/src/Views/SilenceForm/Views.elm +++ b/ui/app/src/Views/SilenceForm/Views.elm @@ -32,16 +32,16 @@ view maybeId silenceFormGetParams defaultCreator { form, filterBar, filterBarVal , timeInput form.startsAt form.endsAt form.duration , matchersInput filterBarValid filterBar , validatedField input - "Creator" + "Creator (Optional)" inputSectionPadding (UpdateCreatedBy >> UpdateField) - (ValidateCreatedBy |> UpdateField) + (Noop |> UpdateField) form.createdBy , validatedTextareaField - "Comment" + "Comment (Optional)" inputSectionPadding (UpdateComment >> UpdateField) - (ValidateComment |> UpdateField) + (Noop |> UpdateField) form.comment , annotationsInput annotationsValid form , div [ class inputSectionPadding ] diff --git a/ui/app/tests/SilenceForm.elm b/ui/app/tests/SilenceForm.elm index fb90fc7ad3..3a689303db 100644 --- a/ui/app/tests/SilenceForm.elm +++ b/ui/app/tests/SilenceForm.elm @@ -1,10 +1,114 @@ -module SilenceForm exposing (parseAnnotation) +module SilenceForm exposing (parseAnnotation, toSilence, validateForm) import Expect import Test exposing (..) +import Time +import Utils.Date +import Utils.DateTimePicker.Types exposing (initDateTimePicker) +import Utils.DateTimePicker.Utils exposing (FirstDayOfWeek(..)) +import Utils.Filter +import Utils.FormValidation exposing (ValidationState(..), initialField) +import Views.FilterBar.Types as FilterBar import Views.SilenceForm.Types +silenceForm : String -> String -> Views.SilenceForm.Types.SilenceForm +silenceForm createdBy comment = + let + startsAt = + Utils.Date.timeToString (Time.millisToPosix 1000000000000) + + endsAt = + Utils.Date.timeToString (Time.millisToPosix 1000003600000) + in + { id = Nothing + , createdBy = initialField createdBy + , comment = initialField comment + , startsAt = initialField startsAt + , endsAt = initialField endsAt + , duration = initialField "1h" + , dateTimePicker = initDateTimePicker Monday + , viewDateTimePicker = False + , annotations = [] + , annotationText = "" + } + + +matcherFilterBar : List Utils.Filter.Matcher -> FilterBar.Model +matcherFilterBar matchers = + FilterBar.initFilterBar matchers + + +toSilence : Test +toSilence = + describe "toSilence" + [ test "accepts an empty creator and comment" <| + \() -> + Expect.notEqual Nothing + (Views.SilenceForm.Types.toSilence + (matcherFilterBar [ { key = "alertname", op = Utils.Filter.Eq, value = "ExampleAlert" } ]) + (silenceForm "" "") + ) + , test "accepts a creator and comment" <| + \() -> + Expect.notEqual Nothing + (Views.SilenceForm.Types.toSilence + (matcherFilterBar [ { key = "alertname", op = Utils.Filter.Eq, value = "ExampleAlert" } ]) + (silenceForm "alice" "maintenance window") + ) + , test "still requires at least one matcher" <| + \() -> + Expect.equal Nothing + (Views.SilenceForm.Types.toSilence + (matcherFilterBar []) + (silenceForm "" "") + ) + ] + + +validateForm : Test +validateForm = + describe "validateForm" + [ test "does not flag an empty creator and comment" <| + \() -> + let + form = + Views.SilenceForm.Types.validateForm (silenceForm "" "") + + isInvalid state = + case state of + Invalid _ -> + True + + _ -> + False + in + Expect.equal ( False, False ) + ( isInvalid form.createdBy.validationState + , isInvalid form.comment.validationState + ) + , test "still flags invalid start and end times" <| + \() -> + let + baseForm = + silenceForm "" "" + + form = + Views.SilenceForm.Types.validateForm + { baseForm | startsAt = initialField "not-a-time" } + + isInvalid state = + case state of + Invalid _ -> + True + + _ -> + False + in + Expect.equal True (isInvalid form.startsAt.validationState) + ] + + parseAnnotation : Test parseAnnotation = describe "parseAnnotation"