From 3948e3b1348c28188ed12082541360601790b1b0 Mon Sep 17 00:00:00 2001 From: Mohamed Khelif Date: Wed, 27 Mar 2024 17:04:02 -0400 Subject: [PATCH] DEVPROD-5762 Replace all hardcoded slugs with an enum slug and consolidate slug name to associated resource (#18) --- .../useDistroSettingsAnalytics.ts | 3 +- .../patches/useProjectPatchesAnalytics.ts | 3 +- .../useProjectSettingsAnalytics.ts | 3 +- .../analytics/task/useAnnotationAnalytics.ts | 9 +-- .../src/analytics/task/useTaskAnalytics.ts | 7 +- apps/spruce/src/components/Content/index.tsx | 26 ++++--- apps/spruce/src/components/Header/Navbar.tsx | 11 +-- .../PatchNotificationModal.tsx | 7 +- .../Redirects/UserPatchesRedirect.tsx | 6 +- .../Redirects/WaterfallCommitsRedirect.tsx | 4 +- apps/spruce/src/constants/routes.ts | 60 +++++++++++----- apps/spruce/src/hooks/useConfigurePatch.ts | 4 +- .../index.ts} | 28 +++++--- .../useLegacyUIURL/useLegacyUIURL.test.tsx | 69 +++++++++++++++++++ .../src/hooks/useProjectRedirect/index.ts | 5 +- apps/spruce/src/pages/CommitQueue.tsx | 4 +- apps/spruce/src/pages/JobLogs.tsx | 4 +- apps/spruce/src/pages/ProjectPatches.tsx | 4 +- apps/spruce/src/pages/Task.tsx | 14 ++-- apps/spruce/src/pages/UserPatches.tsx | 3 +- apps/spruce/src/pages/Version.tsx | 28 ++++---- .../commits/WaterfallMenu/AddNotification.tsx | 3 +- apps/spruce/src/pages/commits/index.tsx | 6 +- .../useConfigurePatch/index.ts | 4 +- .../spruce/src/pages/configurePatch/index.tsx | 12 ++-- .../src/pages/container/EventsTable/index.tsx | 5 +- apps/spruce/src/pages/container/index.tsx | 11 +-- .../distroSettings/DeleteDistro/index.tsx | 3 +- .../distroSettings/NewDistro/CopyModal.tsx | 4 +- .../NewDistro/NewDistroButton.tsx | 3 +- apps/spruce/src/pages/distroSettings/Tabs.tsx | 6 +- .../spruce/src/pages/distroSettings/index.tsx | 7 +- .../src/pages/distroSettings/tabs/BaseTab.tsx | 6 +- .../tabs/EventLogTab/EventLogTab.tsx | 5 +- apps/spruce/src/pages/host/index.tsx | 15 ++-- .../src/pages/preferences/PreferencesTabs.tsx | 12 +++- apps/spruce/src/pages/preferences/index.tsx | 10 ++- .../pages/projectSettings/HeaderButtons.tsx | 5 +- .../spruce/src/pages/projectSettings/Tabs.tsx | 6 +- .../src/pages/projectSettings/index.tsx | 10 +-- .../tabs/EventLogTab/EventLogTab.tsx | 5 +- .../GeneralTab/Fields/DeleteProjectField.tsx | 5 +- apps/spruce/src/pages/spawn/index.tsx | 4 +- apps/spruce/src/pages/task/ActionButtons.tsx | 4 +- apps/spruce/src/pages/task/TaskTabs.tsx | 4 +- .../actionButtons/TaskNotificationModal.tsx | 3 +- .../src/pages/task/taskTabs/logs/LogTypes.tsx | 21 +++--- apps/spruce/src/pages/taskHistory/index.tsx | 9 +-- apps/spruce/src/pages/taskQueue/index.tsx | 18 ++--- .../spruce/src/pages/variantHistory/index.tsx | 9 +-- .../BuildVariantCard.stories.tsx | 7 +- .../pages/version/BuildVariantCard/index.tsx | 12 ++-- .../spruce/src/pages/version/TaskDuration.tsx | 7 +- apps/spruce/src/pages/version/Tasks.tsx | 7 +- apps/spruce/src/pages/version/VersionTabs.tsx | 23 ++++--- .../taskDuration/TaskDurationTable.tsx | 3 +- .../pages/version/tasks/PatchTasksTable.tsx | 3 +- 57 files changed, 366 insertions(+), 213 deletions(-) rename apps/spruce/src/hooks/{useLegacyUIURL.ts => useLegacyUIURL/index.ts} (60%) create mode 100644 apps/spruce/src/hooks/useLegacyUIURL/useLegacyUIURL.test.tsx diff --git a/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts b/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts index d77cd5183..fca6d1702 100644 --- a/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts +++ b/apps/spruce/src/analytics/distroSettings/useDistroSettingsAnalytics.ts @@ -1,5 +1,6 @@ import { useParams } from "react-router-dom"; import { useAnalyticsRoot } from "analytics/useAnalyticsRoot"; +import { slugs } from "constants/routes"; type Action = | { name: "Save distro"; section: string } @@ -7,6 +8,6 @@ type Action = | { name: "Duplicate distro"; newDistroId: string }; export const useDistroSettingsAnalytics = () => { - const { distroId } = useParams<{ distroId: string }>(); + const { [slugs.distroId]: distroId } = useParams(); return useAnalyticsRoot("DistroSettings", { distroId }); }; diff --git a/apps/spruce/src/analytics/patches/useProjectPatchesAnalytics.ts b/apps/spruce/src/analytics/patches/useProjectPatchesAnalytics.ts index a8854682c..b12d9d286 100644 --- a/apps/spruce/src/analytics/patches/useProjectPatchesAnalytics.ts +++ b/apps/spruce/src/analytics/patches/useProjectPatchesAnalytics.ts @@ -1,5 +1,6 @@ import { useParams } from "react-router-dom"; import { useAnalyticsRoot } from "analytics/useAnalyticsRoot"; +import { slugs } from "constants/routes"; type Action = | { name: "Change Page Size" } @@ -11,6 +12,6 @@ type Action = | { name: "Filter Patches"; filterBy: string }; export const useProjectPatchesAnalytics = () => { - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); return useAnalyticsRoot("ProjectPatches", { projectIdentifier }); }; diff --git a/apps/spruce/src/analytics/projectSettings/useProjectSettingsAnalytics.ts b/apps/spruce/src/analytics/projectSettings/useProjectSettingsAnalytics.ts index 424eb3731..9ff432272 100644 --- a/apps/spruce/src/analytics/projectSettings/useProjectSettingsAnalytics.ts +++ b/apps/spruce/src/analytics/projectSettings/useProjectSettingsAnalytics.ts @@ -1,5 +1,6 @@ import { useParams } from "react-router-dom"; import { useAnalyticsRoot } from "analytics/useAnalyticsRoot"; +import { slugs } from "constants/routes"; type Action = | { name: "Save project"; section: string } @@ -12,6 +13,6 @@ type Action = | { name: "Duplicate project"; projectIdToCopy: string }; export const useProjectSettingsAnalytics = () => { - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); return useAnalyticsRoot("ProjectSettings", { projectIdentifier }); }; diff --git a/apps/spruce/src/analytics/task/useAnnotationAnalytics.ts b/apps/spruce/src/analytics/task/useAnnotationAnalytics.ts index c6684e026..84b4e4dc5 100644 --- a/apps/spruce/src/analytics/task/useAnnotationAnalytics.ts +++ b/apps/spruce/src/analytics/task/useAnnotationAnalytics.ts @@ -1,6 +1,7 @@ import { useQuery } from "@apollo/client"; import { useParams } from "react-router-dom"; import { useAnalyticsRoot } from "analytics/useAnalyticsRoot"; +import { slugs } from "constants/routes"; import { BuildBaronQuery, BuildBaronQueryVariables, @@ -26,21 +27,21 @@ type Action = | { name: "Add Task Annotation Suspected Issue" }; export const useAnnotationAnalytics = () => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const [execution] = useQueryParam(RequiredQueryParams.Execution, 0); const { data: eventData } = useQuery< AnnotationEventDataQuery, AnnotationEventDataQueryVariables >(ANNOTATION_EVENT_DATA, { - variables: { taskId: id, execution }, + variables: { taskId, execution }, fetchPolicy: "cache-first", }); const { data: bbData } = useQuery( BUILD_BARON, { - variables: { taskId: id, execution }, + variables: { taskId, execution }, fetchPolicy: "cache-first", }, ); @@ -49,7 +50,7 @@ export const useAnnotationAnalytics = () => { const { buildBaronConfigured } = bbData?.buildBaron || {}; return useAnalyticsRoot("Annotations", { - taskId: id, + taskId, annotation, bbConfigured: buildBaronConfigured, }); diff --git a/apps/spruce/src/analytics/task/useTaskAnalytics.ts b/apps/spruce/src/analytics/task/useTaskAnalytics.ts index 4d609ac94..49f58d0cf 100644 --- a/apps/spruce/src/analytics/task/useTaskAnalytics.ts +++ b/apps/spruce/src/analytics/task/useTaskAnalytics.ts @@ -1,6 +1,7 @@ import { useQuery } from "@apollo/client"; import { useParams } from "react-router-dom"; import { useAnalyticsRoot } from "analytics/useAnalyticsRoot"; +import { slugs } from "constants/routes"; import { SaveSubscriptionForUserMutationVariables, TaskQuery, @@ -69,11 +70,11 @@ type Action = | { name: "Submit Relevant Commit Selector"; type: CommitType }; export const useTaskAnalytics = () => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const [execution] = useQueryParam(RequiredQueryParams.Execution, 0); const { data: eventData } = useQuery(TASK, { - variables: { taskId: id, execution }, + variables: { taskId, execution }, fetchPolicy: "cache-first", }); @@ -89,7 +90,7 @@ export const useTaskAnalytics = () => { taskStatus, execution, isLatestExecution: isLatestExecution.toString(), - taskId: id, + taskId, failedTestCount, projectIdentifier: identifier, }); diff --git a/apps/spruce/src/components/Content/index.tsx b/apps/spruce/src/components/Content/index.tsx index 552e04621..42dc61b5e 100644 --- a/apps/spruce/src/components/Content/index.tsx +++ b/apps/spruce/src/components/Content/index.tsx @@ -5,7 +5,7 @@ import { UserPatchesRedirect, WaterfallCommitsRedirect, } from "components/Redirects"; -import { redirectRoutes, routes } from "constants/routes"; +import { redirectRoutes, routes, slugs } from "constants/routes"; import { CommitQueue } from "pages/CommitQueue"; import { Commits } from "pages/Commits"; import { ConfigurePatch } from "pages/ConfigurePatch"; @@ -33,7 +33,7 @@ export const Content: React.FC = () => ( }> } /> }> - + } /> ( element={} /> }> - + }> - + ( } /> } /> }> - + } /> }> - + }> - + } /> }> - + } /> }> - + } /> }> - + } /> }> - + } /> ( /> } /> }> - + } /> ); - -const tab = ":tab"; diff --git a/apps/spruce/src/components/Header/Navbar.tsx b/apps/spruce/src/components/Header/Navbar.tsx index 518551b4e..871461919 100644 --- a/apps/spruce/src/components/Header/Navbar.tsx +++ b/apps/spruce/src/components/Header/Navbar.tsx @@ -10,7 +10,12 @@ import Icon from "components/Icon"; import HybridTree from "components/Icon/icons/HybridTree.svg"; import { CURRENT_PROJECT } from "constants/cookies"; import { wikiUrl } from "constants/externalResources"; -import { getCommitsRoute, getUserPatchesRoute, routes } from "constants/routes"; +import { + getCommitsRoute, + getUserPatchesRoute, + routes, + slugs, +} from "constants/routes"; import { size } from "constants/tokens"; import { useAuthStateContext } from "context/Auth"; import { UserQuery, SpruceConfigQuery } from "gql/generated/types"; @@ -33,9 +38,7 @@ export const Navbar: React.FC = () => { const { user } = userData || {}; const { userId } = user || {}; - const { projectIdentifier: projectFromUrl } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: projectFromUrl } = useParams(); const currProject = Cookies.get(CURRENT_PROJECT); // Update current project cookie if the project in the URL is not an objectId and is not equal diff --git a/apps/spruce/src/components/PatchActionButtons/addNotification/PatchNotificationModal.tsx b/apps/spruce/src/components/PatchActionButtons/addNotification/PatchNotificationModal.tsx index ffdbb842d..4abc5dad9 100644 --- a/apps/spruce/src/components/PatchActionButtons/addNotification/PatchNotificationModal.tsx +++ b/apps/spruce/src/components/PatchActionButtons/addNotification/PatchNotificationModal.tsx @@ -1,6 +1,7 @@ import { useParams } from "react-router-dom"; import { useVersionAnalytics } from "analytics"; import { NotificationModal } from "components/Notifications"; +import { slugs } from "constants/routes"; import { versionTriggers } from "constants/triggers"; import { subscriptionMethods as versionSubscriptionMethods } from "types/subscription"; @@ -13,14 +14,14 @@ export const PatchNotificationModal: React.FC = ({ onCancel, visible, }) => { - const { id: patchId } = useParams<{ id: string }>(); - const { sendEvent } = useVersionAnalytics(patchId); + const { [slugs.versionId]: versionId } = useParams(); + const { sendEvent } = useVersionAnalytics(versionId); return ( sendEvent({ name: "Add Notification", subscription }) } diff --git a/apps/spruce/src/components/Redirects/UserPatchesRedirect.tsx b/apps/spruce/src/components/Redirects/UserPatchesRedirect.tsx index e7c262b5d..c9365a01c 100644 --- a/apps/spruce/src/components/Redirects/UserPatchesRedirect.tsx +++ b/apps/spruce/src/components/Redirects/UserPatchesRedirect.tsx @@ -1,7 +1,7 @@ import { useParams, Navigate } from "react-router-dom"; -import { getUserPatchesRoute } from "constants/routes"; +import { getUserPatchesRoute, slugs } from "constants/routes"; export const UserPatchesRedirect: React.FC = () => { - const { id } = useParams<{ id: string }>(); - return ; + const { [slugs.userId]: userId } = useParams(); + return ; }; diff --git a/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx b/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx index b51a45c18..221211b46 100644 --- a/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx +++ b/apps/spruce/src/components/Redirects/WaterfallCommitsRedirect.tsx @@ -1,7 +1,7 @@ import { useParams, Navigate } from "react-router-dom"; -import { getCommitsRoute } from "constants/routes"; +import { getCommitsRoute, slugs } from "constants/routes"; export const WaterfallCommitsRedirect: React.FC = () => { - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); return ; }; diff --git a/apps/spruce/src/constants/routes.ts b/apps/spruce/src/constants/routes.ts index 330d9c449..36d874dd0 100644 --- a/apps/spruce/src/constants/routes.ts +++ b/apps/spruce/src/constants/routes.ts @@ -74,38 +74,66 @@ const paths = { version: "/version", waterfall: "/waterfall", }; + +export enum slugs { + buildId = "buildId", + podId = "podId", + distroId = "distroId", + groupId = "groupId", + hostId = "hostId", + patchId = "patchId", + projectIdentifier = "projectIdentifier", + tab = "tab", + taskId = "taskId", + taskName = "taskName", + variantName = "variantName", + versionId = "versionId", + userId = "userId", +} +export const idSlugs = [ + slugs.buildId, + slugs.podId, + slugs.distroId, + slugs.hostId, + slugs.patchId, + slugs.projectIdentifier, + slugs.taskId, + slugs.versionId, + slugs.userId, +]; + export const redirectRoutes = { distroSettings: paths.distros, projectSettings: paths.projects, - userPatches: `${paths.user}/:id`, - waterfall: `${paths.waterfall}/:projectIdentifier`, + userPatches: `${paths.user}/:${slugs.userId}`, + waterfall: `${paths.waterfall}/:${slugs.projectIdentifier}`, }; export const routes = { - commitQueue: `${paths.commitQueue}/:projectIdentifier`, + commitQueue: `${paths.commitQueue}/:${slugs.projectIdentifier}`, commits: paths.commits, - configurePatch: `${paths.patch}/:id/configure`, - container: `${paths.container}/:id`, - distroSettings: `${paths.distro}/:distroId/${PageNames.Settings}`, - host: `${paths.host}/:id`, + configurePatch: `${paths.patch}/:${slugs.patchId}/configure`, + container: `${paths.container}/:${slugs.podId}`, + distroSettings: `${paths.distro}/:${slugs.distroId}/${PageNames.Settings}`, + host: `${paths.host}/:${slugs.hostId}`, hosts: paths.hosts, - jobLogs: `${paths.jobLogs}/:buildId`, + jobLogs: `${paths.jobLogs}/:${slugs.buildId}`, login: paths.login, myPatches: `${paths.user}/${PageNames.Patches}`, - patch: `${paths.patch}/:id`, + patch: `${paths.patch}/:${slugs.versionId}`, preferences: paths.preferences, - projectPatches: `${paths.project}/:projectIdentifier/${PageNames.Patches}`, - projectSettings: `${paths.project}/:projectIdentifier/${PageNames.Settings}`, + projectPatches: `${paths.project}/:${slugs.projectIdentifier}/${PageNames.Patches}`, + projectSettings: `${paths.project}/:${slugs.projectIdentifier}/${PageNames.Settings}`, spawn: paths.spawn, spawnHost: `${paths.spawn}/${SpawnTab.Host}`, spawnVolume: `${paths.spawn}/${SpawnTab.Volume}`, - task: `${paths.task}/:id`, - taskHistory: `${paths.taskHistory}/:projectIdentifier/:taskName`, + task: `${paths.task}/:${slugs.taskId}`, + taskHistory: `${paths.taskHistory}/:${slugs.projectIdentifier}/:${slugs.taskName}`, taskQueue: paths.taskQueue, user: `${paths.user}/*`, - userPatches: `${paths.user}/:id/${PageNames.Patches}`, - variantHistory: `${paths.variantHistory}/:projectIdentifier/:variantName`, - version: `${paths.version}/:id`, + userPatches: `${paths.user}/:${slugs.userId}/${PageNames.Patches}`, + variantHistory: `${paths.variantHistory}/:${slugs.projectIdentifier}/:${slugs.variantName}`, + version: `${paths.version}/:${slugs.versionId}`, }; export const DEFAULT_PATCH_TAB = PatchTab.Tasks; diff --git a/apps/spruce/src/hooks/useConfigurePatch.ts b/apps/spruce/src/hooks/useConfigurePatch.ts index 140c9b139..0273a6039 100644 --- a/apps/spruce/src/hooks/useConfigurePatch.ts +++ b/apps/spruce/src/hooks/useConfigurePatch.ts @@ -1,6 +1,6 @@ import { useEffect, useReducer } from "react"; import { useNavigate, useLocation, useParams } from "react-router-dom"; -import { getPatchRoute } from "constants/routes"; +import { getPatchRoute, slugs } from "constants/routes"; import { ConfigurePatchQuery, ParameterInput, @@ -152,7 +152,7 @@ export const useConfigurePatch = ( ): HookResult => { const navigate = useNavigate(); const location = useLocation(); - const { tab } = useParams<{ tab: PatchTab | null }>(); + const { [slugs.tab]: tab } = useParams<{ [slugs.tab]: PatchTab | null }>(); const { id, project } = patch; const { variants } = project; diff --git a/apps/spruce/src/hooks/useLegacyUIURL.ts b/apps/spruce/src/hooks/useLegacyUIURL/index.ts similarity index 60% rename from apps/spruce/src/hooks/useLegacyUIURL.ts rename to apps/spruce/src/hooks/useLegacyUIURL/index.ts index 2089e33b1..bcc69ab78 100644 --- a/apps/spruce/src/hooks/useLegacyUIURL.ts +++ b/apps/spruce/src/hooks/useLegacyUIURL/index.ts @@ -1,7 +1,6 @@ import { useState, useEffect } from "react"; -import get from "lodash/get"; -import { matchPath, useLocation } from "react-router-dom"; -import { routes } from "constants/routes"; +import { matchPath, useLocation, Params } from "react-router-dom"; +import { idSlugs, routes, slugs } from "constants/routes"; import { environmentVariables } from "utils"; const { getUiUrl } = environmentVariables; @@ -24,7 +23,7 @@ export const useLegacyUIURL = (): string | null => { [`${routes.task}/*`]: `${uiURL}/task/${id}`, [routes.spawnHost]: `${uiURL}/spawn#?resourcetype=hosts`, [routes.spawnVolume]: `${uiURL}/spawn#?resourcetype=volumes`, - [`${routes.commits}/:id`]: `${uiURL}/waterfall/${id}`, + [`${routes.commits}/:${slugs.projectIdentifier}`]: `${uiURL}/waterfall/${id}`, [routes.hosts]: `${uiURL}/hosts`, [routes.host]: `${uiURL}/host/${id}`, }; @@ -32,11 +31,7 @@ export const useLegacyUIURL = (): string | null => { for (let i = 0; i < legacyUIKeys.length; i++) { const matchedPath = matchPath(legacyUIKeys[i], pathname); if (matchedPath !== null) { - setId( - get(matchedPath, "params.id", "") || - get(matchedPath, "params.identifier", "") || - get(matchedPath, "params.distroId", ""), - ); + setId(slugToId(matchedPath.params)); setLegacyUIUrl(legacyUIMap[legacyUIKeys[i]]); break; } @@ -45,3 +40,18 @@ export const useLegacyUIURL = (): string | null => { return legacyUIUrl; }; + +/** + * `slugToId` is a helper function that takes a Params object from react-router-dom's matchPath and returns a slug value from the slugs object if it is a suitable id. + * @param matchedPathParams - Params object from react-router-dom's matchPath + * @returns string | undefined + */ +const slugToId = (matchedPathParams: Params): string | undefined => { + for (let i = 0; i < idSlugs.length; i++) { + const key = idSlugs[i]; + if (matchedPathParams[key]) { + return matchedPathParams[key]; + } + } + return undefined; +}; diff --git a/apps/spruce/src/hooks/useLegacyUIURL/useLegacyUIURL.test.tsx b/apps/spruce/src/hooks/useLegacyUIURL/useLegacyUIURL.test.tsx new file mode 100644 index 000000000..d985a4d6b --- /dev/null +++ b/apps/spruce/src/hooks/useLegacyUIURL/useLegacyUIURL.test.tsx @@ -0,0 +1,69 @@ +import { MemoryRouter, useNavigate } from "react-router-dom"; +import { act, renderHook } from "test_utils"; +import { useLegacyUIURL } from "."; + +describe("useLegacyUIURL", () => { + it("returns a legacy URL on the commits page", () => { + const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + + ); + const { result } = renderHook(() => useLegacyUIURL(), { wrapper }); + expect(result.current).toBe("/waterfall/spruce"); + }); + + it("does not return a legacy URL on the project settings page", () => { + const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + + ); + const { result } = renderHook(() => useLegacyUIURL(), { wrapper }); + expect(result.current).toBeNull(); + }); + + it("clears the legacy URL when navigating from one with to one without", () => { + const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + + ); + const { result } = renderHook( + () => ({ + legacyURL: useLegacyUIURL(), + navigate: useNavigate(), + }), + { wrapper }, + ); + expect(result.current.legacyURL).toBe("/waterfall/spruce"); + + act(() => { + result.current.navigate("/settings/evergreen/general"); + }); + + expect(result.current.legacyURL).toBeNull(); + }); + + it("introduces a legacy URL when navigating from one without to one with", () => { + const wrapper: React.FC<{ children: React.ReactNode }> = ({ children }) => ( + + {children} + + ); + const { result } = renderHook( + () => ({ + legacyURL: useLegacyUIURL(), + navigate: useNavigate(), + }), + { wrapper }, + ); + expect(result.current.legacyURL).toBeNull(); + + act(() => { + result.current.navigate("/spawn/host"); + }); + + expect(result.current.legacyURL).toBe("/spawn#?resourcetype=hosts"); + }); +}); diff --git a/apps/spruce/src/hooks/useProjectRedirect/index.ts b/apps/spruce/src/hooks/useProjectRedirect/index.ts index a3b245097..63269db3a 100644 --- a/apps/spruce/src/hooks/useProjectRedirect/index.ts +++ b/apps/spruce/src/hooks/useProjectRedirect/index.ts @@ -1,5 +1,6 @@ import { useQuery } from "@apollo/client"; import { useParams, useLocation, useNavigate } from "react-router-dom"; +import { slugs } from "constants/routes"; import { ProjectQuery, ProjectQueryVariables } from "gql/generated/types"; import { PROJECT } from "gql/queries"; import { validators } from "utils"; @@ -19,9 +20,7 @@ interface UseProjectRedirectProps { export const useProjectRedirect = ({ sendAnalyticsEvent = () => {}, }: UseProjectRedirectProps) => { - const { projectIdentifier: project } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: project } = useParams(); const navigate = useNavigate(); const location = useLocation(); diff --git a/apps/spruce/src/pages/CommitQueue.tsx b/apps/spruce/src/pages/CommitQueue.tsx index 1e3a51b1e..83a313d63 100644 --- a/apps/spruce/src/pages/CommitQueue.tsx +++ b/apps/spruce/src/pages/CommitQueue.tsx @@ -8,7 +8,7 @@ import { useParams } from "react-router-dom"; import { PageTitle } from "components/PageTitle"; import { ProjectSelect } from "components/ProjectSelect"; import { PageWrapper } from "components/styles"; -import { getCommitQueueRoute } from "constants/routes"; +import { getCommitQueueRoute, slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { CommitQueueQuery, @@ -21,7 +21,7 @@ import { CommitQueueCard } from "./commitqueue/CommitQueueCard"; const { gray } = palette; export const CommitQueue: React.FC = () => { - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); const dispatchToast = useToastContext(); const { data, loading } = useQuery< CommitQueueQuery, diff --git a/apps/spruce/src/pages/JobLogs.tsx b/apps/spruce/src/pages/JobLogs.tsx index b10162d54..f4087dd1c 100644 --- a/apps/spruce/src/pages/JobLogs.tsx +++ b/apps/spruce/src/pages/JobLogs.tsx @@ -6,7 +6,7 @@ import { useParams } from "react-router-dom"; import { useJobLogsAnalytics } from "analytics/joblogs/useJobLogsAnalytics"; import { PageWrapper, StyledRouterLink } from "components/styles"; import { getParsleyBuildLogURL } from "constants/externalResources"; -import { getTaskRoute } from "constants/routes"; +import { getTaskRoute, slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -18,7 +18,7 @@ import { usePageTitle } from "hooks"; import { JobLogsTable } from "./jobLogs/JobLogsTable"; export const JobLogs = () => { - const { buildId } = useParams<{ buildId: string }>(); + const { [slugs.buildId]: buildId } = useParams(); const dispatchToast = useToastContext(); const { sendEvent } = useJobLogsAnalytics(); diff --git a/apps/spruce/src/pages/ProjectPatches.tsx b/apps/spruce/src/pages/ProjectPatches.tsx index bc855289c..2284fcb5d 100644 --- a/apps/spruce/src/pages/ProjectPatches.tsx +++ b/apps/spruce/src/pages/ProjectPatches.tsx @@ -8,7 +8,7 @@ import { usePatchesQueryParams } from "components/PatchesPage/usePatchesQueryPar import { ProjectSelect } from "components/ProjectSelect"; import { INCLUDE_COMMIT_QUEUE_PROJECT_PATCHES } from "constants/cookies"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; -import { getProjectPatchesRoute } from "constants/routes"; +import { getProjectPatchesRoute, slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { ProjectPatchesQuery, @@ -23,7 +23,7 @@ export const ProjectPatches = () => { const dispatchToast = useToastContext(); const analyticsObject = useProjectPatchesAnalytics(); - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); const [isCommitQueueCheckboxChecked] = useQueryParam( PatchPageQueryParams.CommitQueue, Cookies.get(INCLUDE_COMMIT_QUEUE_PROJECT_PATCHES) === "true", diff --git a/apps/spruce/src/pages/Task.tsx b/apps/spruce/src/pages/Task.tsx index fc734e11a..aba6cb3ad 100644 --- a/apps/spruce/src/pages/Task.tsx +++ b/apps/spruce/src/pages/Task.tsx @@ -12,6 +12,7 @@ import { } from "components/styles"; import TaskStatusBadge from "components/TaskStatusBadge"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; +import { slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { TaskQuery, TaskQueryVariables } from "gql/generated/types"; import { TASK } from "gql/queries"; @@ -29,7 +30,7 @@ import { TaskTabs } from "./task/TaskTabs"; const { parseQueryString } = queryString; export const Task = () => { - const { id } = useParams<{ id: string; tab: string | null }>(); + const { [slugs.taskId]: taskId } = useParams(); const dispatchToast = useToastContext(); const taskAnalytics = useTaskAnalytics(); const location = useLocation(); @@ -42,7 +43,7 @@ export const Task = () => { TaskQuery, TaskQueryVariables >(TASK, { - variables: { taskId: id, execution: selectedExecution }, + variables: { taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, fetchPolicy: "network-only", onError: (err) => @@ -105,7 +106,7 @@ export const Task = () => { {latestExecution > 0 && ( { @@ -116,7 +117,12 @@ export const Task = () => { }} /> )} - + diff --git a/apps/spruce/src/pages/UserPatches.tsx b/apps/spruce/src/pages/UserPatches.tsx index c7a7b0618..c291a4f27 100644 --- a/apps/spruce/src/pages/UserPatches.tsx +++ b/apps/spruce/src/pages/UserPatches.tsx @@ -6,6 +6,7 @@ import { PatchesPage } from "components/PatchesPage"; import { usePatchesQueryParams } from "components/PatchesPage/usePatchesQueryParams"; import { INCLUDE_COMMIT_QUEUE_USER_PATCHES } from "constants/cookies"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; +import { slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { UserPatchesQuery, @@ -18,7 +19,7 @@ import { PatchPageQueryParams } from "types/patch"; export const UserPatches = () => { const dispatchToast = useToastContext(); - const { id: userId } = useParams<{ id: string }>(); + const { [slugs.userId]: userId } = useParams(); const analyticsObject = useUserPatchesAnalytics(); const [isCommitQueueCheckboxChecked] = useQueryParam( diff --git a/apps/spruce/src/pages/Version.tsx b/apps/spruce/src/pages/Version.tsx index c340d68b9..9e0aa6523 100644 --- a/apps/spruce/src/pages/Version.tsx +++ b/apps/spruce/src/pages/Version.tsx @@ -12,7 +12,7 @@ import { PageSider, } from "components/styles"; import { commitQueueAlias } from "constants/patch"; -import { getCommitQueueRoute, getPatchRoute } from "constants/routes"; +import { getCommitQueueRoute, getPatchRoute, slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { VersionQuery, @@ -38,7 +38,7 @@ import { NameChangeModal } from "./version/NameChangeModal"; // docs/decisions/2023-12-13_version_page_logic.md export const VersionPage: React.FC = () => { const spruceConfig = useSpruceConfig(); - const { id } = useParams<{ id: string }>(); + const { [slugs.versionId]: versionId } = useParams(); const dispatchToast = useToastContext(); const [redirectURL, setRedirectURL] = useState(undefined); @@ -49,7 +49,7 @@ export const VersionPage: React.FC = () => { VersionQuery, VersionQueryVariables >(VERSION, { - variables: { id }, + variables: { id: versionId }, fetchPolicy: "cache-and-network", onError: (error) => { dispatchToast.error( @@ -64,7 +64,7 @@ export const VersionPage: React.FC = () => { IsPatchConfiguredQuery, IsPatchConfiguredQueryVariables >(IS_PATCH_CONFIGURED, { - variables: { id }, + variables: { id: versionId }, onError: (error) => { dispatchToast.error( `There was an error loading this patch: ${error.message}`, @@ -78,13 +78,13 @@ export const VersionPage: React.FC = () => { HasVersionQuery, HasVersionQueryVariables >(HAS_VERSION, { - variables: { id }, + variables: { id: versionId }, onCompleted: ({ hasVersion }) => { setIsLoadingData(true); if (hasVersion) { - getVersion({ variables: { id } }); + getVersion({ variables: { id: versionId } }); } else { - getPatch({ variables: { id } }); + getPatch({ variables: { id: versionId } }); } }, onError: (error) => { @@ -100,16 +100,16 @@ export const VersionPage: React.FC = () => { const { patch } = patchData; const { activated, alias, projectID } = patch; if (isPatchUnconfigured({ alias, activated })) { - setRedirectURL(getPatchRoute(id, { configure: true })); + setRedirectURL(getPatchRoute(versionId, { configure: true })); setIsLoadingData(false); } else if (!activated && alias === commitQueueAlias) { setRedirectURL(getCommitQueueRoute(projectID)); setIsLoadingData(false); } else { - getVersion({ variables: { id } }); + getVersion({ variables: { id: versionId } }); } } - }, [patchData, getVersion, id]); + }, [patchData, getVersion, versionId]); // If we have successfully loaded a version, we can show the page. useEffect(() => { @@ -157,7 +157,7 @@ export const VersionPage: React.FC = () => { } = patch || {}; const isPatchOnCommitQueue = commitQueuePosition !== null; - const versionText = shortenGithash(revision || id); + const versionText = shortenGithash(revision || versionId); const pageTitle = isPatch ? `Patch - ${patchNumber}` : `Version - ${versionText}`; @@ -188,7 +188,7 @@ export const VersionPage: React.FC = () => { isPatch={isPatch} isPatchOnCommitQueue={isPatchOnCommitQueue} patchDescription={message} - versionId={id} + versionId={versionId} /> } loading={false} @@ -196,13 +196,13 @@ export const VersionPage: React.FC = () => { title={linkifiedMessage || `Version ${order}`} > {isPatch && ( - + )} - + diff --git a/apps/spruce/src/pages/commits/WaterfallMenu/AddNotification.tsx b/apps/spruce/src/pages/commits/WaterfallMenu/AddNotification.tsx index 1f44e1eb7..40591fa12 100644 --- a/apps/spruce/src/pages/commits/WaterfallMenu/AddNotification.tsx +++ b/apps/spruce/src/pages/commits/WaterfallMenu/AddNotification.tsx @@ -3,6 +3,7 @@ import { useParams } from "react-router-dom"; import { useProjectHealthAnalytics } from "analytics/projectHealth/useProjectHealthAnalytics"; import { DropdownItem } from "components/ButtonDropdown"; import { NotificationModal } from "components/Notifications"; +import { slugs } from "constants/routes"; import { waterfallTriggers } from "constants/triggers"; import { subscriptionMethods } from "types/subscription"; @@ -13,7 +14,7 @@ interface AddNotificationProps { export const AddNotification: React.FC = ({ setMenuOpen, }) => { - const { projectIdentifier } = useParams<{ projectIdentifier: string }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); const [isModalVisible, setIsModalVisible] = useState(false); const { sendEvent } = useProjectHealthAnalytics({ page: "Commit chart" }); return ( diff --git a/apps/spruce/src/pages/commits/index.tsx b/apps/spruce/src/pages/commits/index.tsx index 680bf7dd2..c5ac267a6 100644 --- a/apps/spruce/src/pages/commits/index.tsx +++ b/apps/spruce/src/pages/commits/index.tsx @@ -18,7 +18,7 @@ import { CY_DISABLE_COMMITS_WELCOME_MODAL, } from "constants/cookies"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; -import { getCommitsRoute } from "constants/routes"; +import { getCommitsRoute, slugs } from "constants/routes"; import { size } from "constants/tokens"; import { newMainlineCommitsUser } from "constants/welcomeModalProps"; import { useToastContext } from "context/toast"; @@ -67,9 +67,7 @@ const Commits = () => { const { hasUsedMainlineCommitsBefore = true } = useSpruceOptions ?? {}; const [ref, limit, isResizing] = useCommitLimit(); const parsed = parseQueryString(search); - const { projectIdentifier } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: projectIdentifier } = useParams(); usePageTitle(`Project Health | ${projectIdentifier}`); const sendAnalyticsEvent = (id: string, identifier: string) => { diff --git a/apps/spruce/src/pages/configurePatch/configurePatchCore/useConfigurePatch/index.ts b/apps/spruce/src/pages/configurePatch/configurePatchCore/useConfigurePatch/index.ts index dbbd358e9..eb0adaadc 100644 --- a/apps/spruce/src/pages/configurePatch/configurePatchCore/useConfigurePatch/index.ts +++ b/apps/spruce/src/pages/configurePatch/configurePatchCore/useConfigurePatch/index.ts @@ -1,6 +1,6 @@ import { useEffect, useReducer } from "react"; import { useNavigate, useLocation, useParams } from "react-router-dom"; -import { getPatchRoute } from "constants/routes"; +import { getPatchRoute, slugs } from "constants/routes"; import { ConfigurePatchQuery, ParameterInput } from "gql/generated/types"; import { useTabShortcut } from "hooks/useTabShortcut"; import { PatchTab } from "types/patch"; @@ -125,7 +125,7 @@ interface HookResult extends ConfigurePatchState { const useConfigurePatch = (patch: ConfigurePatchQuery["patch"]): HookResult => { const navigate = useNavigate(); const location = useLocation(); - const { tab } = useParams<{ tab: PatchTab | null }>(); + const { [slugs.tab]: tab } = useParams<{ [slugs.tab]: PatchTab }>(); const { id, project } = patch; const { variants } = project; diff --git a/apps/spruce/src/pages/configurePatch/index.tsx b/apps/spruce/src/pages/configurePatch/index.tsx index 34f54818b..808965d8e 100644 --- a/apps/spruce/src/pages/configurePatch/index.tsx +++ b/apps/spruce/src/pages/configurePatch/index.tsx @@ -4,7 +4,7 @@ import { ProjectBanner } from "components/Banners"; import { PatchAndTaskFullPageLoad } from "components/Loading/PatchAndTaskFullPageLoad"; import { PageWrapper } from "components/styles"; import { commitQueueAlias } from "constants/patch"; -import { getVersionRoute } from "constants/routes"; +import { getVersionRoute, slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { ConfigurePatchQuery, @@ -17,13 +17,13 @@ import { validateObjectId } from "utils/validators"; import ConfigurePatchCore from "./configurePatchCore"; const ConfigurePatch: React.FC = () => { - const { id } = useParams<{ id: string }>(); + const { [slugs.patchId]: patchId } = useParams(); const dispatchToast = useToastContext(); const { data, error, loading } = useQuery< ConfigurePatchQuery, ConfigurePatchQueryVariables >(PATCH_CONFIGURE, { - variables: { id }, + variables: { id: patchId }, onError(err) { dispatchToast.error(err.message); }, @@ -33,8 +33,8 @@ const ConfigurePatch: React.FC = () => { usePageTitle(`Configure Patch`); // Can't configure a mainline version so should redirect to the version page - if (!validateObjectId(id)) { - return ; + if (!validateObjectId(patchId)) { + return ; } if (loading) { @@ -45,7 +45,7 @@ const ConfigurePatch: React.FC = () => { } if (patch.alias === commitQueueAlias) { - return ; + return ; } return ( diff --git a/apps/spruce/src/pages/container/EventsTable/index.tsx b/apps/spruce/src/pages/container/EventsTable/index.tsx index 8f5b928b7..faf895d2d 100644 --- a/apps/spruce/src/pages/container/EventsTable/index.tsx +++ b/apps/spruce/src/pages/container/EventsTable/index.tsx @@ -16,6 +16,7 @@ import PageSizeSelector, { } from "components/PageSizeSelector"; import Pagination from "components/Pagination"; import { SiderCard, TableControlInnerRow } from "components/styles"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { PodEventsQuery, PodEventsQueryVariables } from "gql/generated/types"; @@ -32,13 +33,13 @@ const EventsTable: React.FC<{}> = () => { const setPageSize = usePageSizeSelector(); const page = getPageFromSearch(search); const limit = getLimitFromSearch(search); - const { id } = useParams<{ id: string }>(); + const { [slugs.podId]: podId } = useParams(); const dispatchToast = useToastContext(); const { data: podEventsData } = useQuery< PodEventsQuery, PodEventsQueryVariables >(POD_EVENTS, { - variables: { id, page, limit }, + variables: { id: podId, page, limit }, onError: (err) => { dispatchToast.error( `There was an error loading the pod events: ${err.message}`, diff --git a/apps/spruce/src/pages/container/index.tsx b/apps/spruce/src/pages/container/index.tsx index 7ad7aa48a..0db3b8b1e 100644 --- a/apps/spruce/src/pages/container/index.tsx +++ b/apps/spruce/src/pages/container/index.tsx @@ -8,6 +8,7 @@ import { PageWrapper, PageContent, } from "components/styles"; +import { slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { PodQuery, PodQueryVariables } from "gql/generated/types"; import { POD } from "gql/queries"; @@ -17,9 +18,9 @@ import Metadata from "./Metadata"; const Container = () => { const dispatchToast = useToastContext(); - const { id } = useParams<{ id: string }>(); + const { [slugs.podId]: podId } = useParams(); const { data, error, loading } = useQuery(POD, { - variables: { podId: id }, + variables: { podId }, onError: (err) => { dispatchToast.error( `There was an error loading the host: ${err.message}`, @@ -27,12 +28,12 @@ const Container = () => { }, }); const { pod } = data || {}; - const { id: podId, status } = pod || {}; + const { id, status } = pod || {}; return ( } diff --git a/apps/spruce/src/pages/distroSettings/DeleteDistro/index.tsx b/apps/spruce/src/pages/distroSettings/DeleteDistro/index.tsx index 530d09e51..28a2f6c79 100644 --- a/apps/spruce/src/pages/distroSettings/DeleteDistro/index.tsx +++ b/apps/spruce/src/pages/distroSettings/DeleteDistro/index.tsx @@ -6,6 +6,7 @@ import { Description } from "@leafygreen-ui/typography"; import { useParams } from "react-router-dom"; import { ConfirmationModal } from "components/ConfirmationModal"; import ElementWrapper from "components/SpruceForm/ElementWrapper"; +import { slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { DeleteDistroMutation, @@ -66,7 +67,7 @@ const Modal: React.FC = ({ closeModal, distroId, open }) => { }; export const DeleteDistro: React.FC = () => { - const { distroId } = useParams<{ distroId: string }>(); + const { [slugs.distroId]: distroId } = useParams(); const [open, setOpen] = useState(false); const id = "delete-distro-button"; diff --git a/apps/spruce/src/pages/distroSettings/NewDistro/CopyModal.tsx b/apps/spruce/src/pages/distroSettings/NewDistro/CopyModal.tsx index 961656384..9fe7597cb 100644 --- a/apps/spruce/src/pages/distroSettings/NewDistro/CopyModal.tsx +++ b/apps/spruce/src/pages/distroSettings/NewDistro/CopyModal.tsx @@ -4,7 +4,7 @@ import { useNavigate, useParams } from "react-router-dom"; import { useDistroSettingsAnalytics } from "analytics"; import { ConfirmationModal } from "components/ConfirmationModal"; import { SpruceForm } from "components/SpruceForm"; -import { getDistroSettingsRoute } from "constants/routes"; +import { getDistroSettingsRoute, slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { CopyDistroMutation, @@ -21,7 +21,7 @@ interface Props { } export const CopyModal: React.FC = ({ handleClose, open }) => { - const { distroId } = useParams<{ distroId: string }>(); + const { [slugs.distroId]: distroId } = useParams(); const navigate = useNavigate(); const dispatchToast = useToastContext(); const { sendEvent } = useDistroSettingsAnalytics(); diff --git a/apps/spruce/src/pages/distroSettings/NewDistro/NewDistroButton.tsx b/apps/spruce/src/pages/distroSettings/NewDistro/NewDistroButton.tsx index 4aff122de..850b61b6b 100644 --- a/apps/spruce/src/pages/distroSettings/NewDistro/NewDistroButton.tsx +++ b/apps/spruce/src/pages/distroSettings/NewDistro/NewDistroButton.tsx @@ -5,6 +5,7 @@ import Button, { Size, Variant } from "@leafygreen-ui/button"; import { Menu, MenuItem } from "@leafygreen-ui/menu"; import { useParams } from "react-router-dom"; import Icon from "components/Icon"; +import { slugs } from "constants/routes"; import { zIndex } from "constants/tokens"; import { UserDistroSettingsPermissionsQuery, @@ -15,7 +16,7 @@ import { CopyModal } from "./CopyModal"; import { CreateModal } from "./CreateModal"; export const NewDistroButton: React.FC = () => { - const { distroId } = useParams<{ distroId: string }>(); + const { [slugs.distroId]: distroId } = useParams(); const [menuOpen, setMenuOpen] = useState(false); const [copyModalOpen, setCopyModalOpen] = useState(false); const [createModalOpen, setCreateModalOpen] = useState(false); diff --git a/apps/spruce/src/pages/distroSettings/Tabs.tsx b/apps/spruce/src/pages/distroSettings/Tabs.tsx index 2e5bad6f7..f53752f22 100644 --- a/apps/spruce/src/pages/distroSettings/Tabs.tsx +++ b/apps/spruce/src/pages/distroSettings/Tabs.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo } from "react"; import styled from "@emotion/styled"; import { useParams, Routes, Route, Navigate } from "react-router-dom"; -import { DistroSettingsTabRoutes } from "constants/routes"; +import { DistroSettingsTabRoutes, slugs } from "constants/routes"; import { DistroQuery } from "gql/generated/types"; import { useDistroSettingsContext } from "./Context"; import { Header } from "./Header"; @@ -22,7 +22,9 @@ interface Props { } export const DistroSettingsTabs: React.FC = ({ distro }) => { - const { tab } = useParams<{ tab: DistroSettingsTabRoutes }>(); + const { [slugs.tab]: tab } = useParams<{ + [slugs.tab]: DistroSettingsTabRoutes; + }>(); const { setInitialData } = useDistroSettingsContext(); const tabData = useMemo(() => getTabData(distro), [distro]); diff --git a/apps/spruce/src/pages/distroSettings/index.tsx b/apps/spruce/src/pages/distroSettings/index.tsx index 9f0b814f4..b2a126c94 100644 --- a/apps/spruce/src/pages/distroSettings/index.tsx +++ b/apps/spruce/src/pages/distroSettings/index.tsx @@ -11,6 +11,7 @@ import { import { DistroSettingsTabRoutes, getDistroSettingsRoute, + slugs, } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; @@ -26,9 +27,9 @@ import { DistroSettingsTabs } from "./Tabs"; const DistroSettings: React.FC = () => { usePageTitle("Distro Settings"); const dispatchToast = useToastContext(); - const { distroId, tab: currentTab } = useParams<{ - distroId: string; - tab: DistroSettingsTabRoutes; + const { [slugs.distroId]: distroId, [slugs.tab]: currentTab } = useParams<{ + [slugs.distroId]: string; + [slugs.tab]: DistroSettingsTabRoutes; }>(); const { data, loading } = useQuery( diff --git a/apps/spruce/src/pages/distroSettings/tabs/BaseTab.tsx b/apps/spruce/src/pages/distroSettings/tabs/BaseTab.tsx index 603c81553..c304a8ad7 100644 --- a/apps/spruce/src/pages/distroSettings/tabs/BaseTab.tsx +++ b/apps/spruce/src/pages/distroSettings/tabs/BaseTab.tsx @@ -2,6 +2,7 @@ import { useQuery } from "@apollo/client"; import { useParams } from "react-router-dom"; import { Form } from "components/Settings/Form"; import { GetFormSchema, ValidateProps } from "components/SpruceForm"; +import { slugs } from "constants/routes"; import { UserDistroSettingsPermissionsQuery, UserDistroSettingsPermissionsQueryVariables, @@ -21,7 +22,10 @@ export const BaseTab = ({ initialFormState, ...rest }: BaseTabProps) => { - const { distroId, tab } = useParams<{ distroId: string; tab: T }>(); + const { [slugs.distroId]: distroId, [slugs.tab]: tab } = useParams<{ + [slugs.tab]: T; + [slugs.distroId]: string; + }>(); const state = useDistroSettingsContext(); usePopulateForm(initialFormState, tab); diff --git a/apps/spruce/src/pages/distroSettings/tabs/EventLogTab/EventLogTab.tsx b/apps/spruce/src/pages/distroSettings/tabs/EventLogTab/EventLogTab.tsx index 0f9881b11..7fce25d70 100644 --- a/apps/spruce/src/pages/distroSettings/tabs/EventLogTab/EventLogTab.tsx +++ b/apps/spruce/src/pages/distroSettings/tabs/EventLogTab/EventLogTab.tsx @@ -1,5 +1,6 @@ import { useParams } from "react-router-dom"; import { EventDiffTable, EventLog } from "components/Settings/EventLog"; +import { slugs } from "constants/routes"; import { LegacyEventEntry } from "./LegacyEventEntry"; import { useDistroEvents } from "./useDistroEvents"; @@ -8,9 +9,7 @@ type TabProps = { }; export const EventLogTab: React.FC = ({ limit }) => { - const { distroId } = useParams<{ - distroId: string; - }>(); + const { [slugs.distroId]: distroId } = useParams(); const { allEventsFetched, events, fetchMore, loading } = useDistroEvents( distroId, diff --git a/apps/spruce/src/pages/host/index.tsx b/apps/spruce/src/pages/host/index.tsx index 802f53c26..6c7be4194 100644 --- a/apps/spruce/src/pages/host/index.tsx +++ b/apps/spruce/src/pages/host/index.tsx @@ -15,6 +15,7 @@ import { PageLayout, PageContent, } from "components/styles"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -33,14 +34,14 @@ const { getLimitFromSearch, getPageFromSearch } = url; const Host: React.FC = () => { const dispatchToast = useToastContext(); - const { id } = useParams<{ id: string }>(); + const { [slugs.hostId]: hostId } = useParams(); // Query host data const { data: hostData, error, loading: hostMetaDataLoading, } = useQuery(HOST, { - variables: { id }, + variables: { id: hostId }, onError: (err) => { dispatchToast.error( `There was an error loading the host: ${err.message}`, @@ -49,7 +50,7 @@ const Host: React.FC = () => { }); const host = hostData?.host; - const { distro, hostUrl, id: hostId, persistentDnsName, user } = host || {}; + const { distro, hostUrl, persistentDnsName, user } = host || {}; const bootstrapMethod = distro?.bootstrapMethod; const status = host?.status as HostStatus; @@ -66,7 +67,7 @@ const Host: React.FC = () => { HostEventsQuery, HostEventsQueryVariables >(HOST_EVENTS, { - variables: { id, tag, page, limit }, + variables: { id: hostId, tag, page, limit }, }); const hostEvents = hostEventData?.hostEvents; @@ -101,7 +102,7 @@ const Host: React.FC = () => { { { )} setIsUpdateStatusModalVisible(false)} isSingleHost diff --git a/apps/spruce/src/pages/preferences/PreferencesTabs.tsx b/apps/spruce/src/pages/preferences/PreferencesTabs.tsx index ca35a440a..1e8825fdf 100644 --- a/apps/spruce/src/pages/preferences/PreferencesTabs.tsx +++ b/apps/spruce/src/pages/preferences/PreferencesTabs.tsx @@ -1,7 +1,11 @@ import styled from "@emotion/styled"; import { Body, BodyProps, H2 } from "@leafygreen-ui/typography"; import { Route, Routes, useParams, Navigate } from "react-router-dom"; -import { PreferencesTabRoutes, getPreferencesRoute } from "constants/routes"; +import { + PreferencesTabRoutes, + getPreferencesRoute, + slugs, +} from "constants/routes"; import { size } from "constants/tokens"; import { CliTab } from "./preferencesTabs/CliTab"; import { NewUITab } from "./preferencesTabs/NewUITab"; @@ -10,9 +14,11 @@ import { ProfileTab } from "./preferencesTabs/ProfileTab"; import { PublicKeysTab } from "./preferencesTabs/PublicKeysTab"; export const PreferencesTabs: React.FC = () => { - const { tab } = useParams<{ tab: string }>(); + const { [slugs.tab]: tab } = useParams<{ + [slugs.tab]: PreferencesTabRoutes | null; + }>(); - const { subtitle, title } = getTitle(tab as PreferencesTabRoutes); + const { subtitle, title } = getTitle(tab); return ( <> diff --git a/apps/spruce/src/pages/preferences/index.tsx b/apps/spruce/src/pages/preferences/index.tsx index 1c2e184a9..258924c66 100644 --- a/apps/spruce/src/pages/preferences/index.tsx +++ b/apps/spruce/src/pages/preferences/index.tsx @@ -7,13 +7,19 @@ import { SideNavItem, PageWrapper, } from "components/styles"; -import { PreferencesTabRoutes, getPreferencesRoute } from "constants/routes"; +import { + PreferencesTabRoutes, + getPreferencesRoute, + slugs, +} from "constants/routes"; import { usePageTitle } from "hooks"; import { PreferencesTabs } from "pages/preferences/PreferencesTabs"; const Preferences: React.FC = () => { usePageTitle("Preferences"); - const { tab } = useParams<{ tab: string }>(); + const { [slugs.tab]: tab } = useParams<{ + [slugs.tab]: PreferencesTabRoutes; + }>(); const { sendEvent } = usePreferencesAnalytics(); return ( diff --git a/apps/spruce/src/pages/projectSettings/HeaderButtons.tsx b/apps/spruce/src/pages/projectSettings/HeaderButtons.tsx index de231bcdf..314ef8593 100644 --- a/apps/spruce/src/pages/projectSettings/HeaderButtons.tsx +++ b/apps/spruce/src/pages/projectSettings/HeaderButtons.tsx @@ -7,6 +7,7 @@ import { useProjectSettingsAnalytics } from "analytics"; import { getProjectSettingsRoute, ProjectSettingsTabRoutes, + slugs, } from "constants/routes"; import { useToastContext } from "context/toast"; import { @@ -47,9 +48,7 @@ export const HeaderButtons: React.FC = ({ id, projectType, tab }) => { const { getTab, saveTab } = useProjectSettingsContext(); const { formData, hasChanges, hasError } = getTab(tab); const navigate = useNavigate(); - const { projectIdentifier: identifier } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: identifier } = useParams(); const [defaultModalOpen, setDefaultModalOpen] = useState(false); diff --git a/apps/spruce/src/pages/projectSettings/Tabs.tsx b/apps/spruce/src/pages/projectSettings/Tabs.tsx index da189516c..d86f4198d 100644 --- a/apps/spruce/src/pages/projectSettings/Tabs.tsx +++ b/apps/spruce/src/pages/projectSettings/Tabs.tsx @@ -1,7 +1,7 @@ import { useEffect, useMemo } from "react"; import styled from "@emotion/styled"; import { Navigate, Route, Routes, useParams } from "react-router-dom"; -import { ProjectSettingsTabRoutes } from "constants/routes"; +import { ProjectSettingsTabRoutes, slugs } from "constants/routes"; import { ProjectSettingsQuery, RepoSettingsQuery } from "gql/generated/types"; import { useProjectSettingsContext } from "./Context"; import { Header } from "./Header"; @@ -43,7 +43,9 @@ export const ProjectSettingsTabs: React.FC = ({ projectType, repoData, }) => { - const { tab } = useParams<{ tab: ProjectSettingsTabRoutes }>(); + const { [slugs.tab]: tab } = useParams<{ + [slugs.tab]: ProjectSettingsTabRoutes; + }>(); const { setInitialData } = useProjectSettingsContext(); const projectId = projectData?.projectRef?.id; diff --git a/apps/spruce/src/pages/projectSettings/index.tsx b/apps/spruce/src/pages/projectSettings/index.tsx index 8770f5006..c8e900e4d 100644 --- a/apps/spruce/src/pages/projectSettings/index.tsx +++ b/apps/spruce/src/pages/projectSettings/index.tsx @@ -13,6 +13,7 @@ import { import { ProjectSettingsTabRoutes, getProjectSettingsRoute, + slugs, } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; @@ -36,10 +37,11 @@ const { validateObjectId } = validators; const ProjectSettings: React.FC = () => { usePageTitle(`Project Settings`); const dispatchToast = useToastContext(); - const { projectIdentifier: identifier, tab } = useParams<{ - projectIdentifier: string; - tab: ProjectSettingsTabRoutes; - }>(); + const { [slugs.projectIdentifier]: identifier, [slugs.tab]: tab } = + useParams<{ + [slugs.projectIdentifier]: string | null; + [slugs.tab]: ProjectSettingsTabRoutes; + }>(); // If the path includes an Object ID, this page represents a repo and we should not attempt to fetch a project. const isRepo = validateObjectId(identifier); diff --git a/apps/spruce/src/pages/projectSettings/tabs/EventLogTab/EventLogTab.tsx b/apps/spruce/src/pages/projectSettings/tabs/EventLogTab/EventLogTab.tsx index 51f6b5c4c..733482475 100644 --- a/apps/spruce/src/pages/projectSettings/tabs/EventLogTab/EventLogTab.tsx +++ b/apps/spruce/src/pages/projectSettings/tabs/EventLogTab/EventLogTab.tsx @@ -1,5 +1,6 @@ import { useParams } from "react-router-dom"; import { EventLog } from "components/Settings/EventLog"; +import { slugs } from "constants/routes"; import { ProjectType } from "../utils"; import { useProjectSettingsEvents } from "./useProjectSettingsEvents"; @@ -9,9 +10,7 @@ type TabProps = { }; export const EventLogTab: React.FC = ({ limit, projectType }) => { - const { projectIdentifier: identifier } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: identifier } = useParams(); const isRepo = projectType === ProjectType.Repo; const { allEventsFetched, events, fetchMore } = useProjectSettingsEvents( diff --git a/apps/spruce/src/pages/projectSettings/tabs/GeneralTab/Fields/DeleteProjectField.tsx b/apps/spruce/src/pages/projectSettings/tabs/GeneralTab/Fields/DeleteProjectField.tsx index c200f1be7..01aec2aa5 100644 --- a/apps/spruce/src/pages/projectSettings/tabs/GeneralTab/Fields/DeleteProjectField.tsx +++ b/apps/spruce/src/pages/projectSettings/tabs/GeneralTab/Fields/DeleteProjectField.tsx @@ -6,6 +6,7 @@ import { Description } from "@leafygreen-ui/typography"; import { Field } from "@rjsf/core"; import { useParams } from "react-router-dom"; import { ConfirmationModal } from "components/ConfirmationModal"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -22,9 +23,7 @@ interface ModalProps { const Modal: React.FC = ({ closeModal, open, projectId }) => { const dispatchToast = useToastContext(); - const { projectIdentifier: identifier } = useParams<{ - projectIdentifier: string; - }>(); + const { [slugs.projectIdentifier]: identifier } = useParams(); const [deleteProject, { loading }] = useMutation< DeleteProjectMutation, diff --git a/apps/spruce/src/pages/spawn/index.tsx b/apps/spruce/src/pages/spawn/index.tsx index 050ba493f..bfd37d4fc 100644 --- a/apps/spruce/src/pages/spawn/index.tsx +++ b/apps/spruce/src/pages/spawn/index.tsx @@ -6,12 +6,12 @@ import { SideNavGroup, SideNavItem, } from "components/styles"; -import { routes, SpawnTab } from "constants/routes"; +import { routes, SpawnTab, slugs } from "constants/routes"; import { SpawnHost } from "./SpawnHost"; import { SpawnVolume } from "./SpawnVolume"; const Spawn: React.FC = () => { - const { tab } = useParams<{ tab: string }>(); + const { [slugs.tab]: tab } = useParams<{ [slugs.tab]: SpawnTab }>(); const spawnAnalytics = useSpawnAnalytics(); return ( diff --git a/apps/spruce/src/pages/task/ActionButtons.tsx b/apps/spruce/src/pages/task/ActionButtons.tsx index 0e190b812..0268bbfc1 100644 --- a/apps/spruce/src/pages/task/ActionButtons.tsx +++ b/apps/spruce/src/pages/task/ActionButtons.tsx @@ -9,7 +9,7 @@ import { LoadingButton } from "components/Buttons"; import SetPriority from "components/SetPriority"; import { PageButtonRow } from "components/styles"; import { commitQueueRequester } from "constants/patch"; -import { getTaskHistoryRoute } from "constants/routes"; +import { getTaskHistoryRoute, slugs } from "constants/routes"; import { mergeTaskName } from "constants/task"; import { useToastContext } from "context/toast"; import { @@ -80,7 +80,7 @@ export const ActionButtons: React.FC = ({ const dispatchToast = useToastContext(); const [isVisibleModal, setIsVisibleModal] = useState(false); - const { id: taskId } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const taskAnalytics = useTaskAnalytics(); const [, setExecution] = useQueryParam("execution", 0); diff --git a/apps/spruce/src/pages/task/TaskTabs.tsx b/apps/spruce/src/pages/task/TaskTabs.tsx index 5196470aa..c201403e8 100644 --- a/apps/spruce/src/pages/task/TaskTabs.tsx +++ b/apps/spruce/src/pages/task/TaskTabs.tsx @@ -5,7 +5,7 @@ import { useTaskAnalytics } from "analytics"; import { TrendChartsPlugin } from "components/PerfPlugin"; import { StyledTabs } from "components/styles/StyledTabs"; import { TabLabelWithBadge } from "components/TabLabelWithBadge"; -import { getTaskRoute, GetTaskRouteOptions } from "constants/routes"; +import { getTaskRoute, GetTaskRouteOptions, slugs } from "constants/routes"; import { TaskQuery } from "gql/generated/types"; import { usePrevious } from "hooks"; import { useTabShortcut } from "hooks/useTabShortcut"; @@ -24,7 +24,7 @@ interface TaskTabProps { task: TaskQuery["task"]; } export const TaskTabs: React.FC = ({ isDisplayTask, task }) => { - const { tab: urlTab } = useParams<{ id: string; tab: TaskTab | null }>(); + const { [slugs.tab]: urlTab } = useParams<{ [slugs.tab]: TaskTab }>(); const navigate = useNavigate(); const location = useLocation(); diff --git a/apps/spruce/src/pages/task/actionButtons/TaskNotificationModal.tsx b/apps/spruce/src/pages/task/actionButtons/TaskNotificationModal.tsx index cda7d0070..8c723cdae 100644 --- a/apps/spruce/src/pages/task/actionButtons/TaskNotificationModal.tsx +++ b/apps/spruce/src/pages/task/actionButtons/TaskNotificationModal.tsx @@ -1,6 +1,7 @@ import { useParams } from "react-router-dom"; import { useTaskAnalytics } from "analytics"; import { NotificationModal } from "components/Notifications"; +import { slugs } from "constants/routes"; import { taskTriggers } from "constants/triggers"; import { subscriptionMethods as taskSubscriptionMethods } from "types/subscription"; @@ -13,7 +14,7 @@ export const TaskNotificationModal: React.FC = ({ onCancel, visible, }) => { - const { id: taskId } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const taskAnalytics = useTaskAnalytics(); return ( diff --git a/apps/spruce/src/pages/task/taskTabs/logs/LogTypes.tsx b/apps/spruce/src/pages/task/taskTabs/logs/LogTypes.tsx index b9ad15c64..6c88df0cc 100644 --- a/apps/spruce/src/pages/task/taskTabs/logs/LogTypes.tsx +++ b/apps/spruce/src/pages/task/taskTabs/logs/LogTypes.tsx @@ -5,6 +5,7 @@ import { palette } from "@leafygreen-ui/palette"; import { Skeleton } from "antd"; import { useParams, useLocation } from "react-router-dom"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; +import { slugs } from "constants/routes"; import { size, fontSize } from "constants/tokens"; import { TaskEventLogsQuery, @@ -48,7 +49,7 @@ interface Props { } export const AllLog: React.FC = (props) => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const location = useLocation(); const parsed = parseQueryString(location.search); const selectedExecution = Number(parsed[RequiredQueryParams.Execution]); @@ -57,7 +58,7 @@ export const AllLog: React.FC = (props) => { AllLogsQuery, AllLogsQueryVariables >(ALL_LOGS, { - variables: { id, execution: selectedExecution }, + variables: { id: taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); @@ -76,7 +77,7 @@ export const AllLog: React.FC = (props) => { }; export const EventLog: React.FC = (props) => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const location = useLocation(); const parsed = parseQueryString(location.search); const selectedExecution = Number(parsed[RequiredQueryParams.Execution]); @@ -84,7 +85,7 @@ export const EventLog: React.FC = (props) => { TaskEventLogsQuery, TaskEventLogsQueryVariables >(TASK_EVENT_LOGS, { - variables: { id, execution: selectedExecution }, + variables: { id: taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); @@ -106,7 +107,7 @@ export const EventLog: React.FC = (props) => { }; export const SystemLog: React.FC = (props) => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const location = useLocation(); const parsed = parseQueryString(location.search); const selectedExecution = Number(parsed[RequiredQueryParams.Execution]); @@ -114,7 +115,7 @@ export const SystemLog: React.FC = (props) => { SystemLogsQuery, SystemLogsQueryVariables >(SYSTEM_LOGS, { - variables: { id, execution: selectedExecution }, + variables: { id: taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); @@ -132,7 +133,7 @@ export const SystemLog: React.FC = (props) => { }; export const AgentLog: React.FC = (props) => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const location = useLocation(); const parsed = parseQueryString(location.search); const selectedExecution = Number(parsed[RequiredQueryParams.Execution]); @@ -140,7 +141,7 @@ export const AgentLog: React.FC = (props) => { AgentLogsQuery, AgentLogsQueryVariables >(AGENT_LOGS, { - variables: { id, execution: selectedExecution }, + variables: { id: taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); @@ -158,7 +159,7 @@ export const AgentLog: React.FC = (props) => { }; export const TaskLog: React.FC = (props) => { - const { id } = useParams<{ id: string }>(); + const { [slugs.taskId]: taskId } = useParams(); const location = useLocation(); const parsed = parseQueryString(location.search); const selectedExecution = Number(parsed[RequiredQueryParams.Execution]); @@ -166,7 +167,7 @@ export const TaskLog: React.FC = (props) => { TaskLogsQuery, TaskLogsQueryVariables >(TASK_LOGS, { - variables: { id, execution: selectedExecution }, + variables: { id: taskId, execution: selectedExecution }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); diff --git a/apps/spruce/src/pages/taskHistory/index.tsx b/apps/spruce/src/pages/taskHistory/index.tsx index 8dc34edd9..a1ff361ef 100644 --- a/apps/spruce/src/pages/taskHistory/index.tsx +++ b/apps/spruce/src/pages/taskHistory/index.tsx @@ -17,6 +17,7 @@ import { import HistoryTable from "components/HistoryTable/HistoryTable"; import { useHistoryTable } from "components/HistoryTable/HistoryTableContext"; import { PageWrapper } from "components/styles"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -37,10 +38,10 @@ const { useJumpToCommit, useTestFilters } = hooks; const TaskHistoryContents: React.FC = () => { const { sendEvent } = useProjectHealthAnalytics({ page: "Task history" }); - const { projectIdentifier, taskName } = useParams<{ - projectIdentifier: string; - taskName: string; - }>(); + const { + [slugs.projectIdentifier]: projectIdentifier, + [slugs.taskName]: taskName, + } = useParams(); const { ingestNewCommits } = useHistoryTable(); usePageTitle(`Task History | ${projectIdentifier} | ${taskName}`); useTestFilters(); diff --git a/apps/spruce/src/pages/taskQueue/index.tsx b/apps/spruce/src/pages/taskQueue/index.tsx index f8bff1020..916a233ce 100644 --- a/apps/spruce/src/pages/taskQueue/index.tsx +++ b/apps/spruce/src/pages/taskQueue/index.tsx @@ -7,7 +7,7 @@ import { useParams, useNavigate } from "react-router-dom"; import { useTaskQueueAnalytics } from "analytics"; import SearchableDropdown from "components/SearchableDropdown"; import { PageWrapper, StyledRouterLink } from "components/styles"; -import { getTaskQueueRoute, getAllHostsRoute } from "constants/routes"; +import { getTaskQueueRoute, getAllHostsRoute, slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -27,7 +27,7 @@ import TaskQueueTable from "./TaskQueueTable"; const TaskQueue = () => { const taskQueueAnalytics = useTaskQueueAnalytics(); - const { distro } = useParams<{ distro: string }>(); + const { [slugs.distroId]: distroId } = useParams(); const [taskId] = useQueryParam( QueryParams.TaskId, undefined, @@ -37,7 +37,7 @@ const TaskQueue = () => { TaskQueueDistro | undefined >(undefined); const dispatchToast = useToastContext(); - usePageTitle(`Task Queue - ${distro}`); + usePageTitle(`Task Queue - ${distroId}`); const { data: distrosData, loading: loadingDistrosData } = useQuery< TaskQueueDistrosQuery, TaskQueueDistrosQueryVariables @@ -46,9 +46,9 @@ const TaskQueue = () => { onCompleted: (data) => { const { taskQueueDistros } = data; const firstDistroInList = taskQueueDistros[0]?.id; - const defaultDistro = distro ?? firstDistroInList; + const defaultDistro = distroId ?? firstDistroInList; setSelectedDistro(taskQueueDistros.find((d) => d.id === defaultDistro)); - if (distro === undefined) { + if (distroId === undefined) { navigate(getTaskQueueRoute(defaultDistro)); } }, @@ -62,8 +62,8 @@ const TaskQueue = () => { DistroTaskQueueQueryVariables >(DISTRO_TASK_QUEUE, { fetchPolicy: "cache-and-network", - variables: { distroId: distro }, - skip: !distro, + variables: { distroId }, + skip: !distroId, onError: (err) => { dispatchToast.error( `There was an error loading task queue: ${err.message}`, @@ -117,8 +117,8 @@ const TaskQueue = () => { /* Only show name & link if distro exists. */ !loadingDistrosData && ( - {distro} - + {distroId} + View hosts diff --git a/apps/spruce/src/pages/variantHistory/index.tsx b/apps/spruce/src/pages/variantHistory/index.tsx index 7f66db647..176053bd1 100644 --- a/apps/spruce/src/pages/variantHistory/index.tsx +++ b/apps/spruce/src/pages/variantHistory/index.tsx @@ -17,6 +17,7 @@ import { import HistoryTable from "components/HistoryTable/HistoryTable"; import { useHistoryTable } from "components/HistoryTable/HistoryTableContext"; import { PageWrapper } from "components/styles"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -36,10 +37,10 @@ const { useJumpToCommit, useTestFilters } = hooks; const { applyStrictRegex } = string; const VariantHistoryContents: React.FC = () => { - const { projectIdentifier, variantName } = useParams<{ - projectIdentifier: string; - variantName: string; - }>(); + const { + [slugs.projectIdentifier]: projectIdentifier, + [slugs.variantName]: variantName, + } = useParams(); const { sendEvent } = useProjectHealthAnalytics({ page: "Variant history" }); const { ingestNewCommits } = useHistoryTable(); const dispatchToast = useToastContext(); diff --git a/apps/spruce/src/pages/version/BuildVariantCard/BuildVariantCard.stories.tsx b/apps/spruce/src/pages/version/BuildVariantCard/BuildVariantCard.stories.tsx index 3f64fee59..4c03411dd 100644 --- a/apps/spruce/src/pages/version/BuildVariantCard/BuildVariantCard.stories.tsx +++ b/apps/spruce/src/pages/version/BuildVariantCard/BuildVariantCard.stories.tsx @@ -9,13 +9,12 @@ export default { apolloClient: { mocks, }, - reactRouter: { - path: "/version/:id", - route: "/version/version", - }, }, } satisfies CustomMeta; export const Default: CustomStoryObj = { render: (args) => , + args: { + versionId: "version", + }, }; diff --git a/apps/spruce/src/pages/version/BuildVariantCard/index.tsx b/apps/spruce/src/pages/version/BuildVariantCard/index.tsx index 4a490104b..f1773376b 100644 --- a/apps/spruce/src/pages/version/BuildVariantCard/index.tsx +++ b/apps/spruce/src/pages/version/BuildVariantCard/index.tsx @@ -1,6 +1,5 @@ import { useQuery } from "@apollo/client"; import styled from "@emotion/styled"; -import { useParams } from "react-router-dom"; import { navBarHeight } from "components/Header/Navbar"; import { MetadataCard, MetadataTitle } from "components/MetadataCard"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; @@ -13,15 +12,16 @@ import { BUILD_VARIANTS_STATS } from "gql/queries"; import { usePolling } from "hooks"; import VariantTaskGroup from "./VariantTaskGroup"; -const BuildVariantCard: React.FC = () => { - const { id } = useParams<{ id: string }>(); - +interface BuildVariantCardProps { + versionId: string; +} +const BuildVariantCard: React.FC = ({ versionId }) => { const { data, error, loading, refetch, startPolling, stopPolling } = useQuery< BuildVariantStatsQuery, BuildVariantStatsQueryVariables >(BUILD_VARIANTS_STATS, { fetchPolicy: "cache-and-network", - variables: { id }, + variables: { id: versionId }, pollInterval: DEFAULT_POLL_INTERVAL, }); usePolling({ startPolling, stopPolling, refetch }); @@ -38,7 +38,7 @@ const BuildVariantCard: React.FC = () => { displayName={displayName} statusCounts={statusCounts} variant={variant} - versionId={id} + versionId={versionId} /> ), )} diff --git a/apps/spruce/src/pages/version/TaskDuration.tsx b/apps/spruce/src/pages/version/TaskDuration.tsx index 7cdaa5058..e644abc6f 100644 --- a/apps/spruce/src/pages/version/TaskDuration.tsx +++ b/apps/spruce/src/pages/version/TaskDuration.tsx @@ -5,6 +5,7 @@ import { useParams, useLocation } from "react-router-dom"; import { useVersionAnalytics } from "analytics"; import TableControl from "components/Table/TableControl"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; +import { slugs } from "constants/routes"; import { size } from "constants/tokens"; import { useToastContext } from "context/toast"; import { @@ -27,12 +28,12 @@ interface Props { const TaskDuration: React.FC = ({ taskCount }) => { const dispatchToast = useToastContext(); - const { id } = useParams<{ id: string }>(); + const { [slugs.versionId]: versionId } = useParams(); const { search } = useLocation(); const updateQueryParams = useUpdateURLQueryParams(); - const versionAnalytics = useVersionAnalytics(id); - const queryVariables = useQueryVariables(search, id); + const versionAnalytics = useVersionAnalytics(versionId); + const queryVariables = useQueryVariables(search, versionId); const hasQueryVariables = Object.keys(parseQueryString(search)).length > 0; const { limit, page } = queryVariables.taskFilterOptions; diff --git a/apps/spruce/src/pages/version/Tasks.tsx b/apps/spruce/src/pages/version/Tasks.tsx index c392b5371..548723d2b 100644 --- a/apps/spruce/src/pages/version/Tasks.tsx +++ b/apps/spruce/src/pages/version/Tasks.tsx @@ -5,6 +5,7 @@ import { useVersionAnalytics } from "analytics"; import TableControl from "components/Table/TableControl"; import TableWrapper from "components/Table/TableWrapper"; import { DEFAULT_POLL_INTERVAL } from "constants/index"; +import { slugs } from "constants/routes"; import { useToastContext } from "context/toast"; import { VersionTasksQuery, @@ -27,11 +28,11 @@ interface Props { export const Tasks: React.FC = ({ taskCount }) => { const dispatchToast = useToastContext(); - const { id } = useParams<{ id: string }>(); + const { [slugs.versionId]: versionId } = useParams(); const { search } = useLocation(); const updateQueryParams = useUpdateURLQueryParams(); - const versionAnalytics = useVersionAnalytics(id); - const queryVariables = useQueryVariables(search, id); + const versionAnalytics = useVersionAnalytics(versionId); + const queryVariables = useQueryVariables(search, versionId); const hasQueryVariables = Object.keys(parseQueryString(search)).length > 0; const { limit, page, sorts } = queryVariables.taskFilterOptions; diff --git a/apps/spruce/src/pages/version/VersionTabs.tsx b/apps/spruce/src/pages/version/VersionTabs.tsx index 136eaed21..deefecd37 100644 --- a/apps/spruce/src/pages/version/VersionTabs.tsx +++ b/apps/spruce/src/pages/version/VersionTabs.tsx @@ -5,7 +5,7 @@ import { useVersionAnalytics } from "analytics"; import { CodeChanges } from "components/CodeChanges"; import { StyledTabs } from "components/styles/StyledTabs"; import { TabLabelWithBadge } from "components/TabLabelWithBadge"; -import { getVersionRoute } from "constants/routes"; +import { getVersionRoute, slugs } from "constants/routes"; import { VersionQuery } from "gql/generated/types"; import { usePrevious } from "hooks"; import { useTabShortcut } from "hooks/useTabShortcut"; @@ -26,13 +26,13 @@ interface Props { const tabMap = ({ childPatches, numFailedChildPatches, - patchId, taskCount, + versionId, }: { taskCount: number; childPatches: VersionQuery["version"]["patch"]["childPatches"]; numFailedChildPatches: number; - patchId: string; + versionId: string; }) => ({ [PatchTab.Tasks]: ( @@ -56,7 +56,7 @@ const tabMap = ({ data-cy="changes-tab" key="changes-tab" > - + ), [PatchTab.Downstream]: ( @@ -86,9 +86,12 @@ export const VersionTabs: React.FC = ({ isPatch, taskCount, }) => { - const { id, tab } = useParams<{ id: string; tab: PatchTab }>(); + const { [slugs.versionId]: versionId, [slugs.tab]: tab } = useParams<{ + [slugs.versionId]: string; + [slugs.tab]: PatchTab; + }>(); const { search } = useLocation(); - const { sendEvent } = useVersionAnalytics(id); + const { sendEvent } = useVersionAnalytics(versionId); const navigate = useNavigate(); const tabIsActive = useMemo( @@ -109,9 +112,9 @@ export const VersionTabs: React.FC = ({ taskCount, childPatches, numFailedChildPatches, - patchId: id, + versionId, }); - }, [taskCount, childPatches, id]); + }, [taskCount, childPatches, versionId]); const activeTabs = useMemo( () => Object.keys(allTabs).filter((t) => tabIsActive[t] as PatchTab[]), @@ -124,7 +127,7 @@ export const VersionTabs: React.FC = ({ useEffect(() => { // If tab is not valid, set to task tab. if (!isValidTab) { - navigate(getVersionRoute(id), { replace: true }); + navigate(getVersionRoute(versionId), { replace: true }); } // If tab updates in URL without having clicked a tab (e.g. clicked build variant), update state here. else if (selectedTab !== activeTabs.indexOf(tab)) { @@ -136,7 +139,7 @@ export const VersionTabs: React.FC = ({ const selectNewTab = (newTabIndex: number) => { const queryParams = parseQueryString(search); const newTab = activeTabs[newTabIndex]; - const newRoute = getVersionRoute(id, { + const newRoute = getVersionRoute(versionId, { tab: newTab as PatchTab, ...queryParams, }); diff --git a/apps/spruce/src/pages/version/taskDuration/TaskDurationTable.tsx b/apps/spruce/src/pages/version/taskDuration/TaskDurationTable.tsx index 501390be6..afce5c79b 100644 --- a/apps/spruce/src/pages/version/taskDuration/TaskDurationTable.tsx +++ b/apps/spruce/src/pages/version/taskDuration/TaskDurationTable.tsx @@ -14,6 +14,7 @@ import { TablePlaceholder } from "components/Table/TablePlaceholder"; import { onChangeHandler } from "components/Table/utils"; import { TaskLink } from "components/TasksTable/TaskLink"; import TaskStatusBadge from "components/TaskStatusBadge"; +import { slugs } from "constants/routes"; import { VersionTaskDurationsQuery, SortDirection } from "gql/generated/types"; import { useTaskStatuses } from "hooks"; import { useQueryParams } from "hooks/useQueryParam"; @@ -34,7 +35,7 @@ export const TaskDurationTable: React.FC = ({ numLoadingRows, tasks, }) => { - const { id: versionId } = useParams<{ id: string }>(); + const { [slugs.versionId]: versionId } = useParams(); const { sendEvent } = useVersionAnalytics(versionId); const { currentStatuses: statusOptions } = useTaskStatuses({ versionId }); diff --git a/apps/spruce/src/pages/version/tasks/PatchTasksTable.tsx b/apps/spruce/src/pages/version/tasks/PatchTasksTable.tsx index 187c750ce..df5932d67 100644 --- a/apps/spruce/src/pages/version/tasks/PatchTasksTable.tsx +++ b/apps/spruce/src/pages/version/tasks/PatchTasksTable.tsx @@ -2,6 +2,7 @@ import { useParams } from "react-router-dom"; import { useVersionAnalytics } from "analytics"; import { InputFilterProps } from "components/Table/Filters"; import TasksTable from "components/TasksTable"; +import { slugs } from "constants/routes"; import { Task, VersionTasksQuery, SortOrder } from "gql/generated/types"; import { useTaskStatuses, @@ -27,7 +28,7 @@ export const PatchTasksTable: React.FC = ({ sorts, tasks, }) => { - const { id: versionId } = useParams<{ id: string }>(); + const { [slugs.versionId]: versionId } = useParams(); const updateQueryParams = useUpdateURLQueryParams(); const { sendEvent } = useVersionAnalytics(versionId); const filterHookProps = {