Skip to content

fix(site): revamp UI for batch-updating workspaces #18895

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

Open
wants to merge 24 commits into
base: main
Choose a base branch
from

Conversation

Parkreiner
Copy link
Member

@Parkreiner Parkreiner commented Jul 16, 2025

Closes #18879
Builds on #18895

Changes made

  • Deleted BatchUpdateConfirmation component, replacing it with BatchUpdateModalForm
  • Added stories for the new component, trying to capture every variant I could think of

Screenshots

image

Notes

  • There's too many problems to list, but look at the issue to see all the problems we had with the old implementation
  • It's definitely helpful to look at the stories to see all the things the component is meant to cover

@Parkreiner Parkreiner self-assigned this Jul 16, 2025
@Parkreiner Parkreiner requested a review from aslilac as a code owner July 16, 2025 02:34
isLoading={batchActions.isProcessing}
onClose={() => setActiveBatchAction(undefined)}
onConfirm={async () => {
workspacesToUpdate={checkedWorkspaces}
Copy link
Member Author

Choose a reason for hiding this comment

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

Renamed prop because I don't think that a component should need to be aware of how its parent chooses to represent workspace selections

@Parkreiner
Copy link
Member Author

@aslilac I don't think there's a good way to ping you specifically now that you're a code owner, but I was hoping I could have you review this PR

Copy link
Member

@aslilac aslilac left a comment

Choose a reason for hiding this comment

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

I wonder if we should even bother showing the "dormant" workspaces section. you can filter for dormant:true on the workspaces page to easily find offenders. and maybe the "already up to date" section could be collapsed by default?

import { BatchUpdateModalForm } from "./BatchUpdateModalForm";
import { ACTIVE_BUILD_STATUSES } from "./WorkspacesPage";

type Writeable<T> = { -readonly [Key in keyof T]: T[Key] };
Copy link
Member

Choose a reason for hiding this comment

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

I was so prepared to tell you to use a built-in TypeScript util but apparently somehow we don't have a standard inverse of Readonly 🤦‍♀️ is this a useful enough thing to justify having in a different file for reuse?

Copy link
Member Author

Choose a reason for hiding this comment

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

I do think it'll be useful sometime in the future. Any preferences as for where to put it? I feel like we definitely don't want to make another file like nullable.ts, where it's just a single type

};

export const OnlyReadyToUpdate: Story = {
beforeEach: (ctx) => {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
beforeEach: (ctx) => {
beforeEach: (story) => {

I think this makes it a bit clearer what object we're dealing with (it appears to be the fully normalized story)

Copy link
Member Author

@Parkreiner Parkreiner Aug 14, 2025

Choose a reason for hiding this comment

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

I actually disagree – the arg type is StoryContext<TRenderer, TArgs>. The type doesn't even give you access to the main story that the context was generated for

Comment on lines +615 to +624
{/*
* Because of how the Dialog component works, we need to make
* sure that at least the parent stays mounted at all times. But
* if we move all the state into ReviewForm, that means that its
* state only mounts when the user actually opens up the batch
* update form. That saves us from mounting a bunch of extra
* state and firing extra queries, when realistically, the form
* will stay closed 99% of the time while the user is on the
* workspaces page.
*/}
Copy link
Member

Choose a reason for hiding this comment

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

you don't actually explain what it is about the Dialog component that requires this tho. I think the benefits of not mounting a component when you don't need to should be relatively obvious, so this very wordy comment really just raises more questions than answer for me.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I think I have a bad habit of trying to explain why old code was bad. And I guess with you becoming a code owner now, there's a lot less risk of some of those sub-optimal UI patterns creeping back in

onSubmit: () => void;
}>;

export const BatchUpdateModalForm: FC<BatchUpdateModalFormProps> = ({
Copy link
Member

Choose a reason for hiding this comment

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

I feel weird about the titular component being the last thing in the file

Comment on lines +463 to +466
<li
key={ws.id}
className="[&:not(:last-child)]:border-b-border [&:not(:last-child)]:border-b [&:not(:last-child)]:border-solid border-0"
>
Copy link
Member

Choose a reason for hiding this comment

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

This ReviewPanel component is always rendered inside of this exact li, long className and all. could this be refactored?

Copy link
Member Author

@Parkreiner Parkreiner Aug 14, 2025

Choose a reason for hiding this comment

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

I extracted it out into a separate component. I really don't like having li elements be coupled to the top level of a component definition, because it's really easy to wire them up wrong by accident and create invalid HTML (that React won't warn you about either)

id={failedValidationId}
className="m-0 text-highlight-red text-right text-sm pt-2"
>
Please acknowledge consequences to continue.
Copy link
Member

Choose a reason for hiding this comment

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

tbh this feels a little overkill. restarting a workspace can be destructive, but in practice it shouldn't be.

workspaces: readonly Workspace[];
queries: QueryEntry;
}>;
function createPatchedDependencies(size: number): PatchedDependencies {
Copy link
Member

Choose a reason for hiding this comment

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

the more I look at these stories the more I'm worried that this function is the wrong abstraction. we're doing all kinds of hoop jumping to make it work rn and I wonder if it'd just be easier and clearer to write out the workspaces and query results like we usually do.

@aslilac
Copy link
Member

aslilac commented Jul 21, 2025

also sorry I didn't review this sooner. I have no idea how this slid under my radar, I did a ton of reviews on thursday and just absolutely missed this one.

@github-actions github-actions bot added the stale This issue is like stale bread. label Jul 29, 2025

This comment was marked as spam.

This comment was marked as spam.

@github-actions github-actions bot removed the stale This issue is like stale bread. label Jul 30, 2025
@github-actions github-actions bot added the stale This issue is like stale bread. label Aug 7, 2025
@github-actions github-actions bot closed this Aug 10, 2025
@Parkreiner Parkreiner reopened this Aug 14, 2025
Parkreiner added a commit that referenced this pull request Aug 14, 2025
…tions (#18894)

Precursor to #18895
Splitting this off so that the changes are easier to review.

## Changes made
- Improve type-safety for the `withQuery` Storybook decorator
- Centralized almost all queries that deal with template versions to use
a shared, exported query key prefix
- Updated `useFilter` and `usePagination` to have much more strict
return types, and decoupled them from React Router at the interface
level
- Also added some extra input validation and performance optimizations
to `useFilter` and `usePagination`
- Removed a stale data when working with checked workspaces for the
`/workspaces` page
- Removed hacky `useEffect` call for syncing URL state to checked
workspaces, in favor of explicit state syncs
- Did some extra cleanup and removed unused values

## Notes
- Many of the changes here were in service of the main PR. I'll try to
highlight notable areas, but if there's anything that's not clear, feel
free to post a comment in this PR. Ideally you shouldn't really have to
look at the other PR to understand this one, so if something's
confusing, that's a sign that something's wrong

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Refactor**
* Improved handling of URL search parameters and state synchronization
in filter and pagination features across multiple pages.
* Centralized and clarified state management for workspace selection and
batch actions on the Workspaces page.
* Enhanced type safety and naming consistency in batch actions and
filter components.
* Updated filter and pagination hooks to accept explicit parameters and
callbacks for better maintainability.
* Streamlined prop naming and menu handling in workspace filter
components for clarity.

* **Bug Fixes**
* Prevented unnecessary state updates when filter values remain
unchanged.

* **Tests**
  * Updated tests for improved type safety and more precise assertions.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
Base automatically changed from mes/batch-update-01 to main August 14, 2025 04:06
@Parkreiner Parkreiner removed the stale This issue is like stale bread. label Aug 14, 2025
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.

bug(site): UI for batch-updating workspaces is confusing and has destructive race conditions
2 participants