-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Improved Validation #827
Improved Validation #827
Conversation
@@ -8,7 +8,7 @@ import Types | |||
( Route(..) | |||
, Msg | |||
( NavigateToSilenceList | |||
, NavigateToSilence | |||
, NavigateToSilenceView |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not a big thing: Why adding View
to NavigateToSilence
msg? We don't do it with the other ones.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because I renamed Silence
to SilenceView
, which represents a page. I didn't want to confuse it with a Silence type.
@@ -54,7 +54,7 @@ init location = | |||
_ -> | |||
nullFilter | |||
in | |||
update (urlUpdate location) (Model initSilenceList Loading initSilenceForm initAlertList route filter initStatusModel) | |||
update (urlUpdate location) (Model initSilenceList initSilenceView initSilenceForm initAlertList route filter initStatusModel) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
= Initial | ||
| Loading | ||
| Failure e | ||
| Failure String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having a string instead of a type variable forces us to drop information, doesn't it? Can't we keep the type variable here and convert an error to a string in the view functions?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't see where we require this information. I need to create the error myself, when e.g. validation fails. It is easier to create it from String
, rather than manually create Http.Error
, I don't think this is even possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can always change the Failure String
to be a Failure
of our own union type if we need to extract additional information.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
( initialField | ||
, ValidationState(..) | ||
, ValidatedField | ||
, validate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
👍
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I smell a library forming here
} | ||
) | ||
(stringNotEmpty comment.value) | ||
(List.foldr appendMatcher (Ok []) matchers) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When I don't specify any matchers and create a silence, I get redirected to the /silences
page without any error messages in the UI. The API call fails with silence invalid: at least one matcher required
. Should we enforce having at least one matcher in this PR or in an upcoming one? Sorry, I missed this requirement in my last changes as well.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is definitely good enough for now. Thanks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I've got an idea. We should just hide the delete button, when there is only one matcher left. Will do it in another pull request after this is merged.
ui/app/src/Utils/FormValidation.elm
Outdated
) | ||
agg | ||
validate : (String -> Result String a) -> ValidatedField -> ValidatedField | ||
validate validate field = |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could we do validate validator field =
instead? A little confusing to see the same term twice.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renamed
validatedMatcherToMatcher matcher = | ||
Result.map2 (\n v -> Matcher n v matcher.isRegex) matcher.name matcher.value | ||
|> Result.mapError second | ||
stringNotEmpty : String -> Result String String |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the long run we should trim the string as well so a space is not valid either.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added String.trim
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks.
import Time | ||
|
||
|
||
type ApiResponse e a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I see it correctly we get rid of ApiResponse
? 👍 for removing an extra layer of complexity.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I had no idea of why both were there. It was pretty confusing
Related to #798. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks good to me. Thanks @w0rm
@@ -27,8 +28,8 @@ view model = | |||
error msg | |||
|
|||
|
|||
silence2 : Silence -> Html Msg | |||
silence2 silence = | |||
silence2 : ApiData (List Alert) -> Silence -> Html SilenceViewMsg |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a random question: Is there a particular reason we called this silence2
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe because silence
is taken. I guess it's a question to @stuartnelson3
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I renamed it to viewSilence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks awesome!
One thing I noticed: When deleting all matchers and then "previewing" a silence, it returns all available alerts (since we aren't filtering by anything).
I think it would make sense to disable the preview button if len(matchers) == 0
.
import Utils.Filter exposing (Filter) | ||
|
||
|
||
type alias Model = | ||
{ silenceList : SilenceList.Model | ||
, silence : ApiData Silence | ||
, silenceView : SilenceView.Model |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm happy to see the final part of our top-level model refactored into its own model
@@ -30,10 +29,6 @@ type alias Label = | |||
( String, String ) | |||
|
|||
|
|||
type alias ApiData a = | |||
ApiResponse Http.Error a |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think I originally brought this in from reading the RemoteData blog post. I think the other had a generic type, and then made a specific type alias for dealing with http requests.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since that thing was a library, it had to be more generic. We don't need more than String for error at the moment. Changing this to smth else later won't be a problem.
Ok inputValue -> | ||
validatedField : (List (Attribute msg) -> List (Html msg) -> Html msg) -> String -> String -> (String -> msg) -> msg -> ValidatedField -> Html msg | ||
validatedField htmlField labelText classes inputMsg blurMsg field = | ||
case field.validationState of |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The three different cases in this function differ only by the css classes has-success
, has-danger
, or the absence of these two. Would it be possible to just write the HTML once and switch on the css class?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@stuartnelson3 another class is applied to the input field (form-control-success
or form-control-danger
), plus there is an error message in the error case. I think this is fine.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mk
] | ||
div [] | ||
[ p [] [ text <| "Error: " ++ err ] | ||
] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 for moving this logic out of the view
( initialField | ||
, ValidationState(..) | ||
, ValidatedField | ||
, validate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I smell a library forming here
validateForm : SilenceForm -> SilenceForm | ||
validateForm { id, createdBy, comment, startsAt, endsAt, duration, matchers } = | ||
{ id = id | ||
, createdBy = validate stringNotEmpty createdBy |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mmm love that validate
function
, form : SilenceForm | ||
{ form : SilenceForm | ||
, silenceId : ApiData String | ||
, alerts : ApiData (List Alert) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like having alerts exist independently on this and the silence view model, +1
case silence of | ||
Success id -> | ||
( model, Navigation.newUrl ("/#/silences/" ++ id) ) | ||
SilenceCreate silenceId -> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can definitely re-think these names I came up with. SilenceCreate
and CreateSilence
could definitely be better.
(note for the future)
@stuartnelson3 I made it impossible to delete the last matcher, but accidentally committed |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks!
I thought of changing the form styles in this PR, but I think it's too much.