Skip to content

Commit d98a5e0

Browse files
committed
refactor: centralize workspace duplication logic
1 parent 53a9cae commit d98a5e0

File tree

4 files changed

+45
-28
lines changed

4 files changed

+45
-28
lines changed

site/src/pages/CreateWorkspacePage/CreateWorkspacePage.tsx

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -64,8 +64,7 @@ const CreateWorkspacePage: FC = () => {
6464

6565
const defaultBuildParameters = getDefaultBuildParameters(searchParams);
6666
const mode = getWorkspaceMode(searchParams);
67-
const defaultName =
68-
mode === "auto" ? generateUniqueName() : searchParams.get("name") ?? "";
67+
const defaultName = getDefaultName(mode, searchParams);
6968

7069
const onCreateWorkspace = (workspace: Workspace) => {
7170
navigate(`/@${workspace.owner_name}/${workspace.name}`);
@@ -177,15 +176,39 @@ const useExternalAuth = (versionId: string | undefined) => {
177176

178177
const isAllSignedIn = authList?.every((it) => it.authenticated) ?? false;
179178

180-
// Doing inline state sync to minimize extra re-renders that useEffect
181-
// approach would involve
179+
// Doing state sync inline, because doing it inside a useEffect call would add
180+
// unnecessary renders and re-painting.
182181
if (isAllSignedIn && pollingStatus === "polling") {
183182
setPollingStatus("idle");
184183
}
185184

186185
return { authList, isAllSignedIn, pollingStatus, startPolling } as const;
187186
};
188187

188+
/**
189+
* Returns a function that takes a workspace, and then navigates the user to the
190+
* workspace creation page, with the form pre-filled with the provided
191+
* workspace's parameters.
192+
*/
193+
export function useWorkspaceDuplication() {
194+
const navigate = useNavigate();
195+
196+
return useCallback(
197+
(workspace: Workspace) => {
198+
const workspaceCreationParams = new URLSearchParams({
199+
mode: "duplicate" satisfies CreateWorkspaceMode,
200+
name: workspace.name,
201+
});
202+
203+
navigate({
204+
pathname: `/templates/${workspace.template_name}/workspace`,
205+
search: workspaceCreationParams.toString(),
206+
});
207+
},
208+
[navigate],
209+
);
210+
}
211+
189212
function getWorkspaceMode(params: URLSearchParams): CreateWorkspaceMode {
190213
const paramMode = params.get("mode");
191214
if (createWorkspaceModes.includes(paramMode as CreateWorkspaceMode)) {
@@ -195,6 +218,19 @@ function getWorkspaceMode(params: URLSearchParams): CreateWorkspaceMode {
195218
return "form";
196219
}
197220

221+
function getDefaultName(mode: CreateWorkspaceMode, params: URLSearchParams) {
222+
if (mode === "auto") {
223+
return generateUniqueName();
224+
}
225+
226+
const paramsName = params.get("name");
227+
if (mode === "duplicate" && paramsName) {
228+
return `${paramsName}-copy`;
229+
}
230+
231+
return paramsName ?? "";
232+
}
233+
198234
type AutomatedWorkspaceConfig = {
199235
auto: boolean;
200236
payload: AutoCreateWorkspaceOptions;

site/src/pages/WorkspacePage/Workspace.tsx

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,6 @@ export interface WorkspaceProps {
4949
handleUpdate: () => void;
5050
handleCancel: () => void;
5151
handleSettings: () => void;
52-
handleClone: () => void;
5352
handleChangeVersion: () => void;
5453
handleDormantActivate: () => void;
5554
isUpdating: boolean;
@@ -88,7 +87,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
8887
handleUpdate,
8988
handleCancel,
9089
handleSettings,
91-
handleClone,
9290
handleChangeVersion,
9391
handleDormantActivate: handleDormantActivate,
9492
workspace,
@@ -203,7 +201,6 @@ export const Workspace: FC<React.PropsWithChildren<WorkspaceProps>> = ({
203201
handleUpdate={handleUpdate}
204202
handleCancel={handleCancel}
205203
handleSettings={handleSettings}
206-
handleClone={handleClone}
207204
handleChangeVersion={handleChangeVersion}
208205
handleDormantActivate={handleDormantActivate}
209206
canChangeVersions={canChangeVersions}

site/src/pages/WorkspacePage/WorkspaceActions/WorkspaceActions.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import HistoryIcon from "@mui/icons-material/HistoryOutlined";
2626
import DeleteIcon from "@mui/icons-material/DeleteOutlined";
2727
import MoreOptionsIcon from "@mui/icons-material/MoreVertOutlined";
2828
import DuplicateIcon from "@mui/icons-material/FileCopyOutlined";
29+
import { useWorkspaceDuplication } from "pages/CreateWorkspacePage/CreateWorkspacePage";
2930

3031
export interface WorkspaceActionsProps {
3132
workspace: Workspace;
@@ -35,7 +36,6 @@ export interface WorkspaceActionsProps {
3536
handleDelete: () => void;
3637
handleUpdate: () => void;
3738
handleCancel: () => void;
38-
handleClone: () => void;
3939
handleSettings: () => void;
4040
handleChangeVersion: () => void;
4141
handleDormantActivate: () => void;
@@ -54,7 +54,6 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
5454
handleUpdate,
5555
handleCancel,
5656
handleSettings,
57-
handleClone,
5857
handleChangeVersion,
5958
handleDormantActivate: handleDormantActivate,
6059
isUpdating,
@@ -92,6 +91,7 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
9291
const styles = useStyles();
9392
const menuTriggerRef = useRef<HTMLButtonElement>(null);
9493
const [isMenuOpen, setIsMenuOpen] = useState(false);
94+
const duplicateWorkspace = useWorkspaceDuplication();
9595

9696
// Returns a function that will execute the action and close the menu
9797
const onMenuItemClick = (actionFn: () => void) => () => {
@@ -151,7 +151,9 @@ export const WorkspaceActions: FC<WorkspaceActionsProps> = ({
151151
</MenuItem>
152152
)}
153153

154-
<MenuItem onClick={onMenuItemClick(handleClone)}>
154+
<MenuItem
155+
onClick={onMenuItemClick(() => duplicateWorkspace(workspace))}
156+
>
155157
<DuplicateIcon />
156158
Duplicate&hellip;
157159
</MenuItem>

site/src/pages/WorkspacePage/WorkspaceReadyPage.tsx

Lines changed: 0 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import { templateVersion, templateVersions } from "api/queries/templates";
3434
import { Alert } from "components/Alert/Alert";
3535
import { Stack } from "components/Stack/Stack";
3636
import { useWorkspaceBuildLogs } from "hooks/useWorkspaceBuildLogs";
37-
import { type CreateWorkspaceMode } from "../CreateWorkspacePage/CreateWorkspacePage";
3837

3938
interface WorkspaceReadyPageProps {
4039
workspaceState: StateFrom<typeof workspaceMachine>;
@@ -117,22 +116,6 @@ export const WorkspaceReadyPage = ({
117116
bannerSend({ type: "REFRESH_WORKSPACE", workspace });
118117
}, [bannerSend, workspace]);
119118

120-
const handleWorkspaceCloning = () => {
121-
if (template?.name === undefined) {
122-
return;
123-
}
124-
125-
const workspaceCreationParams = new URLSearchParams({
126-
mode: "duplicate" satisfies CreateWorkspaceMode,
127-
name: workspace.name,
128-
});
129-
130-
navigate({
131-
pathname: `/templates/${template.name}/workspace`,
132-
search: workspaceCreationParams.toString(),
133-
});
134-
};
135-
136119
const favicon = getFaviconByStatus(workspace.latest_build);
137120
const deadline = getDeadline(workspace);
138121
const canUpdateWorkspace = Boolean(permissions?.updateWorkspace);
@@ -199,7 +182,6 @@ export const WorkspaceReadyPage = ({
199182
}}
200183
handleCancel={() => workspaceSend({ type: "CANCEL" })}
201184
handleSettings={() => navigate("settings")}
202-
handleClone={handleWorkspaceCloning}
203185
handleBuildRetry={() => workspaceSend({ type: "RETRY_BUILD" })}
204186
handleChangeVersion={() => {
205187
setChangeVersionDialogOpen(true);

0 commit comments

Comments
 (0)