From 15f1911a383c6c0a77e270be8a2c2f3be217f614 Mon Sep 17 00:00:00 2001 From: Andrea Cordoba Date: Tue, 8 Oct 2024 14:33:20 +0200 Subject: [PATCH 1/3] feat: update get project by namespace api --- client/src/features/projectsV2/api/projectV2.api.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/src/features/projectsV2/api/projectV2.api.ts b/client/src/features/projectsV2/api/projectV2.api.ts index c6da9494b..e6de5176c 100644 --- a/client/src/features/projectsV2/api/projectV2.api.ts +++ b/client/src/features/projectsV2/api/projectV2.api.ts @@ -45,7 +45,7 @@ const injectedRtkApi = api.injectEndpoints({ GetProjectsByNamespaceAndSlugApiArg >({ query: (queryArg) => ({ - url: `/projects/${queryArg["namespace"]}/${queryArg.slug}`, + url: `/namespaces/${queryArg["namespace"]}/projects/${queryArg.slug}`, }), }), getProjectsByProjectIdMembers: build.query< From 5c9eb97fac9f5ee8a37311c96807d15712f5d0f6 Mon Sep 17 00:00:00 2001 From: Andrea Cordoba <43388408+andre-code@users.noreply.github.com> Date: Fri, 11 Oct 2024 12:40:02 +0200 Subject: [PATCH 2/3] feat!: support custom env variables in Renku 2.0 (#3227) --- client/.eslintrc.json | 1 + client/src/components/MoreInfo.tsx | 46 ++ .../admin/AddSessionEnvironmentButton.tsx | 28 +- .../SessionEnvironmentAdvancedFields.tsx | 73 ++ .../admin/SessionEnvironmentFormContent.tsx | 28 +- .../admin/SessionEnvironmentsSection.tsx | 46 +- .../admin/UpdateSessionEnvironmentButton.tsx | 46 +- .../src/features/admin/adminSessions.types.ts | 14 + .../src/features/admin/adminSessions.utils.ts | 40 ++ client/src/features/favicon/Favicon.tsx | 2 +- .../sessionsV2/AddSessionLauncherButton.tsx | 5 +- .../sessionsV2/SessionLauncherFormContent.tsx | 621 ------------------ .../SessionView/EnvironmentCard.tsx | 183 ++++++ .../sessionsV2/SessionView/SessionView.tsx | 122 +--- client/src/features/sessionsV2/SessionsV2.tsx | 2 +- .../sessionsV2/UpdateSessionLauncherModal.tsx | 192 ------ .../SessionForm/AdvancedSettingsFields.tsx | 268 ++++++++ .../SessionForm/CustomEnvironmentFields.tsx | 89 +++ .../SessionForm/EditLauncherFormContent.tsx | 217 ++++++ .../SessionForm/EnvironmentField.tsx | 77 +++ .../SessionForm/EnvironmentKindField.tsx | 67 ++ .../SessionForm/GlobalEnvironmentFields.tsx | 101 +++ .../SessionForm/LauncherDetailsFields.tsx | 139 ++++ .../SessionForm/SessionEnvironmentItem.tsx | 134 ++++ .../SessionLauncherBreadcrumbNavbar.tsx | 73 ++ .../components/SessionModals/AddSession.tsx | 364 ---------- .../SessionModals/NewSessionLauncherModal.tsx | 260 ++++++++ .../UpdateSessionLauncherModal.tsx | 178 +++++ .../features/sessionsV2/session.constants.ts | 86 +++ .../src/features/sessionsV2/session.utils.ts | 233 +++++-- .../features/sessionsV2/sessionsV2.types.ts | 77 ++- .../sessionsV2/useSessionLaunchState.hook.ts | 34 +- .../useSessionResourceClass.hook.ts | 2 +- tests/cypress/e2e/projectV2setup.spec.ts | 15 +- .../fixtures/projectV2/session-launchers.json | 8 +- 35 files changed, 2433 insertions(+), 1438 deletions(-) create mode 100644 client/src/components/MoreInfo.tsx create mode 100644 client/src/features/admin/SessionEnvironmentAdvancedFields.tsx create mode 100644 client/src/features/admin/adminSessions.utils.ts create mode 100644 client/src/features/sessionsV2/SessionView/EnvironmentCard.tsx delete mode 100644 client/src/features/sessionsV2/UpdateSessionLauncherModal.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/AdvancedSettingsFields.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/CustomEnvironmentFields.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/EditLauncherFormContent.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/EnvironmentField.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/EnvironmentKindField.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/GlobalEnvironmentFields.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/LauncherDetailsFields.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/SessionEnvironmentItem.tsx create mode 100644 client/src/features/sessionsV2/components/SessionForm/SessionLauncherBreadcrumbNavbar.tsx create mode 100644 client/src/features/sessionsV2/components/SessionModals/NewSessionLauncherModal.tsx create mode 100644 client/src/features/sessionsV2/components/SessionModals/UpdateSessionLauncherModal.tsx create mode 100644 client/src/features/sessionsV2/session.constants.ts diff --git a/client/.eslintrc.json b/client/.eslintrc.json index 63492359f..afdb6584d 100644 --- a/client/.eslintrc.json +++ b/client/.eslintrc.json @@ -169,6 +169,7 @@ "jupyter", "katex", "kernelspec", + "kubernetes", "Keycloak", "Lausanne", "linkify", diff --git a/client/src/components/MoreInfo.tsx b/client/src/components/MoreInfo.tsx new file mode 100644 index 000000000..ccb0955ad --- /dev/null +++ b/client/src/components/MoreInfo.tsx @@ -0,0 +1,46 @@ +/*! + * 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 { ReactNode, useRef } from "react"; +import { InfoCircleFill } from "react-bootstrap-icons"; +import { PopoverBody, UncontrolledPopover } from "reactstrap"; + +export function MoreInfo({ + trigger = "hover focus", + children, +}: { + trigger?: string; + children?: ReactNode; +}) { + const ref = useRef(null); + + return ( + <> + + + + + {children} + + + ); +} diff --git a/client/src/features/admin/AddSessionEnvironmentButton.tsx b/client/src/features/admin/AddSessionEnvironmentButton.tsx index 0ebc9c11e..3eac6d461 100644 --- a/client/src/features/admin/AddSessionEnvironmentButton.tsx +++ b/client/src/features/admin/AddSessionEnvironmentButton.tsx @@ -35,6 +35,7 @@ import SessionEnvironmentFormContent, { SessionEnvironmentForm, } from "./SessionEnvironmentFormContent"; import { useAddSessionEnvironmentMutation } from "./adminSessions.api"; +import { safeParseJSONStringArray } from "../sessionsV2/session.utils"; export default function AddSessionEnvironmentButton() { const [isOpen, setIsOpen] = useState(false); @@ -79,12 +80,26 @@ function AddSessionEnvironmentModal({ }); const onSubmit = useCallback( (data: SessionEnvironmentForm) => { - addSessionEnvironment({ - container_image: data.container_image, - name: data.name, - default_url: data.default_url.trim() ? data.default_url : undefined, - description: data.description.trim() ? data.description : undefined, - }); + const commandParsed = safeParseJSONStringArray(data.command); + const argsParsed = safeParseJSONStringArray(data.args); + if (commandParsed.parsed && argsParsed.parsed) + addSessionEnvironment({ + container_image: data.container_image, + name: data.name, + default_url: data.default_url.trim() ? data.default_url : undefined, + description: data.description.trim() ? data.description : undefined, + port: data.port ?? undefined, + working_directory: data.working_directory.trim() + ? data.working_directory + : undefined, + mount_directory: data.mount_directory.trim() + ? data.mount_directory + : undefined, + uid: data.uid ?? undefined, + gid: data.gid ?? undefined, + command: commandParsed.data, + args: argsParsed.data, + }); }, [addSessionEnvironment] ); @@ -120,7 +135,6 @@ function AddSessionEnvironmentModal({ Add session environment {result.error && } - diff --git a/client/src/features/admin/SessionEnvironmentAdvancedFields.tsx b/client/src/features/admin/SessionEnvironmentAdvancedFields.tsx new file mode 100644 index 000000000..dff06c60c --- /dev/null +++ b/client/src/features/admin/SessionEnvironmentAdvancedFields.tsx @@ -0,0 +1,73 @@ +/*! + * 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, useState } from "react"; +import { Control, FieldErrors } from "react-hook-form"; +import { Collapse } from "reactstrap"; +import ChevronFlippedIcon from "../../components/icons/ChevronFlippedIcon"; +import { SessionEnvironmentForm } from "./SessionEnvironmentFormContent"; +import { AdvancedSettingsFields } from "../sessionsV2/components/SessionForm/AdvancedSettingsFields"; + +interface SessionEnvironmentAdvancedFieldsProps { + control: Control; + errors: FieldErrors; +} + +export default function SessionEnvironmentAdvancedFields({ + control, + errors, +}: SessionEnvironmentAdvancedFieldsProps) { + const [isAdvancedSettingOpen, setIsAdvancedSettingsOpen] = useState(false); + const toggleIsOpen = useCallback( + () => + setIsAdvancedSettingsOpen( + (isAdvancedSettingOpen) => !isAdvancedSettingOpen + ), + [] + ); + return ( + <> +
+ +
+ +
+ + control={control} + errors={errors} + /> +
+
+ + ); +} diff --git a/client/src/features/admin/SessionEnvironmentFormContent.tsx b/client/src/features/admin/SessionEnvironmentFormContent.tsx index 807cfe985..dcd230f42 100644 --- a/client/src/features/admin/SessionEnvironmentFormContent.tsx +++ b/client/src/features/admin/SessionEnvironmentFormContent.tsx @@ -19,12 +19,20 @@ import cx from "classnames"; import { Control, Controller, FieldErrors } from "react-hook-form"; import { Input, Label } from "reactstrap"; +import SessionEnvironmentAdvancedFields from "./SessionEnvironmentAdvancedFields"; export interface SessionEnvironmentForm { container_image: string; default_url: string; description: string; name: string; + port: number; + working_directory: string; + uid: number; + gid: number; + mount_directory: string; + command: string; + args: string; } interface SessionEnvironmentFormContentProps { @@ -101,25 +109,7 @@ export default function SessionEnvironmentFormContent({ />
Please provide a container image
- -
- - ( - - )} - /> -
+ ); } diff --git a/client/src/features/admin/SessionEnvironmentsSection.tsx b/client/src/features/admin/SessionEnvironmentsSection.tsx index fa3c27648..defe2f0e3 100644 --- a/client/src/features/admin/SessionEnvironmentsSection.tsx +++ b/client/src/features/admin/SessionEnvironmentsSection.tsx @@ -26,11 +26,12 @@ import { Container, Row, } from "reactstrap"; - import { Loader } from "../../components/Loader"; import { TimeCaption } from "../../components/TimeCaption"; import { CommandCopy } from "../../components/commandCopy/CommandCopy"; import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert"; +import { ErrorLabel } from "../../components/formlabels/FormLabels.tsx"; +import { safeStringify } from "../sessionsV2/session.utils"; import type { SessionEnvironment, SessionEnvironmentList, @@ -107,8 +108,20 @@ interface SessionEnvironmentDisplayProps { function SessionEnvironmentDisplay({ environment, }: SessionEnvironmentDisplayProps) { - const { container_image, creation_date, name, default_url, description } = - environment; + const { + container_image, + creation_date, + name, + default_url, + description, + port, + gid, + uid, + working_directory, + mount_directory, + command, + args, + } = environment; return ( @@ -135,6 +148,28 @@ function SessionEnvironmentDisplay({ )} + + Mount directory: {mount_directory} + + + Work directory: {working_directory} + + + Port: {port} + + + GID: {gid} + + + UID: {uid} + + + Command:{" "} + + + + Args: + ); } + +function EnvironmentCode({ value }: { value: string | null }) { + if (value === null) return ; + return {value}; +} diff --git a/client/src/features/admin/UpdateSessionEnvironmentButton.tsx b/client/src/features/admin/UpdateSessionEnvironmentButton.tsx index e06364cd0..e3f63c1dc 100644 --- a/client/src/features/admin/UpdateSessionEnvironmentButton.tsx +++ b/client/src/features/admin/UpdateSessionEnvironmentButton.tsx @@ -28,15 +28,16 @@ import { ModalFooter, ModalHeader, } from "reactstrap"; - import { Loader } from "../../components/Loader"; import ButtonStyles from "../../components/buttons/Buttons.module.scss"; import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert"; +import { safeParseJSONStringArray } from "../sessionsV2/session.utils"; import { SessionEnvironment } from "../sessionsV2/sessionsV2.types"; import SessionEnvironmentFormContent, { SessionEnvironmentForm, } from "./SessionEnvironmentFormContent"; import { useUpdateSessionEnvironmentMutation } from "./adminSessions.api"; +import { getSessionEnvironmentValues } from "./adminSessions.utils"; interface UpdateSessionEnvironmentButtonProps { environment: SessionEnvironment; @@ -93,22 +94,31 @@ function UpdateSessionEnvironmentModal({ handleSubmit, reset, } = useForm({ - defaultValues: { - container_image: environment.container_image, - default_url: environment.default_url, - description: environment.description, - name: environment.name, - }, + defaultValues: getSessionEnvironmentValues(environment), }); const onSubmit = useCallback( (data: SessionEnvironmentForm) => { - updateSessionEnvironment({ - environmentId: environment.id, - container_image: data.container_image, - name: data.name, - default_url: data.default_url.trim() ? data.default_url : "", - description: data.description.trim() ? data.description : "", - }); + const commandParsed = safeParseJSONStringArray(data.command); + const argsParsed = safeParseJSONStringArray(data.args); + if (commandParsed.parsed && argsParsed.parsed) + updateSessionEnvironment({ + environmentId: environment.id, + container_image: data.container_image, + name: data.name, + default_url: data.default_url.trim() ? data.default_url : "", + description: data.description.trim() ? data.description : "", + port: data.port ?? undefined, + working_directory: data.working_directory.trim() + ? data.working_directory + : undefined, + mount_directory: data.mount_directory.trim() + ? data.mount_directory + : undefined, + uid: data.uid ?? undefined, + gid: data.gid ?? undefined, + command: commandParsed.data, + args: argsParsed.data, + }); }, [environment.id, updateSessionEnvironment] ); @@ -127,12 +137,7 @@ function UpdateSessionEnvironmentModal({ }, [isOpen, result]); useEffect(() => { - reset({ - container_image: environment.container_image, - default_url: environment.default_url ?? "", - description: environment.description ?? "", - name: environment.name, - }); + reset(getSessionEnvironmentValues(environment)); }, [environment, reset]); return ( @@ -152,7 +157,6 @@ function UpdateSessionEnvironmentModal({ Update session environment {result.error && } - diff --git a/client/src/features/admin/adminSessions.types.ts b/client/src/features/admin/adminSessions.types.ts index ea6cdb3bd..22c5893f6 100644 --- a/client/src/features/admin/adminSessions.types.ts +++ b/client/src/features/admin/adminSessions.types.ts @@ -21,6 +21,13 @@ export interface AddSessionEnvironmentParams { name: string; default_url?: string; description?: string; + uid?: number; + gid?: number; + working_directory?: string; + mount_directory?: string; + port?: number; + command?: string[] | null; + args?: string[] | null; } export interface UpdateSessionEnvironmentParams { @@ -29,6 +36,13 @@ export interface UpdateSessionEnvironmentParams { default_url?: string; description?: string; name?: string; + uid?: number; + gid?: number; + working_directory?: string; + mount_directory?: string; + port?: number; + command?: string[] | null; + args?: string[] | null; } export interface DeleteSessionEnvironmentParams { diff --git a/client/src/features/admin/adminSessions.utils.ts b/client/src/features/admin/adminSessions.utils.ts new file mode 100644 index 000000000..e0e8a2c5b --- /dev/null +++ b/client/src/features/admin/adminSessions.utils.ts @@ -0,0 +1,40 @@ +/*! + * 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 { getJSONStringArray } from "../sessionsV2/session.utils"; +import { SessionEnvironment } from "../sessionsV2/sessionsV2.types"; + +export function getSessionEnvironmentValues(environment: SessionEnvironment) { + return { + container_image: environment.container_image, + default_url: environment.default_url, + description: environment.description, + name: environment.name, + port: environment.port ?? undefined, + working_directory: environment.working_directory?.trim() + ? environment.working_directory + : undefined, + mount_directory: environment.mount_directory?.trim() + ? environment.mount_directory + : undefined, + uid: environment.uid ?? undefined, + gid: environment.gid ?? undefined, + command: getJSONStringArray(environment.command), + args: getJSONStringArray(environment.args), + }; +} diff --git a/client/src/features/favicon/Favicon.tsx b/client/src/features/favicon/Favicon.tsx index 4ed544910..e5a584a06 100644 --- a/client/src/features/favicon/Favicon.tsx +++ b/client/src/features/favicon/Favicon.tsx @@ -18,7 +18,7 @@ import { useEffect } from "react"; import useAppSelector from "../../utils/customHooks/useAppSelector.hook"; -import { FAVICON_BY_SESSION_STATUS } from "../sessionsV2/session.utils"; +import { FAVICON_BY_SESSION_STATUS } from "../sessionsV2/session.constants"; export function Favicon() { const favicon = useAppSelector(({ display }) => display.favicon); diff --git a/client/src/features/sessionsV2/AddSessionLauncherButton.tsx b/client/src/features/sessionsV2/AddSessionLauncherButton.tsx index f63a5e646..1bdbc23ab 100644 --- a/client/src/features/sessionsV2/AddSessionLauncherButton.tsx +++ b/client/src/features/sessionsV2/AddSessionLauncherButton.tsx @@ -20,8 +20,7 @@ import cx from "classnames"; import { useCallback, useState } from "react"; import { PlusLg } from "react-bootstrap-icons"; import { Button } from "reactstrap"; - -import { Step1AddSessionModal } from "./components/SessionModals/AddSession.tsx"; +import NewSessionLauncherModal from "./components/SessionModals/NewSessionLauncherModal.tsx"; export default function AddSessionLauncherButton({ "data-cy": dataCy, @@ -52,7 +51,7 @@ export default function AddSessionLauncherButton({ )} - + ); } diff --git a/client/src/features/sessionsV2/SessionLauncherFormContent.tsx b/client/src/features/sessionsV2/SessionLauncherFormContent.tsx index 9df5aaed9..e69de29bb 100644 --- a/client/src/features/sessionsV2/SessionLauncherFormContent.tsx +++ b/client/src/features/sessionsV2/SessionLauncherFormContent.tsx @@ -1,621 +0,0 @@ -/*! - * 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 { useEffect, useMemo, useState } from "react"; -import { Globe2 } from "react-bootstrap-icons"; -import { - Control, - Controller, - ControllerRenderProps, - FieldErrors, - FieldNamesMarkedBoolean, - UseFormResetField, - UseFormSetValue, - UseFormWatch, -} from "react-hook-form"; -import { SingleValue } from "react-select"; -import { Input, Label, ListGroup, ListGroupItem } from "reactstrap"; - -import { ErrorAlert, WarnAlert } from "../../components/Alert"; -import { Loader } from "../../components/Loader"; -import { TimeCaption } from "../../components/TimeCaption"; -import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert"; -import { useGetResourcePoolsQuery } from "../dataServices/computeResources.api"; -import { ResourceClass } from "../dataServices/dataServices.types"; -import { SessionClassSelectorV2 } from "../session/components/options/SessionClassOption"; -import { useGetSessionEnvironmentsQuery } from "./sessionsV2.api"; -import { EnvironmentKind, SessionEnvironment } from "./sessionsV2.types"; - -export interface SessionLauncherForm { - name: string; - description: string; - environment_kind: EnvironmentKind; - environment_id: string; - container_image: string; - default_url: string; - resourceClass: ResourceClass; -} - -/* Edit session launcher */ -interface SessionLauncherFormContentProps { - control: Control; - errors: FieldErrors; - watch: UseFormWatch; - touchedFields: Partial< - Readonly> - >; -} -export default function SessionLauncherFormContent({ - control, - errors, - watch, - touchedFields, -}: SessionLauncherFormContentProps) { - const { - data: environments, - error, - isLoading, - } = useGetSessionEnvironmentsQuery(); - const watchEnvironmentKind = watch("environment_kind"); - - return ( - <> -
- - ( - - )} - rules={{ required: true }} - /> -
Please provide a name
-
-
- - ( -