Skip to content
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

feat(app): Add Error Recovery orchestrator #15077

Merged
merged 1 commit into from
May 6, 2024

Conversation

mjhuff
Copy link
Contributor

@mjhuff mjhuff commented May 2, 2024

Closes EXEC-389

Overview

Error Recovery Flows are "special and unique"™ compared to other wizard flows. This PR addresses the underlying structure of ER and how to best solve for that uniqueness. I've outlined some of that uniqueness below and appreciate feedback/thoughts!

We May (or Not) Support Keeping Apps in Sync

Because there may be a world down the road in which we want to keep users across multiple desktop apps/the ODD in sync, we need some sort of way for the client to report where it is in ER. To that end, I propose breaking all content within ER flows into recovery routes and recovery steps. A recovery route is a logically-related collection of recovery steps or a single step if unrelated to any existing recovery route. A recovery step is analogous to a "step" in other wizard flows - a deterministic ordering of content for a given route.

While it would be great if we could use React Router to create our own mini-router, in practice this doesn't work. There is only one route at any one time (as far as React Router is concerned), and we can't simply use a subset of routes within a modal (React router will redirect the page). While this could work if all apps were the ODD, where we could effectively make the modal a page), we can't do this with desktop apps, so this option is out.

My solution to the above has been to recreate react-router without using react-router. We navigate around the modal when it's opened using a route, and the step applies changes to the view when needed. What this means is that when we decide to keep clients in sync across apps, we just need to create some hook that creates a URL-esque string from the route and step.

The major drawback of this approach is that we don't really do this anywhere else on the FE, so it's a new thing to learn. I've tried to compensate for the newness by enforcing strict type checking, so as soon as someone tries adding a new route somewhere, lint should be helpful.

ER Flows are Non-Deterministic

Unlike other flows, ER does not use a step counter. This is because a user may select from one or more recovery options, each with a variable number of steps. These recovery options all branch from a central node, the SelectRecoveryOptions route. The major utility to help navigate these flows is useRouteUpdateActions.

Despite being non-determinstic include a lot of "Go back." The way we handle this in other flows is to return the previous step within a series of steps, which I've also emulated when we are in a particular route. The drawback of this is that there are a bunch of routes, so that requires us to always account for the ordering of steps for each route, even if the route has one step (which is pretty common at the moment, but I don't want to make assumptions with how things will turn out when designs are still changing). When we can't "go back" anymore, we return to the SelectRecoveryOptions by default.

ER Should be DRY

One issue I've noticed in other wizards in the past is that we break the wizard down into steps, but then each step is independent from other steps entirely in terms of the components that comprise the step. This means that if the same button that's used in every page needs a change, every single component needs to change: we create a relatively large bug surface. To attempt to address this, I've introduced a shared directory within RecoveryOptions. I may include a shared in the broader ErrorRecoveryFlows dir if needed. The point is that things that get reused a lot should probably be grouped. I've tried encapsulating exports as well so only the things that should be used by higher-order components are exported.

This being said, I'm open to other ideas. I admit that these footer buttons require some props for overriding behavior, but I still think this is better than doing nothing...it also has the added benefit of keeping things more testable/readable.

Screen.Recording.2024-05-02.at.2.56.52.PM.mov

Test Plan

  • Enable both the BE/FE FFs for ER (let me know if you want an explanation on how to do this).
  • Try to pick up a tip but fail.
  • Follow the flow on the video!

Changelog

  • Add the error recovery orchestrator (hidden behind feature flags)

Review requests

While the focus of this PR is more on overall structure and approach rather than emulating wireframe designs (these designs aren't finalized, and that's why I'm using crazy colors), high-level design feedback is welcome!

Risk assessment

low

@mjhuff mjhuff requested review from a team May 2, 2024 20:16
@mjhuff mjhuff requested a review from a team as a code owner May 2, 2024 20:16
@mjhuff mjhuff requested review from jerader and removed request for a team and jerader May 2, 2024 20:16
Copy link
Member

@sfoster1 sfoster1 left a comment

Choose a reason for hiding this comment

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

This is generally good, but depending on how often we want to reuse step visualization I wonder if we might want a separate per-route orchestrator or something. I'm a little worried about encoding the paths between steps as an array because I could certainly see a case where that flow is not linear even within a route, which we'd only be able to model by breaking down routes more and more pathologically.

If instead, the top-level orchestrator hosted some route object (one for each route), and each route object hosted the appropriate step object, we might be able to admit more complex step sequencing behavior in code in the route objects while keeping that behavior localizxed just to the route objects.

@@ -1,3 +1,19 @@
{
"run_paused": "Run paused"
"are_you_sure_you_want_to_resume": "Are you sure you want to resume?",
Copy link
Member

Choose a reason for hiding this comment

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

I think we need an anonymized version of this, right @brenthagen

Copy link
Contributor Author

@mjhuff mjhuff May 3, 2024

Choose a reason for hiding this comment

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

Yeah @brenthagen let me know what I need to do for localization/OEM stuff in general, since I'm pretty out of the loop on that.

return createPortal(
<Flex
flexDirection={DIRECTION_COLUMN}
width="992px"
Copy link
Member

Choose a reason for hiding this comment

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

do we have a place to derive these? i guess we use them in very few places

Copy link
Contributor Author

@mjhuff mjhuff May 3, 2024

Choose a reason for hiding this comment

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

I should have left a comment about this - we don't have a unified component for this currently, but it's becoming common enough that we need one IMO. I have a running list of components that should be unified and this one is on it. I plan on giving that list to Mel for feedback when we formally start unification work.

alignItems={ALIGN_CENTER}
gridGap={SPACING.spacing24}
height="100%"
width="848px"
Copy link
Member

Choose a reason for hiding this comment

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

can we derive this from somewhere?

Copy link
Contributor Author

@mjhuff mjhuff May 3, 2024

Choose a reason for hiding this comment

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

I don't think so. This is special-cased by design. My guess is Sue didn't like the way the copy looks in the modal if we use the default width, so there's a hardcoded one-off adjustment to it.

@mjhuff
Copy link
Contributor Author

mjhuff commented May 3, 2024

If instead, the top-level orchestrator hosted some route object (one for each route), and each route object hosted the appropriate step object, we might be able to admit more complex step sequencing behavior in code in the route objects while keeping that behavior localizxed just to the route objects.

Thanks for the feedback. I'll mull this over for a bit.

@mjhuff
Copy link
Contributor Author

mjhuff commented May 6, 2024

After in person convo with Seth, we've decided we'll keep this for now and most likely refactor the route/step state as a directed graph when the time is right. For now though, this is sufficient.

@mjhuff mjhuff merged commit 05cbbfb into edge May 6, 2024
20 checks passed
@mjhuff mjhuff deleted the app_add-error-recovery-orchestrator branch May 6, 2024 14:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants