-
Notifications
You must be signed in to change notification settings - Fork 928
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 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
Changes from all commits
Commits
Show all changes
24 commits
Select commit
Hold shift + click to select a range
9e4f999
chore: add queries for workspace build info
Parkreiner 112bc95
refactor: clean up logic for CreateWorkspacePage to support multiple …
Parkreiner 15fdfbf
chore: add custom workspace duplication hook
Parkreiner d007b86
chore: integrate mode into CreateWorkspacePageView
Parkreiner 294156e
fix: add mode to CreateWorkspacePageView stories
Parkreiner 4554895
refactor: extract workspace duplication outside CreateWorkspacePage file
Parkreiner 25bacf2
chore: integrate useWorkspaceDuplication into WorkspaceActions
Parkreiner 0947031
chore: delete unnecessary function
Parkreiner 1d4d4d7
Merge branch 'main' into mes/workspace-clone-feat
Parkreiner d71acf6
refactor: swap useReducer for useState
Parkreiner c0a8c56
fix: swap warning alert for info alert
Parkreiner 0b3e954
refactor: move info alert message
Parkreiner 7a763a9
refactor: simplify UI logic for mode alerts
Parkreiner da488fa
fix: prevent dismissed Alerts from affecting layouts
Parkreiner 5c7242f
fix: remove unnecessary prop binding
Parkreiner 98d1b1b
docs: reword comment for clarity
Parkreiner aeacda5
chore: update msw build params to return multiple params
Parkreiner 230a4f1
chore: rename duplicationReady to isDuplicationReady
Parkreiner 75b1839
chore: expose root component for testing/re-rendering
Parkreiner 7cf446f
chore: get tests in place (still have act warnings)
Parkreiner bf21656
refactor: move stuff around for clarity
Parkreiner 38ba3b2
chore: finish tests
Parkreiner 923d080
chore: revamp tests
Parkreiner 8b3d4dd
chore: merge main into branch
Parkreiner File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.test.tsx
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { waitFor } from "@testing-library/react"; | ||
import * as M from "../../testHelpers/entities"; | ||
import { type Workspace } from "api/typesGenerated"; | ||
import { useWorkspaceDuplication } from "./useWorkspaceDuplication"; | ||
import { MockWorkspace } from "testHelpers/entities"; | ||
import CreateWorkspacePage from "./CreateWorkspacePage"; | ||
import { renderHookWithAuth } from "testHelpers/renderHelpers"; | ||
|
||
function render(workspace?: Workspace) { | ||
return renderHookWithAuth( | ||
({ workspace }: { workspace?: Workspace }) => { | ||
return useWorkspaceDuplication(workspace); | ||
}, | ||
{ | ||
initialProps: { workspace }, | ||
extraRoutes: [ | ||
{ | ||
path: "/templates/:template/workspace", | ||
element: <CreateWorkspacePage />, | ||
}, | ||
], | ||
}, | ||
); | ||
} | ||
|
||
type RenderResult = Awaited<ReturnType<typeof render>>; | ||
|
||
async function performNavigation( | ||
result: RenderResult["result"], | ||
router: RenderResult["router"], | ||
) { | ||
await waitFor(() => expect(result.current.isDuplicationReady).toBe(true)); | ||
result.current.duplicateWorkspace(); | ||
|
||
return waitFor(() => { | ||
expect(router.state.location.pathname).toEqual( | ||
`/templates/${MockWorkspace.template_name}/workspace`, | ||
); | ||
}); | ||
} | ||
|
||
describe(`${useWorkspaceDuplication.name}`, () => { | ||
it("Will never be ready when there is no workspace passed in", async () => { | ||
const { result, rerender } = await render(undefined); | ||
expect(result.current.isDuplicationReady).toBe(false); | ||
|
||
for (let i = 0; i < 10; i++) { | ||
rerender({ workspace: undefined }); | ||
expect(result.current.isDuplicationReady).toBe(false); | ||
} | ||
}); | ||
|
||
it("Will become ready when workspace is provided and build params are successfully fetched", async () => { | ||
const { result } = await render(MockWorkspace); | ||
|
||
expect(result.current.isDuplicationReady).toBe(false); | ||
await waitFor(() => expect(result.current.isDuplicationReady).toBe(true)); | ||
}); | ||
|
||
it("Is able to navigate the user to the workspace creation page", async () => { | ||
const { result, router } = await render(MockWorkspace); | ||
await performNavigation(result, router); | ||
}); | ||
|
||
test("Navigating populates the URL search params with the workspace's build params", async () => { | ||
const { result, router } = await render(MockWorkspace); | ||
await performNavigation(result, router); | ||
|
||
const parsedParams = new URLSearchParams(router.state.location.search); | ||
const mockBuildParams = [ | ||
M.MockWorkspaceBuildParameter1, | ||
M.MockWorkspaceBuildParameter2, | ||
M.MockWorkspaceBuildParameter3, | ||
M.MockWorkspaceBuildParameter4, | ||
M.MockWorkspaceBuildParameter5, | ||
]; | ||
|
||
for (const { name, value } of mockBuildParams) { | ||
const key = `param.${name}`; | ||
expect(parsedParams.get(key)).toEqual(value); | ||
} | ||
}); | ||
|
||
test("Navigating appends other necessary metadata to the search params", async () => { | ||
const { result, router } = await render(MockWorkspace); | ||
await performNavigation(result, router); | ||
|
||
const parsedParams = new URLSearchParams(router.state.location.search); | ||
const extraMetadataEntries = [ | ||
["mode", "duplicate"], | ||
["name", MockWorkspace.name], | ||
["version", MockWorkspace.template_active_version_id], | ||
] as const; | ||
|
||
for (const [key, value] of extraMetadataEntries) { | ||
expect(parsedParams.get(key)).toBe(value); | ||
} | ||
}); | ||
}); |
71 changes: 71 additions & 0 deletions
71
site/src/pages/CreateWorkspacePage/useWorkspaceDuplication.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { useNavigate } from "react-router-dom"; | ||
import { useQuery } from "react-query"; | ||
import { type CreateWorkspaceMode } from "./CreateWorkspacePage"; | ||
import { | ||
type Workspace, | ||
type WorkspaceBuildParameter, | ||
} from "api/typesGenerated"; | ||
import { workspaceBuildParameters } from "api/queries/workspaceBuilds"; | ||
import { useCallback } from "react"; | ||
|
||
function getDuplicationUrlParams( | ||
workspaceParams: readonly WorkspaceBuildParameter[], | ||
workspace: Workspace, | ||
): URLSearchParams { | ||
// Record type makes sure that every property key added starts with "param."; | ||
// page is also set up to parse params with this prefix for auto mode | ||
const consolidatedParams: Record<`param.${string}`, string> = {}; | ||
|
||
for (const p of workspaceParams) { | ||
consolidatedParams[`param.${p.name}`] = p.value; | ||
} | ||
|
||
return new URLSearchParams({ | ||
...consolidatedParams, | ||
mode: "duplicate" satisfies CreateWorkspaceMode, | ||
name: workspace.name, | ||
version: workspace.template_active_version_id, | ||
}); | ||
} | ||
|
||
/** | ||
* Takes a workspace, and returns out a function that will navigate the user to | ||
* the 'Create Workspace' page, pre-filling the form with as much information | ||
* about the workspace as possible. | ||
*/ | ||
export function useWorkspaceDuplication(workspace?: Workspace) { | ||
const navigate = useNavigate(); | ||
const buildParametersQuery = useQuery( | ||
workspace !== undefined | ||
? workspaceBuildParameters(workspace.latest_build.id) | ||
: { enabled: false }, | ||
); | ||
|
||
// Not using useEffectEvent for this, because useEffect isn't really an | ||
// intended use case for this custom hook | ||
const duplicateWorkspace = useCallback(() => { | ||
const buildParams = buildParametersQuery.data; | ||
if (buildParams === undefined || workspace === undefined) { | ||
return; | ||
} | ||
|
||
const newUrlParams = getDuplicationUrlParams(buildParams, workspace); | ||
|
||
// Necessary for giving modals/popups time to flush their state changes and | ||
// close the popup before actually navigating. MUI does provide the | ||
// disablePortal prop, which also side-steps this issue, but you have to | ||
// remember to put it on any component that calls this function. Better to | ||
// code defensively and have some redundancy in case someone forgets | ||
void Promise.resolve().then(() => { | ||
navigate({ | ||
pathname: `/templates/${workspace.template_name}/workspace`, | ||
search: newUrlParams.toString(), | ||
}); | ||
}); | ||
}, [navigate, workspace, buildParametersQuery.data]); | ||
|
||
return { | ||
duplicateWorkspace, | ||
isDuplicationReady: buildParametersQuery.isSuccess, | ||
} as const; | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
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:
mutable
property is alwaystrue
inside themutable
array, and alwaysfalse
inside theimmutable
array