Skip to content

Commit

Permalink
feat: support unlinking projects
Browse files Browse the repository at this point in the history
  • Loading branch information
ciyer committed Dec 18, 2024
1 parent cae6aa2 commit 743daa5
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import useProjectPermissions from "../../utils/useProjectPermissions.hook";
import ProjectSessionSecrets from "../SessionSecrets/ProjectSessionSecrets";
import ProjectPageDelete from "./ProjectDelete";
import ProjectPageSettingsMembers from "./ProjectSettingsMembers";
import ProjectUnlinkTemplate from "./ProjectUnlinkTemplate";

function notificationProjectUpdated(
notifications: NotificationsManager,
Expand Down Expand Up @@ -381,30 +382,6 @@ function ProjectSettingsDisplay({ project }: ProjectPageSettingsProps) {
);
}

function ProjectSettingsTemplateLink({ project }: { project: Project }) {
if (project.template_id === null) return null;
return (
<Card id="copy">
<CardHeader>
<h4>
<Diagram3Fill className={cx("bi", "me-1")} />
Break template link
</h4>
<p className="m-0">
This will break the link between this project and the template it was
created from.
</p>
</CardHeader>
<CardBody>
<div className="d-flex">
<div className="fst-italic me-2">(Not yet implemented)</div>
<div className="flex-grow-1"></div>
</div>
</CardBody>
</Card>
);
}

function ProjectSettingsMetadata({ project }: ProjectPageSettingsProps) {
const permissions = useProjectPermissions({ projectId: project.id });

Expand Down Expand Up @@ -480,7 +457,7 @@ export default function ProjectPageSettings() {
<ProjectSessionSecrets />
<PermissionsGuard
disabled={null}
enabled={<ProjectSettingsTemplateLink project={project} />}
enabled={<ProjectUnlinkTemplate project={project} />}
requestedPermission="write"
userPermissions={permissions}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
/*!
* Copyright 2024 - Swiss Data Science Center (SDSC)
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
* Eidgenössische Technische Hochschule Zürich (ETHZ).
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import cx from "classnames";
import { useCallback, useContext, useEffect, useState } from "react";
import { Diagram3Fill, NodeMinus } from "react-bootstrap-icons";
import { useNavigate } from "react-router-dom-v5-compat";
import { Button, Card, CardBody, CardHeader, Input } from "reactstrap";

import { Loader } from "../../../../components/Loader";
import { NOTIFICATION_TOPICS } from "../../../../notifications/Notifications.constants";
import { NotificationsManager } from "../../../../notifications/notifications.types";
import AppContext from "../../../../utils/context/appContext";
import { Project } from "../../../projectsV2/api/projectV2.api";
import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api";

export function notificationProjectDeleted(
notifications: NotificationsManager,
projectName: string
) {
notifications.addSuccess(
NOTIFICATION_TOPICS.PROJECT_UPDATED,
<>
Project <code>{projectName}</code> successfully unlinked.
</>
);
}

interface ProjectUnlinkTemplateProps {
project: Project;
}
export default function ProjectUnlinkTemplate({
project,
}: ProjectUnlinkTemplateProps) {
const [patchProject, result] = usePatchProjectsByProjectIdMutation();
const navigate = useNavigate();
const { notifications } = useContext(AppContext);
const onUnlink = useCallback(() => {
patchProject({
projectId: project.id,
"If-Match": project.etag ?? "",
projectPatch: {
template_id: "",
},
});
}, [patchProject, project.etag, project.id]);

useEffect(() => {
if (result.isSuccess) {
if (notifications)
notificationProjectDeleted(notifications, project.name);
}
}, [result.isSuccess, navigate, notifications, project.name]);

const [typedName, setTypedName] = useState("");
const onChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setTypedName(e.target.value.trim());
},
[setTypedName]
);

if (project.template_id === null) return null;
return (
<Card id="copy">
<CardHeader>
<h4>
<Diagram3Fill className={cx("bi", "me-1")} />
Break template link
</h4>
<p className="m-0">
This will break the link between this project and the template it was
created from.
</p>
</CardHeader>
<CardBody>
<p className="fw-bold">
Are you sure you want to unlink this project from its template?
</p>
<p>
This cannot be undone. Please type <strong>{project.slug}</strong>,
the slug of the project, to confirm.
</p>
<div className="mb-3">
<Input
data-cy="unlink-confirmation-input"
value={typedName}
onChange={onChange}
/>
</div>
<div className="text-end">
<Button
color="danger"
disabled={typedName !== project.slug?.trim() || result.isLoading}
onClick={onUnlink}
>
{result.isLoading ? (
<Loader className="me-1" inline size={16} />
) : (
<NodeMinus className={cx("bi", "me-1")} />
)}
Unlink project
</Button>
</div>
</CardBody>
</Card>
);
}
12 changes: 10 additions & 2 deletions tests/cypress/e2e/projectV2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -753,12 +753,20 @@ describe("Project templates and copies", () => {
});

it("break the template link", () => {
fixtures.getProjectV2Permissions().listNamespaceV2().copyProjectV2();
fixtures
.getProjectV2Permissions()
.listNamespaceV2()
.copyProjectV2()
.updateProjectV2();
cy.visit("/v2/projects/user1-uuid/test-2-v2-project");
cy.wait("@readProjectV2");

cy.get("a[title='Settings']").should("be.visible").click();
cy.contains("Break template link").should("be.visible").click();
cy.contains("Break template link").should("be.visible");
cy.contains("Unlink project").should("be.disabled");
cy.getDataCy("unlink-confirmation-input").clear().type("test-2-v2-project");
cy.contains("Unlink project").should("be.enabled").click();
cy.wait("@updateProjectV2");
});
});

Expand Down
2 changes: 1 addition & 1 deletion tests/cypress/support/renkulab-fixtures/projectV2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ interface ProjectV2DeleteFixture extends NameOnlyFixture {
interface ProjectV2ListCopiesFixture
extends Omit<ProjectV2IdArgs, "overrides"> {
writeable?: boolean;
count?: 0 | 1 | undefined | null;
count?: number | null;
}

interface ProjectV2NameArgs extends SimpleFixture {
Expand Down

0 comments on commit 743daa5

Please sign in to comment.