chore: refactor frontend to use workspace status directly#4361
Conversation
|
Just spotted a bug in the deletion banner, will fix! |
| <AlertTitle>{Language.bannerTitle}</AlertTitle> | ||
| </Alert> | ||
| <Maybe condition={workspace.latest_build.status === "deleted"}> | ||
| <Alert |
There was a problem hiding this comment.
No change to the Alert here, just wrapping in a Maybe and getting rid of the isWorkspaceDeleted call
| dayjs.extend(utc) | ||
| dayjs.extend(minMax) | ||
|
|
||
| // all the possible states returned by the API |
There was a problem hiding this comment.
The backend gives us the WorkspaceStatus type and the actual workspace status now, so we can delete this. The display language is deleted just because it's moved into i18n.
| ) | ||
| expect(screen.getByTestId("secondary-ctas")).toHaveTextContent( | ||
| t("actionButton.delete", { ns: "workspacePage" }), | ||
| ) |
There was a problem hiding this comment.
Thanks for doing all these translations!!!
| // 'Secondary' actions are ctas housed within the popover | ||
| export const WorkspaceStateActions: StateActionsType = { | ||
| [WorkspaceStateEnum.starting]: { | ||
| starting: { |
There was a problem hiding this comment.
One of the reasons we made WorkspaceStateEnum was so that we could have a definitive mapping of the state returned from the BE to button UI state. Although this simplification makes my heart sing, I wonder if we're losing a little bit of that clarity and safety here. It is no longer immediately apparent that the keys in this data structure map to the types in the API's WorkspaceStatus. And the keys here aren't really typed anymore, right? This is a case where I really love enums and wish the API were returning an enum. It would make indexing much easier. @presleyp or @jsjoeio are there any easy workarounds with string literals I'm missing?
There was a problem hiding this comment.
I think it's still typesafe because the keys have to be values of WorkspaceStatus. I could be misremembering where I did this check, but I think it complained if I added a key that didn't belong or left one out.
There was a problem hiding this comment.
Ahhhhh, I didn't even see that. You're right - still typesafe! Feel free to ignore the clarify part of my comment - I'm just still figuring out where I land on this whole enum literal debate :)
There was a problem hiding this comment.
Me too honestly! Just following the generated type.
There was a problem hiding this comment.
Good point! I left a comment above related to this.
| "starting": "Starting...", | ||
| "stopping": "Stopping...", | ||
| "deleting": "Deleting..." | ||
| }, |
There was a problem hiding this comment.
Same sort of comment here: I like linking the language to the actual typing that's coming back from the API - that way we get a 1:1 mapping of BE state and UI. In the past, I've managed with constants like:
export const ActionButtonLabels = {
[WorkspaceStatus.starting]: i18n.t('workspacePage:actionButton.starting'),
[WorkspaceStatus.stopping]: i18n.t('workspacePage:actionButton.stopping'),
etc...
} as const;
But that was with enums. @presleyp or @jsjoeio again, is there a way to do this with literals? I feel like I could come over to the dark side if there is.
There was a problem hiding this comment.
I think we could define this but with "starting" instead of [WorkspaceStatus.starting], which is not as clear that it's typesafe, but is typesafe. And then we'd know that every value of WorkspaceStatus mapped to some text. How's that? (@jsjoeio def interested in your input on all of this when you're available!)
There was a problem hiding this comment.
That would be nice, if you feel it's a good add. Not a dealbreaker if you don't!
Also yeah, so curious to hear if there's a way to directly index.
There was a problem hiding this comment.
I ended up just changing the way I handle text for the disabled buttons, so that it's not a dynamic lookup.
There was a problem hiding this comment.
I'm not sure how i18n could let us pull in TypeScript types 🤔 Ahhh I see @Kira-Pilot's comment above. I think that's a great idea! Happy to look into this after this PR is merged too.
| export const WithCancel = Template.bind({}) | ||
| WithCancel.args = { | ||
| primaryAction: <DisabledButton workspaceState={WorkspaceStateEnum.deleting} />, | ||
| primaryAction: <DisabledButton workspaceStatus="deleting" />, |
There was a problem hiding this comment.
Curious why we're no longer using the enum here?
There was a problem hiding this comment.
The WorkspaceStatus type is defined by the backend now
| */ | ||
| const canAcceptJobs = (workspaceStatus: WorkspaceStatus) => | ||
| ["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus) | ||
| ["running", "stopped", "deleted", "failed", "canceled"].includes(workspaceStatus) |
There was a problem hiding this comment.
Nice catch! This approach makes it easy to fall out of sync with valid WorkspaceStatus values. Here's one way we could fix this:
const CAN_ACCEPT_JOBS_STATUS: Array<
Extract<
WorkspaceStatus,
"running" | "stopped" | "deleted" | "failed" | "canceled"
>
> = ["running", "stopped", "deleted", "failed", "canceled"];
const canAcceptJobs = (workspaceStatus: WorkspaceStatus) => CAN_ACCEPT_JOBS_STATUS.includes(workspaceStatus)There was a problem hiding this comment.
I'm actually fixing this in a future refactor, thanks for pointing it out!
| import { WorkspaceStateEnum } from "util/workspace" | ||
|
|
||
| // the button types we have | ||
| export enum ButtonTypesEnum { |
There was a problem hiding this comment.
I assume these are based on the WorkspaceStatus? We could leverage those types here as well.
There was a problem hiding this comment.
These are similar to WorkspaceStatus but not identical.
| // 'Secondary' actions are ctas housed within the popover | ||
| export const WorkspaceStateActions: StateActionsType = { | ||
| [WorkspaceStateEnum.starting]: { | ||
| starting: { |
There was a problem hiding this comment.
Good point! I left a comment above related to this.
| "starting": "Starting...", | ||
| "stopping": "Stopping...", | ||
| "deleting": "Deleting..." | ||
| }, |
There was a problem hiding this comment.
I'm not sure how i18n could let us pull in TypeScript types 🤔 Ahhh I see @Kira-Pilot's comment above. I think that's a great idea! Happy to look into this after this PR is merged too.
| latest_build: { | ||
| ...MockWorkspaceBuildStop, | ||
| job: MockRunningProvisionerJob, | ||
| status: "stopping", |
There was a problem hiding this comment.
ideally we're pulling these values from the types that way they stay in sync
There was a problem hiding this comment.
Yeah, since the generated type is a union of strings, I don't know how to do that. Do you have a way? Or would I just have to create an enum to layer on top of the generated type?
There was a problem hiding this comment.
Hmm...we might have to dig in. At first glance, I'm not sure. I mean you do have the TypesGen.Workspace there so maybe we don't have to worry. We could look later!
|
Since CI was stuck, I went ahead and added my additional refactor to this PR. It puts |
The frontend used to derive the workspace status from the build transition and job status, but now the backend supplies the status. This allows us to delete code and get rid of some indirection in one of the most complex areas of the frontend.
loadingworkspace status or action button because the workspace status is loaded with the workspace, and these things aren't rendered if the workspace is still loading (feel free to double check me!)