Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

feat: allow users to duplicate workspaces by parameters#10362

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 ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Merged
Parkreiner merged 24 commits intomainfrommes/workspace-clone-feat
Nov 3, 2023

Conversation

Parkreiner
Copy link
Member

@ParkreinerParkreiner commentedOct 20, 2023
edited
Loading

Addresses the UI portion of#9923

Changes made

  • Updates theCreateWorkspace components to support a newduplicate mode. If the page is in that mode (via the query parameters), it will warn the user that duplication only copies workspace parameters, not existing workspace state
  • Adds auseWorkspaceDuplication hook that allows any components in the future to use this same duplication functionality

clitters reacted with thumbs up emoji
@ParkreinerParkreiner changed the titlefeat: give users ability to duplicate workspaces by parametersfeat: allow users to duplicate workspaces by parametersOct 20, 2023
@@ -220,20 +221,6 @@ const getDefaultBuildParameters = (
return buildValues;
};

export const orderedTemplateParameters = (
Copy link
MemberAuthor

@ParkreinerParkreinerOct 20, 2023
edited
Loading

Choose a reason for hiding this comment

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

Didn't see this function get used anywhere, but also, one concern I have with the function is that even though it groups the mutable/immutable params, they're still all in one array, so you have to iterate through it to see where one group starts, and the other stops

I feel like, if this does need to get added back down the line, I'd consider these changes:

  1. Returning this as an object, so that you know which one is which from a glance
    typeReturn={immutable:TemplateVersionParameter[];mutable:TemplateVersionParameter[];}
  2. Maybe use some custom type predicates under the hood, so that TypeScript is able to say that themutable property is alwaystrue inside themutable array, and alwaysfalse inside theimmutable array

</FormFields>
</FormSection>
<>
{mode === "duplicate" && <DuplicateWarningMessage />}
Copy link
MemberAuthor

@ParkreinerParkreinerOct 20, 2023
edited
Loading

Choose a reason for hiding this comment

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

The only thing that changes with this chunk of code is that a React fragment got added, so I could squeeze in theDuplicateWarningMessage component. Everything else is the same – it just changed by an indentation level.

Copy link
Member

Choose a reason for hiding this comment

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

you could also put it next to the{Boolean(error) ... and that might look nice

@ParkreinerParkreiner marked this pull request as ready for reviewOctober 20, 2023 15:28
@ParkreinerParkreiner requested review froma team andaslilac and removed request fora teamOctober 20, 2023 15:28
@github-actionsgithub-actionsbot added the staleThis issue is like stale bread. labelOct 28, 2023
@ParkreinerParkreiner removed the request for review fromaslilacOctober 30, 2023 12:36
@ParkreinerParkreiner marked this pull request as draftOctober 30, 2023 12:36
@aslilac
Copy link
Member

I see you converted back to a draft, but let me know if a little pre-review would be helpful! happy to still give it a quick read

@Parkreiner
Copy link
MemberAuthor

Parkreiner commentedOct 30, 2023
edited
Loading

I see you converted back to a draft, but let me know if a little pre-review would be helpful! happy to still give it a quick read

Yeah, this is really embarrassing, but when I opened up the PR for review late Friday, I wasn't thinking and forgot that I hadn't added in the tests yet.

I don't expect the logic to change much (maybe shifting things around), but I'd definitely appreciate any kind of pre-review

</FormFields>
</FormSection>
<>
{mode === "duplicate" && <DuplicateWarningMessage />}
Copy link
Member

Choose a reason for hiding this comment

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

you could also put it next to the{Boolean(error) ... and that might look nice

@@ -279,6 +297,31 @@ const useExternalAuthVerification = (
};
};

function DuplicateWarningMessage() {
const [isDismissed, dismiss] = useReducer(() => true, false);
Copy link
Member

Choose a reason for hiding this comment

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

this is clever, but maybe too clever. 😅 it took me a second to realize what this does, and others who are less familiar withuseReducer could probably be quite confused by this. can we just useuseState instead?

Copy link
MemberAuthor

@ParkreinerParkreinerOct 30, 2023
edited
Loading

Choose a reason for hiding this comment

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

Yeah, that's fair, and I figured I'd get some pushback on that.

I use the action-less reducer pattern a lot in my personal code, where I want to make my expected state transitions super, super explicit (in this case, making it clear that state can only ever transition from false to true, and shouldn't be undone). It helps that useReducer has function overloads to augment the type of the dispatch if it is action-less

But as far as readability/communication, it's probably a bit much/too convoluted for a simple use case like this. I'll change this

return (
<div css={{ paddingTop: theme.spacing(6) }}>
<Margins size="medium">
<Alert severity="warning" dismissible onDismiss={dismiss}>
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 like this should be an"info". Nothing actually went wrong, the user isn't doing anything sketchy. It's good context to provide, but I think warnings should result from users doing something worth complaining about, not from following the "blessed path"

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

Yeah, agreed. Will get that fixed up

@github-actionsgithub-actionsbot removed the staleThis issue is like stale bread. labelOct 31, 2023
@ParkreinerParkreiner marked this pull request as ready for reviewNovember 1, 2023 15:47
@Parkreiner
Copy link
MemberAuthor

@aslilac Ended up taking all your feedback into account, and I think the code is a lot better now.

Changes since you last looked at things:

  • Tests got added (one test file for useWorkspaceDuplication, one test case for WorkspaceCreationPage)
  • Updated the MSW response for build params to include five params instead of just one
  • Had to make a small update to theAlert component to make sure it worked with CSS better
  • RenamedduplicationReady property from theuseWorkspaceDuplication hook toisDuplicationReady

@Parkreiner
Copy link
MemberAuthor

Also have a possible flake inSSHKeysPage.test.tsx. I'm going to look into it this week.

Comment on lines 12 to 25
// Tried really hard to get these tests working with RTL's renderHook, but I
// kept running into weird function mismatches, mostly stemming from the fact
// that React Router's RouteProvider does not accept children, meaning that you
// can't inject values into it with renderHook's wrapper
function WorkspaceMock({ workspace }: { workspace?: Workspace }) {
const { duplicateWorkspace, isDuplicationReady } =
useWorkspaceDuplication(workspace);

return (
<button onClick={duplicateWorkspace} disabled={!isDuplicationReady}>
Click me!
</button>
);
}
Copy link
Member

Choose a reason for hiding this comment

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

oh I really don't like setting this trend 💀 I don't exactly know what to recommend to fix it tho, maybe@BrunoQuaresma does?

Copy link
MemberAuthor

@ParkreinerParkreinerNov 2, 2023
edited
Loading

Choose a reason for hiding this comment

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

Yeah, I'm not a fan, either, but I wasn't sure about other options.

One alternative is using the baseMemoryRouter component (which does support children), but my main worries are:

  • Then the test isn't using the the main setup function we've got defined for most other cases
  • I kind of get the sense that the baseMemoryRouter component might become deprecated down the line, becausecreateMemoryRouter is more in line with their new data loading patterns. (It's also the React Router team, so they're not afraid to ship major breaking changes).

I'll see if I can find any discussions for testing hooks with the new APIs, but if anyone can come up with an improvement, I would gladly remove this in a heartbeat

Copy link
Collaborator

Choose a reason for hiding this comment

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

TherenderWithAuth function should let you pass children routes. Can you explain a bit more what issues did you face on tests?

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

So, the main issue is that I'm stuck testing an individual hook, rather than a whole component. Ideally, my function (however it's implemented) would give me back the custom hook result, as well as a React Router router object (so I can check its internal state after modifying the hook result).

But what I was running into was that I couldn't figure out how to set things up so that I could get both values back. It seemed like it was one or the other, and I chose the one that would've been harder to mock out. I can't remember the precise syntax issues, but I'll try to recreate the problem after the FE Variety in a few minutes

I guess mocking outuseNavigate itself is an option, but I feel like that makes the tests get further from our real code

Copy link
Collaborator

Choose a reason for hiding this comment

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

We usehttps://react-hooks-testing-library.com/, not sure if that is helpful but if that is the only way, I'm good with that for now.

Copy link
MemberAuthor

@ParkreinerParkreinerNov 2, 2023
edited
Loading

Choose a reason for hiding this comment

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

Yeah, I didn't realize that the React Hooks test library was a separate initiative, butrenderHook comes from there, so sadly, there's nothing in the API docs that I haven't tried yet

I went through things again, and my take is that we're stuck choosing from two less-than-ideal compromises:

  1. We render with the current approach, which is kind of cursed, but it side-steps some of the limitations of hookingrenderHook up to a React Router MemoryRouter
  2. We go withrenderHook and swapcreateMemoryRouter for the direct<MemoryRouter> component, but then we have to mock out parts of React Router itself (likeuseNavigate), because this approach prevents us from accessing the router object itself

Copy link
MemberAuthor

@ParkreinerParkreinerNov 2, 2023
edited
Loading

Choose a reason for hiding this comment

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

Writing this out, so that we at least have something to reference in the future:

So, the main problem stems from the fact that whilerenderHook lets you test custom hooks and supports some form of dependency injection via itswrapper property, the API assumes that all components being injected support directchildren composition. React Router'screateMemoryRouter, on the other hand, assumes that you have all components defined ahead of time, so it doesn't supportchildren in the same way, nor does it give you the ability to add more routes after the router has been created.

If I were only testing the React Query parts, I could do this:

consttestResult=renderHook(()=>useWorkspaceDuplication(MockWorkspace),{wrapper:({ children})=>(<QueryProviderclient={testQueryClient}>{children}</QueryProvider>)});

But since the code also tests React Router, there's two choices:

  1. Use the<MemoryRouter> component directly
consttestResult=renderHook(()=>useWorkspaceDuplication(MockWorkspace),{wrapper:({ children})=>(<QueryProviderclient={testQueryClient}><MemoryRouter><Routes><Routepath="/"element={<>{children}</>}/><Routepath="/templates/:template/workspace"element={<CreateWorkspacePage/>}/></Routes></MemoryRouter></QueryProvider>)});

This seemed to hold up until I needed to verify that the navigation went through correctly. There wasn't really a way to access the router and verify the navigation had happened, and basically all the older StackOverflow answers said that you would need to mock outuseNavigate itself

  1. UsecreateMemoryRouter to make amemoryRouter to feed into<RouterProvider>
    This is where things get wonky – it basically turns into a chicken-or-the-egg situation, mainly becauseRouterProvider does not acceptchildren, andmemoryRouter is locked tight after being created.

    I need a component that acceptschildren in some way, so I can hook it up torenderHook'swrapper. ButcreateMemoryRouter can't make amemoryRouter object to hook into a provider unless it has all the page views ready to go in advance (includingrenderHook's mock component).

Copy link
MemberAuthor

Choose a reason for hiding this comment

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

So, writing this out gave me a slightly more cursed idea, but it might work better with all the existing APIs. I'm going to explore it more tomorrow

Copy link
MemberAuthor

@ParkreinerParkreinerNov 2, 2023
edited
Loading

Choose a reason for hiding this comment

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

Would need to test this out (and make sure it's resilient to re-renders), but uh...it sure is something:

functionrenderHookWithRouter(workspace?:Workspace){constqueryClient=newQueryClient({defaultOptions:{queries:{retry:false,cacheTime:0,refetchOnWindowFocus:false,networkMode:"offlineFirst",},},});// Easy to miss – there's an evil definite assignment via the !letescapedRouter!:ReturnType<typeofcreateMemoryRouter>;// eslint-disable-next-line testing-library/render-result-naming-convention -- Cursed stuff to make sure both router and hook result are exposedconstrenderHookResult=renderHook(()=>useWorkspaceDuplication(workspace),{wrapper:({ children})=>{// eslint-disable-next-line react-hooks/rules-of-hooks -- This is actually processed as a component; React just isn't aware of thatconst[readonlyStatefulRouter]=useState(()=>{returncreateMemoryRouter([{path:"/",element:<>{children}</>},{path:"/templates/:template/workspace",element:<CreateWorkspacePage/>,},]);});escapedRouter=readonlyStatefulRouter;return(<QueryClientProviderclient={queryClient}><RouterProviderrouter={readonlyStatefulRouter}/></QueryClientProvider>);},},);return{    ...renderHookResult,router:escapedRouter,};}

Guess the question is: as horrific as this is, are the result and API stable enough that we can abstract the nasty implementation details behind a function, and pretend they don't exist? Is this worth it?

Copy link
MemberAuthor

@ParkreinerParkreinerNov 3, 2023
edited
Loading

Choose a reason for hiding this comment

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

Update: this is cursed and evil, but it works, and I like how it simplifies the testing, making sure the wacky mocking stays in one spot

I'm going to clean this up, make sure it supports any kind of hook, and post this in the dev channel to get people's thoughts

Copy link
Member

@aslilacaslilac left a comment

Choose a reason for hiding this comment

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

nice!

@ParkreinerParkreiner merged commit744c733 intomainNov 3, 2023
@ParkreinerParkreiner deleted the mes/workspace-clone-feat branchNovember 3, 2023 22:23
@github-actionsgithub-actionsbot locked and limited conversation to collaboratorsNov 3, 2023
Sign up for freeto subscribe to this conversation on GitHub. Already have an account?Sign in.
Reviewers

@BrunoQuaresmaBrunoQuaresmaBrunoQuaresma left review comments

@aslilacaslilacaslilac approved these changes

Assignees

@ParkreinerParkreiner

Labels
None yet
Projects
None yet
Milestone
No milestone
Development

Successfully merging this pull request may close these issues.

3 participants
@Parkreiner@aslilac@BrunoQuaresma

[8]ページ先頭

©2009-2025 Movatter.jp