Skip to content

Commit

Permalink
When duplicating a project request user to name the duplicated project
Browse files Browse the repository at this point in the history
Change-Id: Iee13ad5d7d42035fd3174a2b016e4d0deea2e974
GitOrigin-RevId: 0420485d3ca09170596f1adce1739614ea00e1d9
  • Loading branch information
sarahsga authored and Copybara committed Jan 30, 2024
1 parent 4ccc7c8 commit 92832bd
Show file tree
Hide file tree
Showing 3 changed files with 90 additions and 45 deletions.
8 changes: 6 additions & 2 deletions platform/wab/src/wab/client/components/ProjectListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,8 @@ function ProjectListItem(props: ProjectListItemProps) {
appCtx,
null,
false,
"Duplicate"
"Duplicate",
project.name
);
if (response === undefined) {
return;
Expand All @@ -199,7 +200,10 @@ function ProjectListItem(props: ProjectListItemProps) {
appCtx.api.cloneProject(
project.id,
response.result === "workspace"
? { workspaceId: response.workspace.id }
? {
workspaceId: response.workspace.id,
name: response.name,
}
: undefined
)
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
/** @format */

import { parseProjectLocation, U } from "@/wab/client/cli-routes";
import { promptMoveToWorkspace } from "@/wab/client/components/dashboard/dashboard-actions";
import { useAppCtx } from "@/wab/client/contexts/AppContexts";
import { assert, spawn } from "@/wab/common";
import { ApiProject, MainBranchId } from "@/wab/shared/ApiSchema";
import { observer } from "mobx-react-lite";
import { useEffect } from "react";
import { assert, spawn } from "../../../../common";
import { ApiProject, MainBranchId } from "../../../../shared/ApiSchema";
import { parseProjectLocation, U } from "../../../cli-routes";
import { useAppCtx } from "../../../contexts/AppContexts";
import { promptMoveToWorkspace } from "../../dashboard/dashboard-actions";

interface CloneProjectModalProps {
project: ApiProject;
Expand All @@ -25,22 +25,24 @@ export const CloneProjectModal = observer(function ProjectNameModal({
spawn(
(async () => {
if (showCloneProjectModal) {
const destWorkspaceId = await promptMoveToWorkspace(
const response = await promptMoveToWorkspace(
appCtx,
null,
false,
"Duplicate"
"Duplicate",
project.name
);
if (!destWorkspaceId) {
if (!response) {
await setShowCloneProjectModal(false);
return;
}
assert(destWorkspaceId.result === "workspace", "");
assert(response.result === "workspace", "");
const parsedLocation = parseProjectLocation(appCtx.history.location);

const { projectId: newProjectId } = await appCtx.app.withSpinner(
appCtx.api.cloneProject(project.id, {
workspaceId: destWorkspaceId.workspace.id,
workspaceId: response.workspace.id,
name: response.name,
...(parsedLocation?.branchName &&
parsedLocation.branchName !== MainBranchId
? { branchName: parsedLocation.branchName }
Expand Down
105 changes: 72 additions & 33 deletions platform/wab/src/wab/client/components/dashboard/dashboard-actions.tsx
Original file line number Diff line number Diff line change
@@ -1,39 +1,40 @@
import { Form, Menu } from "antd";
import { History } from "history";
import * as React from "react";
import { useHistory } from "react-router-dom";
import { MakeADT } from "ts-adt/MakeADT";
import { ensure } from "../../../common";
import { AppCtx } from "@/wab/client/app-ctx";
import { U } from "@/wab/client/cli-routes";
import { MenuBuilder } from "@/wab/client/components/menu-builder";
import { ContentEditorConfigModal } from "@/wab/client/components/modals/ContentEditorConfigModal";
import { maybeShowPaywall } from "@/wab/client/components/modals/PricingModal";
import {
reactConfirm,
reactPrompt,
showTemporaryPrompt,
} from "@/wab/client/components/quick-modals";
import Button from "@/wab/client/components/widgets/Button";
import { Modal } from "@/wab/client/components/widgets/Modal";
import Select from "@/wab/client/components/widgets/Select";
import Textbox from "@/wab/client/components/widgets/Textbox";
import { ensure } from "@/wab/common";
import {
ApiPermission,
ApiTeam,
ApiWorkspace,
CmsDatabaseId,
TeamId,
WorkspaceId,
} from "../../../shared/ApiSchema";
import { accessLevelRank } from "../../../shared/EntUtil";
} from "@/wab/shared/ApiSchema";
import { accessLevelRank } from "@/wab/shared/EntUtil";
import {
ORGANIZATION_CAP,
ORGANIZATION_LOWER,
ORGANIZATION_PLURAL_LOWER,
PERSONAL_WORKSPACE,
} from "../../../shared/Labels";
import { getAccessLevelToResource } from "../../../shared/perms";
import { mergeUiConfigs, UiConfig } from "../../../shared/ui-config-utils";
import { AppCtx } from "../../app-ctx";
import { U } from "../../cli-routes";
import { Modal } from "../../components/widgets/Modal";
import { MenuBuilder } from "../menu-builder";
import { ContentEditorConfigModal } from "../modals/ContentEditorConfigModal";
import { maybeShowPaywall } from "../modals/PricingModal";
import {
reactConfirm,
reactPrompt,
showTemporaryPrompt,
} from "../quick-modals";
import Button from "../widgets/Button";
import Select from "../widgets/Select";
} from "@/wab/shared/Labels";
import { getAccessLevelToResource } from "@/wab/shared/perms";
import { mergeUiConfigs, UiConfig } from "@/wab/shared/ui-config-utils";
import { Form, Menu } from "antd";
import { History } from "history";
import * as React from "react";
import { useHistory } from "react-router-dom";
import { MakeADT } from "ts-adt/MakeADT";

// import Checkbox from "../widgets/Checkbox";

Expand Down Expand Up @@ -347,20 +348,22 @@ async function promptContentCreatorConfig(
)
);
}
type PromptMoveToWorkspaceOperation = "Duplicate" | "Move";
// { result: "noWorkspace" } is used when user submitted the form choosing
// to move the project to their personal drafts space.
type PromptWorkspaceResponse = MakeADT<
"result",
{
workspace: { workspace: ApiWorkspace };
workspace: { workspace: ApiWorkspace; name?: string };
noWorkspace: {};
}
>;
export async function promptMoveToWorkspace(
appCtx: AppCtx,
currentWorkspaceId: WorkspaceId | null,
allowNoWorkspace: boolean,
operationLabel: string
operationLabel: PromptMoveToWorkspaceOperation,
projectName?: string
): Promise<PromptWorkspaceResponse | undefined> {
const selfInfo = ensure(appCtx.selfInfo, "Unexpected nullish selfInfo");
const { workspaces, perms } = appCtx;
Expand All @@ -386,17 +389,19 @@ export async function promptMoveToWorkspace(
message: "Select a destination workspace:",
noWorkspacesMessage: "No workspaces available.",
selectedWorkspaceId: currentWorkspaceId ?? undefined,
projectName,
});
}

interface PromptWorkspaceProps {
workspaces: ApiWorkspace[];
message: string;
noWorkspacesMessage: string;
confirmButtonMessage?: string;
confirmButtonMessage?: PromptMoveToWorkspaceOperation;
selectedWorkspaceId?: WorkspaceId;
allowNoWorkspace: boolean;
allTeams: ApiTeam[];
projectName?: string;
}

export async function promptWorkspace({
Expand All @@ -407,7 +412,9 @@ export async function promptWorkspace({
selectedWorkspaceId,
allowNoWorkspace,
allTeams,
projectName,
}: PromptWorkspaceProps) {
const defaultNewName = projectName ? `Copy of ${projectName}` : "New Project";
const teams = allTeams.filter((t) =>
workspaces.find((w) => w.team.id === t.id)
);
Expand All @@ -425,7 +432,11 @@ export async function promptWorkspace({
onFinish={(e) => {
const workspace = workspaces.find((w) => w.id === e.select);
if (workspace) {
onSubmit({ result: "workspace", workspace });
onSubmit({
result: "workspace",
workspace,
name: e.name || undefined,
});
} else {
if (allowNoWorkspace) {
onSubmit({ result: "noWorkspace" });
Expand All @@ -436,6 +447,21 @@ export async function promptWorkspace({
}}
layout="vertical"
>
{confirmButtonMessage === "Duplicate" && (
<Form.Item
name="name"
label={"Enter a name for the duplicated project"}
>
<Textbox
name="name"
placeholder={defaultNewName}
defaultValue={defaultNewName}
styleType={["bordered"]}
autoFocus
data-test-id="promptName"
/>
</Form.Item>
)}
<Form.Item name="select" label={message}>
<Select name="select" autoFocus>
{selectedWorkspaceId && allowNoWorkspace ? (
Expand All @@ -461,11 +487,24 @@ export async function promptWorkspace({
)}
</Select>
</Form.Item>
<Form.Item style={{ margin: 0 }}>
<Button className="mr-sm" type="primary" htmlType="submit">
{confirmButtonMessage ?? "Confirm"}
</Button>
<Button onClick={() => onCancel()}>Cancel</Button>
<Form.Item style={{ margin: 0 }} shouldUpdate>
{({ getFieldsValue }) => {
const { select } = getFieldsValue();

return (
<>
<Button
disabled={allowNoWorkspace ? false : !Boolean(select)}
className="mr-sm"
type="primary"
htmlType="submit"
>
{confirmButtonMessage ?? "Confirm"}
</Button>
<Button onClick={() => onCancel()}>Cancel</Button>
</>
);
}}
</Form.Item>
</Form>
</Modal>
Expand Down

0 comments on commit 92832bd

Please sign in to comment.