Skip to content

fix: display explicit 'retry' button(s) when a workspace fails #10720

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

Merged
merged 16 commits into from
Nov 22, 2023

Conversation

Parkreiner
Copy link
Member

@Parkreiner Parkreiner commented Nov 15, 2023

Closes #7329

Screenshot 2023-11-17 at 9 46 03 AM

Changes made

  • Makes it so that if a workspace fails, the "workspace button tray" will display a Retry button
    • If the user has the ability to see debug logs, an additional "Retry (Debug)" button will also display
    • We already had some logic in place for the retry debug button before, but it was only ever displayed in an error alert. If the user didn't have debug log permissions, no retry button would appear at all, and they wouldn't have a clear action to take. The alert is still there, but now, if the user has no debug permissions, they'll get the lower-level Retry button inside the alert instead
    • Having the buttons in multiple spots introduces a little UI redundancy, but since this logic deals with errors, it felt like it made sense, just to make sure the user was super clear on what to do
  • Removes enums in favor of union types for defining workspace errors and workspace button types. The new code is more concise and has fewer imports, with no loss in type-safety
  • Adds in tests for Retry functionality (six cases total: three workspace transitions with two debug levels)
    • Also updates the previous test utilities to parameterize the workspace being used for mocking, rather than assuming it will always be the same default mock workspace

const { saveLocal, getLocal } = useLocalStorage();

const buildError = Boolean(workspaceErrors[WorkspaceErrors.BUILD_ERROR]) && (
Copy link
Member Author

@Parkreiner Parkreiner Nov 17, 2023

Choose a reason for hiding this comment

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

These two error JSX elements were moved into the return value, to bring them closer to where they're actually used

// 2023-11-15 - MES - This effect will be called every single render because
// "now" will always change and invalidate the dependency array. Need to
// figure out if this effect really should run every render (possibly meaning
// no dependency array at all), or how to get the array stabilized (ideal)
Copy link
Member Author

@Parkreiner Parkreiner Nov 17, 2023

Choose a reason for hiding this comment

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

Having the effect re-run every single render isn't ideal, but I don't think it's breaking anything

During the sprint right before Christmas, I'm planning to do an audit for every single useEffect/useLayoutEffect call we have. I know we have a couple of other wonky calls, too. Just figured it'd be good to flag this for now, if someone has the bandwidth to fix it early while working on another issue

[ButtonTypesEnum.starting]: (
// A mapping of button type to their corresponding React components
const buttonMapping: Record<ButtonType, ReactNode> = {
update: <UpdateButton handleAction={handleUpdate} />,
Copy link
Member Author

Choose a reason for hiding this comment

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

Lot of diffs, but only two major things changed:

  • Enums got factored out, cleaning up how the properties were defined
  • retry and retryDebug properties got added

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 that enum was my complication; thanks for fixing :)

? buttonMapping[ButtonTypesEnum.updating]
: buttonMapping[ButtonTypesEnum.update])}
{canBeUpdated && (
<>{isUpdating ? buttonMapping.updating : buttonMapping.update}</>
Copy link
Member Author

Choose a reason for hiding this comment

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

Not a huge change, but I wanted to make it super explicit that the logic was accounting for three cases:

  1. Render nothing if canBeUpdated is false
  2. Render updating component
  3. Render update component

* requests and that responses produce the correct UI.
*
* We don't need to test the UI exhaustively because Storybook does that; just
* enough to prove that the workspaceStatus was calculated correctly.
Copy link
Member Author

Choose a reason for hiding this comment

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

Just reformatted the comment to properly fit an 80-character width

@Parkreiner Parkreiner changed the title fix: display explicit 'retry' button when a workspace fails fix: display explicit 'retry' button(s) when a workspace fails Nov 17, 2023
@Parkreiner Parkreiner marked this pull request as ready for review November 17, 2023 13:56
@Parkreiner
Copy link
Member Author

Going to double-check the Storybook tests later this morning – I don't have great internet where I am right now, and the pages are taking ages to load in

}
}}
handleBuildRetry={() => handleBuildRetry(false)}
handleBuildRetryDebug={() => handleBuildRetry(true)}
Copy link
Member

Choose a reason for hiding this comment

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

Instead of passing 2 props here, can we just have handleBuildRetry and then pass it a boolean in the Workspace component?

Copy link
Member Author

Choose a reason for hiding this comment

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

That would work – my main concern with setting up the functions the way I did was insulating the downstream components from changes, or having implementation details leak out

Right now, handleBuildRetry and handleBuildRetryDebug happen to use the same implementation under the hood, but that's just kind of by coincidence. If WorkspaceReadyPage ever gets to the point where the two functions need to look drastically different, we can change things in the one file, and none of the other components have to be any the wiser

I am aware that it does kind of increase the number of props (when we already have a lot), but keeping the other components blind to the implementation details seemed like the lesser of two evils to me

Copy link
Member

@Kira-Pilot Kira-Pilot Nov 20, 2023

Choose a reason for hiding this comment

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

If WorkspaceReadyPage ever gets to the point where the two functions need to look drastically different, we can change things in the one file, and none of the other components have to be any the wiser

I think we might be pre-optimizing here, but I'm happy to keep as-is!

/**
* A button type supported by the workspace actions UI
*/
export type ButtonType = (typeof buttonTypes)[number];
Copy link
Member

Choose a reason for hiding this comment

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

I'm not familiar with this syntax...what is going on here?

Copy link
Member Author

Choose a reason for hiding this comment

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

There's a little bit to unpack, but:

  1. We take the runtime buttonTypes value and turn it into a type definition via typeof
  2. And because buttonTypes is defined via as const, it's treated as a fixed-length tuple. So, TypeScript knows there are 16 elements in the array exactly, and it also knows the string literal that goes in each slot
  3. Then, when we index that type definition via [number], we're saying to grab the type values at each slot. (typeof buttonTypes)[0] would give us "start", while (typeof buttonTypes)[0 | 1 | 2] would give us "start" | "starting" | "stop". So the [number] index basically says "give us everything at every numeric index you have"
  4. At the end, we have a union of all the types, but the types are defined runtime-first via the const array, so we're free to use it as a value (like combining it with Array.includes)

I did forget to actually export the array, though, so I'll fix that

Copy link
Member

Choose a reason for hiding this comment

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

Appreciate the explanation. It would be nice to use this syntax with TypesGen.WorkspaceTransitions but idk how to make that happen on the Go side

Copy link
Member

@Kira-Pilot Kira-Pilot left a comment

Choose a reason for hiding this comment

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

This looks great! Nice work.

@Parkreiner Parkreiner merged commit 491e0e3 into main Nov 22, 2023
@Parkreiner Parkreiner deleted the mes/bugs/retry-workspace-button branch November 22, 2023 21:03
@github-actions github-actions bot locked and limited conversation to collaborators Nov 22, 2023
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

UI shows Stop & Start buttons for workspace in a failed state
2 participants