Skip to content

Commit ffcf464

Browse files
committed
wip: show internal gitlab reference warning
change banner content change banner content
1 parent 7cba41f commit ffcf464

File tree

8 files changed

+506
-0
lines changed

8 files changed

+506
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
@import "~bootstrap/scss/functions";
2+
@import "~bootstrap/scss/variables";
3+
@import "~bootstrap/scss/variables-dark";
4+
@import "../styles/renku_bootstrap_customization.scss";
5+
6+
.dangerAlert {
7+
--bs-danger-bg-subtle: #{tint-color(#dc3545, 0%)};
8+
--bs-danger-text-emphasis: #{tint-color(#dc3545, 100%)};
9+
--bs-danger-border-subtle: #{tint-color(#dc3545, 0%)};
10+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/*!
2+
* Copyright 2024 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
import cx from "classnames";
20+
import { Alert } from "reactstrap";
21+
import styles from "./TakeActionAlert.module.scss";
22+
23+
interface TakeActionAlertProps {
24+
children: React.ReactNode;
25+
"data-cy"?: string;
26+
icon?: React.ReactNode;
27+
className?: string;
28+
}
29+
export default function TakeActionAlert({
30+
children,
31+
icon,
32+
...props
33+
}: TakeActionAlertProps) {
34+
return (
35+
<Alert
36+
color="danger"
37+
isOpen
38+
data-cy={props["data-cy"]}
39+
className={cx(styles.dangerAlert, props.className, "overflow-y-auto")}
40+
>
41+
<div className={cx("d-flex", "gap-3")}>
42+
{icon && <div>{icon}</div>}
43+
<div className={cx("my-auto", "w-100")}>{children}</div>
44+
</div>
45+
</Alert>
46+
);
47+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
.modalBody {
2+
max-height: 50vh;
3+
}
4+
5+
/* this is needed for correct alignment */
6+
.gitlabReferencesButton {
7+
position: relative;
8+
top: -2px;
9+
--bs-link-color: white;
10+
}
Lines changed: 218 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,218 @@
1+
/*!
2+
* Copyright 2024 - Swiss Data Science Center (SDSC)
3+
* A partnership between École Polytechnique Fédérale de Lausanne (EPFL) and
4+
* Eidgenössische Technische Hochschule Zürich (ETHZ).
5+
*
6+
* Licensed under the Apache License, Version 2.0 (the "License");
7+
* you may not use this file except in compliance with the License.
8+
* You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
14+
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License
17+
*/
18+
import cx from "classnames";
19+
import { useCallback, useState } from "react";
20+
import { FlagFill } from "react-bootstrap-icons";
21+
import { Button, ListGroup, ModalBody, ModalHeader } from "reactstrap";
22+
23+
import ScrollableModal from "~/components/modal/ScrollableModal";
24+
import TakeActionAlert from "~/components/TakeActionAlert";
25+
import PermissionsGuard from "~/features/permissionsV2/PermissionsGuard";
26+
import type { Project } from "~/features/projectsV2/api/projectV2.api";
27+
import { useGetProjectsByProjectIdSessionLaunchersQuery } from "~/features/sessionsV2/api/sessionLaunchersV2.api";
28+
import type { SessionLaunchersList } from "~/features/sessionsV2/api/sessionLaunchersV2.api";
29+
import { useGetUserQueryState } from "~/features/usersV2/api/users.api";
30+
31+
import useProjectPermissions from "../utils/useProjectPermissions.hook";
32+
33+
import styles from "./ProjectGitLabWarnBanner.module.css";
34+
35+
function projectRenkulabGitLabReferences(
36+
project: Project,
37+
allLaunchers: SessionLaunchersList
38+
) {
39+
const repositories =
40+
project.repositories?.filter((repo) =>
41+
repo.includes("gitlab.renkulab.io")
42+
) ?? [];
43+
const launchers = allLaunchers.filter((launcher) =>
44+
launcher.environment.container_image?.includes("registry.renkulab.io")
45+
);
46+
return { repositories, launchers };
47+
}
48+
49+
function doesProjectReferenceRenkulabGitLab(
50+
project: Project,
51+
allLaunchers: SessionLaunchersList
52+
) {
53+
const { repositories, launchers } = projectRenkulabGitLabReferences(
54+
project,
55+
allLaunchers
56+
);
57+
return repositories.length > 0 || launchers.length > 0;
58+
}
59+
60+
function GitLabReferencesMessage({
61+
repositories,
62+
launchers,
63+
}: Pick<ProjectGitLabWarnModalProps, "repositories" | "launchers">) {
64+
const repoCount = repositories.length;
65+
const launcherCount = launchers.length;
66+
// This should not actually happen, but handle this case anyway
67+
if (repoCount === 0 && launcherCount === 0) return null;
68+
if (repoCount > 0 && launcherCount > 0) {
69+
return (
70+
<>
71+
Follow these instructions to fix problems in your{" "}
72+
{repositories.length === 1 ? "repository" : "repositories"} and{" "}
73+
{launchers.length === 1 ? "launcher" : "launchers"}
74+
</>
75+
);
76+
}
77+
if (repoCount > 0) {
78+
return (
79+
<>
80+
Follow these instructions to fix problems in your{" "}
81+
{repositories.length === 1 ? "repository" : "repositories"}
82+
</>
83+
);
84+
}
85+
return (
86+
<>
87+
Follow these instructions to fix problems in your{" "}
88+
{launchers.length === 1 ? "launcher" : "launchers"}
89+
</>
90+
);
91+
}
92+
93+
interface ProjectGitLabWarnModalProps {
94+
isOpen: boolean;
95+
launchers: SessionLaunchersList;
96+
project: Project;
97+
repositories: string[];
98+
title: string;
99+
toggle: () => void;
100+
}
101+
102+
export function ProjectGitLabWarnModal({
103+
isOpen,
104+
launchers,
105+
project,
106+
repositories,
107+
title,
108+
toggle,
109+
}: ProjectGitLabWarnModalProps) {
110+
return (
111+
<ScrollableModal
112+
data-cy="migrate-list-modal"
113+
backdrop="static"
114+
isOpen={isOpen}
115+
toggle={toggle}
116+
size="lg"
117+
centered
118+
>
119+
<ModalHeader toggle={toggle}>
120+
<span className="fw-normal">{title} </span>
121+
{project.namespace}/{project.slug}
122+
</ModalHeader>
123+
<ModalBody className={cx(styles.modalBody)}>
124+
<ListGroup flush data-cy="repository-list">
125+
{repositories.map((repo) => (
126+
<span key={repo}>{repo}</span>
127+
))}
128+
{launchers.map((launcher) => (
129+
<span key={launcher.id}>
130+
{launcher.environment.container_image}
131+
</span>
132+
))}
133+
</ListGroup>
134+
</ModalBody>
135+
</ScrollableModal>
136+
);
137+
}
138+
139+
interface ProjectEditorWarnBannerProps {
140+
project: Project;
141+
launchers: SessionLaunchersList;
142+
}
143+
function ProjectEditorWarnBanner({
144+
project,
145+
launchers: allLaunchers,
146+
}: ProjectEditorWarnBannerProps) {
147+
const { repositories, launchers } = projectRenkulabGitLabReferences(
148+
project,
149+
allLaunchers
150+
);
151+
const [isModalOpen, setModalOpen] = useState(false);
152+
const toggleOpen = useCallback(() => {
153+
setModalOpen((open) => !open);
154+
}, []);
155+
return (
156+
<>
157+
<TakeActionAlert className="p-2" icon={null}>
158+
<div className="pt-2">
159+
<h2>
160+
<FlagFill className={cx("bi", "me-3")} />
161+
You <b>must take action</b> to avoid losing access to your data.
162+
</h2>
163+
<div>
164+
<Button
165+
className={cx("p-0", styles.gitlabReferencesButton)}
166+
color="link"
167+
data-cy="list-gitlab-references-link"
168+
onClick={toggleOpen}
169+
>
170+
<GitLabReferencesMessage
171+
repositories={repositories}
172+
launchers={launchers}
173+
/>
174+
</Button>
175+
</div>
176+
</div>
177+
</TakeActionAlert>
178+
{isModalOpen && (
179+
<ProjectGitLabWarnModal
180+
isOpen={isModalOpen}
181+
launchers={launchers}
182+
project={project}
183+
repositories={repositories}
184+
title="Repositories and Launchers referencing GitLab"
185+
toggle={toggleOpen}
186+
/>
187+
)}
188+
</>
189+
);
190+
}
191+
192+
export default function ProjectGitLabWarnBanner({
193+
project,
194+
}: {
195+
project: Project;
196+
}) {
197+
const { data: currentUser } = useGetUserQueryState();
198+
const userPermissions = useProjectPermissions({ projectId: project.id });
199+
200+
const {
201+
data: launchers,
202+
error: launchersError,
203+
isLoading: isLoadingLaunchers,
204+
} = useGetProjectsByProjectIdSessionLaunchersQuery({ projectId: project.id });
205+
if (currentUser == null) return null;
206+
if (isLoadingLaunchers || launchersError || launchers == null) return null;
207+
if (!doesProjectReferenceRenkulabGitLab(project, launchers)) return null;
208+
return (
209+
<PermissionsGuard
210+
disabled={null}
211+
enabled={
212+
<ProjectEditorWarnBanner project={project} launchers={launchers} />
213+
}
214+
requestedPermission="write"
215+
userPermissions={userPermissions}
216+
/>
217+
);
218+
}

client/src/features/ProjectPageV2/ProjectPageHeader/ProjectPageHeader.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { ProjectImageView } from "../ProjectPageContent/ProjectInformation/Proje
2626

2727
import ProjectAutostartRedirectBanner from "./ProjectAutostartRedirectBanner";
2828
import ProjectCopyBanner from "./ProjectCopyBanner";
29+
import ProjectGitLabWarnBanner from "./ProjectGitLabWarnBanner";
2930
import ProjectTemplateInfoBanner from "./ProjectTemplateInfoBanner";
3031

3132
interface ProjectPageHeaderProps {
@@ -43,6 +44,7 @@ export default function ProjectPageHeader({ project }: ProjectPageHeaderProps) {
4344

4445
return (
4546
<header>
47+
<ProjectGitLabWarnBanner project={project} />
4648
<Row>
4749
<Col xs={12} lg={2}>
4850
<div className={cx("d-none", "d-lg-block")}>

0 commit comments

Comments
 (0)