Skip to content

Commit 3f396da

Browse files
committed
feat: Add silence annotation to web UI
Signed-off-by: Christoph Maser <christoph.maser+github@gmail.com>
1 parent 338ccf9 commit 3f396da

8 files changed

Lines changed: 370 additions & 16 deletions

File tree

ui/app/script.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ui/app/src/Utils/Views.elm

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
module Utils.Views exposing
2-
( apiData
2+
( annotationButton
3+
, apiData
34
, checkbox
45
, error
56
, labelButton
@@ -47,7 +48,7 @@ labelButton maybeMsg labelText =
4748
, style "-moz-user-select" "text"
4849
, style "-webkit-user-select" "text"
4950
]
50-
[ text labelText ]
51+
[ span [ class "text-muted" ] [ text labelText ] ]
5152

5253
Just msg ->
5354
button
@@ -57,6 +58,17 @@ labelButton maybeMsg labelText =
5758
[ span [ class "text-muted" ] [ text labelText ] ]
5859

5960

61+
annotationButton : ( String, String ) -> Html msg
62+
annotationButton ( key, value ) =
63+
span
64+
[ class "btn btn-sm btn-light border mr-2 mb-2"
65+
, style "user-select" "text"
66+
, style "-moz-user-select" "text"
67+
, style "-webkit-user-select" "text"
68+
]
69+
[ span [ class "text-muted" ] [ text (key ++ "=" ++ value) ] ]
70+
71+
6072
linkifyText : String -> List (Html msg)
6173
linkifyText str =
6274
List.map

ui/app/src/Views/SilenceForm/Types.elm

Lines changed: 77 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@ module Views.SilenceForm.Types exposing
66
, fromDateTimePicker
77
, fromMatchersAndCommentAndTime
88
, fromSilence
9+
, hasAnnotationKey
910
, initSilenceForm
11+
, parseAnnotation
1012
, parseEndsAt
1113
, toSilence
14+
, validateAnnotations
1215
, validateForm
1316
, validateMatchers
1417
)
@@ -19,6 +22,7 @@ import Data.GettableSilence exposing (GettableSilence)
1922
import Data.Matcher
2023
import Data.PostableSilence exposing (PostableSilence)
2124
import DateTime
25+
import Dict
2226
import Silences.Types exposing (nullSilence)
2327
import Time exposing (Posix)
2428
import Utils.Date exposing (addDuration, durationFormat, parseDuration, timeDifference, timeFromString, timeToString)
@@ -41,6 +45,7 @@ type alias Model =
4145
{ form : SilenceForm
4246
, filterBar : FilterBar.Model
4347
, filterBarValid : ValidationState
48+
, annotationsValid : ValidationState
4449
, silenceId : ApiData String
4550
, alerts : ApiData (List GettableAlert)
4651
, activeAlertId : Maybe String
@@ -58,6 +63,8 @@ type alias SilenceForm =
5863
, duration : ValidatedField
5964
, dateTimePicker : DateTimePicker
6065
, viewDateTimePicker : Bool
66+
, annotations : List ( String, String )
67+
, annotationText : String
6168
}
6269

6370

@@ -89,13 +96,18 @@ type SilenceFormFieldMsg
8996
| UpdateTimesFromPicker
9097
| OpenDateTimePicker
9198
| CloseDateTimePicker
99+
| UpdateAnnotationText String
100+
| AddAnnotation
101+
| DeleteAnnotation Bool ( String, String )
102+
| Noop
92103

93104

94105
initSilenceForm : Key -> FirstDayOfWeek -> Model
95106
initSilenceForm key firstDayOfWeek =
96107
{ form = empty firstDayOfWeek
97108
, filterBar = FilterBar.initFilterBar []
98109
, filterBarValid = Utils.FormValidation.Initial
110+
, annotationsValid = Utils.FormValidation.Valid
99111
, silenceId = Utils.Types.Initial
100112
, alerts = Utils.Types.Initial
101113
, activeAlertId = Nothing
@@ -105,7 +117,7 @@ initSilenceForm key firstDayOfWeek =
105117

106118

107119
toSilence : FilterBar.Model -> SilenceForm -> Maybe PostableSilence
108-
toSilence filterBar { id, comment, createdBy, startsAt, endsAt } =
120+
toSilence filterBar { id, comment, createdBy, startsAt, endsAt, annotations } =
109121
Result.map5
110122
(\nonEmptyMatchers nonEmptyComment nonEmptyCreatedBy parsedStartsAt parsedEndsAt ->
111123
{ nullSilence
@@ -115,6 +127,12 @@ toSilence filterBar { id, comment, createdBy, startsAt, endsAt } =
115127
, createdBy = nonEmptyCreatedBy
116128
, startsAt = parsedStartsAt
117129
, endsAt = parsedEndsAt
130+
, annotations =
131+
if List.isEmpty annotations then
132+
Nothing
133+
134+
else
135+
Just (Dict.fromList annotations)
118136
}
119137
)
120138
(validMatchers filterBar)
@@ -139,8 +157,53 @@ validMatchers { matchers, matcherText } =
139157
Ok (List.map Utils.Filter.toApiMatcher nonEmptyMatchers)
140158

141159

160+
parseAnnotation : String -> Maybe ( String, String )
161+
parseAnnotation text =
162+
-- Split on the first equals sign only, allowing values to contain "="
163+
case String.indices "=" text of
164+
firstIndex :: _ ->
165+
let
166+
key =
167+
String.left firstIndex text
168+
169+
value =
170+
String.dropLeft (firstIndex + 1) text
171+
in
172+
if String.isEmpty (String.trim key) || String.isEmpty (String.trim value) then
173+
Nothing
174+
175+
else
176+
Just ( String.trim key, String.trim value )
177+
178+
[] ->
179+
Nothing
180+
181+
182+
hasAnnotationKey : String -> List ( String, String ) -> Bool
183+
hasAnnotationKey key annotations =
184+
List.any (\( k, _ ) -> k == key) annotations
185+
186+
187+
validateAnnotations : SilenceForm -> ValidationState
188+
validateAnnotations { annotationText, annotations } =
189+
if annotationText == "" then
190+
Utils.FormValidation.Valid
191+
192+
else
193+
case parseAnnotation annotationText of
194+
Just ( key, _ ) ->
195+
if hasAnnotationKey key annotations then
196+
Utils.FormValidation.Invalid ("Key '" ++ key ++ "' already exists. Duplicate keys will result in only the last value being retained.")
197+
198+
else
199+
Utils.FormValidation.Valid
200+
201+
Nothing ->
202+
Utils.FormValidation.Invalid "Please complete adding the annotation or clear the field"
203+
204+
142205
fromSilence : GettableSilence -> FirstDayOfWeek -> SilenceForm
143-
fromSilence { id, createdBy, comment, startsAt, endsAt } firstDayOfWeek =
206+
fromSilence { id, createdBy, comment, startsAt, endsAt, annotations } firstDayOfWeek =
144207
let
145208
startsPosix =
146209
Utils.Date.timeFromString (DateTime.toString startsAt)
@@ -158,11 +221,13 @@ fromSilence { id, createdBy, comment, startsAt, endsAt } firstDayOfWeek =
158221
, duration = initialField (durationFormat (timeDifference startsAt endsAt) |> Maybe.withDefault "")
159222
, dateTimePicker = initFromStartAndEndTime startsPosix endsPosix firstDayOfWeek
160223
, viewDateTimePicker = False
224+
, annotations = annotations |> Maybe.map Dict.toList |> Maybe.withDefault []
225+
, annotationText = ""
161226
}
162227

163228

164229
validateForm : SilenceForm -> SilenceForm
165-
validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker } =
230+
validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicker, annotations, annotationText } =
166231
{ id = id
167232
, createdBy = validate stringNotEmpty createdBy
168233
, comment = validate stringNotEmpty comment
@@ -171,6 +236,8 @@ validateForm { id, createdBy, comment, startsAt, endsAt, duration, dateTimePicke
171236
, duration = validate parseDuration duration
172237
, dateTimePicker = dateTimePicker
173238
, viewDateTimePicker = False
239+
, annotations = annotations
240+
, annotationText = annotationText
174241
}
175242

176243

@@ -208,6 +275,8 @@ empty firstDayOfWeek =
208275
, duration = initialField ""
209276
, dateTimePicker = initDateTimePicker firstDayOfWeek
210277
, viewDateTimePicker = False
278+
, annotations = []
279+
, annotationText = ""
211280
}
212281

213282

@@ -227,11 +296,13 @@ fromMatchersAndCommentAndTime defaultCreator comment now firstDayOfWeek =
227296
, comment = initialField comment
228297
, dateTimePicker = initFromStartAndEndTime (Just now) (Just (addDuration defaultDuration now)) firstDayOfWeek
229298
, viewDateTimePicker = False
299+
, annotations = []
300+
, annotationText = ""
230301
}
231302

232303

233304
fromDateTimePicker : SilenceForm -> DateTimePicker -> SilenceForm
234-
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration } newPicker =
305+
fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration, annotations, annotationText } newPicker =
235306
{ id = id
236307
, createdBy = createdBy
237308
, comment = comment
@@ -240,4 +311,6 @@ fromDateTimePicker { id, createdBy, comment, startsAt, endsAt, duration } newPic
240311
, duration = duration
241312
, dateTimePicker = newPicker
242313
, viewDateTimePicker = True
314+
, annotations = annotations
315+
, annotationText = annotationText
243316
}

ui/app/src/Views/SilenceForm/Updates.elm

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,11 @@ import Views.SilenceForm.Types
2323
, fromDateTimePicker
2424
, fromMatchersAndCommentAndTime
2525
, fromSilence
26+
, hasAnnotationKey
27+
, parseAnnotation
2628
, parseEndsAt
2729
, toSilence
30+
, validateAnnotations
2831
, validateForm
2932
, validateMatchers
3033
)
@@ -169,6 +172,39 @@ updateForm msg form =
169172
| viewDateTimePicker = False
170173
}
171174

175+
UpdateAnnotationText text ->
176+
{ form | annotationText = text }
177+
178+
AddAnnotation ->
179+
case parseAnnotation form.annotationText of
180+
Just (( key, _ ) as annotation) ->
181+
if hasAnnotationKey key form.annotations then
182+
-- Don't add if the key already exists
183+
form
184+
185+
else
186+
{ form
187+
| annotations = form.annotations ++ [ annotation ]
188+
, annotationText = ""
189+
}
190+
191+
Nothing ->
192+
form
193+
194+
DeleteAnnotation setAnnotationText annotation ->
195+
{ form
196+
| annotations = List.filter ((/=) annotation) form.annotations
197+
, annotationText =
198+
if setAnnotationText then
199+
Tuple.first annotation ++ "=" ++ Tuple.second annotation
200+
201+
else
202+
form.annotationText
203+
}
204+
205+
Noop ->
206+
form
207+
172208

173209
update : SilenceFormMsg -> Model -> String -> String -> ( Model, Cmd Msg )
174210
update msg model basePath apiUrl =
@@ -189,6 +225,7 @@ update msg model basePath apiUrl =
189225
| silenceId = Failure "Could not submit the form, Silence is not yet valid."
190226
, form = validateForm model.form
191227
, filterBarValid = validateMatchers model.filterBar
228+
, annotationsValid = validateAnnotations model.form
192229
}
193230
, Cmd.none
194231
)
@@ -215,6 +252,7 @@ update msg model basePath apiUrl =
215252
, silenceId = Initial
216253
, filterBar = FilterBar.initFilterBar matchers
217254
, filterBarValid = Utils.FormValidation.Initial
255+
, annotationsValid = Utils.FormValidation.Valid
218256
, key = model.key
219257
, firstDayOfWeek = model.firstDayOfWeek
220258
}
@@ -228,6 +266,7 @@ update msg model basePath apiUrl =
228266
( { form = fromSilence silence model.firstDayOfWeek
229267
, filterBar = FilterBar.initFilterBar (List.map Utils.Filter.fromApiMatcher silence.matchers)
230268
, filterBarValid = Utils.FormValidation.Initial
269+
, annotationsValid = Utils.FormValidation.Valid
231270
, silenceId = model.silenceId
232271
, alerts = Initial
233272
, activeAlertId = Nothing
@@ -270,10 +309,15 @@ update msg model basePath apiUrl =
270309
)
271310

272311
UpdateField fieldMsg ->
312+
let
313+
updatedForm =
314+
updateForm fieldMsg model.form
315+
in
273316
( { model
274-
| form = updateForm fieldMsg model.form
317+
| form = updatedForm
275318
, alerts = Initial
276319
, silenceId = Initial
320+
, annotationsValid = validateAnnotations updatedForm
277321
}
278322
, Cmd.none
279323
)

0 commit comments

Comments
 (0)