Skip to content

chore: refactor frontend to use workspace status directly #4361

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 11 commits into from
Oct 5, 2022

Conversation

presleyp
Copy link
Contributor

@presleyp presleyp commented Oct 4, 2022

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.

  • workspace status copy remains in "common", because it's used in the workspace status badge on the workspace page and the workspaces page
  • workspace action button copy is in "workspacePage", under "actionButton" for the verbs and under "disabledButton" for the statuses on disabled buttons (they are not always displayed identically to the workspace status badge)
  • consistent naming: pending, not queued; failed, not error; running, not started
  • there is no loading workspace 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!)

@presleyp presleyp requested a review from a team as a code owner October 4, 2022 15:59
@presleyp presleyp requested review from BrunoQuaresma and removed request for a team October 4, 2022 15:59
@presleyp presleyp requested a review from Kira-Pilot October 4, 2022 16:02
@presleyp
Copy link
Contributor Author

presleyp commented Oct 4, 2022

Just spotted a bug in the deletion banner, will fix!

<AlertTitle>{Language.bannerTitle}</AlertTitle>
</Alert>
<Maybe condition={workspace.latest_build.status === "deleted"}>
<Alert
Copy link
Contributor Author

Choose a reason for hiding this comment

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

No change to the Alert here, just wrapping in a Maybe and getting rid of the isWorkspaceDeleted call

import * as TypesGen from "../api/typesGenerated"

dayjs.extend(duration)
dayjs.extend(utc)
dayjs.extend(minMax)

// all the possible states returned by the API
Copy link
Contributor Author

Choose a reason for hiding this comment

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

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" }),
)
Copy link
Member

Choose a reason for hiding this comment

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

Thanks for doing all these translations!!!


// A mapping of workspace state to button type
// 'Primary' actions are the main ctas
// 'Secondary' actions are ctas housed within the popover
export const WorkspaceStateActions: StateActionsType = {
[WorkspaceStateEnum.starting]: {
starting: {
Copy link
Member

Choose a reason for hiding this comment

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

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?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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.

Copy link
Member

Choose a reason for hiding this comment

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

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 :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Me too honestly! Just following the generated type.

Copy link
Contributor

Choose a reason for hiding this comment

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

Good point! I left a comment above related to this.

"starting": "Starting...",
"stopping": "Stopping...",
"deleting": "Deleting..."
},
Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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!)

Copy link
Member

Choose a reason for hiding this comment

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

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I ended up just changing the way I handle text for the disabled buttons, so that it's not a dynamic lookup.

Copy link
Contributor

Choose a reason for hiding this comment

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

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.

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.

Nice!

Copy link
Collaborator

@BrunoQuaresma BrunoQuaresma left a comment

Choose a reason for hiding this comment

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

Great work! 👍

@@ -23,7 +22,7 @@ WithDropdown.args = {

export const WithCancel = Template.bind({})
WithCancel.args = {
primaryAction: <DisabledButton workspaceState={WorkspaceStateEnum.deleting} />,
primaryAction: <DisabledButton workspaceStatus="deleting" />,
Copy link
Contributor

Choose a reason for hiding this comment

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

Curious why we're no longer using the enum here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The WorkspaceStatus type is defined by the backend now

@@ -18,7 +17,7 @@ import { ButtonMapping, ButtonTypesEnum, WorkspaceStateActions } from "./constan
* so check whether workspace job status has reached completion (whether successful or not).
*/
const canAcceptJobs = (workspaceStatus: WorkspaceStatus) =>
["started", "stopped", "deleted", "error", "canceled"].includes(workspaceStatus)
["running", "stopped", "deleted", "failed", "canceled"].includes(workspaceStatus)
Copy link
Contributor

Choose a reason for hiding this comment

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

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)

TypeScript Playground

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm actually fixing this in a future refactor, thanks for pointing it out!

import { ReactNode } from "react"
import { WorkspaceStateEnum } from "util/workspace"

// the button types we have
export enum ButtonTypesEnum {
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume these are based on the WorkspaceStatus? We could leverage those types here as well.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

These are similar to WorkspaceStatus but not identical.


// A mapping of workspace state to button type
// 'Primary' actions are the main ctas
// 'Secondary' actions are ctas housed within the popover
export const WorkspaceStateActions: StateActionsType = {
[WorkspaceStateEnum.starting]: {
starting: {
Copy link
Contributor

Choose a reason for hiding this comment

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

Good point! I left a comment above related to this.

"starting": "Starting...",
"stopping": "Stopping...",
"deleting": "Deleting..."
},
Copy link
Contributor

Choose a reason for hiding this comment

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

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 MockStoppingWorkspace: TypesGen.Workspace = {
...MockWorkspace,
latest_build: {
...MockWorkspaceBuildStop,
job: MockRunningProvisionerJob,
status: "stopping",
Copy link
Contributor

Choose a reason for hiding this comment

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

ideally we're pulling these values from the types that way they stay in sync

Copy link
Contributor Author

Choose a reason for hiding this comment

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

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?

Copy link
Contributor

Choose a reason for hiding this comment

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

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!

@presleyp
Copy link
Contributor Author

presleyp commented Oct 5, 2022

Since CI was stuck, I went ahead and added my additional refactor to this PR. It puts canAcceptJobs info in the big workspace status -> info mapping, which is more typesafe, and handles both possible overrides to primaryAction in the same place so it's more obvious which buttons you'll get. @Kira-Pilot @jsjoeio

Copy link
Contributor

@jsjoeio jsjoeio left a comment

Choose a reason for hiding this comment

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

Great work!

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.

4 participants