diff --git a/client/package.json b/client/package.json index 37c51912e2..f3336ba48a 100644 --- a/client/package.json +++ b/client/package.json @@ -22,13 +22,14 @@ "storybook-wait-server": "wait-on http://127.0.0.1:6006", "storybook-test": "test-storybook", "storybook-compile-and-test": "concurrently -k -s first -n 'BUILD,TEST' -c 'magenta,blue' 'npm run storybook-build && npm run storybook-start-server' 'npm run storybook-wait-server && npm run storybook-test'", - "generate-api": "npm run generate-api:dataServicesUser && npm run generate-api:namespaceV2 && npm run generate-api:projectV2 && npm run generate-api:platform && npm run generate-api:searchV2 && npm run generate-api:storages", + "generate-api": "npm run generate-api:dataServicesUser && npm run generate-api:namespaceV2 && npm run generate-api:groupsV2 && npm run generate-api:projectsV2 && generate-api:platform && npm run generate-api:searchV2 && npm run generate-api:storagesV2", "generate-api:dataServicesUser": "rtk-query-codegen-openapi src/features/user/dataServicesUser.api/dataServicesUser.api-config.ts", "generate-api:namespaceV2": "rtk-query-codegen-openapi src/features/projectsV2/api/namespace.api-config.ts", - "generate-api:projectV2": "rtk-query-codegen-openapi src/features/projectsV2/api/projectV2.api-config.ts", + "generate-api:groupsV2": "rtk-query-codegen-openapi src/features/groupsV2/api/groupsV2.api-config.ts", + "generate-api:projectsV2": "rtk-query-codegen-openapi src/features/projectsV2/api/projectsV2.api-config.ts", "generate-api:platform": "rtk-query-codegen-openapi src/features/platform/api/platform.api-config.ts", "generate-api:searchV2": "rtk-query-codegen-openapi src/features/searchV2/api/searchV2.api-config.ts", - "generate-api:storages": "rtk-query-codegen-openapi src/features/projectsV2/api/storages.api-config.ts" + "generate-api:storagesV2": "rtk-query-codegen-openapi src/features/storagesV2/api/storagesV2.api-config.ts" }, "type": "module", "dependencies": { diff --git a/client/src/features/ProjectPageV2/ProjectPageContainer/ProjectPageContainer.tsx b/client/src/features/ProjectPageV2/ProjectPageContainer/ProjectPageContainer.tsx index eb9d1f845a..7d9905a8d7 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContainer/ProjectPageContainer.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContainer/ProjectPageContainer.tsx @@ -25,8 +25,8 @@ import { Col, Row } from "reactstrap"; import { Loader } from "../../../components/Loader"; import ContainerWrap from "../../../components/container/ContainerWrap"; -import type { Project } from "../../projectsV2/api/projectV2.api"; -import { useGetProjectsByNamespaceAndSlugQuery } from "../../projectsV2/api/projectV2.api"; +import type { Project } from "../../projectsV2/api/projectsV2.api"; +import { useGetProjectBySlugQuery } from "../../projectsV2/api/projectsV2.api"; import ProjectNotFound from "../../projectsV2/notFound/ProjectNotFound"; import ProjectPageHeader from "../ProjectPageHeader/ProjectPageHeader"; import ProjectPageNav from "../ProjectPageNav/ProjectPageNav"; @@ -37,7 +37,7 @@ export default function ProjectPageContainer() { namespace: string | undefined; slug: string | undefined; }>(); - const { data, isLoading, error } = useGetProjectsByNamespaceAndSlugQuery({ + const { data, isLoading, error } = useGetProjectBySlugQuery({ namespace: namespace ?? "", slug: slug ?? "", }); diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/AddCodeRepositoryModal.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/AddCodeRepositoryModal.tsx index 07f793530d..6e313e8d6d 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/AddCodeRepositoryModal.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/AddCodeRepositoryModal.tsx @@ -37,8 +37,8 @@ import { import { Loader } from "../../../../components/Loader"; import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; import BootstrapGitLabIcon from "../../../../components/icons/BootstrapGitLabIcon"; -import { Project } from "../../../projectsV2/api/projectV2.api"; -import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; +import type { Project } from "../../../projectsV2/api/projectsV2.api"; +import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectsV2.api"; interface AddCodeRepositoryModalProps { project: Project; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx index 1a2581064f..c6c3b6bf51 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay.tsx @@ -48,8 +48,8 @@ import { skipToken } from "@reduxjs/toolkit/query"; import { Loader } from "../../../../components/Loader"; import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; import { safeNewUrl } from "../../../../utils/helpers/safeNewUrl.utils"; -import { Project } from "../../../projectsV2/api/projectV2.api"; -import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; +import type { Project } from "../../../projectsV2/api/projectsV2.api"; +import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectsV2.api"; import { ErrorAlert, RenkuAlert, diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/RepositoriesBox.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/RepositoriesBox.tsx index e36f134e62..e738e2b014 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/RepositoriesBox.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/CodeRepositories/RepositoriesBox.tsx @@ -18,12 +18,6 @@ import cx from "classnames"; import { useCallback, useState } from "react"; import { FileCode, PlusLg } from "react-bootstrap-icons"; - -import { Project } from "../../../projectsV2/api/projectV2.api.ts"; -import { AddCodeRepositoryStep1Modal } from "./AddCodeRepositoryModal.tsx"; -import AccessGuard from "../../utils/AccessGuard.tsx"; -import useProjectAccess from "../../utils/useProjectAccess.hook"; -import { RepositoryItem } from "./CodeRepositoryDisplay.tsx"; import { Badge, Button, @@ -33,6 +27,12 @@ import { ListGroup, } from "reactstrap"; +import { Project } from "../../../projectsV2/api/projectsV2.api"; +import AccessGuard from "../../utils/AccessGuard"; +import useProjectAccess from "../../utils/useProjectAccess.hook"; +import { AddCodeRepositoryStep1Modal } from "./AddCodeRepositoryModal"; +import { RepositoryItem } from "./CodeRepositoryDisplay"; + export function CodeRepositoriesDisplay({ project }: { project: Project }) { const { userRole } = useProjectAccess({ projectId: project.id }); const [isOpen, setIsOpen] = useState(false); diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceCredentialsModal.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceCredentialsModal.tsx index 6ddd5d6444..f202fc42d0 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceCredentialsModal.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceCredentialsModal.tsx @@ -18,20 +18,20 @@ import cx from "classnames"; import { useCallback, useEffect } from "react"; -import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap"; import { XLg } from "react-bootstrap-icons"; +import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap"; import { RtkErrorAlert } from "../../../../components/errors/RtkErrorAlert"; +import CloudStorageSecretsModal from "../../../sessionsV2/CloudStorageSecretsModal"; +import type { SessionStartCloudStorageConfiguration } from "../../../sessionsV2/startSessionOptionsV2.types"; import { useDeleteStoragesV2ByStorageIdSecretsMutation, usePostStoragesV2ByStorageIdSecretsMutation, -} from "../../../projectsV2/api/projectV2.enhanced-api"; -import type { CloudStorageGetRead } from "../../../projectsV2/api/storagesV2.api"; -import type { SessionStartCloudStorageConfiguration } from "../../../sessionsV2/startSessionOptionsV2.types"; -import CloudStorageSecretsModal from "../../../sessionsV2/CloudStorageSecretsModal"; + type CloudStorageGetRead, +} from "../../../storagesV2/api/storagesV2.api"; -import useDataSourceConfiguration from "./useDataSourceConfiguration.hook"; import { Loader } from "../../../../components/Loader"; +import useDataSourceConfiguration from "./useDataSourceConfiguration.hook"; interface DataSourceCredentialsModalProps { isOpen: boolean; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceDisplay.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceDisplay.tsx index 0f7c2d2de2..c82c428629 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceDisplay.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceDisplay.tsx @@ -30,15 +30,15 @@ import { Row, } from "reactstrap"; -import { Loader } from "../../../../components/Loader.tsx"; -import AddCloudStorageModal from "../../../project/components/cloudStorage/CloudStorageModal.tsx"; +import { ButtonWithMenuV2 } from "../../../../components/buttons/Button"; +import { Loader } from "../../../../components/Loader"; +import AddCloudStorageModal from "../../../project/components/cloudStorage/CloudStorageModal"; import { - CloudStorageGetRead, useDeleteStoragesV2ByStorageIdMutation, -} from "../../../projectsV2/api/storagesV2.api"; + type CloudStorageGetRead, +} from "../../../storagesV2/api/storagesV2.api"; import DataSourceCredentialsModal from "./DataSourceCredentialsModal.tsx"; -import { DataSourceView } from "./DataSourceView.tsx"; -import { ButtonWithMenuV2 } from "../../../../components/buttons/Button.tsx"; +import { DataSourceView } from "./DataSourceView"; interface DataSourceDeleteModalProps { storage: CloudStorageGetRead; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceView.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceView.tsx index 124f7a0b3a..7feacaf336 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceView.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourceView.tsx @@ -22,8 +22,8 @@ import { Offcanvas, OffcanvasBody } from "reactstrap"; import { CredentialMoreInfo } from "../../../project/components/cloudStorage/CloudStorageItem"; import { CLOUD_STORAGE_SAVED_SECRET_DISPLAY_VALUE } from "../../../project/components/cloudStorage/projectCloudStorage.constants"; import { getCredentialFieldDefinitions } from "../../../project/utils/projectCloudStorage.utils"; -import type { CloudStorageGetV2Read } from "../../../projectsV2/api/storagesV2.api"; import { storageSecretNameToFieldName } from "../../../secrets/secrets.utils"; +import type { CloudStorageGetV2Read } from "../../../storagesV2/api/storagesV2.api"; import { DataSourceActions } from "./DataSourceDisplay"; interface DataSourceViewProps { diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourcesBox.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourcesBox.tsx index 90425b3313..7cafa72f47 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourcesBox.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/DataSourcesBox.tsx @@ -18,13 +18,6 @@ import cx from "classnames"; import { useCallback, useState } from "react"; import { Database, PlusLg } from "react-bootstrap-icons"; -import { Loader } from "../../../../components/Loader.tsx"; -import AddCloudStorageModal from "../../../project/components/cloudStorage/CloudStorageModal.tsx"; -import { Project } from "../../../projectsV2/api/projectV2.api"; -import { useGetStoragesV2Query } from "../../../projectsV2/api/storagesV2.api"; -import AccessGuard from "../../utils/AccessGuard.tsx"; -import useProjectAccess from "../../utils/useProjectAccess.hook"; -import { DataSourceDisplay } from "./DataSourceDisplay.tsx"; import { Badge, Button, @@ -34,6 +27,14 @@ import { ListGroup, } from "reactstrap"; +import { Loader } from "../../../../components/Loader"; +import AddCloudStorageModal from "../../../project/components/cloudStorage/CloudStorageModal"; +import type { Project } from "../../../projectsV2/api/projectsV2.api"; +import { useGetStoragesV2Query } from "../../../storagesV2/api/storagesV2.api"; +import AccessGuard from "../../utils/AccessGuard"; +import useProjectAccess from "../../utils/useProjectAccess.hook"; +import { DataSourceDisplay } from "./DataSourceDisplay"; + export function DataSourcesDisplay({ project }: { project: Project }) { const [isOpen, setIsOpen] = useState(false); const { userRole } = useProjectAccess({ projectId: project.id }); diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/useDataSourceConfiguration.hook.ts b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/useDataSourceConfiguration.hook.ts index f1fd1c3174..c96ac71c01 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/useDataSourceConfiguration.hook.ts +++ b/client/src/features/ProjectPageV2/ProjectPageContent/DataSources/useDataSourceConfiguration.hook.ts @@ -19,13 +19,12 @@ import { useMemo } from "react"; import { CLOUD_OPTIONS_OVERRIDE } from "../../../project/components/cloudStorage/projectCloudStorage.constants"; -import { RCloneOption } from "../../../projectsV2/api/storagesV2.api"; +import type { SessionStartCloudStorageConfiguration } from "../../../sessionsV2/startSessionOptionsV2.types"; import type { CloudStorageGetV2Read, CloudStorageSecretGet, -} from "../../../projectsV2/api/storagesV2.api"; - -import type { SessionStartCloudStorageConfiguration } from "../../../sessionsV2/startSessionOptionsV2.types"; + RCloneOption, +} from "../../../storagesV2/api/storagesV2.generated-api"; interface UseDataSourceConfigurationArgs { storages: CloudStorageGetV2Read[] | undefined; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/ProjectInformation/ProjectInformation.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/ProjectInformation/ProjectInformation.tsx index f7d8439c80..5bd3fb39be 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/ProjectInformation/ProjectInformation.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/ProjectInformation/ProjectInformation.tsx @@ -34,18 +34,17 @@ import { UnderlineArrowLink, } from "../../../../components/buttons/Button"; import { ABSOLUTE_ROUTES } from "../../../../routing/routes.constants"; -import projectPreviewImg from "../../../../styles/assets/projectImagePreview.svg"; +import { useGetNamespacesByNamespaceSlugQuery } from "../../../groupsV2/api/groupsV2.api"; import type { ProjectMemberListResponse, ProjectMemberResponse, -} from "../../../projectsV2/api/projectV2.api"; -import { - useGetNamespacesByNamespaceSlugQuery, - useGetProjectsByProjectIdMembersQuery, -} from "../../../projectsV2/api/projectV2.enhanced-api"; +} from "../../../projectsV2/api/projectsV2.api"; +import { useGetProjectsByProjectIdMembersQuery } from "../../../projectsV2/api/projectsV2.api"; import { useProject } from "../../ProjectPageContainer/ProjectPageContainer"; import MembershipGuard from "../../utils/MembershipGuard"; import { getMemberNameToDisplay, toSortedMembers } from "../../utils/roleUtils"; + +import projectPreviewImg from "../../../../styles/assets/projectImagePreview.svg"; import styles from "./ProjectInformation.module.scss"; const MAX_MEMBERS_DISPLAYED = 5; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectDelete.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectDelete.tsx index ba6beb0eca..c46f7fefee 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectDelete.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectDelete.tsx @@ -27,8 +27,8 @@ import { NOTIFICATION_TOPICS } from "../../../../notifications/Notifications.con import { NotificationsManager } from "../../../../notifications/notifications.types"; import { ABSOLUTE_ROUTES } from "../../../../routing/routes.constants"; import AppContext from "../../../../utils/context/appContext"; -import { Project } from "../../../projectsV2/api/projectV2.api"; -import { useDeleteProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; +import type { Project } from "../../../projectsV2/api/projectsV2.api"; +import { useDeleteProjectsByProjectIdMutation } from "../../../projectsV2/api/projectsV2.api"; export function notificationProjectDeleted( notifications: NotificationsManager, diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettings.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettings.tsx index 5a77d8509c..956f4e362d 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettings.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettings.tsx @@ -42,8 +42,8 @@ import { NOTIFICATION_TOPICS } from "../../../../notifications/Notifications.con import { NotificationsManager } from "../../../../notifications/notifications.types"; import { ABSOLUTE_ROUTES } from "../../../../routing/routes.constants"; import AppContext from "../../../../utils/context/appContext"; -import type { Project } from "../../../projectsV2/api/projectV2.api"; -import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; +import type { Project } from "../../../projectsV2/api/projectsV2.api"; +import { usePatchProjectsByProjectIdMutation } from "../../../projectsV2/api/projectsV2.api"; import ProjectDescriptionFormField from "../../../projectsV2/fields/ProjectDescriptionFormField"; import ProjectNameFormField from "../../../projectsV2/fields/ProjectNameFormField"; import ProjectNamespaceFormField from "../../../projectsV2/fields/ProjectNamespaceFormField"; diff --git a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettingsMembers.tsx b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettingsMembers.tsx index 99e1a484d4..54e6d17937 100644 --- a/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettingsMembers.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageContent/Settings/ProjectSettingsMembers.tsx @@ -37,21 +37,23 @@ import { Row, UncontrolledTooltip, } from "reactstrap"; -import { ButtonWithMenuV2 } from "../../../../components/buttons/Button.tsx"; + +import { ButtonWithMenuV2 } from "../../../../components/buttons/Button"; import { RtkErrorAlert } from "../../../../components/errors/RtkErrorAlert"; import { Loader } from "../../../../components/Loader"; import type { Project, ProjectMemberResponse, -} from "../../../projectsV2/api/projectV2.api"; -import { useGetProjectsByProjectIdMembersQuery } from "../../../projectsV2/api/projectV2.enhanced-api"; +} from "../../../projectsV2/api/projectsV2.api"; +import { useGetProjectsByProjectIdMembersQuery } from "../../../projectsV2/api/projectsV2.api"; import AddProjectMemberModal from "../../../projectsV2/fields/AddProjectMemberModal"; -import EditProjectMemberModal from "../../../projectsV2/fields/EditProjectMemberModal.tsx"; +import EditProjectMemberModal from "../../../projectsV2/fields/EditProjectMemberModal"; import RemoveProjectMemberModal from "../../../projectsV2/fields/RemoveProjectMemberModal"; import { ProjectMemberDisplay } from "../../../projectsV2/shared/ProjectMemberDisplay"; -import MembershipGuard from "../../utils/MembershipGuard.tsx"; -import { toSortedMembers } from "../../utils/roleUtils.ts"; + +import MembershipGuard from "../../utils/MembershipGuard"; +import { toSortedMembers } from "../../utils/roleUtils"; type MemberActionMenuProps = Omit< ProjectPageSettingsMembersListItemProps, diff --git a/client/src/features/ProjectPageV2/ProjectPageHeader/ProjectPageHeader.tsx b/client/src/features/ProjectPageV2/ProjectPageHeader/ProjectPageHeader.tsx index aec83bee2d..cca069140f 100644 --- a/client/src/features/ProjectPageV2/ProjectPageHeader/ProjectPageHeader.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageHeader/ProjectPageHeader.tsx @@ -21,7 +21,7 @@ import { Col, Row } from "reactstrap"; import { UnderlineArrowLink } from "../../../components/buttons/Button"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import { Project } from "../../projectsV2/api/projectV2.api"; +import { Project } from "../../projectsV2/api/projectsV2.api"; import { ProjectImageView } from "../ProjectPageContent/ProjectInformation/ProjectInformation"; interface ProjectPageHeaderProps { diff --git a/client/src/features/ProjectPageV2/ProjectPageNav/ProjectPageNav.tsx b/client/src/features/ProjectPageV2/ProjectPageNav/ProjectPageNav.tsx index c089a002e7..44e5854daa 100644 --- a/client/src/features/ProjectPageV2/ProjectPageNav/ProjectPageNav.tsx +++ b/client/src/features/ProjectPageV2/ProjectPageNav/ProjectPageNav.tsx @@ -22,7 +22,7 @@ import { Nav, NavItem } from "reactstrap"; import RenkuNavLinkV2 from "../../../components/RenkuNavLinkV2"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import type { Project } from "../../projectsV2/api/projectV2.api"; +import type { Project } from "../../projectsV2/api/projectsV2.api"; export default function ProjectPageNav({ project }: { project: Project }) { const { namespace = "", slug = "" } = project; diff --git a/client/src/features/ProjectPageV2/settings/ProjectDeleteConfirmation.tsx b/client/src/features/ProjectPageV2/settings/ProjectDeleteConfirmation.tsx index 836ab1b5dd..a27c0b93ce 100644 --- a/client/src/features/ProjectPageV2/settings/ProjectDeleteConfirmation.tsx +++ b/client/src/features/ProjectPageV2/settings/ProjectDeleteConfirmation.tsx @@ -34,8 +34,8 @@ import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import AppContext from "../../../utils/context/appContext"; import { notificationProjectDeleted } from "../../ProjectPageV2/ProjectPageContent/Settings/ProjectDelete"; -import type { Project } from "../../projectsV2/api/projectV2.api"; -import { useDeleteProjectsByProjectIdMutation } from "../../projectsV2/api/projectV2.enhanced-api"; +import type { Project } from "../../projectsV2/api/projectsV2.api"; +import { useDeleteProjectsByProjectIdMutation } from "../../projectsV2/api/projectsV2.api"; interface ProjectDeleteConfirmationProps { isOpen: boolean; diff --git a/client/src/features/ProjectPageV2/settings/projectSettings.types.ts b/client/src/features/ProjectPageV2/settings/projectSettings.types.ts index 77677850fd..f7ccc1a71a 100644 --- a/client/src/features/ProjectPageV2/settings/projectSettings.types.ts +++ b/client/src/features/ProjectPageV2/settings/projectSettings.types.ts @@ -16,6 +16,6 @@ * limitations under the License. */ -import type { ProjectPatch } from "../../projectsV2/api/projectV2.api"; +import type { ProjectPatch } from "../../projectsV2/api/projectsV2.api"; export type ProjectV2Metadata = Omit; diff --git a/client/src/features/ProjectPageV2/utils/AccessGuard.tsx b/client/src/features/ProjectPageV2/utils/AccessGuard.tsx index 7a842e5b5f..9fa0d9f1d8 100644 --- a/client/src/features/ProjectPageV2/utils/AccessGuard.tsx +++ b/client/src/features/ProjectPageV2/utils/AccessGuard.tsx @@ -16,7 +16,7 @@ * limitations under the License. */ -import type { Role } from "../../projectsV2/api/projectV2.api.ts"; +import type { Role } from "../../projectsV2/api/projectsV2.api.ts"; import { toNumericRole } from "./roleUtils.ts"; diff --git a/client/src/features/ProjectPageV2/utils/MembershipGuard.tsx b/client/src/features/ProjectPageV2/utils/MembershipGuard.tsx index 0e523ac04f..f5f2b728ec 100644 --- a/client/src/features/ProjectPageV2/utils/MembershipGuard.tsx +++ b/client/src/features/ProjectPageV2/utils/MembershipGuard.tsx @@ -22,7 +22,7 @@ import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook import type { ProjectMemberListResponse, ProjectMemberResponse, -} from "../../projectsV2/api/projectV2.api.ts"; +} from "../../projectsV2/api/projectsV2.api.ts"; import AccessGuard from "./AccessGuard.tsx"; import { toNumericRole } from "./roleUtils.ts"; diff --git a/client/src/features/ProjectPageV2/utils/roleUtils.ts b/client/src/features/ProjectPageV2/utils/roleUtils.ts index 432562753b..32a768e02f 100644 --- a/client/src/features/ProjectPageV2/utils/roleUtils.ts +++ b/client/src/features/ProjectPageV2/utils/roleUtils.ts @@ -20,7 +20,7 @@ import type { ProjectMemberListResponse, ProjectMemberResponse, Role, -} from "../../projectsV2/api/projectV2.api"; +} from "../../projectsV2/api/projectsV2.api"; export type RoleOrNone = Role | "none"; const ROLE_MAP: Record = { diff --git a/client/src/features/ProjectPageV2/utils/useProjectAccess.hook.ts b/client/src/features/ProjectPageV2/utils/useProjectAccess.hook.ts index c378c7a482..a2e051e7a1 100644 --- a/client/src/features/ProjectPageV2/utils/useProjectAccess.hook.ts +++ b/client/src/features/ProjectPageV2/utils/useProjectAccess.hook.ts @@ -17,10 +17,13 @@ */ import { skipToken } from "@reduxjs/toolkit/query"; -import { useGetUserQuery } from "../../user/dataServicesUser.api/index.ts"; -import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook.ts"; -import type { Role } from "../../projectsV2/api/projectV2.api.ts"; -import { useGetProjectsByProjectIdMembersQuery } from "../../projectsV2/api/projectV2.enhanced-api.ts"; + +import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; +import { + useGetProjectsByProjectIdMembersQuery, + type Role, +} from "../../projectsV2/api/projectsV2.api"; +import { useGetUserQuery } from "../../user/dataServicesUser.api/index"; interface UseProjectAccessArgs { projectId: string; diff --git a/client/src/features/dashboardV2/DashboardV2.tsx b/client/src/features/dashboardV2/DashboardV2.tsx index e54e550a64..78a02106c2 100644 --- a/client/src/features/dashboardV2/DashboardV2.tsx +++ b/client/src/features/dashboardV2/DashboardV2.tsx @@ -29,17 +29,15 @@ import { } from "reactstrap"; import { WarnAlert } from "../../components/Alert"; +import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert"; import { ExternalLink } from "../../components/ExternalLinks"; import { Loader } from "../../components/Loader"; -import { - useGetGroupsQuery, - useGetProjectsQuery, -} from "../projectsV2/api/projectV2.enhanced-api"; +import { useGetGroupsQuery } from "../groupsV2/api/groupsV2.api"; +import { useGetProjectsQuery } from "../projectsV2/api/projectsV2.api"; import BackToV1Button from "../projectsV2/shared/BackToV1Button"; -import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert"; -import DashboardV2Sessions from "./DashboardV2Sessions"; import GroupShortHandDisplay from "../projectsV2/show/GroupShortHandDisplay"; import ProjectShortHandDisplay from "../projectsV2/show/ProjectShortHandDisplay"; +import DashboardV2Sessions from "./DashboardV2Sessions"; export default function DashboardV2() { return ( @@ -137,8 +135,10 @@ function ProjectsDashboard() { function ProjectList() { const { data, error, isLoading } = useGetProjectsQuery({ - page: 1, - perPage: 5, + params: { + page: 1, + per_page: 5, + }, }); const noProjects = isLoading ? ( @@ -207,8 +207,10 @@ function GroupsDashboard() { function GroupsList() { const { data, error, isLoading } = useGetGroupsQuery({ - page: 1, - perPage: 5, + params: { + page: 1, + per_page: 5, + }, }); const noGroups = isLoading ? ( diff --git a/client/src/features/dashboardV2/DashboardV2Sessions.tsx b/client/src/features/dashboardV2/DashboardV2Sessions.tsx index ec2da1f281..07f64fafdb 100644 --- a/client/src/features/dashboardV2/DashboardV2Sessions.tsx +++ b/client/src/features/dashboardV2/DashboardV2Sessions.tsx @@ -11,7 +11,7 @@ import { NotebooksHelper } from "../../notebooks"; import { NotebookAnnotations } from "../../notebooks/components/session.types"; import { ABSOLUTE_ROUTES } from "../../routing/routes.constants"; import useAppSelector from "../../utils/customHooks/useAppSelector.hook"; -import { useGetProjectsByProjectIdQuery } from "../projectsV2/api/projectV2.enhanced-api"; +import { useGetProjectsByProjectIdQuery } from "../projectsV2/api/projectsV2.api"; import { useGetSessionsQuery } from "../session/sessions.api"; import { Session } from "../session/sessions.types"; import { filterSessionsWithCleanedAnnotations } from "../session/sessions.utils"; diff --git a/client/src/features/projectsV2/api/namespace.api-config.ts b/client/src/features/groupsV2/api/groupsV2.api-config.ts similarity index 74% rename from client/src/features/projectsV2/api/namespace.api-config.ts rename to client/src/features/groupsV2/api/groupsV2.api-config.ts index 0e10e417e8..0543234907 100644 --- a/client/src/features/projectsV2/api/namespace.api-config.ts +++ b/client/src/features/groupsV2/api/groupsV2.api-config.ts @@ -16,18 +16,17 @@ * limitations under the License. */ -// Run `npm run generate-api:namespaceV2` to generate the API +// Run `npm run generate-api:groupsV2` to generate the API import type { ConfigFile } from "@rtk-query/codegen-openapi"; import path from "path"; const config: ConfigFile = { - // Configure to inject endpoints into the projectV2Api - apiFile: "./projectV2.api.ts", - apiImport: "projectV2Api", - outputFile: "./namespace.api.ts", - exportName: "projectAndNamespaceApi", + apiFile: "./groupsV2.empty-api.ts", + apiImport: "groupsV2EmptyApi", + outputFile: "./groupsV2.generated-api.ts", + exportName: "groupsV2GeneratedApi", hooks: true, - schemaFile: path.join(__dirname, "namespace.openapi.json"), + schemaFile: path.join(__dirname, "groupsV2.openapi.json"), }; export default config; diff --git a/client/src/features/groupsV2/api/groupsV2.api.ts b/client/src/features/groupsV2/api/groupsV2.api.ts new file mode 100644 index 0000000000..c86901962a --- /dev/null +++ b/client/src/features/groupsV2/api/groupsV2.api.ts @@ -0,0 +1,148 @@ +/*! + * 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 { groupsV2GeneratedApi } from "./groupsV2.generated-api"; +import { AbstractKgPaginatedResponse } from "../../../utils/types/pagination.types"; +import { processPaginationHeaders } from "../../../utils/helpers/kgPagination.utils"; +import type { + GetGroupsApiArg, + GetGroupsApiResponse as GetGroupsApiResponseOrig, + GroupResponseList, + GetNamespacesApiArg, + GetNamespacesApiResponse as GetNamespacesApiResponseOrig, + NamespaceResponseList, +} from "./groupsV2.generated-api"; + +interface GetGroupsApiResponse extends AbstractKgPaginatedResponse { + groups: GetGroupsApiResponseOrig; +} + +export interface GetNamespacesApiResponse extends AbstractKgPaginatedResponse { + namespaces: GetNamespacesApiResponseOrig; +} + +const withPaged = groupsV2GeneratedApi.injectEndpoints({ + endpoints: (builder) => ({ + getGroupsPaged: builder.query({ + query: ({ params }) => ({ + url: "/groups", + params, + }), + transformResponse: (response, meta, queryArg) => { + const groups = response as GroupResponseList; + const headers = meta?.response?.headers; + const headerResponse = processPaginationHeaders( + headers, + queryArg.params ?? {}, + groups + ); + + return { + groups, + page: headerResponse.page, + perPage: headerResponse.perPage, + total: headerResponse.total, + totalPages: headerResponse.totalPages, + }; + }, + }), + getNamespacesPaged: builder.query< + GetNamespacesApiResponse, + GetNamespacesApiArg + >({ + query: ({ params }) => ({ + url: "/namespaces", + params, + }), + transformResponse: (response, meta, queryArg) => { + const namespaces = response as NamespaceResponseList; + const headers = meta?.response?.headers; + const headerResponse = processPaginationHeaders( + headers, + queryArg.params ?? {}, + namespaces + ); + + return { + namespaces, + page: headerResponse.page, + perPage: headerResponse.perPage, + total: headerResponse.total, + totalPages: headerResponse.totalPages, + }; + }, + }), + }), +}); + +export const groupsV2Api = withPaged.enhanceEndpoints({ + addTagTypes: ["Group", "GroupMembers", "Namespace"], + endpoints: { + deleteGroupsByGroupSlug: { + invalidatesTags: ["Group", "Namespace"], + }, + deleteGroupsByGroupSlugMembersAndUserId: { + invalidatesTags: ["GroupMembers"], + }, + getGroups: { + providesTags: ["Group"], + }, + getGroupsByGroupSlug: { + providesTags: ["Group"], + }, + getGroupsPaged: { + providesTags: ["Group"], + }, + getGroupsByGroupSlugMembers: { + providesTags: ["GroupMembers"], + }, + getNamespaces: { + providesTags: ["Namespace"], + }, + getNamespacesPaged: { + providesTags: ["Namespace"], + }, + patchGroupsByGroupSlug: { + invalidatesTags: ["Group", "Namespace"], + }, + patchGroupsByGroupSlugMembers: { + invalidatesTags: ["GroupMembers"], + }, + postGroups: { + invalidatesTags: ["Group", "Namespace"], + }, + }, +}); + +export const { + // group hooks + useGetGroupsPagedQuery: useGetGroupsQuery, + usePostGroupsMutation, + useGetGroupsByGroupSlugQuery, + usePatchGroupsByGroupSlugMutation, + useDeleteGroupsByGroupSlugMutation, + useGetGroupsByGroupSlugMembersQuery, + usePatchGroupsByGroupSlugMembersMutation, + useDeleteGroupsByGroupSlugMembersAndUserIdMutation, + + //namespace hooks + useGetNamespacesPagedQuery: useGetNamespacesQuery, + useLazyGetNamespacesPagedQuery: useLazyGetNamespacesQuery, + useGetNamespacesByNamespaceSlugQuery, +} = groupsV2Api; +export type * from "./groupsV2.generated-api"; diff --git a/client/src/features/projectsV2/api/projectV2-empty.api.ts b/client/src/features/groupsV2/api/groupsV2.empty-api.ts similarity index 80% rename from client/src/features/projectsV2/api/projectV2-empty.api.ts rename to client/src/features/groupsV2/api/groupsV2.empty-api.ts index 6b8693da4e..b8ebd894a1 100644 --- a/client/src/features/projectsV2/api/projectV2-empty.api.ts +++ b/client/src/features/groupsV2/api/groupsV2.empty-api.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2023 - Swiss Data Science Center (SDSC) + * 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). * @@ -16,11 +16,11 @@ * limitations under the License. */ -import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/dist/query/react"; +import { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; // initialize an empty api service that we'll inject endpoints into later as needed -export const projectV2EmptyApi = createApi({ +export const groupsV2EmptyApi = createApi({ baseQuery: fetchBaseQuery({ baseUrl: "/ui-server/api/data" }), endpoints: () => ({}), - reducerPath: "projectV2Api", + reducerPath: "groupsV2Api", }); diff --git a/client/src/features/projectsV2/api/namespace.api.ts b/client/src/features/groupsV2/api/groupsV2.generated-api.ts similarity index 92% rename from client/src/features/projectsV2/api/namespace.api.ts rename to client/src/features/groupsV2/api/groupsV2.generated-api.ts index bf7fc4e360..a472fb0c42 100644 --- a/client/src/features/projectsV2/api/namespace.api.ts +++ b/client/src/features/groupsV2/api/groupsV2.generated-api.ts @@ -1,10 +1,10 @@ -import { projectV2Api as api } from "./projectV2.api"; +import { groupsV2EmptyApi as api } from "./groupsV2.empty-api"; const injectedRtkApi = api.injectEndpoints({ endpoints: (build) => ({ getGroups: build.query({ query: (queryArg) => ({ url: `/groups`, - params: { page: queryArg.page, per_page: queryArg.perPage }, + params: { params: queryArg.params }, }), }), postGroups: build.mutation({ @@ -67,11 +67,7 @@ const injectedRtkApi = api.injectEndpoints({ getNamespaces: build.query({ query: (queryArg) => ({ url: `/namespaces`, - params: { - page: queryArg.page, - per_page: queryArg.perPage, - minimum_role: queryArg.minimumRole, - }, + params: { params: queryArg.params }, }), }), getNamespacesByNamespaceSlug: build.query< @@ -83,14 +79,12 @@ const injectedRtkApi = api.injectEndpoints({ }), overrideExisting: false, }); -export { injectedRtkApi as projectAndNamespaceApi }; +export { injectedRtkApi as groupsV2GeneratedApi }; export type GetGroupsApiResponse = /** status 200 List of groups */ GroupResponseList; export type GetGroupsApiArg = { - /** Result's page number starting from 1 */ - page?: number; - /** The number of results per page */ - perPage?: number; + /** query parameters */ + params?: PaginationRequest; }; export type PostGroupsApiResponse = /** status 201 The group was created */ GroupResponse; @@ -134,12 +128,8 @@ export type DeleteGroupsByGroupSlugMembersAndUserIdApiArg = { export type GetNamespacesApiResponse = /** status 200 List of namespaces */ NamespaceResponseList; export type GetNamespacesApiArg = { - /** Result's page number starting from 1 */ - page?: number; - /** The number of results per page */ - perPage?: number; - /** A minimum role to filter results by. */ - minimumRole?: GroupRole; + /** query parameters */ + params?: NamespaceGetQuery; }; export type GetNamespacesByNamespaceSlugApiResponse = /** status 200 The namespace */ NamespaceResponse; @@ -168,6 +158,12 @@ export type ErrorResponse = { message: string; }; }; +export type PaginationRequest = { + /** Result's page number starting from 1 */ + page?: number; + /** The number of results per page */ + per_page?: number; +}; export type GroupPostRequest = { name: NamespaceName; slug: Slug; @@ -205,6 +201,10 @@ export type NamespaceResponse = { namespace_kind: NamespaceKind; }; export type NamespaceResponseList = NamespaceResponse[]; +export type NamespaceGetQuery = PaginationRequest & { + /** A minimum role to filter results by. */ + minimum_role?: GroupRole; +}; export const { useGetGroupsQuery, usePostGroupsMutation, diff --git a/client/src/features/projectsV2/api/namespace.openapi.json b/client/src/features/groupsV2/api/groupsV2.openapi.json similarity index 94% rename from client/src/features/projectsV2/api/namespace.openapi.json rename to client/src/features/groupsV2/api/groupsV2.openapi.json index 6ebb9dd37a..4f6b0c6495 100644 --- a/client/src/features/projectsV2/api/namespace.openapi.json +++ b/client/src/features/groupsV2/api/groupsV2.openapi.json @@ -20,25 +20,12 @@ "parameters": [ { "in": "query", - "description": "Result's page number starting from 1", - "name": "page", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "default": 1 - } - }, - { - "in": "query", - "description": "The number of results per page", - "name": "per_page", - "required": false, + "description": "query parameters", + "name": "params", + "style": "form", + "explode": true, "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 + "$ref": "#/components/schemas/PaginationRequest" } } ], @@ -370,34 +357,12 @@ "parameters": [ { "in": "query", - "description": "Result's page number starting from 1", - "name": "page", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "default": 1 - } - }, - { - "in": "query", - "description": "The number of results per page", - "name": "per_page", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 - } - }, - { - "in": "query", - "name": "minimum_role", - "description": "A minimum role to filter results by.", - "required": false, + "description": "query parameters", + "name": "params", + "style": "form", + "explode": true, "schema": { - "$ref": "#/components/schemas/GroupRole" + "$ref": "#/components/schemas/NamespaceGetQuery" } } ], @@ -570,7 +535,7 @@ "type": "string", "minLength": 26, "maxLength": 26, - "pattern": "^[A-Z0-9]{26}$" + "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" }, "NamespaceName": { "description": "Renku group or namespace name", @@ -754,10 +719,44 @@ }, "UserEmail": { "type": "string", - "format": "email", "description": "User email", "example": "some-user@gmail.com" }, + "NamespaceGetQuery": { + "description": "Query params for namespace get request", + "allOf": [ + { + "$ref": "#/components/schemas/PaginationRequest" + }, + { + "properties": { + "minimum_role": { + "description": "A minimum role to filter results by.", + "$ref": "#/components/schemas/GroupRole" + } + } + } + ] + }, + "PaginationRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "page": { + "description": "Result's page number starting from 1", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "per_page": { + "description": "The number of results per page", + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + } + }, "ErrorResponse": { "type": "object", "properties": { diff --git a/client/src/features/groupsV2/members/GroupV2MemberListDisplay.tsx b/client/src/features/groupsV2/members/GroupV2MemberListDisplay.tsx index c0a5e73725..66e7e0cf29 100644 --- a/client/src/features/groupsV2/members/GroupV2MemberListDisplay.tsx +++ b/client/src/features/groupsV2/members/GroupV2MemberListDisplay.tsx @@ -26,9 +26,9 @@ import { Loader } from "../../../components/Loader"; import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import { toSortedMembers } from "../../ProjectPageV2/utils/roleUtils"; -import type { ProjectMemberResponse } from "../../projectsV2/api/projectV2.api"; -import { useGetGroupsByGroupSlugMembersQuery } from "../../projectsV2/api/projectV2.enhanced-api"; +import type { ProjectMemberResponse } from "../../projectsV2/api/projectsV2.api"; import UserAvatar from "../../usersV2/show/UserAvatar"; +import { useGetGroupsByGroupSlugMembersQuery } from "../api/groupsV2.api"; interface GroupV2MemberListDisplayProps { group: string; diff --git a/client/src/features/groupsV2/settings/GroupV2Settings.tsx b/client/src/features/groupsV2/settings/GroupV2Settings.tsx index 87bdadb853..64663b7b0e 100644 --- a/client/src/features/groupsV2/settings/GroupV2Settings.tsx +++ b/client/src/features/groupsV2/settings/GroupV2Settings.tsx @@ -25,12 +25,12 @@ import { Loader } from "../../../components/Loader"; import ContainerWrap from "../../../components/container/ContainerWrap"; import LazyNotFound from "../../../not-found/LazyNotFound"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import { useGetGroupsByGroupSlugQuery } from "../../projectsV2/api/projectV2.enhanced-api"; import GroupNotFound from "../../projectsV2/notFound/GroupNotFound"; import { GroupMembersForm, GroupMetadataForm, } from "../../projectsV2/show/groupEditForms"; +import { useGetGroupsByGroupSlugQuery } from "../api/groupsV2.api"; export default function GroupV2Settings() { const { slug } = useParams<{ slug: string }>(); diff --git a/client/src/features/groupsV2/show/GroupV2Show.tsx b/client/src/features/groupsV2/show/GroupV2Show.tsx index 18903188bf..9304a6eb4e 100644 --- a/client/src/features/groupsV2/show/GroupV2Show.tsx +++ b/client/src/features/groupsV2/show/GroupV2Show.tsx @@ -32,15 +32,15 @@ import ContainerWrap from "../../../components/container/ContainerWrap"; import LazyNotFound from "../../../not-found/LazyNotFound"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import MembershipGuard from "../../ProjectPageV2/utils/MembershipGuard"; -import type { GroupResponse } from "../../projectsV2/api/namespace.api"; +import ProjectV2ListDisplay from "../../projectsV2/list/ProjectV2ListDisplay"; +import GroupNotFound from "../../projectsV2/notFound/GroupNotFound"; +import UserAvatar from "../../usersV2/show/UserAvatar"; import { + type GroupResponse, useGetGroupsByGroupSlugMembersQuery, useGetGroupsByGroupSlugQuery, useGetNamespacesByNamespaceSlugQuery, -} from "../../projectsV2/api/projectV2.enhanced-api"; -import ProjectV2ListDisplay from "../../projectsV2/list/ProjectV2ListDisplay"; -import GroupNotFound from "../../projectsV2/notFound/GroupNotFound"; -import UserAvatar from "../../usersV2/show/UserAvatar"; +} from "../api/groupsV2.api"; import GroupV2MemberListDisplay from "../members/GroupV2MemberListDisplay"; import { EntityPill } from "../../searchV2/components/SearchV2Results"; diff --git a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx index 578c032664..a4966cda3d 100644 --- a/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx +++ b/client/src/features/project/components/cloudStorage/AddOrEditCloudStorage.tsx @@ -68,7 +68,7 @@ import { } from "../../utils/projectCloudStorage.utils"; import { ExternalLink } from "../../../../components/ExternalLinks"; import { WarnAlert } from "../../../../components/Alert"; -import type { CloudStorageSecretGet } from "../../../../features/projectsV2/api/storagesV2.api"; +import type { CloudStorageSecretGet } from "../../../../features/storagesV2/api/storagesV2.api"; import AddStorageMountSaveCredentialsInfo from "./AddStorageMountSaveCredentialsInfo"; diff --git a/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx b/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx index 34d34bd99c..ed157174b7 100644 --- a/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx +++ b/client/src/features/project/components/cloudStorage/CloudStorageModal.tsx @@ -24,17 +24,18 @@ import { ArrowCounterclockwise } from "react-bootstrap-icons"; import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap"; import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; -import { usePostStoragesV2ByStorageIdSecretsMutation } from "../../../projectsV2/api/projectV2.enhanced-api"; -import { +import type { CloudStorageGetRead, CloudStorageGetV2Read, CloudStoragePatch, PostStoragesV2ApiArg, RCloneConfig, +} from "../../../storagesV2/api/storagesV2.api"; +import { usePatchStoragesV2ByStorageIdMutation, + usePostStoragesV2ByStorageIdSecretsMutation, usePostStoragesV2Mutation, -} from "../../../projectsV2/api/storagesV2.api"; - +} from "../../../storagesV2/api/storagesV2.api"; import { findSensitive, getCurrentStorageDetails, @@ -66,12 +67,12 @@ import { } from "./projectCloudStorage.types"; import { - AddCloudStorageContinueButton, AddCloudStorageBackButton, AddCloudStorageBodyContent, AddCloudStorageConnectionTestResult, + AddCloudStorageContinueButton, AddCloudStorageHeaderContent, -} from "./cloudStorageModalComponents.tsx"; +} from "./cloudStorageModalComponents"; import styles from "./CloudStorage.module.scss"; diff --git a/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx b/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx index 6e76b1fe32..52ab64f4f1 100644 --- a/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx +++ b/client/src/features/project/components/cloudStorage/cloudStorageModalComponents.tsx @@ -33,6 +33,7 @@ import { Button, UncontrolledTooltip } from "reactstrap"; import { SuccessAlert } from "../../../../components/Alert"; import { Loader } from "../../../../components/Loader"; import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; +import type { CloudStorageSecretGet } from "../../../../features/storagesV2/api/storagesV2.api"; import AddOrEditCloudStorage, { AddOrEditCloudStorageV2, } from "./AddOrEditCloudStorage"; @@ -44,7 +45,6 @@ import { CloudStorageSchema, CredentialSaveStatus, } from "./projectCloudStorage.types"; -import type { CloudStorageSecretGet } from "../../../../features/projectsV2/api/storagesV2.api"; import { SerializedError } from "@reduxjs/toolkit"; diff --git a/client/src/features/project/utils/projectCloudStorage.utils.ts b/client/src/features/project/utils/projectCloudStorage.utils.ts index d9fd91de2b..0b49e0c4bc 100644 --- a/client/src/features/project/utils/projectCloudStorage.utils.ts +++ b/client/src/features/project/utils/projectCloudStorage.utils.ts @@ -16,12 +16,13 @@ * limitations under the License. */ -import { +import { SessionStartCloudStorageConfiguration } from "../../sessionsV2/startSessionOptionsV2.types"; +import type { CloudStorageGetRead, CloudStorageWithIdRead, RCloneConfig, RCloneOption, -} from "../../projectsV2/api/storagesV2.api"; +} from "../../storagesV2/api/storagesV2.generated-api"; import { CLOUD_OPTIONS_OVERRIDE, CLOUD_STORAGE_MOUNT_PATH_HELP, @@ -41,8 +42,6 @@ import { CloudStorageSchemaOptions, } from "../components/cloudStorage/projectCloudStorage.types"; -import { SessionStartCloudStorageConfiguration } from "../../sessionsV2/startSessionOptionsV2.types"; - const LAST_POSITION = 1000; export interface CloudStorageOptions extends RCloneOption { diff --git a/client/src/features/projectsV2/api/projectV2.enhanced-api.ts b/client/src/features/projectsV2/api/projectV2.enhanced-api.ts deleted file mode 100644 index b47bdc8b3a..0000000000 --- a/client/src/features/projectsV2/api/projectV2.enhanced-api.ts +++ /dev/null @@ -1,320 +0,0 @@ -import { AbstractKgPaginatedResponse } from "../../../utils/types/pagination.types"; -import { processPaginationHeaders } from "../../../utils/helpers/kgPagination.utils"; - -import { projectStoragesApi as api } from "./storagesV2.api"; -import type { - GetProjectsApiArg, - GetProjectsApiResponse as GetProjectsApiResponseOrig, - ProjectsList, -} from "./projectV2.api"; - -import type { - GetGroupsApiArg, - GetGroupsApiResponse as GetGroupsApiResponseOrig, - GroupResponseList, - GetNamespacesApiArg, - GetNamespacesApiResponse as GetNamespacesApiResponseOrig, - NamespaceResponseList, -} from "./namespace.api"; -import { - GetStoragesV2ApiArg, - GetStoragesV2ApiResponse as GetStoragesV2ApiResponseOrig, - GetStoragesV2ByStorageIdSecretsApiArg, - GetStoragesV2ByStorageIdSecretsApiResponse, -} from "./storagesV2.api"; -import type { - CloudStorageSecretGetList, - PostStoragesV2ByStorageIdSecretsApiArg, - PostStoragesV2ByStorageIdSecretsApiResponse, -} from "./storagesV2.api"; - -interface GetGroupsApiResponse extends AbstractKgPaginatedResponse { - groups: GetGroupsApiResponseOrig; -} - -export interface GetNamespacesApiResponse extends AbstractKgPaginatedResponse { - namespaces: GetNamespacesApiResponseOrig; -} - -interface GetProjectsApiResponse extends AbstractKgPaginatedResponse { - projects: GetProjectsApiResponseOrig; -} - -interface GetStoragesV2ApiResponse extends AbstractKgPaginatedResponse { - storages: GetStoragesV2ApiResponseOrig; -} - -type GetStoragesV2StorageIdSecretsApiResponse = Record< - string, - GetStoragesV2ByStorageIdSecretsApiResponse ->; - -interface GetStoragesV2StorageIdSecretsApiArg { - storageIds: GetStoragesV2ByStorageIdSecretsApiArg["storageId"][]; -} - -const injectedApi = api.injectEndpoints({ - endpoints: (builder) => ({ - getGroupsPaged: builder.query({ - query: (queryArg) => ({ - url: "/groups", - params: { page: queryArg.page, per_page: queryArg.perPage }, - }), - transformResponse: (response, meta, queryArg) => { - const groups = response as GroupResponseList; - const headers = meta?.response?.headers; - const headerResponse = processPaginationHeaders( - headers, - queryArg, - groups - ); - - return { - groups, - page: headerResponse.page, - perPage: headerResponse.perPage, - total: headerResponse.total, - totalPages: headerResponse.totalPages, - }; - }, - }), - getNamespacesPaged: builder.query< - GetNamespacesApiResponse, - GetNamespacesApiArg - >({ - query: (queryArg) => ({ - url: "/namespaces", - params: { - page: queryArg.page, - per_page: queryArg.perPage, - minimum_role: queryArg.minimumRole, - }, - }), - transformResponse: (response, meta, queryArg) => { - const namespaces = response as NamespaceResponseList; - const headers = meta?.response?.headers; - const headerResponse = processPaginationHeaders( - headers, - queryArg, - namespaces - ); - - return { - namespaces, - page: headerResponse.page, - perPage: headerResponse.perPage, - total: headerResponse.total, - totalPages: headerResponse.totalPages, - }; - }, - }), - getProjectsPaged: builder.query({ - query: (queryArg) => ({ - url: "/projects", - params: { - namespace: queryArg["namespace"], - page: queryArg.page, - per_page: queryArg.perPage, - }, - }), - transformResponse: (response, meta, queryArg) => { - const projects = response as ProjectsList; - const headers = meta?.response?.headers; - const headerResponse = processPaginationHeaders( - headers, - queryArg, - projects - ); - - return { - projects, - page: headerResponse.page, - perPage: headerResponse.perPage, - total: headerResponse.total, - totalPages: headerResponse.totalPages, - }; - }, - }), - getStoragesPaged: builder.query< - GetStoragesV2ApiResponse, - GetStoragesV2ApiArg - >({ - query: (queryArg) => ({ - url: "/storages", - params: queryArg, - }), - }), - getStorageSecretsByV2StorageId: builder.query< - GetStoragesV2StorageIdSecretsApiResponse, - GetStoragesV2StorageIdSecretsApiArg - >({ - async queryFn(queryArg, _api, _options, fetchWithBQ) { - const { storageIds } = queryArg; - const result: GetStoragesV2StorageIdSecretsApiResponse = {}; - for (const storageId of storageIds) { - const response = await fetchWithBQ( - `/storages_v2/${storageId}/secrets` - ); - if (response.error) { - return response; - } - result[storageId] = response.data as CloudStorageSecretGetList; - } - return { data: result }; - }, - }), - postStoragesV2SecretsForSessionLaunch: builder.mutation< - PostStoragesV2ByStorageIdSecretsApiResponse, - PostStoragesV2ByStorageIdSecretsApiArg - >({ - query: (queryArg) => ({ - url: `/storages_v2/${queryArg.storageId}/secrets`, - method: "POST", - body: queryArg.cloudStorageSecretPostList, - }), - }), - }), -}); - -const enhancedApi = injectedApi.enhanceEndpoints({ - addTagTypes: [ - "Group", - "GroupMembers", - "Namespace", - "Project", - "ProjectMembers", - "Storages", - "StorageSecrets", - ], - endpoints: { - deleteGroupsByGroupSlug: { - invalidatesTags: ["Group", "Namespace"], - }, - deleteGroupsByGroupSlugMembersAndUserId: { - invalidatesTags: ["GroupMembers"], - }, - deleteProjectsByProjectId: { - invalidatesTags: ["Project"], - }, - deleteProjectsByProjectIdMembersAndMemberId: { - invalidatesTags: ["ProjectMembers"], - }, - deleteStoragesV2ByStorageId: { - invalidatesTags: ["Storages"], - }, - deleteStoragesV2ByStorageIdSecrets: { - invalidatesTags: ["Storages", "StorageSecrets"], - }, - getGroups: { - providesTags: ["Group"], - }, - getGroupsByGroupSlug: { - providesTags: ["Group"], - }, - getGroupsPaged: { - providesTags: ["Group"], - }, - getGroupsByGroupSlugMembers: { - providesTags: ["GroupMembers"], - }, - getNamespaces: { - providesTags: ["Namespace"], - }, - getNamespacesPaged: { - providesTags: ["Namespace"], - }, - // alternatively, define a function which is called with the endpoint definition as an argument - getProjects: { - providesTags: ["Project"], - }, - getProjectsPaged: { - providesTags: ["Project"], - }, - getProjectsByNamespaceAndSlug: { - providesTags: ["Project"], - }, - getProjectsByProjectId: { - providesTags: ["Project"], - }, - getProjectsByProjectIdMembers: { - providesTags: ["ProjectMembers"], - }, - getStorageSecretsByV2StorageId: { - providesTags: ["StorageSecrets"], - }, - getStoragesV2: { - providesTags: ["Storages"], - }, - getStoragesV2ByStorageIdSecrets: { - providesTags: ["StorageSecrets"], - }, - patchGroupsByGroupSlug: { - invalidatesTags: ["Group", "Namespace"], - }, - patchGroupsByGroupSlugMembers: { - invalidatesTags: ["GroupMembers"], - }, - patchProjectsByProjectId: { - invalidatesTags: ["Project"], - }, - patchProjectsByProjectIdMembers: { - invalidatesTags: ["ProjectMembers"], - }, - patchStoragesV2ByStorageId: { - invalidatesTags: ["Storages"], - }, - postGroups: { - invalidatesTags: ["Group", "Namespace"], - }, - postProjects: { - invalidatesTags: ["Project"], - }, - postStoragesV2: { - invalidatesTags: ["Storages"], - }, - postStoragesV2ByStorageIdSecrets: { - invalidatesTags: ["Storages", "StorageSecrets"], - }, - postStoragesV2SecretsForSessionLaunch: { - invalidatesTags: ["StorageSecrets"], - }, - }, -}); - -export { enhancedApi as projectV2Api }; -export const { - // project hooks - useGetProjectsPagedQuery: useGetProjectsQuery, - usePostProjectsMutation, - useGetProjectsByNamespaceAndSlugQuery, - useGetProjectsByProjectIdQuery, - usePatchProjectsByProjectIdMutation, - useDeleteProjectsByProjectIdMutation, - useGetProjectsByProjectIdMembersQuery, - usePatchProjectsByProjectIdMembersMutation, - useDeleteProjectsByProjectIdMembersAndMemberIdMutation, - - // group hooks - useGetGroupsPagedQuery: useGetGroupsQuery, - usePostGroupsMutation, - useGetGroupsByGroupSlugQuery, - usePatchGroupsByGroupSlugMutation, - useDeleteGroupsByGroupSlugMutation, - useGetGroupsByGroupSlugMembersQuery, - usePatchGroupsByGroupSlugMembersMutation, - useDeleteGroupsByGroupSlugMembersAndUserIdMutation, - - //namespace hooks - useGetNamespacesPagedQuery: useGetNamespacesQuery, - useLazyGetNamespacesPagedQuery: useLazyGetNamespacesQuery, - useGetNamespacesByNamespaceSlugQuery, - - // storages hooks - useDeleteStoragesV2ByStorageIdSecretsMutation, - useGetStoragesV2Query, - useGetStorageSecretsByV2StorageIdQuery, - useGetStoragesV2ByStorageIdSecretsQuery, - usePostStoragesV2Mutation, - usePostStoragesV2ByStorageIdSecretsMutation, - usePostStoragesV2SecretsForSessionLaunchMutation, -} = enhancedApi; diff --git a/client/src/features/projectsV2/api/projectV2.api-config.ts b/client/src/features/projectsV2/api/projectsV2.api-config.ts similarity index 70% rename from client/src/features/projectsV2/api/projectV2.api-config.ts rename to client/src/features/projectsV2/api/projectsV2.api-config.ts index a058ec0cf0..acdadf53f6 100644 --- a/client/src/features/projectsV2/api/projectV2.api-config.ts +++ b/client/src/features/projectsV2/api/projectsV2.api-config.ts @@ -1,5 +1,5 @@ /*! - * Copyright 2023 - Swiss Data Science Center (SDSC) + * 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). * @@ -16,17 +16,17 @@ * limitations under the License. */ -// Run `npx @rtk-query/codegen-openapi projectV2.api-config.ts` in this folder to generate the API +// Run `npm run generate-api:projectsV2` to generate the API import type { ConfigFile } from "@rtk-query/codegen-openapi"; import path from "path"; const config: ConfigFile = { - apiFile: "./projectV2-empty.api.ts", - apiImport: "projectV2EmptyApi", - outputFile: "./projectV2.api.ts", - exportName: "projectV2Api", + apiFile: "./projectsV2.empty-api.ts", + apiImport: "projectsV2EmptyApi", + outputFile: "./projectsV2.generated-api.ts", + exportName: "projectsV2GeneratedApi", hooks: true, - schemaFile: path.join(__dirname, "projectV2.openapi.json"), + schemaFile: path.join(__dirname, "projectsV2.openapi.json"), }; export default config; diff --git a/client/src/features/projectsV2/api/projectsV2.api.ts b/client/src/features/projectsV2/api/projectsV2.api.ts new file mode 100644 index 0000000000..65d3b0185f --- /dev/null +++ b/client/src/features/projectsV2/api/projectsV2.api.ts @@ -0,0 +1,107 @@ +/*! + * 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 { processPaginationHeaders } from "../../../utils/helpers/kgPagination.utils"; +import { AbstractKgPaginatedResponse } from "../../../utils/types/pagination.types"; +import type { + GetProjectsApiArg, + GetProjectsApiResponse as GetProjectsApiResponseOrig, + ProjectsList, +} from "./projectsV2.generated-api"; +import { projectsV2GeneratedApi } from "./projectsV2.generated-api"; + +interface GetProjectsApiResponse extends AbstractKgPaginatedResponse { + projects: GetProjectsApiResponseOrig; +} + +const withPaged = projectsV2GeneratedApi.injectEndpoints({ + endpoints: (builder) => ({ + getProjectsPaged: builder.query({ + query: ({ params }) => ({ + url: "/projects", + params, + }), + transformResponse: (response, meta, queryArg) => { + const projects = response as ProjectsList; + const headers = meta?.response?.headers; + const headerResponse = processPaginationHeaders( + headers, + queryArg.params ?? {}, + projects + ); + return { + projects, + page: headerResponse.page, + perPage: headerResponse.perPage, + total: headerResponse.total, + totalPages: headerResponse.totalPages, + }; + }, + }), + }), +}); + +export const projectsV2Api = withPaged.enhanceEndpoints({ + addTagTypes: ["Project", "ProjectMembers"], + endpoints: { + deleteProjectsByProjectId: { + invalidatesTags: ["Project"], + }, + deleteProjectsByProjectIdMembersAndMemberId: { + invalidatesTags: ["ProjectMembers"], + }, + // alternatively, define a function which is called with the endpoint definition as an argument + getProjects: { + providesTags: ["Project"], + }, + getProjectsPaged: { + providesTags: ["Project"], + }, + getNamespacesByNamespaceProjectsAndSlug: { + providesTags: ["Project"], + }, + getProjectsByProjectId: { + providesTags: ["Project"], + }, + getProjectsByProjectIdMembers: { + providesTags: ["ProjectMembers"], + }, + patchProjectsByProjectId: { + invalidatesTags: ["Project"], + }, + patchProjectsByProjectIdMembers: { + invalidatesTags: ["ProjectMembers"], + }, + postProjects: { + invalidatesTags: ["Project"], + }, + }, +}); + +export const { + useGetProjectsPagedQuery: useGetProjectsQuery, + usePostProjectsMutation, + useGetNamespacesByNamespaceProjectsAndSlugQuery: useGetProjectBySlugQuery, + useGetProjectsByProjectIdQuery, + usePatchProjectsByProjectIdMutation, + useDeleteProjectsByProjectIdMutation, + useGetProjectsByProjectIdMembersQuery, + usePatchProjectsByProjectIdMembersMutation, + useDeleteProjectsByProjectIdMembersAndMemberIdMutation, +} = projectsV2Api; +export type * from "./projectsV2.generated-api"; diff --git a/client/src/features/projectsV2/api/projectsV2.empty-api.ts b/client/src/features/projectsV2/api/projectsV2.empty-api.ts new file mode 100644 index 0000000000..408964dc06 --- /dev/null +++ b/client/src/features/projectsV2/api/projectsV2.empty-api.ts @@ -0,0 +1,26 @@ +/*! + * 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 { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +// initialize an empty api service that we'll inject endpoints into later as needed +export const projectsV2EmptyApi = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: "/ui-server/api/data" }), + endpoints: () => ({}), + reducerPath: "projectsV2Api", +}); diff --git a/client/src/features/projectsV2/api/projectV2.api.ts b/client/src/features/projectsV2/api/projectsV2.generated-api.ts similarity index 89% rename from client/src/features/projectsV2/api/projectV2.api.ts rename to client/src/features/projectsV2/api/projectsV2.generated-api.ts index d685b66a41..0299dc529b 100644 --- a/client/src/features/projectsV2/api/projectV2.api.ts +++ b/client/src/features/projectsV2/api/projectsV2.generated-api.ts @@ -1,14 +1,10 @@ -import { projectV2EmptyApi as api } from "./projectV2-empty.api"; +import { projectsV2EmptyApi as api } from "./projectsV2.empty-api"; const injectedRtkApi = api.injectEndpoints({ endpoints: (build) => ({ getProjects: build.query({ query: (queryArg) => ({ url: `/projects`, - params: { - namespace: queryArg["namespace"], - page: queryArg.page, - per_page: queryArg.perPage, - }, + params: { params: queryArg.params }, }), }), postProjects: build.mutation({ @@ -44,12 +40,12 @@ const injectedRtkApi = api.injectEndpoints({ method: "DELETE", }), }), - getProjectsByNamespaceAndSlug: build.query< - GetProjectsByNamespaceAndSlugApiResponse, - GetProjectsByNamespaceAndSlugApiArg + getNamespacesByNamespaceProjectsAndSlug: build.query< + GetNamespacesByNamespaceProjectsAndSlugApiResponse, + GetNamespacesByNamespaceProjectsAndSlugApiArg >({ query: (queryArg) => ({ - url: `/projects/${queryArg["namespace"]}/${queryArg.slug}`, + url: `/namespaces/${queryArg["namespace"]}/projects/${queryArg.slug}`, }), }), getProjectsByProjectIdMembers: build.query< @@ -80,16 +76,12 @@ const injectedRtkApi = api.injectEndpoints({ }), overrideExisting: false, }); -export { injectedRtkApi as projectV2Api }; +export { injectedRtkApi as projectsV2GeneratedApi }; export type GetProjectsApiResponse = /** status 200 List of projects */ ProjectsList; export type GetProjectsApiArg = { - /** A namespace, used as a filter. */ - namespace?: string; - /** Result's page number starting from 1 */ - page?: number; - /** The number of results per page */ - perPage?: number; + /** query parameters */ + params?: ProjectGetQuery; }; export type PostProjectsApiResponse = /** status 201 The project was created */ Project; @@ -114,9 +106,9 @@ export type DeleteProjectsByProjectIdApiResponse = export type DeleteProjectsByProjectIdApiArg = { projectId: Ulid; }; -export type GetProjectsByNamespaceAndSlugApiResponse = +export type GetNamespacesByNamespaceProjectsAndSlugApiResponse = /** status 200 The project */ Project; -export type GetProjectsByNamespaceAndSlugApiArg = { +export type GetNamespacesByNamespaceProjectsAndSlugApiArg = { namespace: string; slug: string; }; @@ -171,6 +163,16 @@ export type ErrorResponse = { message: string; }; }; +export type PaginationRequest = { + /** Result's page number starting from 1 */ + page?: number; + /** The number of results per page */ + per_page?: number; +}; +export type ProjectGetQuery = PaginationRequest & { + /** A namespace, used as a filter. */ + namespace?: string; +}; export type ProjectPost = { name: ProjectName; namespace: Slug; @@ -210,7 +212,7 @@ export const { useGetProjectsByProjectIdQuery, usePatchProjectsByProjectIdMutation, useDeleteProjectsByProjectIdMutation, - useGetProjectsByNamespaceAndSlugQuery, + useGetNamespacesByNamespaceProjectsAndSlugQuery, useGetProjectsByProjectIdMembersQuery, usePatchProjectsByProjectIdMembersMutation, useDeleteProjectsByProjectIdMembersAndMemberIdMutation, diff --git a/client/src/features/projectsV2/api/projectV2.openapi.json b/client/src/features/projectsV2/api/projectsV2.openapi.json similarity index 95% rename from client/src/features/projectsV2/api/projectV2.openapi.json rename to client/src/features/projectsV2/api/projectsV2.openapi.json index a82f6afda9..7a4d090f0f 100644 --- a/client/src/features/projectsV2/api/projectV2.openapi.json +++ b/client/src/features/projectsV2/api/projectsV2.openapi.json @@ -20,34 +20,12 @@ "parameters": [ { "in": "query", - "description": "A namespace, used as a filter.", - "name": "namespace", - "required": false, + "description": "query parameters", + "name": "params", + "style": "form", + "explode": true, "schema": { - "type": "string" - } - }, - { - "in": "query", - "description": "Result's page number starting from 1", - "name": "page", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "default": 1 - } - }, - { - "in": "query", - "description": "The number of results per page", - "name": "per_page", - "required": false, - "schema": { - "type": "integer", - "minimum": 1, - "maximum": 100, - "default": 20 + "$ref": "#/components/schemas/ProjectGetQuery" } } ], @@ -243,7 +221,7 @@ "tags": ["projects"] } }, - "/projects/{namespace}/{slug}": { + "/namespaces/{namespace}/projects/{slug}": { "get": { "summary": "Get a project by namespace and project slug", "parameters": [ @@ -556,7 +534,7 @@ "type": "string", "minLength": 26, "maxLength": 26, - "pattern": "^[A-Z0-9]{26}$" + "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" }, "ProjectName": { "description": "Renku project name", @@ -709,7 +687,6 @@ }, "UserEmail": { "type": "string", - "format": "email", "description": "User email", "example": "some-user@gmail.com" }, @@ -718,6 +695,42 @@ "description": "Entity Tag", "example": "9EE498F9D565D0C41E511377425F32F3" }, + "ProjectGetQuery": { + "description": "Query params for project get request", + "allOf": [ + { + "$ref": "#/components/schemas/PaginationRequest" + }, + { + "properties": { + "namespace": { + "description": "A namespace, used as a filter.", + "type": "string", + "default": "" + } + } + } + ] + }, + "PaginationRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "page": { + "description": "Result's page number starting from 1", + "type": "integer", + "minimum": 1, + "default": 1 + }, + "per_page": { + "description": "The number of results per page", + "type": "integer", + "minimum": 1, + "maximum": 100, + "default": 20 + } + } + }, "ErrorResponse": { "type": "object", "properties": { diff --git a/client/src/features/projectsV2/fields/AddGroupMemberModal.tsx b/client/src/features/projectsV2/fields/AddGroupMemberModal.tsx index bc8eb63e1e..af2eeae0a5 100644 --- a/client/src/features/projectsV2/fields/AddGroupMemberModal.tsx +++ b/client/src/features/projectsV2/fields/AddGroupMemberModal.tsx @@ -32,12 +32,13 @@ import { ModalHeader, } from "reactstrap"; import { RtkErrorAlert } from "../../../components/errors/RtkErrorAlert"; + +import { + usePatchGroupsByGroupSlugMembersMutation, + type GroupMemberPatchRequest, + type GroupMemberResponseList, +} from "../../groupsV2/api/groupsV2.api"; import { User } from "../../searchV2/api/searchV2Api.api"; -import type { - GroupMemberPatchRequest, - GroupMemberResponseList, -} from "../api/namespace.api"; -import { usePatchGroupsByGroupSlugMembersMutation } from "../api/projectV2.enhanced-api"; import { UserControl } from "./UserSelector"; interface AddGroupMemberModalProps { diff --git a/client/src/features/projectsV2/fields/AddProjectMemberModal.tsx b/client/src/features/projectsV2/fields/AddProjectMemberModal.tsx index 99c9b785c8..f84ae66e4d 100644 --- a/client/src/features/projectsV2/fields/AddProjectMemberModal.tsx +++ b/client/src/features/projectsV2/fields/AddProjectMemberModal.tsx @@ -32,12 +32,13 @@ import { ModalHeader, } from "reactstrap"; import { RtkErrorAlert } from "../../../components/errors/RtkErrorAlert"; + import { User } from "../../searchV2/api/searchV2Api.api"; -import type { - ProjectMemberPatchRequest, - ProjectMemberResponse, -} from "../api/projectV2.api"; -import { usePatchProjectsByProjectIdMembersMutation } from "../api/projectV2.enhanced-api"; +import { + usePatchProjectsByProjectIdMembersMutation, + type ProjectMemberPatchRequest, + type ProjectMemberResponse, +} from "../api/projectsV2.api"; import { UserControl } from "./UserSelector"; interface AddProjectMemberModalProps { diff --git a/client/src/features/projectsV2/fields/EditProjectMemberModal.tsx b/client/src/features/projectsV2/fields/EditProjectMemberModal.tsx index 3a17ad5e7d..76f802b480 100644 --- a/client/src/features/projectsV2/fields/EditProjectMemberModal.tsx +++ b/client/src/features/projectsV2/fields/EditProjectMemberModal.tsx @@ -31,11 +31,12 @@ import { ModalHeader, } from "reactstrap"; import { RtkErrorAlert } from "../../../components/errors/RtkErrorAlert"; -import type { - ProjectMemberPatchRequest, - ProjectMemberResponse, -} from "../api/projectV2.api"; -import { usePatchProjectsByProjectIdMembersMutation } from "../api/projectV2.enhanced-api"; + +import { + usePatchProjectsByProjectIdMembersMutation, + type ProjectMemberPatchRequest, + type ProjectMemberResponse, +} from "../api/projectsV2.api"; import { ProjectMemberDisplay } from "../shared/ProjectMemberDisplay"; interface EditProjectMemberModalProps { diff --git a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx index c57b47e37b..8c7c9f65da 100644 --- a/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx +++ b/client/src/features/projectsV2/fields/ProjectNamespaceFormField.tsx @@ -32,17 +32,19 @@ import Select, { components, } from "react-select"; import { Button, FormText, Label, UncontrolledTooltip } from "reactstrap"; + import { ErrorAlert } from "../../../components/Alert"; import { Loader } from "../../../components/Loader"; import useAppDispatch from "../../../utils/customHooks/useAppDispatch.hook"; -import type { PaginatedState } from "../../session/components/options/fetchMore.types"; -import type { GetNamespacesApiResponse } from "../api/projectV2.enhanced-api"; +import type { GetNamespacesApiResponse } from "../../groupsV2/api/groupsV2.api"; import { - projectV2Api, + groupsV2Api, useGetNamespacesQuery, useLazyGetNamespacesQuery, -} from "../api/projectV2.enhanced-api"; +} from "../../groupsV2/api/groupsV2.api"; +import type { PaginatedState } from "../../session/components/options/fetchMore.types"; import type { GenericFormFieldProps } from "./formField.types"; + import styles from "./ProjectNamespaceFormField.module.scss"; type ResponseNamespaces = GetNamespacesApiResponse["namespaces"]; @@ -206,7 +208,7 @@ export default function ProjectNamespaceFormField({ // Handle forced refresh const dispatch = useAppDispatch(); const refetch = useCallback(() => { - dispatch(projectV2Api.util.invalidateTags(["Namespace"])); + dispatch(groupsV2Api.util.invalidateTags(["Namespace"])); }, [dispatch]); return (
@@ -264,7 +266,11 @@ function ProjectNamespaceControl(props: ProjectNamespaceControlProps) { isError, isFetching, requestId, - } = useGetNamespacesQuery({ minimumRole: "editor" }); + } = useGetNamespacesQuery({ + params: { + minimum_role: "editor", + }, + }); const [ { data: allNamespaces, fetchedPages, hasMore, currentRequestId }, @@ -280,9 +286,11 @@ function ProjectNamespaceControl(props: ProjectNamespaceControlProps) { useLazyGetNamespacesQuery(); const onFetchMore = useCallback(() => { const request = fetchNamespacesPage({ - page: fetchedPages + 1, - perPage: namespacesFirstPage?.perPage, - minimumRole: "editor", + params: { + minimum_role: "editor", + page: fetchedPages + 1, + per_page: namespacesFirstPage?.perPage, + }, }); setState((prevState: PaginatedState) => ({ ...prevState, diff --git a/client/src/features/projectsV2/fields/RemoveProjectMemberModal.tsx b/client/src/features/projectsV2/fields/RemoveProjectMemberModal.tsx index bd2a7a3bed..a8f3d58088 100644 --- a/client/src/features/projectsV2/fields/RemoveProjectMemberModal.tsx +++ b/client/src/features/projectsV2/fields/RemoveProjectMemberModal.tsx @@ -29,9 +29,10 @@ import { ModalFooter, ModalHeader, } from "reactstrap"; + import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; -import type { ProjectMemberResponse } from "../api/projectV2.api"; -import { useDeleteProjectsByProjectIdMembersAndMemberIdMutation } from "../api/projectV2.enhanced-api"; +import type { ProjectMemberResponse } from "../api/projectsV2.api"; +import { useDeleteProjectsByProjectIdMembersAndMemberIdMutation } from "../api/projectsV2.api"; import { ProjectMemberDisplay } from "../shared/ProjectMemberDisplay"; interface RemoveProjectMemberModalProps { diff --git a/client/src/features/projectsV2/list/GroupList.tsx b/client/src/features/projectsV2/list/GroupList.tsx index 8dde39426a..70224d5acb 100644 --- a/client/src/features/projectsV2/list/GroupList.tsx +++ b/client/src/features/projectsV2/list/GroupList.tsx @@ -17,20 +17,22 @@ */ import cx from "classnames"; import { useState } from "react"; -import { Card, CardBody, Col, Row } from "reactstrap"; import { generatePath, Link } from "react-router-dom-v5-compat"; +import { Card, CardBody, Col, Row } from "reactstrap"; +import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; import ContainerWrap from "../../../components/container/ContainerWrap"; +import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; import FormSchema from "../../../components/formschema/FormSchema"; import { Loader } from "../../../components/Loader"; import Pagination from "../../../components/Pagination"; import { TimeCaption } from "../../../components/TimeCaption"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import type { GroupResponse } from "../api/namespace.api"; -import { useGetGroupsQuery } from "../api/projectV2.enhanced-api"; +import { + GroupResponse, + useGetGroupsQuery, +} from "../../groupsV2/api/groupsV2.api"; import WipBadge from "../shared/WipBadge"; -import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; -import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; interface GroupListGroupProps { group: GroupResponse; @@ -64,8 +66,10 @@ function GroupListDisplay() { const perPage = 12; const [page, setPage] = useState(1); const { data, error, isLoading } = useGetGroupsQuery({ - page, - perPage, + params: { + page, + per_page: perPage, + }, }); if (isLoading) diff --git a/client/src/features/projectsV2/list/ProjectV2ListDisplay.tsx b/client/src/features/projectsV2/list/ProjectV2ListDisplay.tsx index 1abb3db2f6..a9c49f87c5 100644 --- a/client/src/features/projectsV2/list/ProjectV2ListDisplay.tsx +++ b/client/src/features/projectsV2/list/ProjectV2ListDisplay.tsx @@ -29,14 +29,12 @@ import { Card, CardBody, Col, Row } from "reactstrap"; import { Loader } from "../../../components/Loader"; import Pagination from "../../../components/Pagination"; import { TimeCaption } from "../../../components/TimeCaption"; +import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import type { Project } from "../api/projectV2.api"; -import { - useGetNamespacesByNamespaceSlugQuery, - useGetProjectsQuery, -} from "../api/projectV2.enhanced-api"; -import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; +import { useGetNamespacesByNamespaceSlugQuery } from "../../groupsV2/api/groupsV2.api"; +import type { Project } from "../api/projectsV2.api"; +import { useGetProjectsQuery } from "../api/projectsV2.api"; const DEFAULT_PER_PAGE = 12; const DEFAULT_PAGE_PARAM = "page"; @@ -92,9 +90,11 @@ export default function ProjectListDisplay({ }, [pageParam, searchParams]); const { data, error, isLoading } = useGetProjectsQuery({ - namespace: ns, - page, - perPage, + params: { + namespace: ns, + page, + per_page: perPage, + }, }); useEffect(() => { diff --git a/client/src/features/projectsV2/new/GroupNew.tsx b/client/src/features/projectsV2/new/GroupNew.tsx index 94b021d856..efb8d3edb9 100644 --- a/client/src/features/projectsV2/new/GroupNew.tsx +++ b/client/src/features/projectsV2/new/GroupNew.tsx @@ -30,8 +30,8 @@ import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook import { slugFromTitle } from "../../../utils/helpers/HelperFunctions"; import { RtkOrNotebooksError } from "../../../components/errors/RtkErrorAlert"; -import type { GroupPostRequest } from "../api/namespace.api"; -import { usePostGroupsMutation } from "../api/projectV2.enhanced-api"; +import type { GroupPostRequest } from "../../groupsV2/api/groupsV2.api"; +import { usePostGroupsMutation } from "../../groupsV2/api/groupsV2.api"; import DescriptionFormField from "../fields/DescriptionFormField"; import NameFormField from "../fields/NameFormField"; import SlugFormField from "../fields/SlugFormField"; diff --git a/client/src/features/projectsV2/new/ProjectV2New.tsx b/client/src/features/projectsV2/new/ProjectV2New.tsx index 4cc9321cce..a1edcbfa8b 100644 --- a/client/src/features/projectsV2/new/ProjectV2New.tsx +++ b/client/src/features/projectsV2/new/ProjectV2New.tsx @@ -27,8 +27,8 @@ import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import useAppSelector from "../../../utils/customHooks/useAppSelector.hook"; import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; -import type { ProjectPost } from "../api/projectV2.api"; -import { usePostProjectsMutation } from "../api/projectV2.enhanced-api"; +import type { ProjectPost } from "../api/projectsV2.api"; +import { usePostProjectsMutation } from "../api/projectsV2.api"; import WipBadge from "../shared/WipBadge"; import { ProjectV2DescriptionAndRepositories } from "../show/ProjectV2DescriptionAndRepositories"; diff --git a/client/src/features/projectsV2/projectV2.types.ts b/client/src/features/projectsV2/projectV2.types.ts index b4b1a02694..21f6f3e91f 100644 --- a/client/src/features/projectsV2/projectV2.types.ts +++ b/client/src/features/projectsV2/projectV2.types.ts @@ -1,4 +1,4 @@ -import type { Role, Visibility } from "./api/projectV2.api"; +import type { Role, Visibility } from "./api/projectsV2.api"; export type ProjectVisibility = Visibility; export type ProjectRole = Role; diff --git a/client/src/features/projectsV2/shared/ProjectMemberDisplay.tsx b/client/src/features/projectsV2/shared/ProjectMemberDisplay.tsx index 8ae7cd2ae1..036a43da17 100644 --- a/client/src/features/projectsV2/shared/ProjectMemberDisplay.tsx +++ b/client/src/features/projectsV2/shared/ProjectMemberDisplay.tsx @@ -17,7 +17,7 @@ */ import { getMemberNameToDisplay } from "../../ProjectPageV2/utils/roleUtils"; -import { ProjectMemberResponse } from "../api/projectV2.api"; +import type { ProjectMemberResponse } from "../api/projectsV2.api"; interface ProjectMemberDisplayProps { member: ProjectMemberResponse; diff --git a/client/src/features/projectsV2/show/GroupShortHandDisplay.tsx b/client/src/features/projectsV2/show/GroupShortHandDisplay.tsx index 7dd0e39a98..2fa3836a3f 100644 --- a/client/src/features/projectsV2/show/GroupShortHandDisplay.tsx +++ b/client/src/features/projectsV2/show/GroupShortHandDisplay.tsx @@ -19,11 +19,11 @@ import cx from "classnames"; import { Link, generatePath } from "react-router-dom-v5-compat"; -import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; +import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; import VisibilityIcon from "../../../components/entities/VisibilityIcon"; import { TimeCaption } from "../../../components/TimeCaption"; -import ClampedParagraph from "../../../components/clamped/ClampedParagraph"; -import { GroupResponse } from "../api/namespace.api"; +import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; +import type { GroupResponse } from "../../groupsV2/api/groupsV2.api"; interface GroupShortHandDisplayProps { className?: string; diff --git a/client/src/features/projectsV2/show/ProjectShortHandDisplay.tsx b/client/src/features/projectsV2/show/ProjectShortHandDisplay.tsx index 791e0c87f1..858f4b934c 100644 --- a/client/src/features/projectsV2/show/ProjectShortHandDisplay.tsx +++ b/client/src/features/projectsV2/show/ProjectShortHandDisplay.tsx @@ -19,7 +19,7 @@ import cx from "classnames"; import { Link, generatePath } from "react-router-dom-v5-compat"; -import { Project } from "../api/projectV2.api"; +import { Project } from "../api/projectsV2.api"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import VisibilityIcon from "../../../components/entities/VisibilityIcon"; import { TimeCaption } from "../../../components/TimeCaption"; diff --git a/client/src/features/projectsV2/show/ProjectV2DescriptionAndRepositories.tsx b/client/src/features/projectsV2/show/ProjectV2DescriptionAndRepositories.tsx index d64a570019..b62d787afd 100644 --- a/client/src/features/projectsV2/show/ProjectV2DescriptionAndRepositories.tsx +++ b/client/src/features/projectsV2/show/ProjectV2DescriptionAndRepositories.tsx @@ -18,7 +18,7 @@ import cx from "classnames"; import { Label } from "reactstrap"; -import type { Project } from "../api/projectV2.api"; +import type { Project } from "../api/projectsV2.api"; function ProjectV2Description({ description }: Pick) { const desc = diff --git a/client/src/features/projectsV2/show/ProjectV2ShowByProjectId.tsx b/client/src/features/projectsV2/show/ProjectV2ShowByProjectId.tsx index 0a97c90c34..687b49852c 100644 --- a/client/src/features/projectsV2/show/ProjectV2ShowByProjectId.tsx +++ b/client/src/features/projectsV2/show/ProjectV2ShowByProjectId.tsx @@ -25,7 +25,7 @@ import { import { Loader } from "../../../components/Loader"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import { useGetProjectsByProjectIdQuery } from "../api/projectV2.enhanced-api"; +import { useGetProjectsByProjectIdQuery } from "../api/projectsV2.api"; import ProjectNotFound from "../notFound/ProjectNotFound"; export default function ProjectV2ShowByProjectId() { diff --git a/client/src/features/projectsV2/show/groupEditForms.tsx b/client/src/features/projectsV2/show/groupEditForms.tsx index 9586f53026..56c58a50f4 100644 --- a/client/src/features/projectsV2/show/groupEditForms.tsx +++ b/client/src/features/projectsV2/show/groupEditForms.tsx @@ -33,19 +33,20 @@ import { ModalFooter, ModalHeader, } from "reactstrap"; + import { Loader } from "../../../components/Loader"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import type { GroupMemberResponse, GroupPatchRequest, GroupResponse, -} from "../api/namespace.api"; +} from "../../groupsV2/api/groupsV2.api"; import { useDeleteGroupsByGroupSlugMembersAndUserIdMutation, useDeleteGroupsByGroupSlugMutation, useGetGroupsByGroupSlugMembersQuery, usePatchGroupsByGroupSlugMutation, -} from "../api/projectV2.enhanced-api"; +} from "../../groupsV2/api/groupsV2.api"; import AddGroupMemberModal from "../fields/AddGroupMemberModal"; import DescriptionFormField from "../fields/DescriptionFormField"; import NameFormField from "../fields/NameFormField"; diff --git a/client/src/features/searchV2/components/SearchV2Filters.tsx b/client/src/features/searchV2/components/SearchV2Filters.tsx index 10d024e486..31ac57b430 100644 --- a/client/src/features/searchV2/components/SearchV2Filters.tsx +++ b/client/src/features/searchV2/components/SearchV2Filters.tsx @@ -31,7 +31,7 @@ import { import useAppDispatch from "../../../utils/customHooks/useAppDispatch.hook"; import useAppSelector from "../../../utils/customHooks/useAppSelector.hook"; -import type { Role } from "../../projectsV2/api/projectV2.api"; +import type { Role } from "../../projectsV2/api/projectsV2.api"; import { FILTER_KEY_LABELS, FILTER_VALUE_LABELS, diff --git a/client/src/features/searchV2/searchV2.constants.ts b/client/src/features/searchV2/searchV2.constants.ts index e31bd3a0d7..b865a093bd 100644 --- a/client/src/features/searchV2/searchV2.constants.ts +++ b/client/src/features/searchV2/searchV2.constants.ts @@ -24,7 +24,8 @@ import { People, Person, } from "react-bootstrap-icons"; -import type { Role } from "../projectsV2/api/projectV2.api"; + +import type { Role } from "../projectsV2/api/projectsV2.api"; import type { AfterDateValue, BeforeDateValue, diff --git a/client/src/features/searchV2/searchV2.slice.ts b/client/src/features/searchV2/searchV2.slice.ts index b2fa3ee028..10175975a8 100644 --- a/client/src/features/searchV2/searchV2.slice.ts +++ b/client/src/features/searchV2/searchV2.slice.ts @@ -19,7 +19,7 @@ import { createSlice, PayloadAction } from "@reduxjs/toolkit"; import { toNumericRole } from "../ProjectPageV2/utils/roleUtils"; -import type { Role } from "../projectsV2/api/projectV2.api"; +import type { Role } from "../projectsV2/api/projectsV2.api"; import { DEFAULT_CREATION_DATE_FILTER, DEFAULT_ROLE_FILTER, diff --git a/client/src/features/searchV2/searchV2.types.ts b/client/src/features/searchV2/searchV2.types.ts index e6eb02a48a..0cb3a0993f 100644 --- a/client/src/features/searchV2/searchV2.types.ts +++ b/client/src/features/searchV2/searchV2.types.ts @@ -18,7 +18,7 @@ import { DateTime } from "luxon"; -import type { Role } from "../projectsV2/api/projectV2.api"; +import type { Role } from "../projectsV2/api/projectsV2.api"; import type { SearchEntity, Visibility } from "./api/searchV2Api.api"; export interface SearchV2State { diff --git a/client/src/features/secrets/SecretDelete.tsx b/client/src/features/secrets/SecretDelete.tsx index 080e9446ab..5226f5664f 100644 --- a/client/src/features/secrets/SecretDelete.tsx +++ b/client/src/features/secrets/SecretDelete.tsx @@ -24,7 +24,7 @@ import { Button, Modal, ModalBody, ModalFooter, ModalHeader } from "reactstrap"; import { RtkOrNotebooksError } from "../../components/errors/RtkErrorAlert"; import useAppDispatch from "../../utils/customHooks/useAppDispatch.hook"; -import { projectV2Api } from "../projectsV2/api/projectV2.enhanced-api"; +import { storagesV2Api } from "../storagesV2/api/storagesV2.api"; import { useDeleteSecretMutation } from "./secrets.api"; import { SecretDetails } from "./secrets.types"; @@ -44,7 +44,7 @@ export default function SecretDelete({ secret }: SecretsDeleteProps) { const [deleteSecretMutation, result] = useDeleteSecretMutation(); const deleteSecret = useCallback(() => { deleteSecretMutation(secret.id); - dispatch(projectV2Api.util.invalidateTags(["Storages"])); + dispatch(storagesV2Api.util.invalidateTags(["Storages"])); }, [deleteSecretMutation, dispatch, secret.id]); // Automatically close the modal when the secret is modified diff --git a/client/src/features/session/sessions.api.utils.ts b/client/src/features/session/sessions.api.utils.ts index 346aa0686f..d3694908dd 100644 --- a/client/src/features/session/sessions.api.utils.ts +++ b/client/src/features/session/sessions.api.utils.ts @@ -16,9 +16,9 @@ * limitations under the License. */ +import { CloudStorageDetailsOptions } from "../project/components/cloudStorage/projectCloudStorage.types"; +import { CloudStorageWithIdRead } from "../storagesV2/api/storagesV2.api"; import { SessionCloudStorage } from "./startSessionOptions.types"; -import { CloudStorageDetailsOptions } from "../project/components/cloudStorage/projectCloudStorage.types.ts"; -import { CloudStorageWithIdRead } from "../projectsV2/api/storagesV2.api.ts"; export function convertCloudStorageForSessionApi( cloudStorage: SessionCloudStorage | CloudStorageWithIdRead diff --git a/client/src/features/session/sessions.types.ts b/client/src/features/session/sessions.types.ts index b6db23ada6..05844e8f00 100644 --- a/client/src/features/session/sessions.types.ts +++ b/client/src/features/session/sessions.types.ts @@ -16,7 +16,7 @@ * limitations under the License. */ -import { CloudStorageWithIdRead } from "../projectsV2/api/storagesV2.api.ts"; +import { CloudStorageWithIdRead } from "../storagesV2/api/storagesV2.api"; import { SessionCloudStorage } from "./startSessionOptions.types"; export interface DockerImage { diff --git a/client/src/features/sessionsV2/CloudStorageSecretsModal.tsx b/client/src/features/sessionsV2/CloudStorageSecretsModal.tsx index 45754a4526..eade8c55d9 100644 --- a/client/src/features/sessionsV2/CloudStorageSecretsModal.tsx +++ b/client/src/features/sessionsV2/CloudStorageSecretsModal.tsx @@ -52,9 +52,9 @@ import type { TestCloudStorageConnectionParams, } from "../project/components/cloudStorage/projectCloudStorage.types"; import { storageDefinitionFromConfig } from "../project/utils/projectCloudStorage.utils"; -import type { RCloneOption } from "../projectsV2/api/storagesV2.api"; -import type { SessionStartCloudStorageConfiguration } from "./startSessionOptionsV2.types"; import { storageSecretNameToFieldName } from "../secrets/secrets.utils"; +import type { RCloneOption } from "../storagesV2/api/storagesV2.api"; +import type { SessionStartCloudStorageConfiguration } from "./startSessionOptionsV2.types"; const CONTEXT_STRINGS = { session: { diff --git a/client/src/features/sessionsV2/SessionList/SessionItem.tsx b/client/src/features/sessionsV2/SessionList/SessionItem.tsx index df93d2fb65..2459ecf633 100644 --- a/client/src/features/sessionsV2/SessionList/SessionItem.tsx +++ b/client/src/features/sessionsV2/SessionList/SessionItem.tsx @@ -19,7 +19,7 @@ import cx from "classnames"; import { CircleFill } from "react-bootstrap-icons"; import { Col, ListGroupItem, Row } from "reactstrap"; -import { Project } from "../../projectsV2/api/projectV2.api"; +import { Project } from "../../projectsV2/api/projectsV2.api"; import { getShowSessionUrlByProject } from "../SessionsV2"; import StartSessionButton from "../StartSessionButton"; import ActiveSessionButton from "../components/SessionButton/ActiveSessionButton"; diff --git a/client/src/features/sessionsV2/SessionList/SessionItemDisplay.tsx b/client/src/features/sessionsV2/SessionList/SessionItemDisplay.tsx index 98df67b1a6..b443c96f2c 100644 --- a/client/src/features/sessionsV2/SessionList/SessionItemDisplay.tsx +++ b/client/src/features/sessionsV2/SessionList/SessionItemDisplay.tsx @@ -18,7 +18,7 @@ import { useMemo, useState } from "react"; import { NotebookAnnotations } from "../../../notebooks/components/session.types"; -import { Project } from "../../projectsV2/api/projectV2.api"; +import { Project } from "../../projectsV2/api/projectsV2.api"; import sessionsApi from "../../session/sessions.api"; import { filterSessionsWithCleanedAnnotations } from "../../session/sessions.utils"; import { SessionView } from "../SessionView/SessionView"; diff --git a/client/src/features/sessionsV2/SessionStartPage.tsx b/client/src/features/sessionsV2/SessionStartPage.tsx index b1ae4a78d8..290faa3375 100644 --- a/client/src/features/sessionsV2/SessionStartPage.tsx +++ b/client/src/features/sessionsV2/SessionStartPage.tsx @@ -42,14 +42,16 @@ import { storageDefinitionAfterSavingCredentialsFromConfig, storageDefinitionFromConfig, } from "../project/utils/projectCloudStorage.utils"; -import type { Project } from "../projectsV2/api/projectV2.api"; import { - projectV2Api, - useGetProjectsByNamespaceAndSlugQuery, - usePostStoragesV2SecretsForSessionLaunchMutation, -} from "../projectsV2/api/projectV2.enhanced-api"; + useGetProjectBySlugQuery, + type Project, +} from "../projectsV2/api/projectsV2.api"; import { storageSecretNameToFieldName } from "../secrets/secrets.utils"; import { useStartRenku2SessionMutation } from "../session/sessions.api"; +import { + storagesV2Api, + usePostStoragesV2ByStorageIdSecretsMutation, +} from "../storagesV2/api/storagesV2.api"; import type { CloudStorageConfiguration } from "./CloudStorageSecretsModal"; import CloudStorageSecretsModal from "./CloudStorageSecretsModal"; import { SelectResourceClassModal } from "./components/SessionModals/SelectResourceClass"; @@ -74,7 +76,7 @@ function SaveCloudStorage({ const dispatch = useAppDispatch(); const [steps, setSteps] = useState([]); const [saveCredentials, saveCredentialsResult] = - usePostStoragesV2SecretsForSessionLaunchMutation(); + usePostStoragesV2ByStorageIdSecretsMutation(); const credentialsToSave = useMemo(() => { return startSessionOptionsV2.cloudStorage @@ -156,7 +158,7 @@ function SaveCloudStorage({ startSessionOptionsV2Slice.actions.setCloudStorage(cloudStorageConfigs) ); // After all the changes have been made, indicate that the storages need to be reloaded - dispatch(projectV2Api.util.invalidateTags(["Storages"])); + dispatch(storagesV2Api.util.invalidateTags(["Storages"])); } }, [ dispatch, @@ -526,7 +528,7 @@ export default function SessionStartPage() { data: project, isLoading: isLoadingProject, error: projectError, - } = useGetProjectsByNamespaceAndSlugQuery( + } = useGetProjectBySlugQuery( namespace && slug ? { namespace, slug } : skipToken ); const projectId = project?.id ?? ""; diff --git a/client/src/features/sessionsV2/SessionView/SessionView.tsx b/client/src/features/sessionsV2/SessionView/SessionView.tsx index 9afb0474df..7a9fd614e2 100644 --- a/client/src/features/sessionsV2/SessionView/SessionView.tsx +++ b/client/src/features/sessionsV2/SessionView/SessionView.tsx @@ -20,13 +20,13 @@ import cx from "classnames"; import { ReactNode, useCallback, useMemo, useState } from "react"; import { Boxes, - Clock, CircleFill, + Clock, Database, ExclamationTriangleFill, + FileCode, Globe2, Pencil, - FileCode, } from "react-bootstrap-icons"; import { Badge, @@ -47,13 +47,22 @@ import { TimeCaption } from "../../../components/TimeCaption"; import { CommandCopy } from "../../../components/commandCopy/CommandCopy"; import { toHumanDateTime } from "../../../utils/helpers/DateTimeUtils"; import { RepositoryItem } from "../../ProjectPageV2/ProjectPageContent/CodeRepositories/CodeRepositoryDisplay"; -import { Project } from "../../projectsV2/api/projectV2.api"; -import { useGetStoragesV2Query } from "../../projectsV2/api/storagesV2.api"; +import MembershipGuard from "../../ProjectPageV2/utils/MembershipGuard"; +import { + useGetResourceClassByIdQuery, + useGetResourcePoolsQuery, +} from "../../dataServices/computeResources.api"; +import type { Project } from "../../projectsV2/api/projectsV2.api"; +import { useGetProjectsByProjectIdMembersQuery } from "../../projectsV2/api/projectsV2.api"; import { SessionRowResourceRequests } from "../../session/components/SessionsList"; import { Session, Sessions } from "../../session/sessions.types"; +import { useGetStoragesV2Query } from "../../storagesV2/api/storagesV2.api"; + import { SessionV2Actions, getShowSessionUrlByProject } from "../SessionsV2"; import StartSessionButton from "../StartSessionButton"; +import UpdateSessionLauncherModal from "../UpdateSessionLauncherModal"; import ActiveSessionButton from "../components/SessionButton/ActiveSessionButton"; +import { ModifyResourcesLauncherModal } from "../components/SessionModals/ModifyResourcesLauncher"; import { SessionBadge, SessionStatusV2Description, @@ -63,15 +72,6 @@ import { import sessionsV2Api from "../sessionsV2.api"; import { SessionEnvironment, SessionLauncher } from "../sessionsV2.types"; -import MembershipGuard from "../../ProjectPageV2/utils/MembershipGuard"; -import { - useGetResourceClassByIdQuery, - useGetResourcePoolsQuery, -} from "../../dataServices/computeResources.api"; -import { useGetProjectsByProjectIdMembersQuery } from "../../projectsV2/api/projectV2.enhanced-api"; -import UpdateSessionLauncherModal from "../UpdateSessionLauncherModal"; -import { ModifyResourcesLauncherModal } from "../components/SessionModals/ModifyResourcesLauncher"; - interface SessionCardContentProps { color: string; contentDescription: ReactNode; @@ -294,9 +294,7 @@ export function SessionView({ }, [environments, launcher]); const { data: dataSources } = useGetStoragesV2Query({ - storageV2Params: { - project_id: project.id, - }, + storageV2Params: { project_id: project.id }, }); const { data: resourcePools } = useGetResourcePoolsQuery({}); diff --git a/client/src/features/sessionsV2/SessionsV2.tsx b/client/src/features/sessionsV2/SessionsV2.tsx index 7fcc310435..9d9074f50f 100644 --- a/client/src/features/sessionsV2/SessionsV2.tsx +++ b/client/src/features/sessionsV2/SessionsV2.tsx @@ -31,24 +31,24 @@ import { } from "reactstrap"; import { Loader } from "../../components/Loader"; +import { ButtonWithMenuV2 } from "../../components/buttons/Button"; import { RtkErrorAlert } from "../../components/errors/RtkErrorAlert"; import { NotebookAnnotations } from "../../notebooks/components/session.types"; import { ABSOLUTE_ROUTES } from "../../routing/routes.constants"; import AccessGuard from "../ProjectPageV2/utils/AccessGuard"; import useProjectAccess from "../ProjectPageV2/utils/useProjectAccess.hook"; -import type { Project } from "../projectsV2/api/projectV2.api"; +import type { Project } from "../projectsV2/api/projectsV2.api"; import { useGetSessionsQuery } from "../session/sessions.api"; import { Session } from "../session/sessions.types"; import { filterSessionsWithCleanedAnnotations } from "../session/sessions.utils"; import AddSessionLauncherButton from "./AddSessionLauncherButton"; import DeleteSessionV2Modal from "./DeleteSessionLauncherModal"; +import SessionItem from "./SessionList/SessionItem"; import { SessionItemDisplay } from "./SessionList/SessionItemDisplay"; import { SessionView } from "./SessionView/SessionView"; import UpdateSessionLauncherModal from "./UpdateSessionLauncherModal"; import { useGetProjectSessionLaunchersQuery } from "./sessionsV2.api"; import { SessionLauncher } from "./sessionsV2.types"; -import SessionItem from "./SessionList/SessionItem"; -import { ButtonWithMenuV2 } from "../../components/buttons/Button"; // Required for logs formatting import "../../notebooks/Notebooks.css"; diff --git a/client/src/features/sessionsV2/ShowSessionPage.tsx b/client/src/features/sessionsV2/ShowSessionPage.tsx index a142a777a6..08728d6048 100644 --- a/client/src/features/sessionsV2/ShowSessionPage.tsx +++ b/client/src/features/sessionsV2/ShowSessionPage.tsx @@ -16,6 +16,7 @@ * limitations under the License. */ +import { skipToken } from "@reduxjs/toolkit/query"; import cx from "classnames"; import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { @@ -43,33 +44,31 @@ import { UncontrolledTooltip, } from "reactstrap"; +import { Loader } from "../../components/Loader"; +import { EnvironmentLogs } from "../../components/Logs"; +import { TimeCaption } from "../../components/TimeCaption"; +import { CommandCopy } from "../../components/commandCopy/CommandCopy"; import RenkuFrogIcon from "../../components/icons/RenkuIcon"; import { User } from "../../model/renkuModels.types"; +import { NotebooksHelper } from "../../notebooks"; +import { NotebookAnnotations } from "../../notebooks/components/session.types"; import { ABSOLUTE_ROUTES } from "../../routing/routes.constants"; +import useAppDispatch from "../../utils/customHooks/useAppDispatch.hook"; import useLegacySelector from "../../utils/customHooks/useLegacySelector.hook"; import useWindowSize from "../../utils/helpers/UseWindowsSize"; -import { resetFavicon, setFavicon } from "../display"; +import { displaySlice, resetFavicon, setFavicon } from "../display"; +import { useGetProjectBySlugQuery } from "../projectsV2/api/projectsV2.api"; import SessionHibernated from "../session/components/SessionHibernated"; import SessionJupyter from "../session/components/SessionJupyter"; import SessionUnavailable from "../session/components/SessionUnavailable"; +import { SessionRowResourceRequests } from "../session/components/SessionsList"; +import styles from "../session/components/ShowSession.module.scss"; import StartSessionProgressBar from "../session/components/StartSessionProgressBar"; import { useGetSessionsQuery } from "../session/sessions.api"; +import { Session } from "../session/sessions.types"; + import PauseOrDeleteSessionModal from "./PauseOrDeleteSessionModal"; import { getSessionFavicon } from "./session.utils"; - -import { skipToken } from "@reduxjs/toolkit/query"; -import { Loader } from "../../components/Loader"; -import { EnvironmentLogs } from "../../components/Logs"; -import { TimeCaption } from "../../components/TimeCaption"; -import { CommandCopy } from "../../components/commandCopy/CommandCopy"; -import { NotebooksHelper } from "../../notebooks"; -import { NotebookAnnotations } from "../../notebooks/components/session.types"; -import useAppDispatch from "../../utils/customHooks/useAppDispatch.hook"; -import { displaySlice } from "../display"; -import { useGetProjectsByNamespaceAndSlugQuery } from "../projectsV2/api/projectV2.enhanced-api"; -import { SessionRowResourceRequests } from "../session/components/SessionsList"; -import styles from "../session/components/ShowSession.module.scss"; -import { Session } from "../session/sessions.types"; import { useGetProjectSessionLaunchersQuery } from "./sessionsV2.api"; export default function ShowSessionPage() { @@ -410,7 +409,7 @@ function SessionDetails({ error: launchersError, } = useGetProjectSessionLaunchersQuery(projectId ? { projectId } : skipToken); const { data: project, isLoading: isLoadingProject } = - useGetProjectsByNamespaceAndSlugQuery( + useGetProjectBySlugQuery( namespace && slug ? { namespace, slug } : skipToken ); diff --git a/client/src/features/sessionsV2/components/SessionModals/AddSession.tsx b/client/src/features/sessionsV2/components/SessionModals/AddSession.tsx index 27652b5c8c..d9e9741ffd 100644 --- a/client/src/features/sessionsV2/components/SessionModals/AddSession.tsx +++ b/client/src/features/sessionsV2/components/SessionModals/AddSession.tsx @@ -32,9 +32,10 @@ import { ModalHeader, Row, } from "reactstrap"; +import { InfoAlert } from "../../../../components/Alert"; import { Loader } from "../../../../components/Loader"; import { RtkOrNotebooksError } from "../../../../components/errors/RtkErrorAlert"; -import { useGetProjectsByNamespaceAndSlugQuery } from "../../../projectsV2/api/projectV2.enhanced-api"; +import { useGetProjectBySlugQuery } from "../../../projectsV2/api/projectsV2.api"; import { CustomEnvFormContent, ExistingEnvFormContent, @@ -42,7 +43,6 @@ import { } from "../../SessionLauncherFormContent"; import { useAddSessionLauncherMutation } from "../../sessionsV2.api"; import { SessionLauncherEnvironment } from "../../sessionsV2.types"; -import { InfoAlert } from "../../../../components/Alert"; interface AddSessionLauncherModalProps { isOpen: boolean; @@ -53,7 +53,7 @@ function AddSessionCustomImageModal({ toggle, }: AddSessionLauncherModalProps) { const { namespace, slug } = useParams<{ namespace: string; slug: string }>(); - const { data: project } = useGetProjectsByNamespaceAndSlugQuery( + const { data: project } = useGetProjectBySlugQuery( namespace && slug ? { namespace, slug } : skipToken ); const projectId = project?.id; @@ -164,7 +164,7 @@ function AddSessionExistingEnvModal({ toggle, }: AddSessionLauncherModalProps) { const { namespace, slug } = useParams<{ namespace: string; slug: string }>(); - const { data: project } = useGetProjectsByNamespaceAndSlugQuery( + const { data: project } = useGetProjectBySlugQuery( namespace && slug ? { namespace, slug } : skipToken ); const projectId = project?.id; diff --git a/client/src/features/sessionsV2/startSessionOptionsV2.types.ts b/client/src/features/sessionsV2/startSessionOptionsV2.types.ts index c5d4b7757f..ee795565d2 100644 --- a/client/src/features/sessionsV2/startSessionOptionsV2.types.ts +++ b/client/src/features/sessionsV2/startSessionOptionsV2.types.ts @@ -20,7 +20,7 @@ import type { DockerImageStatus, SessionEnvironmentVariable, } from "../session/startSessionOptions.types"; -import type { CloudStorageGetRead } from "../projectsV2/api/storagesV2.api"; +import type { CloudStorageGetRead } from "../storagesV2/api/storagesV2.api"; export interface SessionStartCloudStorageConfiguration { active: boolean; diff --git a/client/src/features/sessionsV2/useSessionLaunchState.hook.ts b/client/src/features/sessionsV2/useSessionLaunchState.hook.ts index 5de80c6cc0..2614176529 100644 --- a/client/src/features/sessionsV2/useSessionLaunchState.hook.ts +++ b/client/src/features/sessionsV2/useSessionLaunchState.hook.ts @@ -24,11 +24,11 @@ import useAppSelector from "../../utils/customHooks/useAppSelector.hook"; import { useGetResourcePoolsQuery } from "../dataServices/computeResources.api"; import useDataSourceConfiguration from "../ProjectPageV2/ProjectPageContent/DataSources/useDataSourceConfiguration.hook"; -import type { Project } from "../projectsV2/api/projectV2.api"; -import { useGetStoragesV2Query } from "../projectsV2/api/storagesV2.api"; +import type { Project } from "../projectsV2/api/projectsV2.api"; import { useGetDockerImageQuery } from "../session/sessions.api"; import { SESSION_CI_PIPELINE_POLLING_INTERVAL_MS } from "../session/startSessionOptions.constants"; import { DockerImageStatus } from "../session/startSessionOptions.types"; +import { useGetStoragesV2Query } from "../storagesV2/api/storagesV2.api"; import { useGetSessionEnvironmentsQuery } from "./sessionsV2.api"; import { SessionLauncher } from "./sessionsV2.types"; @@ -53,9 +53,7 @@ export default function useSessionLauncherState({ isFetching: isFetchingStorages, isLoading: isLoadingStorages, } = useGetStoragesV2Query({ - storageV2Params: { - project_id: project.id, - }, + storageV2Params: { project_id: project.id }, }); const { data: resourcePools } = useGetResourcePoolsQuery({}); const { isPendingResourceClass, setResourceClass } = useSessionResourceClass({ diff --git a/client/src/features/projectsV2/api/storages.api-config.ts b/client/src/features/storagesV2/api/storagesV2.api-config.ts similarity index 74% rename from client/src/features/projectsV2/api/storages.api-config.ts rename to client/src/features/storagesV2/api/storagesV2.api-config.ts index a1d4db80fc..bff011a5b1 100644 --- a/client/src/features/projectsV2/api/storages.api-config.ts +++ b/client/src/features/storagesV2/api/storagesV2.api-config.ts @@ -16,16 +16,15 @@ * limitations under the License. */ -// Run `npx @rtk-query/codegen-openapi storages.api-config.ts` in this folder to generate the API +// Run `npm run generate-api:storagesV2` to generate the API import type { ConfigFile } from "@rtk-query/codegen-openapi"; import path from "path"; const config: ConfigFile = { - // Configure to inject endpoints into the namespaceApi that already includes projectV2Api - apiFile: "./namespace.api.ts", - apiImport: "projectAndNamespaceApi", - outputFile: "./storagesV2.api.ts", - exportName: "projectStoragesApi", + apiFile: "./storagesV2.empty-api.ts", + apiImport: "storagesV2EmptyApi", + outputFile: "./storagesV2.generated-api.ts", + exportName: "storagesV2GeneratedApi", hooks: true, schemaFile: path.join(__dirname, "storagesV2.openapi.json"), }; diff --git a/client/src/features/storagesV2/api/storagesV2.api.ts b/client/src/features/storagesV2/api/storagesV2.api.ts new file mode 100644 index 0000000000..220d347add --- /dev/null +++ b/client/src/features/storagesV2/api/storagesV2.api.ts @@ -0,0 +1,77 @@ +/*! + * 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 type { + GetStorageApiArg, + GetStorageApiResponse, + GetStoragesV2ApiArg, + GetStoragesV2ApiResponse, +} from "./storagesV2.generated-api"; +import { storagesV2GeneratedApi } from "./storagesV2.generated-api"; + +// Fixes the generated code with query param object. +const withFixedEndpoints = storagesV2GeneratedApi.injectEndpoints({ + overrideExisting: true, + endpoints: (builder) => ({ + getStorage: builder.query({ + query: ({ storageParams }) => ({ + url: `/storage`, + params: storageParams, + }), + }), + getStoragesV2: builder.query( + { + query: ({ storageV2Params }) => ({ + url: `/storages_v2`, + params: storageV2Params, + }), + } + ), + }), +}); + +export const storagesV2Api = withFixedEndpoints.enhanceEndpoints({ + addTagTypes: ["Storages"], + endpoints: { + deleteStoragesV2ByStorageId: { + invalidatesTags: ["Storages"], + }, + getStoragesV2: { + providesTags: ["Storages"], + }, + patchStoragesV2ByStorageId: { + invalidatesTags: ["Storages"], + }, + postStoragesV2: { + invalidatesTags: ["Storages"], + }, + postStoragesV2ByStorageIdSecrets: { + invalidatesTags: ["Storages"], + }, + }, +}); + +export const { + useGetStoragesV2Query, + usePostStoragesV2Mutation, + usePatchStoragesV2ByStorageIdMutation, + useDeleteStoragesV2ByStorageIdMutation, + usePostStoragesV2ByStorageIdSecretsMutation, + useDeleteStoragesV2ByStorageIdSecretsMutation, +} = storagesV2Api; +export type * from "./storagesV2.generated-api"; diff --git a/client/src/features/storagesV2/api/storagesV2.empty-api.ts b/client/src/features/storagesV2/api/storagesV2.empty-api.ts new file mode 100644 index 0000000000..997d61301c --- /dev/null +++ b/client/src/features/storagesV2/api/storagesV2.empty-api.ts @@ -0,0 +1,26 @@ +/*! + * 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 { createApi, fetchBaseQuery } from "@reduxjs/toolkit/query/react"; + +// initialize an empty api service that we'll inject endpoints into later as needed +export const storagesV2EmptyApi = createApi({ + baseQuery: fetchBaseQuery({ baseUrl: "/ui-server/api/data" }), + endpoints: () => ({}), + reducerPath: "storagesV2Api", +}); diff --git a/client/src/features/projectsV2/api/storagesV2.api.ts b/client/src/features/storagesV2/api/storagesV2.generated-api.ts similarity index 92% rename from client/src/features/projectsV2/api/storagesV2.api.ts rename to client/src/features/storagesV2/api/storagesV2.generated-api.ts index 91789451eb..ecdf4fa43a 100644 --- a/client/src/features/projectsV2/api/storagesV2.api.ts +++ b/client/src/features/storagesV2/api/storagesV2.generated-api.ts @@ -1,4 +1,4 @@ -import { projectAndNamespaceApi as api } from "./namespace.api"; +import { storagesV2EmptyApi as api } from "./storagesV2.empty-api"; const injectedRtkApi = api.injectEndpoints({ endpoints: (build) => ({ getStoragesV2ByStorageId: build.query< @@ -29,7 +29,7 @@ const injectedRtkApi = api.injectEndpoints({ getStoragesV2: build.query({ query: (queryArg) => ({ url: `/storages_v2`, - params: queryArg.storageV2Params, + params: { storage_v2_params: queryArg.storageV2Params }, }), }), postStoragesV2: build.mutation< @@ -130,7 +130,7 @@ const injectedRtkApi = api.injectEndpoints({ query: (queryArg) => ({ url: `/storage_schema/validate`, method: "POST", - body: queryArg.rCloneConfig, + body: queryArg.rCloneConfigValidate, }), }), postStorageSchemaTestConnection: build.mutation< @@ -156,32 +156,32 @@ const injectedRtkApi = api.injectEndpoints({ }), overrideExisting: false, }); -export { injectedRtkApi as projectStoragesApi }; +export { injectedRtkApi as storagesV2GeneratedApi }; export type GetStoragesV2ByStorageIdApiResponse = /** status 200 Found the cloud storage */ CloudStorageGetV2Read; export type GetStoragesV2ByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type PatchStoragesV2ByStorageIdApiResponse = /** status 201 The cloud storage entry was updated */ CloudStorageGetRead; export type PatchStoragesV2ByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; cloudStoragePatch: CloudStoragePatch; }; export type DeleteStoragesV2ByStorageIdApiResponse = - /** status 204 The rcloud storage was removed or did not exist in the first place */ void; + /** status 204 The cloud storage was removed or did not exist in the first place */ void; export type DeleteStoragesV2ByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type GetStoragesV2ApiResponse = /** status 200 the storage configurations for the project */ CloudStorageGetV2Read[]; export type GetStoragesV2ApiArg = { /** query parameters */ storageV2Params?: { - project_id: UlidId; + project_id: Ulid; }; }; export type PostStoragesV2ApiResponse = @@ -193,46 +193,46 @@ export type GetStoragesV2ByStorageIdSecretsApiResponse = /** status 200 The saved storage secrets */ CloudStorageSecretGetList; export type GetStoragesV2ByStorageIdSecretsApiArg = { /** The id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type PostStoragesV2ByStorageIdSecretsApiResponse = /** status 201 The secrets for cloud storage were saved */ CloudStorageSecretGetList; export type PostStoragesV2ByStorageIdSecretsApiArg = { /** The id of the storage */ - storageId: UlidId; + storageId: Ulid; cloudStorageSecretPostList: CloudStorageSecretPostList; }; export type DeleteStoragesV2ByStorageIdSecretsApiResponse = /** status 204 The secrets were removed or did not exist in the first place or the storage doesn't exist */ void; export type DeleteStoragesV2ByStorageIdSecretsApiArg = { /** The id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type GetStorageByStorageIdApiResponse = /** status 200 Found the cloud storage */ CloudStorageGetRead; export type GetStorageByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type PutStorageByStorageIdApiResponse = /** status 201 The cloud storage entry was created */ CloudStorageGetRead; export type PutStorageByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; body: CloudStorage | CloudStorageUrl; }; export type PatchStorageByStorageIdApiResponse = /** status 201 The cloud storage entry was created */ CloudStorageGetRead; export type PatchStorageByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; body: CloudStoragePatch; }; export type DeleteStorageByStorageIdApiResponse = - /** status 204 The rcloud storage was removed or did not exist in the first place */ void; + /** status 204 The cloud storage was removed or did not exist in the first place */ void; export type DeleteStorageByStorageIdApiArg = { /** the id of the storage */ - storageId: UlidId; + storageId: Ulid; }; export type GetStorageApiResponse = /** status 200 the storage configurations for the project */ CloudStorageGetRead[]; @@ -253,7 +253,7 @@ export type GetStorageSchemaApiArg = void; export type PostStorageSchemaValidateApiResponse = /** status 204 The configuration is valid */ void; export type PostStorageSchemaValidateApiArg = { - rCloneConfig: RCloneConfig; + rCloneConfigValidate: RCloneConfigValidate; }; export type PostStorageSchemaTestConnectionApiResponse = /** status 204 The configuration is valid */ void; @@ -271,9 +271,9 @@ export type PostStorageSchemaObscureApiArg = { }; }; export type GitlabProjectId = string; -export type UlidId = string; +export type Ulid = string; export type ProjectId = { - project_id: GitlabProjectId | UlidId; + project_id: GitlabProjectId | Ulid; }; export type StorageType = string; export type StorageTypeRead = string; @@ -304,10 +304,10 @@ export type CloudStorageRead = ProjectId & { readonly?: boolean; }; export type CloudStorageWithId = CloudStorage & { - storage_id: UlidId; + storage_id: Ulid; }; export type CloudStorageWithIdRead = CloudStorageRead & { - storage_id: UlidId; + storage_id: Ulid; }; export type RCloneOption = { /** name of the option */ @@ -317,7 +317,7 @@ export type RCloneOption = { /** The cloud provider the option is for (See 'provider' RCloneOption in the schema for potential values) */ provider?: string; /** default value for the option */ - default?: number | string | boolean | object | any; + default?: number | string | boolean | object | any; // eslint-disable-line @typescript-eslint/no-explicit-any /** string representation of the default value */ default_str?: string; /** These list potential values for this option, like an enum. With `exclusive: true`, only a value from the list is allowed. */ @@ -353,7 +353,7 @@ export type CloudStorageGetRead = { export type CloudStorageSecretGet = { /** Name of the field to store credential for */ name: string; - secret_id: UlidId; + secret_id: Ulid; }; export type CloudStorageGetV2 = CloudStorageGet & { secrets?: CloudStorageSecretGet[]; @@ -370,7 +370,7 @@ export type ErrorResponse = { }; export type SourcePath = string; export type CloudStoragePatch = { - project_id?: GitlabProjectId | UlidId; + project_id?: GitlabProjectId | Ulid; storage_type?: StorageType; name?: StorageName; configuration?: RCloneConfig; @@ -381,7 +381,7 @@ export type CloudStoragePatch = { readonly?: boolean; }; export type CloudStoragePatchRead = { - project_id?: GitlabProjectId | UlidId; + project_id?: GitlabProjectId | Ulid; storage_type?: StorageTypeRead; name?: StorageName; configuration?: RCloneConfig; @@ -418,6 +418,9 @@ export type RCloneEntry = { options?: RCloneOption[]; }; export type RCloneSchema = RCloneEntry[]; +export type RCloneConfigValidate = { + [key: string]: number | (string | null) | boolean | object; +}; export const { useGetStoragesV2ByStorageIdQuery, usePatchStoragesV2ByStorageIdMutation, diff --git a/client/src/features/projectsV2/api/storagesV2.openapi.json b/client/src/features/storagesV2/api/storagesV2.openapi.json similarity index 96% rename from client/src/features/projectsV2/api/storagesV2.openapi.json rename to client/src/features/storagesV2/api/storagesV2.openapi.json index 0fdf13ab5e..2dfe423544 100644 --- a/client/src/features/projectsV2/api/storagesV2.openapi.json +++ b/client/src/features/storagesV2/api/storagesV2.openapi.json @@ -21,7 +21,7 @@ "name": "storage_id", "required": true, "schema": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" }, "description": "the id of the storage" } @@ -102,7 +102,7 @@ "additionalProperties": false, "properties": { "project_id": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" } }, "required": ["project_id"] @@ -183,7 +183,7 @@ "name": "storage_id", "required": true, "schema": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" }, "description": "The id of the storage" } @@ -266,7 +266,7 @@ "name": "storage_id", "required": true, "schema": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" }, "description": "the id of the storage" } @@ -486,7 +486,7 @@ "content": { "application/json": { "schema": { - "$ref": "#/components/schemas/RCloneConfig" + "$ref": "#/components/schemas/RCloneConfigValidate" } } } @@ -590,7 +590,7 @@ "$ref": "#/components/schemas/GitlabProjectId" }, { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" } ] } @@ -621,6 +621,27 @@ ] } }, + "RCloneConfigValidate": { + "type": "object", + "description": "Dictionary of rclone key:value pairs (based on schema from '/storage_schema')", + "additionalProperties": { + "oneOf": [ + { + "type": "integer" + }, + { + "type": "string", + "nullable": true + }, + { + "type": "boolean" + }, + { + "type": "object" + } + ] + } + }, "CloudStorageUrl": { "allOf": [ { @@ -699,7 +720,7 @@ "$ref": "#/components/schemas/GitlabProjectId" }, { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" } ] }, @@ -736,7 +757,7 @@ "required": ["storage_id"], "properties": { "storage_id": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" } } } @@ -818,7 +839,7 @@ "maxLength": 99 }, "secret_id": { - "$ref": "#/components/schemas/UlidId" + "$ref": "#/components/schemas/Ulid" } }, "required": ["name", "secret_id"] @@ -950,12 +971,12 @@ } } }, - "UlidId": { - "description": "ULID identifier of an object", + "Ulid": { + "description": "ULID identifier", "type": "string", "minLength": 26, "maxLength": 26, - "pattern": "^[A-Z0-9]+$" + "pattern": "^[0-7][0-9A-HJKMNP-TV-Z]{25}$" }, "GitlabProjectId": { "description": "Project id of a gitlab project (only int project id allowed, encoded as string for future-proofing)", diff --git a/client/src/features/usersV2/show/UserShow.tsx b/client/src/features/usersV2/show/UserShow.tsx index 85eac715ed..5741539182 100644 --- a/client/src/features/usersV2/show/UserShow.tsx +++ b/client/src/features/usersV2/show/UserShow.tsx @@ -29,7 +29,7 @@ import { Badge } from "reactstrap"; import { Loader } from "../../../components/Loader"; import ContainerWrap from "../../../components/container/ContainerWrap"; import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; -import { useGetNamespacesByNamespaceSlugQuery } from "../../projectsV2/api/projectV2.enhanced-api"; +import { useGetNamespacesByNamespaceSlugQuery } from "../../groupsV2/api/groupsV2.api"; import ProjectV2ListDisplay from "../../projectsV2/list/ProjectV2ListDisplay"; import UserNotFound from "../../projectsV2/notFound/UserNotFound"; import { diff --git a/client/src/utils/helpers/EnhancedState.ts b/client/src/utils/helpers/EnhancedState.ts index 3907c18637..f2a83162b6 100644 --- a/client/src/utils/helpers/EnhancedState.ts +++ b/client/src/utils/helpers/EnhancedState.ts @@ -35,16 +35,19 @@ import { dashboardMessageSlice } from "../../features/dashboard/message/dashboar import computeResourcesApi from "../../features/dataServices/computeResources.api"; import { datasetsCoreApi } from "../../features/datasets/datasetsCore.api"; import { displaySlice } from "../../features/display/displaySlice"; +import { groupsV2EmptyApi as groupsV2Api } from "../../features/groupsV2/api/groupsV2.empty-api"; import { inactiveKgProjectsApi } from "../../features/inactiveKgProjects/InactiveKgProjectsApi"; import { kgInactiveProjectsSlice } from "../../features/inactiveKgProjects/inactiveKgProjectsSlice"; import { kgSearchApi } from "../../features/kgSearch"; +import { platformEmptyApi as platformApi } from "../../features/platform/api/platform-empty.api"; +import { statuspageEmptyApi as statuspageApi } from "../../features/platform/statuspage-api/statuspage-empty.api"; import projectCloudStorageApi from "../../features/project/components/cloudStorage/projectCloudStorage.api"; import { datasetFormSlice } from "../../features/project/dataset"; import { projectCoreApi } from "../../features/project/projectCoreApi"; import projectGitLabApi from "../../features/project/projectGitLab.api"; import { projectKgApi } from "../../features/project/projectKg.api"; import { projectsApi } from "../../features/projects/projects.api"; -import { projectV2Api } from "../../features/projectsV2/api/projectV2.enhanced-api"; +import { projectsV2EmptyApi as projectsV2Api } from "../../features/projectsV2/api/projectsV2.empty-api"; import { projectV2NewSlice } from "../../features/projectsV2/new/projectV2New.slice"; import { recentUserActivityApi } from "../../features/recentUserActivity/RecentUserActivityApi"; import repositoriesApi from "../../features/repositories/repositories.api"; @@ -57,6 +60,7 @@ import startSessionSlice from "../../features/session/startSession.slice"; import { startSessionOptionsSlice } from "../../features/session/startSessionOptionsSlice"; import sessionsV2Api from "../../features/sessionsV2/sessionsV2.api"; import startSessionOptionsV2Slice from "../../features/sessionsV2/startSessionOptionsV2.slice"; +import { storagesV2EmptyApi as storagesV2Api } from "../../features/storagesV2/api/storagesV2.empty-api"; import termsApi from "../../features/terms/terms.api"; import { dataServicesUserApi } from "../../features/user/dataServicesUser.api"; import keycloakUserApi from "../../features/user/keycloakUser.api"; @@ -65,8 +69,6 @@ import { versionsApi } from "../../features/versions/versions.api"; import { workflowsApi } from "../../features/workflows/WorkflowsApi"; import { workflowsSlice } from "../../features/workflows/WorkflowsSlice"; import featureFlagsSlice from "../feature-flags/featureFlags.slice"; -import { platformEmptyApi as platformApi } from "../../features/platform/api/platform-empty.api"; -import { statuspageEmptyApi as statuspageApi } from "../../features/platform/statuspage-api/statuspage-empty.api"; // eslint-disable-next-line @typescript-eslint/no-explicit-any export const createStore = ( @@ -94,6 +96,7 @@ export const createStore = ( [connectedServicesApi.reducerPath]: connectedServicesApi.reducer, [dataServicesUserApi.reducerPath]: dataServicesUserApi.reducer, [datasetsCoreApi.reducerPath]: datasetsCoreApi.reducer, + [groupsV2Api.reducerPath]: groupsV2Api.reducer, [inactiveKgProjectsApi.reducerPath]: inactiveKgProjectsApi.reducer, [keycloakUserApi.reducerPath]: keycloakUserApi.reducer, [kgSearchApi.reducerPath]: kgSearchApi.reducer, @@ -103,7 +106,7 @@ export const createStore = ( [projectGitLabApi.reducerPath]: projectGitLabApi.reducer, [projectKgApi.reducerPath]: projectKgApi.reducer, [projectsApi.reducerPath]: projectsApi.reducer, - [projectV2Api.reducerPath]: projectV2Api.reducer, + [projectsV2Api.reducerPath]: projectsV2Api.reducer, [recentUserActivityApi.reducerPath]: recentUserActivityApi.reducer, [repositoriesApi.reducerPath]: repositoriesApi.reducer, [searchV2Api.reducerPath]: searchV2Api.reducer, @@ -112,6 +115,7 @@ export const createStore = ( [sessionSidecarApi.reducerPath]: sessionSidecarApi.reducer, [sessionsV2Api.reducerPath]: sessionsV2Api.reducer, [statuspageApi.reducerPath]: statuspageApi.reducer, + [storagesV2Api.reducerPath]: storagesV2Api.reducer, [termsApi.reducerPath]: termsApi.reducer, [userPreferencesApi.reducerPath]: userPreferencesApi.reducer, [versionsApi.reducerPath]: versionsApi.reducer, @@ -133,6 +137,7 @@ export const createStore = ( // this is causing some problems, and I do not know why .concat(dataServicesUserApi.middleware) .concat(datasetsCoreApi.middleware) + .concat(groupsV2Api.middleware) .concat(inactiveKgProjectsApi.middleware) .concat(keycloakUserApi.middleware) .concat(kgSearchApi.middleware) @@ -142,7 +147,7 @@ export const createStore = ( .concat(projectGitLabApi.middleware) .concat(projectKgApi.middleware) .concat(projectsApi.middleware) - .concat(projectV2Api.middleware) + .concat(projectsV2Api.middleware) .concat(recentUserActivityApi.middleware) .concat(repositoriesApi.middleware) .concat(searchV2Api.middleware) @@ -152,6 +157,7 @@ export const createStore = ( .concat(sessionSidecarApi.middleware) .concat(sessionsV2Api.middleware) .concat(statuspageApi.middleware) + .concat(storagesV2Api.middleware) .concat(termsApi.middleware) .concat(userPreferencesApi.middleware) .concat(versionsApi.middleware) diff --git a/tests/cypress/e2e/projectV2DataSourceCredentials.spec.ts b/tests/cypress/e2e/projectV2DataSourceCredentials.spec.ts index 283aa95d56..6485b44de0 100644 --- a/tests/cypress/e2e/projectV2DataSourceCredentials.spec.ts +++ b/tests/cypress/e2e/projectV2DataSourceCredentials.spec.ts @@ -215,11 +215,11 @@ describe("Set up data sources with credentials", () => { .cloudStorage({ isV2: true, fixture: "cloudStorage/cloud-storage-with-secrets-values-full.json", - name: "getCloudStorageV2", + name: "getCloudStorageV2WithSecrets", }) .cloudStorageSecrets({ fixture: "cloudStorage/cloud-storage-secrets.json", - name: "getCloudStorageSecrets2", + name: "getCloudStorageSecretsFull", }); cy.getDataCy("cloud-storage-credentials-modal") @@ -233,7 +233,7 @@ describe("Set up data sources with credentials", () => { .click(); cy.wait("@testCloudStorage"); cy.wait("@postCloudStorageSecrets"); - cy.wait("@getCloudStorageV2"); + cy.wait("@getCloudStorageV2WithSecrets"); // Credentials should be stored cy.getDataCy("data-storage-name").should("contain.text", "example-storage"); diff --git a/tests/cypress/support/renkulab-fixtures/projectV2.ts b/tests/cypress/support/renkulab-fixtures/projectV2.ts index cfaa4048f4..1ee018499e 100644 --- a/tests/cypress/support/renkulab-fixtures/projectV2.ts +++ b/tests/cypress/support/renkulab-fixtures/projectV2.ts @@ -208,7 +208,7 @@ export function ProjectV2(Parent: T) { }; cy.intercept( "GET", - `/ui-server/api/data/projects/${namespace}/${projectSlug}`, + `/ui-server/api/data/namespaces/${namespace}/projects/${projectSlug}`, response ).as(name); return this; @@ -224,7 +224,7 @@ export function ProjectV2(Parent: T) { const response = { fixture }; cy.intercept( "GET", - `/ui-server/api/data/projects/${namespace}/${projectSlug}`, + `/ui-server/api/data/namespaces/${namespace}/projects/${projectSlug}`, response ).as(name); return this;