-
Notifications
You must be signed in to change notification settings - Fork 1
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
2256 error handling #2260
2256 error handling #2260
Conversation
8ac9995
to
5b192bd
Compare
@@ -36,41 +32,3 @@ def register_edit_operation_information( | |||
request: HttpRequest, operation_id: UUID, payload: OperationInformationIn | |||
) -> Tuple[Literal[200], Operation]: | |||
return 200, OperationServiceV2.register_operation_information(get_current_user_guid(request), operation_id, payload) | |||
|
|||
|
|||
@router.put( |
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.
These endpoints were duplicated, cleaning them up in this PR
15bdb3f
to
f9b5476
Compare
@@ -143,12 +138,10 @@ const FacilityInformationForm = ({ | |||
<MultiStepBase | |||
allowBackNavigation | |||
baseUrl={`/register-an-operation/${operation}`} | |||
baseUrlParams="title=Placeholder+Title" |
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 wireframes don't show anything besides Register an Operation
in the breadcrumbs, so we don't need a title placeholder at the moment
8b04ee2
to
759ced3
Compare
cancelUrl="/" | ||
error={error} | ||
formData={formState} | ||
onChange={handleChange} | ||
onSubmit={handleSubmit} |
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.
Re lines 69-75ish: I was thinking of moving the isSubmitting
state logic to the MultiStepBase. However, right now we've only got this submitting schema in a few of the form components, and I'm not sure if that's deliberate and shouldn't be changed. Why are we avoiding showing the read-only schema? Do we specifically need the show this schema, or would any loading component work? Thoughts?
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.
Well I think this is a good idea to handle internally either way so we don't have to do this every time we reuse MultiStepBase. If you look inside FormBase
we also handle it there to stop form data reset on submit. Though we may also need to have isSubmitting
state in components where we use MultiStepBase
for other reasons.
In regards to not showing the read only schema I think the comment could have been more helpful here though I just tested it and I think this is outdated. Before the rich text field titles were added, when you submitted the form it looked very buggy. The titles would disappear and you would just see a list of No
momentarily, so we added that to stop that behaviour. At the time it was a nice improvement.
Though I just tried it again and that is fixed. Seeing it shift to the read only form still isn't ideal, though something that may be fixed if we remove the read only schema and the radio buttons will just be disabled.
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 was able to move all the isSubmitting
logic to MultiStepBase. This revealed some weird behaviour when switching steps that I think must have always been there: https://www.loom.com/share/99f835e99463426fb3a855205c7b43d9. I'll make a bug card
0bd06cd
to
e0e7099
Compare
try { | ||
const response = await onSubmit(data); | ||
if (response?.error) { | ||
setError(response?.error); | ||
return; | ||
} | ||
// First step | ||
if (step === 1) { | ||
// The URL below is customized for the registration workflow. It can be generalized later if needed. | ||
const nextStepUrl = `/register-an-operation/${response.id}/${step + 1}${ | ||
baseUrlParams ? `?${baseUrlParams}` : "" | ||
}`; | ||
router.push(nextStepUrl); | ||
return; | ||
} | ||
// Middle steps | ||
if (isNotFinalStep && baseUrl) { | ||
const nextStepUrl = `${baseUrl}/${step + 1}${ | ||
baseUrlParams ? `?${baseUrlParams}` : "" | ||
}`; | ||
router.push(nextStepUrl); | ||
return; | ||
} | ||
// Last step | ||
if (!isNotFinalStep) { | ||
setIsSuccessfullySubmitted(true); | ||
} |
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 supposed to be a general use form base component, we shouldn't have routes such as register-an-operation
hardcoded in it. I get that this is trying to reduce duplication but I find this kind of confusing and it makes this component less reusable. I'm definitely pro automating a lot of this though some things make sense to handle in each form component.
Suggest keeping baseUrl
prop required and remove most of this refactor except perhaps the last step block.
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.
As discussed, the first step does need to be handled differently, I went with a prop.
611b24d
to
720a871
Compare
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.
Great work on this PR, there are a lot of solid improvements! I do have some concerns about a couple of additions though and have added some suggestions.
Though I'm mindful that there may have been some issues that I've missed with my suggestions since I haven't been immersed in this as you have. Happy to hop on a call and discuss it! Let me know what you think, thanks :)
return isSuccessfullySubmitted ? ( | ||
successComponent | ||
) : isSubmitting ? ( | ||
<div>Submitting...</div> | ||
) : ( |
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.
Why are we doing this in here? It feels like we are trying to solve problems that don't exist, the MultiStepBase component is starting to look overly complicated and hard to follow. Now that we have split up the forms into individual pages we can keep logic specific to those pages in those components.
This also dictates that we have to display a different component on submit (we may want to redirect to a page or do something else).
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.
Now that we have split up the forms into individual pages we can keep logic specific to those pages in those components.
There isn't much logic that actually is specific to individual pages, which is why I've moved some things into MultiStepBase. Error handling and isSubmitting
apply to all form pages. Showing a loading message <div>Submitting...</div>
while isSubmitting
is true also seemed general and like it should apply to all steps (previously it was on about half of them, and not the ones I'd expect like the ones with attachments).
Re successComponent
, previously it was conditionally shown in the last step based on the value of isSubmitting
. Since isSubmitting
is now being managed by MultiStepBase, and showing a success message is a general form submission thing, it makes sense to me to have it in MultiStepBase. It could be a redirect to a different page instead, but this was how it was originally set up so I didn't change it.
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.
Showing Submitting...
in place of the entire component is really jarring, and especially noticeable after page 1 with file uploads. I believe we were asked about adding a loading spinner to the Save and continue
button.
Looking at the RegistrationSubmissionForm
component it is really clean and simple, I don't see the need to further complicate MultiStepBase
. We can await the onSubmit
response like in the example and setIsSubmitted
and do what we need to in that component if reponse.ok
or whatever.
onSubmit={handleSubmit} | ||
firstStepExtraHandling={(response) => { | ||
// Since our form's route includes the operation's id, which doesn't exist until after the first step, we need to pass in a custom function that uses the response to generate a redirect url | ||
const nextStepUrl = `/register-an-operation/${response.id}/${step + 1}`; | ||
router.push(nextStepUrl); | ||
}} |
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.
onSubmit={handleSubmit} | |
firstStepExtraHandling={(response) => { | |
// Since our form's route includes the operation's id, which doesn't exist until after the first step, we need to pass in a custom function that uses the response to generate a redirect url | |
const nextStepUrl = `/register-an-operation/${response.id}/${step + 1}`; | |
router.push(nextStepUrl); | |
}} | |
onSubmit={async (data: any) => { | |
const response = await handleSubmit(data); | |
const nextStepUrl = `/register-an-operation/${response.id}/${step + 1}`; | |
router.push(nextStepUrl); | |
}} |
// Clear any old errors | ||
setError(undefined); | ||
try { | ||
const response = await onSubmit(data); | ||
if (response?.error) { | ||
setError(response?.error); | ||
return; | ||
} | ||
// In some cases, for the first step, we need to do something beyond simply redirecting to the baseUrl after successful onSubmit. | ||
if (step === 1 && firstStepExtraHandling) { | ||
firstStepExtraHandling(response); | ||
return; | ||
} | ||
if (isNotFinalStep && baseUrl) { | ||
const nextStepUrl = `${baseUrl}/${step + 1}${ | ||
baseUrlParams ? `?${baseUrlParams}` : "" | ||
}`; | ||
router.push(nextStepUrl); | ||
return; | ||
} | ||
// Last step | ||
if (!isNotFinalStep) { | ||
setIsSuccessfullySubmitted(true); | ||
} | ||
} catch (err) { | ||
// Handle any errors beyond response.error | ||
setError("An unexpected error occurred. Please try again."); | ||
} finally { |
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.
// Clear any old errors | |
setError(undefined); | |
try { | |
const response = await onSubmit(data); | |
if (response?.error) { | |
setError(response?.error); | |
return; | |
} | |
// In some cases, for the first step, we need to do something beyond simply redirecting to the baseUrl after successful onSubmit. | |
if (step === 1 && firstStepExtraHandling) { | |
firstStepExtraHandling(response); | |
return; | |
} | |
if (isNotFinalStep && baseUrl) { | |
const nextStepUrl = `${baseUrl}/${step + 1}${ | |
baseUrlParams ? `?${baseUrlParams}` : "" | |
}`; | |
router.push(nextStepUrl); | |
return; | |
} | |
// Last step | |
if (!isNotFinalStep) { | |
setIsSuccessfullySubmitted(true); | |
} | |
} catch (err) { | |
// Handle any errors beyond response.error | |
setError("An unexpected error occurred. Please try again."); | |
} finally { | |
const submitHandler = async (data: any) => { | |
// Set isSubmitting to true to disable submit buttons and prevent multiple form submissions | |
setIsSubmitting(true); | |
// Clear any old errors | |
setError(undefined); | |
try { | |
const response = await onSubmit(data); | |
if (response?.error) { | |
setError(response?.error); | |
return response; | |
} | |
if (baseUrl) { | |
const nextStepUrl = `${baseUrl}/${step + 1}${ | |
baseUrlParams ? `?${baseUrlParams}` : "" | |
}`; | |
router.push(nextStepUrl); | |
return response; | |
} | |
return response; | |
} catch (err) { | |
// Handle any errors beyond response.error | |
setError("An unexpected error occurred. Please try again."); | |
} finally { | |
setIsSubmitting(false); | |
} | |
}; |
For your consideration: I think this is getting overly complicated and hard to follow and will be difficult to maintain in the future. I think we should just not handle navigation if the baseUrl
doesn't exist, and return the response to the parent component if they need that information.
With this solution I did notice a long Submitting...
suspense issue. Though I think we need to remove suspense from this and add a spinner to the submit button for file uploads.
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.
return the response to the parent component if they need that information
What do you mean by this? I've only ever done this with state, and that's not working here (maybe because it takes too long to get the response?) Or do you mean like your suggestion above, which does get me the response, but then we're back to the problem of having to handle errors in individual components:
onSubmit={async (data: any) => {
const response = await handleSubmit(data);
const nextStepUrl = `/register-an-operation/${response.id}/${step + 1}`;
router.push(nextStepUrl);
}}
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 that's what I meant, in your MultiStepBase changes we just do a plain return. In the suggestion I made we return reponse
and can access it in the parent component as shown here.
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.
Sorry I think I misunderstood you, though I think we can pass handleSubmit
and to a .then
to get the response from in the parent?
0875924
to
9466fce
Compare
return; | ||
} | ||
|
||
if (setOnSubmitSuccessfulResponse) setOnSubmitSuccessfulResponse(response); |
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 what I ended up with to give the parent component access to the response. It takes a moment for the response to arrive, so we need to use useEffect
in the parent component
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.
Why can't we use a useEffect without the extra prop? We should always have access to the response in the parent component.
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 should always have access to the response in the parent component.
How do we have access to this without state? We need the response from theMultiStepBase
'sonSubmit
(not the response from the parent's, because that doesn't include error handling)
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.
Well we had it before this refactor, so currently this seems like a step down. There are many reasons why we would want the response in the parent (ie page 1 and submission). I'm working on a POC where we get the response in the parent component, though while I get a successful response I'm not getting the errors for some reason. Will let you know if I figure this out!
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.
Hey! I created a simple POC for the submission page with console logs showing that we can access the response in the MultiStepBase and the parent component. Let me know what you think, perhaps there is something I didn't consider. Would love to get on a call and pair on this later :)
7633aa7
to
a37c090
Compare
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.
Awesome work, thanks for doing this and being receptive to my pestering review suggestions 😅 I enjoyed the back and forth and think this is a solid improvement!
Just a couple clean up comments!
}${`?title=${response.name}`}`; | ||
router.push(nextStepUrl); | ||
).then((resolve) => { | ||
console.log("!!!!!resolve", resolve); |
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.
✂️
@@ -63,19 +63,10 @@ const RegistrationSubmissionForm = ({ | |||
<MultiStepBase | |||
allowBackNavigation | |||
baseUrl={`/register-an-operation/${operation}`} |
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.
Is baseUrl
being used for the final page?
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.
Nope!
a501f7d
to
6c2762a
Compare
8ff8257
to
2b3bfda
Compare
2b3bfda
to
7c3236c
Compare
card: https://github.com/orgs/bcgov/projects/122/views/2?pane=issue&itemId=80555060
This PR:
response.error
errors)move step 1 handling to the MultiStepBase so we don't have to do extra handling in the OperationInformationForm. Step 1 of any workflow will always need to be handled specially because we won't ever have an id before create, but I didn't write a generic case--premature optimization evil and all thatcreated new prop for handling step 1To test, raise an exception in each endpoint of the registration workflow, and you should see your exception appear on the front end when you hit the endpoint. You can also raise exceptions in
/v2/operations/{uuid:operation_id}
etc. to test that errors in the form components are passed to the multistepbase correctlyUpdates after revisions:
isSubmitting
is true--it was only randomly applied, and a loading spinner will be applied in another ticket