Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions asset/assets_vfsdata.go

Large diffs are not rendered by default.

16 changes: 14 additions & 2 deletions ui/app/src/Utils/Views.elm
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
module Utils.Views exposing
( apiData
( annotationButton
, apiData
, checkbox
, error
, labelButton
Expand Down Expand Up @@ -47,7 +48,7 @@ labelButton maybeMsg labelText =
, style "-moz-user-select" "text"
, style "-webkit-user-select" "text"
]
[ text labelText ]
[ span [ class "text-muted" ] [ text labelText ] ]

Just msg ->
button
Expand All @@ -57,6 +58,17 @@ labelButton maybeMsg labelText =
[ span [ class "text-muted" ] [ text labelText ] ]


annotationButton : ( String, String ) -> Html msg
annotationButton ( key, value ) =
span
[ class "btn btn-sm btn-light border mr-2 mb-2"
, style "user-select" "text"
, style "-moz-user-select" "text"
, style "-webkit-user-select" "text"
]
[ span [ class "text-muted" ] [ text (key ++ "=" ++ value) ] ]
Comment on lines +63 to +69
Copy link

Copilot AI Feb 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

annotationButton duplicates the exact attributes/markup of labelButton Nothing with only the label text differing. To reduce maintenance overhead, consider implementing annotationButton in terms of labelButton Nothing (or extracting the common pill attributes into a single helper) so styling changes don't need to be updated in multiple places.

Suggested change
span
[ class "btn btn-sm btn-light border mr-2 mb-2"
, style "user-select" "text"
, style "-moz-user-select" "text"
, style "-webkit-user-select" "text"
]
[ span [ class "text-muted" ] [ text (key ++ "=" ++ value) ] ]
labelButton Nothing (key ++ "=" ++ value)

Copilot uses AI. Check for mistakes.


linkifyText : String -> List (Html msg)
linkifyText str =
List.map
Expand Down
81 changes: 77 additions & 4 deletions ui/app/src/Views/SilenceForm/Types.elm
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,12 @@ module Views.SilenceForm.Types exposing
, fromDateTimePicker
, fromMatchersAndCommentAndTime
, fromSilence
, hasAnnotationKey
, initSilenceForm
, parseAnnotation
, parseEndsAt
, toSilence
, validateAnnotations
, validateForm
, validateMatchers
)
Expand All @@ -19,6 +22,7 @@ import Data.GettableSilence exposing (GettableSilence)
import Data.Matcher
import Data.PostableSilence exposing (PostableSilence)
import DateTime
import Dict
import Silences.Types exposing (nullSilence)
import Time exposing (Posix)
import Utils.Date exposing (addDuration, durationFormat, parseDuration, timeDifference, timeFromString, timeToString)
Expand All @@ -41,6 +45,7 @@ type alias Model =
{ form : SilenceForm
, filterBar : FilterBar.Model
, filterBarValid : ValidationState
, annotationsValid : ValidationState
, silenceId : ApiData String
, alerts : ApiData (List GettableAlert)
, activeAlertId : Maybe String
Expand All @@ -58,6 +63,8 @@ type alias SilenceForm =
, duration : ValidatedField
, dateTimePicker : DateTimePicker
, viewDateTimePicker : Bool
, annotations : List ( String, String )
, annotationText : String
}


Expand Down Expand Up @@ -89,13 +96,18 @@ type SilenceFormFieldMsg
| UpdateTimesFromPicker
| OpenDateTimePicker
| CloseDateTimePicker
| UpdateAnnotationText String
| AddAnnotation
| DeleteAnnotation Bool ( String, String )
| Noop


initSilenceForm : Key -> FirstDayOfWeek -> Model
initSilenceForm key firstDayOfWeek =
{ form = empty firstDayOfWeek
, filterBar = FilterBar.initFilterBar []
, filterBarValid = Utils.FormValidation.Initial
, annotationsValid = Utils.FormValidation.Valid
, silenceId = Utils.Types.Initial
, alerts = Utils.Types.Initial
, activeAlertId = Nothing
Expand All @@ -105,7 +117,7 @@ initSilenceForm key firstDayOfWeek =


toSilence : FilterBar.Model -> SilenceForm -> Maybe PostableSilence
toSilence filterBar { id, comment, createdBy, startsAt, endsAt } =
toSilence filterBar { id, comment, createdBy, startsAt, endsAt, annotations } =
Result.map5
(\nonEmptyMatchers nonEmptyComment nonEmptyCreatedBy parsedStartsAt parsedEndsAt ->
{ nullSilence
Expand All @@ -115,6 +127,12 @@ toSilence filterBar { id, comment, createdBy, startsAt, endsAt } =
, createdBy = nonEmptyCreatedBy
, startsAt = parsedStartsAt
, endsAt = parsedEndsAt
, annotations =
if List.isEmpty annotations then
Nothing

else
Just (Dict.fromList annotations)
}
)
(validMatchers filterBar)
Expand All @@ -139,8 +157,53 @@ validMatchers { matchers, matcherText } =
Ok (List.map Utils.Filter.toApiMatcher nonEmptyMatchers)


parseAnnotation : String -> Maybe ( String, String )
parseAnnotation text =
-- Split on the first equals sign only, allowing values to contain "="
case String.indices "=" text of
firstIndex :: _ ->
let
key =
String.left firstIndex text

value =
String.dropLeft (firstIndex + 1) text
in
if String.isEmpty (String.trim key) || String.isEmpty (String.trim value) then
Nothing

else
Just ( String.trim key, String.trim value )

[] ->
Nothing


hasAnnotationKey : String -> List ( String, String ) -> Bool
hasAnnotationKey key annotations =
List.any (\( k, _ ) -> k == key) annotations


validateAnnotations : SilenceForm -> ValidationState
validateAnnotations { annotationText, annotations } =
if annotationText == "" then
Utils.FormValidation.Valid

else
case parseAnnotation annotationText of
Just ( key, _ ) ->
if hasAnnotationKey key annotations then
Utils.FormValidation.Invalid ("Key '" ++ key ++ "' already exists. Duplicate keys will result in only the last value being retained.")

else
Utils.FormValidation.Valid

Nothing ->
Utils.FormValidation.Invalid "Please complete adding the annotation or clear the field"


fromSilence : GettableSilence -> FirstDayOfWeek -> SilenceForm
fromSilence { id, createdBy, comment, startsAt, endsAt } firstDayOfWeek =
fromSilence { id, createdBy, comment, startsAt, endsAt, annotations } firstDayOfWeek =
let
startsPosix =
Utils.Date.timeFromString (DateTime.toString startsAt)
Expand All @@ -158,11 +221,13 @@ fromSilence { id, createdBy, comment, startsAt, endsAt } firstDayOfWeek =
, duration = initialField (durationFormat (timeDifference startsAt endsAt) |> Maybe.withDefault "")
, dateTimePicker = initFromStartAndEndTime startsPosix endsPosix firstDayOfWeek
, viewDateTimePicker = False
, annotations = annotations |> Maybe.map Dict.toList |> Maybe.withDefault []
, annotationText = ""
}


validateForm : SilenceForm -> SilenceForm
validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker } =
validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker, annotations, annotationText } =
{ id = id
, createdBy = validate stringNotEmpty createdBy
, comment = validate stringNotEmpty comment
Expand All @@ -171,6 +236,8 @@ validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicke
, duration = validate parseDuration duration
, dateTimePicker = dateTimePicker
, viewDateTimePicker = False
, annotations = annotations
, annotationText = annotationText
}


Expand Down Expand Up @@ -208,6 +275,8 @@ empty firstDayOfWeek =
, duration = initialField ""
, dateTimePicker = initDateTimePicker firstDayOfWeek
, viewDateTimePicker = False
, annotations = []
, annotationText = ""
}


Expand All @@ -227,11 +296,13 @@ fromMatchersAndCommentAndTime defaultCreator comment now firstDayOfWeek =
, comment = initialField comment
, dateTimePicker = initFromStartAndEndTime (Just now) (Just (addDuration defaultDuration now)) firstDayOfWeek
, viewDateTimePicker = False
, annotations = []
, annotationText = ""
}


fromDateTimePicker : SilenceForm -> DateTimePicker -> SilenceForm
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration } newPicker =
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration, annotations, annotationText } newPicker =
{ id = id
, createdBy = createdBy
, comment = comment
Expand All @@ -240,4 +311,6 @@ fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration } newPic
, duration = duration
, dateTimePicker = newPicker
, viewDateTimePicker = True
, annotations = annotations
, annotationText = annotationText
}
46 changes: 45 additions & 1 deletion ui/app/src/Views/SilenceForm/Updates.elm
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import Views.SilenceForm.Types
, fromDateTimePicker
, fromMatchersAndCommentAndTime
, fromSilence
, hasAnnotationKey
, parseAnnotation
, parseEndsAt
, toSilence
, validateAnnotations
, validateForm
, validateMatchers
)
Expand Down Expand Up @@ -169,6 +172,39 @@ updateForm msg form =
| viewDateTimePicker = False
}

UpdateAnnotationText text ->
{ form | annotationText = text }

AddAnnotation ->
case parseAnnotation form.annotationText of
Just (( key, _ ) as annotation) ->
if hasAnnotationKey key form.annotations then
-- Don't add if the key already exists
form

else
{ form
| annotations = form.annotations ++ [ annotation ]
, annotationText = ""
}

Nothing ->
form

DeleteAnnotation setAnnotationText annotation ->
{ form
| annotations = List.filter ((/=) annotation) form.annotations
, annotationText =
if setAnnotationText then
Tuple.first annotation ++ "=" ++ Tuple.second annotation

else
form.annotationText
}

Noop ->
form


update : SilenceFormMsg -> Model -> String -> String -> ( Model, Cmd Msg )
update msg model basePath apiUrl =
Expand All @@ -189,6 +225,7 @@ update msg model basePath apiUrl =
| silenceId = Failure "Could not submit the form, Silence is not yet valid."
, form = validateForm model.form
, filterBarValid = validateMatchers model.filterBar
, annotationsValid = validateAnnotations model.form
}
, Cmd.none
)
Expand All @@ -215,6 +252,7 @@ update msg model basePath apiUrl =
, silenceId = Initial
, filterBar = FilterBar.initFilterBar matchers
, filterBarValid = Utils.FormValidation.Initial
, annotationsValid = Utils.FormValidation.Valid
, key = model.key
, firstDayOfWeek = model.firstDayOfWeek
}
Expand All @@ -228,6 +266,7 @@ update msg model basePath apiUrl =
( { form = fromSilence silence model.firstDayOfWeek
, filterBar = FilterBar.initFilterBar (List.map Utils.Filter.fromApiMatcher silence.matchers)
, filterBarValid = Utils.FormValidation.Initial
, annotationsValid = Utils.FormValidation.Valid
, silenceId = model.silenceId
, alerts = Initial
, activeAlertId = Nothing
Expand Down Expand Up @@ -270,10 +309,15 @@ update msg model basePath apiUrl =
)

UpdateField fieldMsg ->
let
updatedForm =
updateForm fieldMsg model.form
in
( { model
| form = updateForm fieldMsg model.form
| form = updatedForm
, alerts = Initial
, silenceId = Initial
, annotationsValid = validateAnnotations updatedForm
}
, Cmd.none
)
Expand Down
Loading
Loading