diff --git a/README.md b/README.md index b9227823e3..dfa456e578 100644 --- a/README.md +++ b/README.md @@ -460,19 +460,21 @@ flowchart LR subgraph L1 A(/)-->DA(/datasets) A-->HE(/help) - A-->LOOUT(/logout) + A-->LOGOUT(/logout) A-->PR(/projects) A-->SEA(/search) A-->SE(/sessions) + A-->V1(/v1) end subgraph L2 - PR-->PR1(/new) PR-->PRID(/:id) DA-->DAID(/:id) HE-->HE1(/changes) HE-->HE2(/docs) HE-->HE3(/features) HE-->HE4(/status) + V1-->V1_HE(/help) + V1-->V1_PR_NEW(/projects/new) end subgraph L3 PRID-->PRID1(/overview) diff --git a/client/src/App.jsx b/client/src/App.jsx index 5fd7751c14..0419b1ac4d 100644 --- a/client/src/App.jsx +++ b/client/src/App.jsx @@ -49,8 +49,7 @@ import LazyNotFound from "./not-found/LazyNotFound"; import NotificationsManager from "./notifications/NotificationsManager"; import Cookie from "./privacy/Cookie"; import LazyProjectView from "./project/LazyProjectView"; -import LazyProjectList from "./project/list/LazyProjectList"; -import LazyNewProject from "./project/new/LazyNewProject"; +import { ABSOLUTE_ROUTES } from "./routing/routes.constants"; import AppContext from "./utils/context/appContext"; import useLegacySelector from "./utils/customHooks/useLegacySelector.hook"; import { setupWebSocket } from "./websocket"; @@ -92,18 +91,6 @@ function CentralContentContainer({ user, socket }) { )} - {["/projects", "/projects/starred", "/projects/all"].map((path) => ( - - - - - - ))} - - - - - - + diff --git a/client/src/components/navbar/NavBarItems.tsx b/client/src/components/navbar/NavBarItems.tsx index 2edabe88a7..3538edf615 100644 --- a/client/src/components/navbar/NavBarItems.tsx +++ b/client/src/components/navbar/NavBarItems.tsx @@ -74,7 +74,7 @@ export function RenkuToolbarItemPlus() { Project diff --git a/client/src/components/quicknav/QuickNav.container.jsx b/client/src/components/quicknav/QuickNav.container.jsx index 0eef85ec4f..a7a6388983 100644 --- a/client/src/components/quicknav/QuickNav.container.jsx +++ b/client/src/components/quicknav/QuickNav.container.jsx @@ -34,7 +34,7 @@ export const defaultSuggestionQuickBar = { type: "fixed", path: "", id: "link-projects", - url: "/search", + url: "/v1/search", label: "My Projects", icon: "/project-icon.svg", }, @@ -42,7 +42,7 @@ export const defaultSuggestionQuickBar = { type: "fixed", path: "", id: "link-datasets", - url: "/search", + url: "/v1/search", label: "My datasets", icon: "/dataset-icon.svg", }, @@ -57,7 +57,7 @@ export const defaultAnonymousSuggestionQuickBar = { type: "fixed", path: "", id: "link-projects", - url: "/search", + url: "/v1/search", label: "Projects", icon: "/project-icon.svg", }, @@ -65,7 +65,7 @@ export const defaultAnonymousSuggestionQuickBar = { type: "fixed", path: "", id: "link-datasets", - url: "/search", + url: "/v1/search", label: "Datasets", icon: "/dataset-icon.svg", }, @@ -98,7 +98,7 @@ export function QuickNavContainer({ user }) { type: "last-queries", path: "", id: "last-queries", - url: "/search", + url: "/v1/search", label: query, query, }); @@ -132,8 +132,8 @@ export function QuickNavContainer({ user }) { e.preventDefault(); setPhrase(currentPhrase); refetchLastQueries(e.currentTarget); - if (location.pathname === "/search") return; - navigate("/search"); + if (location.pathname === "/v1/search") return; + navigate("/v1/search"); }; const onSuggestionsFetchRequested = () => { diff --git a/client/src/features/dashboard/components/InactiveKgProjects.tsx b/client/src/features/dashboard/components/InactiveKgProjects.tsx index 855cbcbe36..77201b9a5e 100644 --- a/client/src/features/dashboard/components/InactiveKgProjects.tsx +++ b/client/src/features/dashboard/components/InactiveKgProjects.tsx @@ -18,6 +18,7 @@ import { Link } from "react-router-dom"; import { WarnAlert } from "../../../components/Alert"; +import { ABSOLUTE_ROUTES } from "../../../routing/routes.constants"; import useAppSelector from "../../../utils/customHooks/useAppSelector.hook"; import useLegacySelector from "../../../utils/customHooks/useLegacySelector.hook"; import { useGetInactiveKgProjectsQuery } from "../../inactiveKgProjects/InactiveKgProjectsApi"; @@ -38,7 +39,7 @@ function InactiveProjectsWarning({
Metadata indexing is not activated on {totalProjectsText}.{" "} - + Activate indexing on your projects {" "} to properly integrate them with Renku. diff --git a/client/src/features/dashboard/components/ProjectsDashboard.tsx b/client/src/features/dashboard/components/ProjectsDashboard.tsx index 5f6c93c6ea..09f5fc47e6 100644 --- a/client/src/features/dashboard/components/ProjectsDashboard.tsx +++ b/client/src/features/dashboard/components/ProjectsDashboard.tsx @@ -92,21 +92,21 @@ function ProjectAlert({ total }: ProjectAlertProps) { ,{" "} {" "} or{" "} {" "} for a specific project or dataset. diff --git a/client/src/features/rootV1/ProjectRootV1.tsx b/client/src/features/rootV1/ProjectRootV1.tsx new file mode 100644 index 0000000000..a34125a619 --- /dev/null +++ b/client/src/features/rootV1/ProjectRootV1.tsx @@ -0,0 +1,71 @@ +/*! + * Copyright 2025 - 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 { Route, Routes } from "react-router-dom-v5-compat"; +import ContainerWrap from "../../components/container/ContainerWrap"; +import LazyNotFound from "../../not-found/LazyNotFound"; +import LazyProjectList from "../../project/list/LazyProjectList"; +import LazyNewProject from "../../project/new/LazyNewProject"; +import { RELATIVE_ROUTES } from "../../routing/routes.constants"; + +export default function RootV1() { + return ( + + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + + + } + /> + + ); +} diff --git a/client/src/features/rootV1/RootV1.tsx b/client/src/features/rootV1/RootV1.tsx index fd6608e371..9ebb6189a0 100644 --- a/client/src/features/rootV1/RootV1.tsx +++ b/client/src/features/rootV1/RootV1.tsx @@ -33,6 +33,8 @@ import LazySearchPage from "../kgSearch/LazySearchPage"; import LazySecrets from "../secrets/LazySecrets"; import LazyAnonymousSessionsList from "../session/components/LazyAnonymousSessionsList"; +import ProjectRootV1 from "./ProjectRootV1"; + export default function RootV1({ user, }: { @@ -101,6 +103,14 @@ export default function RootV1({ } /> + + + + } + /> @@ -121,12 +124,11 @@ function FooterNavbarLoggedInLinks({ privacyLink }) { } function FooterNavbar() { - const location = useLocation(); - - return ; + return ; } -function FooterNavbarInner({ location }) { +function FooterNavbarInner() { + const location = useLocation(); const projectMetadata = useLegacySelector( (state) => state.stateModel.project?.metadata ); @@ -190,7 +192,10 @@ function FooterNavbarInner({ location }) { location.pathname === Url.get(Url.pages.landing) ? ( ) : ( - + )}
diff --git a/client/src/namespace/Namespace.present.jsx b/client/src/namespace/Namespace.present.jsx index 602319fda5..1f323a378e 100644 --- a/client/src/namespace/Namespace.present.jsx +++ b/client/src/namespace/Namespace.present.jsx @@ -31,20 +31,21 @@ import cx from "classnames"; import { ExternalLink } from "../components/ExternalLinks"; import { Loader } from "../components/Loader"; import NotFound from "../not-found/NotFound"; +import { ABSOLUTE_ROUTES } from "../routing/routes.constants"; const fakeHistory = createMemoryHistory({ initialEntries: ["/"], initialIndex: 0, }); fakeHistory.push({ - pathname: "/projects", + pathname: ABSOLUTE_ROUTES.v1.projects.root, search: "?page=1", }); const NamespaceProjects = (props) => { const { namespace } = props; // TODO: I should get the URLs from the redux store: #779 - const searchUrl = "/projects/all"; + const searchUrl = ABSOLUTE_ROUTES.v1.projects.all; const searchProjectUrl = (project) => { return `${searchUrl}?q=${project}`; }; diff --git a/client/src/not-found/NotFound.tsx b/client/src/not-found/NotFound.tsx index d28b8c1288..87d9812287 100644 --- a/client/src/not-found/NotFound.tsx +++ b/client/src/not-found/NotFound.tsx @@ -24,7 +24,7 @@ */ import cx from "classnames"; import { ReactNode } from "react"; -import { Link } from "react-router-dom"; +import { Link, useLocation } from "react-router-dom-v5-compat"; import { ArrowLeft } from "react-bootstrap-icons"; import ContainerWrap from "../components/container/ContainerWrap"; @@ -44,6 +44,7 @@ export default function NotFound({ description: description_, children, }: NotFoundProps) { + const location = useLocation(); const isV2 = !isRenkuLegacy(location.pathname); const title = title_ ?? "Page not found"; const description = diff --git a/client/src/routing/routes.constants.ts b/client/src/routing/routes.constants.ts index 1ede1423d0..aa62ee9d65 100644 --- a/client/src/routing/routes.constants.ts +++ b/client/src/routing/routes.constants.ts @@ -18,6 +18,29 @@ export const ABSOLUTE_ROUTES = { root: "/", + v1: { + root: "/v1", + inactiveKGProjects: "/v1/inactive-kg-projects", + search: "/v1/search", + projects: { + root: "/v1/projects", + all: "/v1/projects/all", + new: "/v1/projects/new", + starred: "/v1/projects/starred", + }, + help: { + root: "/v1/help", + contact: "/v1/help/contact", + status: "/v1/help/status", + release: "/v1/help/release", + tos: "/v1/help/tos", + privacy: "/v1/help/privacy", + }, + notifications: "/v1/notifications", + styleGuide: "/v1/style-guide", + secrets: "/v1/secrets", + sessions: "/v1/sessions", + }, v2: { root: "/v2", user: "/v2/user", @@ -56,22 +79,6 @@ export const ABSOLUTE_ROUTES = { connectedServices: "/v2/connected-services", secrets: "/v2/secrets", }, - v1: { - root: "/v1", - search: "/v1/search", - help: { - root: "/v1/help", - contact: "/v1/help/contact", - status: "/v1/help/status", - release: "/v1/help/release", - tos: "/v1/help/tos", - privacy: "/v1/help/privacy", - }, - notifications: "/v1/notifications", - styleGuide: "/v1/style-guide", - secrets: "/v1/secrets", - sessions: "/v1/sessions", - }, } as const; export const RELATIVE_ROUTES = { @@ -79,14 +86,20 @@ export const RELATIVE_ROUTES = { datasets: "/datasets", projects: "/projects", v1: { - root: "/v1", - search: "search", + root: "v1/*", + projects: { + root: "projects/*", + all: "all", + new: "new", + starred: "starred", + }, help: "help/*", - sessions: "sessions", + inactiveKGProjects: "inactive-kg-projects", notifications: "notifications", + search: "search", secrets: "secrets", + sessions: "sessions", styleGuide: "style-guide", - inactiveKGProjects: "inactive-kg-projects", }, v2: { root: "v2/*", diff --git a/client/src/utils/helpers/url/Url.js b/client/src/utils/helpers/url/Url.js index 48eee2d36a..8464928689 100644 --- a/client/src/utils/helpers/url/Url.js +++ b/client/src/utils/helpers/url/Url.js @@ -147,7 +147,7 @@ function searchValidation(data) { function projectsSearchUrlBuilder(subSection) { return (data) => { // create base url - let url = subSection ? `/projects/${subSection}` : "/projects"; + let url = subSection ? `/v1/projects/${subSection}` : "/v1/projects"; // add optional parameters if (!data || !Object.keys(data).length) return url; @@ -164,7 +164,7 @@ function projectsSearchUrlBuilder(subSection) { function projectNewUrlBuilder() { return (data) => { // create base url - let url = "/projects/new"; + let url = "/v1/projects/new"; if (!data || !data.data) return url; const search = new URLSearchParams(); search.append("data", data.data); @@ -271,33 +271,33 @@ const Url = { // Please assign only strings or UrlRule objects. pages: { landing: "/", - search: "/search", - inactiveKgProjects: "/inactive-kg-projects", + search: "/v1/search", + inactiveKgProjects: "/v1/inactive-kg-projects", help: { - base: "/help", - documentation: "/help/docs", - getting: "/help/getting", - privacy: "/help/privacy", - release: "/help/release", - status: "/help/status", - tos: "/help/tos", + base: "/v1/help", + documentation: "/v1/help/docs", + getting: "/v1/help/getting", + privacy: "/v1/help/privacy", + release: "/v1/help/release", + status: "/v1/help/status", + tos: "/v1/help/tos", }, projects: { base: new UrlRule(projectsSearchUrlBuilder(), [], searchValidation, [ - "/projects", - "/projects?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", + "/v1/projects", + "/v1/projects?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", ]), all: new UrlRule(projectsSearchUrlBuilder("all"), [], searchValidation, [ - "/projects/all", - "/projects/all?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", + "/v1/projects/all", + "/v1/projects/all?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", ]), starred: new UrlRule( projectsSearchUrlBuilder("starred"), [], searchValidation, [ - "/projects/starred", - "/projects/starred?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", + "/v1/projects/starred", + "/v1/projects/starred?q=test&page=1&orderBy=last_activity_at&orderSearchAsc=false&searchIn=projects", ] ), }, @@ -309,8 +309,8 @@ const Url = { ["/projects/namespace/path", "/projects/group/subgroup/path"] ), new: new UrlRule(projectNewUrlBuilder(), [], null, [ - "/projects/new", - "/projects/new?data=eyJ0aXRsZSI6InRlC3QifQ==", + "/v1/projects/new", + "/v1/projects/new?data=eyJ0aXRsZSI6InRlC3QifQ==", ]), datasets: { base: new UrlRule(projectDatabaseUrlBuilder(), ["path"], null, [ @@ -466,7 +466,7 @@ const Url = { base: "/datasets", }, searchEntities: { - base: "/search", + base: "/v1/search", }, secrets: { base: "/secrets", diff --git a/client/src/utils/helpers/url/Url.test.js b/client/src/utils/helpers/url/Url.test.js index 18ac977af3..6a944a3ccf 100644 --- a/client/src/utils/helpers/url/Url.test.js +++ b/client/src/utils/helpers/url/Url.test.js @@ -267,10 +267,10 @@ describe("Url helper class", () => { const { get, pages } = Url; expect(get(pages.landing)).toBe("/"); - expect(get(pages.projects)).toBe("/projects"); - expect(get(pages.projects.all)).toBe("/projects/all"); + expect(get(pages.projects)).toBe("/v1/projects"); + expect(get(pages.projects.all)).toBe("/v1/projects/all"); expect(get(pages.projects.all, { q: "test", searchIn: "projects" })).toBe( - "/projects/all?q=test&searchIn=projects" + "/v1/projects/all?q=test&searchIn=projects" ); expect(() => { diff --git a/client/src/websocket/handlers/kgActivationStatusHandler.ts b/client/src/websocket/handlers/kgActivationStatusHandler.ts index 3ea9b88f38..79ff55949a 100644 --- a/client/src/websocket/handlers/kgActivationStatusHandler.ts +++ b/client/src/websocket/handlers/kgActivationStatusHandler.ts @@ -25,6 +25,7 @@ import { updateProgress, } from "../../features/inactiveKgProjects/inactiveKgProjectsSlice"; import type { KgInactiveProjectsState } from "../../features/inactiveKgProjects/inactiveKgProjectsSlice"; +import { ABSOLUTE_ROUTES } from "../../routing/routes.constants"; type ActivationStatus = { [key: string]: number; @@ -105,9 +106,9 @@ function processStatusForNotifications( notifications.addSuccess( notifications.Topics.KG_ACTIVATION, "Project indexing has been activated.", - "/inactive-kg-projects", + ABSOLUTE_ROUTES.v1.inactiveKGProjects, "Go to activation page", - "/inactive-kg-projects", + ABSOLUTE_ROUTES.v1.inactiveKGProjects, "Check the status of projects that need to be indexed." ); } @@ -115,9 +116,9 @@ function processStatusForNotifications( notifications.addError( notifications.Topics.KG_ACTIVATION, "Project indexing has been activated, but with errors.", - "/inactive-kg-projects", + ABSOLUTE_ROUTES.v1.inactiveKGProjects, "Go to activation page", - "/inactive-kg-projects", + ABSOLUTE_ROUTES.v1.inactiveKGProjects, "Check the status of projects that need to be indexed" ); } diff --git a/tests/cypress/e2e/newProject.spec.ts b/tests/cypress/e2e/newProject.spec.ts index e143dfe4c7..624d383786 100644 --- a/tests/cypress/e2e/newProject.spec.ts +++ b/tests/cypress/e2e/newProject.spec.ts @@ -26,7 +26,7 @@ describe("Add new project", () => { beforeEach(() => { fixtures.config().versions().userTest().namespaces(); fixtures.projects().landingUserProjects(); - cy.visit("projects/new"); + cy.visit("/v1/projects/new"); }); it("create a new project that should change name", () => { @@ -183,7 +183,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages @@ -228,7 +228,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages @@ -249,7 +249,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages @@ -278,7 +278,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages @@ -310,7 +310,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages @@ -337,7 +337,7 @@ describe("Add new project shared link", () => { namespace: "internal-space", name: "getInternalNamespace", }); - cy.visit(`projects/new${customValues}`); + cy.visit(`/v1/projects/new${customValues}`); cy.wait("@getTemplates"); // Check feedback messages