From a846b45c69dd481ef2efb9e50fe591d6bda21194 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Mon, 24 Mar 2025 17:31:37 -0400 Subject: [PATCH 01/10] initial commit - save work --- packages/manager/.eslintrc.cjs | 1 + packages/manager/src/MainContent.tsx | 5 -- .../ObjectStorage/BucketDetail/index.tsx | 7 --- .../ObjectStorage/ObjectStorageLanding.tsx | 54 ++++++++----------- packages/manager/src/routes/index.tsx | 1 + .../manager/src/routes/objectStorage/index.ts | 26 ++++++--- .../objectStorage/objectStorageLazyRoutes.ts | 16 ++++++ 7 files changed, 57 insertions(+), 53 deletions(-) create mode 100644 packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts diff --git a/packages/manager/.eslintrc.cjs b/packages/manager/.eslintrc.cjs index fd5c9ac21e0..9f112907654 100644 --- a/packages/manager/.eslintrc.cjs +++ b/packages/manager/.eslintrc.cjs @@ -147,6 +147,7 @@ module.exports = { 'src/features/Images/**/*', 'src/features/Longview/**/*', 'src/features/NodeBalancers/**/*', + 'src/features/ObjectStorage/**/*', 'src/features/PlacementGroups/**/*', 'src/features/StackScripts/**/*', 'src/features/Volumes/**/*', diff --git a/packages/manager/src/MainContent.tsx b/packages/manager/src/MainContent.tsx index 02c79b145d4..58642e15758 100644 --- a/packages/manager/src/MainContent.tsx +++ b/packages/manager/src/MainContent.tsx @@ -123,7 +123,6 @@ const Kubernetes = React.lazy(() => default: module.Kubernetes, })) ); -const ObjectStorage = React.lazy(() => import('src/features/ObjectStorage')); const Profile = React.lazy(() => import('src/features/Profile/Profile').then((module) => ({ default: module.Profile, @@ -366,10 +365,6 @@ export const MainContent = () => { - {isIAMEnabled && ( diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index 0cd7065df01..b1434c1fab7 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -1,6 +1,5 @@ import { useAccount } from '@linode/queries'; import { isFeatureEnabledV2 } from '@linode/utilities'; -import { createLazyRoute } from '@tanstack/react-router'; import * as React from 'react'; import { matchPath } from 'react-router-dom'; @@ -133,10 +132,4 @@ export const BucketDetailLanding = React.memo((props: Props) => { ); }); -export const bucketDetailLandingLazyRoute = createLazyRoute( - '/object-storage/buckets/$clusterId/$bucketName' -)({ - component: BucketDetailLanding, -}); - export default BucketDetailLanding; diff --git a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx index 01536707018..d2a9ca92eaa 100644 --- a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx @@ -1,10 +1,9 @@ import { StyledLinkButton, Typography } from '@linode/ui'; import { isFeatureEnabledV2, useOpenClose } from '@linode/utilities'; import { styled } from '@mui/material/styles'; -import { createLazyRoute } from '@tanstack/react-router'; +import { useMatch, useNavigate } from '@tanstack/react-router'; import { DateTime } from 'luxon'; import * as React from 'react'; -import { useHistory, useParams } from 'react-router-dom'; import { DismissibleBanner } from 'src/components/DismissibleBanner/DismissibleBanner'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; @@ -13,12 +12,13 @@ import { Link } from 'src/components/Link'; import { PromotionalOfferCard } from 'src/components/PromotionalOfferCard/PromotionalOfferCard'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; -import { TabLinkList } from 'src/components/Tabs/TabLinkList'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; +import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; import { useAccountManagement } from 'src/hooks/useAccountManagement'; import { useFlags } from 'src/hooks/useFlags'; import { useRestrictedGlobalGrantCheck } from 'src/hooks/useRestrictedGlobalGrantCheck'; +import { useTabs } from 'src/hooks/useTabs'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; import { getRestrictedResourceText } from '../Account/utils'; @@ -40,12 +40,9 @@ const AccessKeyLanding = React.lazy(() => ); export const ObjectStorageLanding = () => { - const history = useHistory(); + const navigate = useNavigate(); + const match = useMatch({ from: '/object-storage' }); const [mode, setMode] = React.useState('creating'); - const { action, tab } = useParams<{ - action?: 'create'; - tab?: 'access-keys' | 'buckets'; - }>(); const { _isRestrictedUser, @@ -71,18 +68,16 @@ export const ObjectStorageLanding = () => { const openDrawer = useOpenClose(); - const tabs = [ - { routeName: `/object-storage/buckets`, title: 'Buckets' }, - { routeName: `/object-storage/access-keys`, title: 'Access Keys' }, - ]; + const { handleTabChange, tabIndex, tabs } = useTabs([ + { title: 'Buckets', to: `/object-storage/buckets` }, + { title: 'Access Keys', to: `/object-storage/access-keys` }, + ]); const handleOpenAccessDrawer = (mode: MODE) => { setMode(mode); openDrawer.open(); }; - const navToURL = (index: number) => history.push(tabs[index].routeName); - const objPromotionalOffers = flags.promotionalOffers?.filter((offer) => offer.features.includes('Object Storage') @@ -101,10 +96,10 @@ export const ObjectStorageLanding = () => { }); const shouldHideDocsAndCreateButtons = - !areBucketsLoading && tab === 'buckets' && userHasNoBucketCreated; + !areBucketsLoading && tabIndex === 0 && userHasNoBucketCreated; - const isAccessKeysTab = tab === 'access-keys'; - const isCreateAction = action === 'create'; + const isAccessKeysTab = tabIndex === 1; + const isCreateAction = match?.routeId.includes('create'); const createButtonText = isAccessKeysTab ? 'Create Access Key' @@ -113,14 +108,13 @@ export const ObjectStorageLanding = () => { const createButtonAction = () => { if (isAccessKeysTab) { setMode('creating'); - history.replace('/object-storage/access-keys/create'); + navigate({ to: '/object-storage/access-keys/create' }); openDrawer.open(); } else { - history.replace('/object-storage/buckets/create'); + navigate({ to: '/object-storage/buckets/create' }); } }; - const tabIndex = tab === 'access-keys' ? 1 : 0; const isCreateBucketOpen = !isAccessKeysTab && isCreateAction; const isCreateAccessKeyOpen = isAccessKeysTab && isCreateAction; @@ -151,8 +145,8 @@ export const ObjectStorageLanding = () => { shouldHideDocsAndCreateButtons={shouldHideDocsAndCreateButtons} title="Object Storage" /> - - + + {objPromotionalOffers.map((promotionalOffer) => ( { { openDrawer.close(); - history.replace('/object-storage/access-keys'); + navigate({ to: '/object-storage/access-keys' }); }} accessDrawerOpen={isCreateAccessKeyOpen || openDrawer.isOpen} isRestrictedUser={_isRestrictedUser} @@ -190,12 +184,12 @@ export const ObjectStorageLanding = () => { {isObjMultiClusterEnabled ? ( history.replace('/object-storage/buckets')} + onClose={() => navigate({ to: '/object-storage/buckets' })} /> ) : ( history.replace('/object-storage/buckets')} + onClose={() => navigate({ to: '/object-storage/buckets' })} /> )} @@ -206,7 +200,7 @@ export const ObjectStorageLanding = () => { const NOTIFICATION_KEY = 'obj-billing-notification'; export const BillingNotice = React.memo(() => { - const history = useHistory(); + const navigate = useNavigate(); return ( { can cancel Object Storage in your{' '} Account Settings, or{' '} history.replace('/object-storage/buckets/create')} + onClick={() => navigate({ to: '/object-storage/buckets/create' })} > create a Bucket. @@ -237,9 +231,3 @@ const StyledPromotionalOfferCard = styled(PromotionalOfferCard, { })(({ theme }) => ({ marginBottom: theme.spacing(0.5), })); - -export const objectStorageLandingLazyRoute = createLazyRoute('/object-storage')( - { - component: ObjectStorageLanding, - } -); diff --git a/packages/manager/src/routes/index.tsx b/packages/manager/src/routes/index.tsx index 5fd7ada5b49..60a55a3abeb 100644 --- a/packages/manager/src/routes/index.tsx +++ b/packages/manager/src/routes/index.tsx @@ -93,6 +93,7 @@ export const migrationRouteTree = migrationRootRoute.addChildren([ imagesRouteTree, longviewRouteTree, nodeBalancersRouteTree, + objectStorageRouteTree, placementGroupsRouteTree, stackScriptsRouteTree, volumesRouteTree, diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index f2ccbfaaec3..a872e429435 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -13,7 +13,7 @@ const objectStorageIndexRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: '/', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); @@ -22,7 +22,7 @@ const objectStorageBucketsRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'buckets', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); @@ -31,7 +31,7 @@ const objectStorageAccessKeysRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'access-keys', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); @@ -40,7 +40,16 @@ const objectStorageBucketCreateRoute = createRoute({ getParentRoute: () => objectStorageBucketsRoute, path: 'create', }).lazy(() => - import('src/features/ObjectStorage/ObjectStorageLanding').then( + import('./objectStorageLazyRoutes').then( + (m) => m.objectStorageLandingLazyRoute + ) +); + +const objectStorageAccessKeyCreateRoute = createRoute({ + getParentRoute: () => objectStorageAccessKeysRoute, + path: 'create', +}).lazy(() => + import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute ) ); @@ -49,7 +58,7 @@ const objectStorageBucketDetailRoute = createRoute({ getParentRoute: () => objectStorageBucketsRoute, path: '$clusterId/$bucketName', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -58,7 +67,7 @@ const objectStorageBucketDetailObjectsRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'objects', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -67,7 +76,7 @@ const objectStorageBucketDetailAccessRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'access', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -76,7 +85,7 @@ const objectStorageBucketSSLRoute = createRoute({ getParentRoute: () => objectStorageBucketDetailRoute, path: 'ssl', }).lazy(() => - import('src/features/ObjectStorage/BucketDetail').then( + import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute ) ); @@ -89,6 +98,7 @@ export const objectStorageRouteTree = objectStorageRoute.addChildren([ objectStorageBucketSSLRoute, ]), objectStorageBucketCreateRoute, + objectStorageAccessKeyCreateRoute, ]), objectStorageAccessKeysRoute, ]); diff --git a/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts b/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts new file mode 100644 index 00000000000..68b112b495f --- /dev/null +++ b/packages/manager/src/routes/objectStorage/objectStorageLazyRoutes.ts @@ -0,0 +1,16 @@ +import { createLazyRoute } from '@tanstack/react-router'; + +import { BucketDetailLanding } from 'src/features/ObjectStorage/BucketDetail'; +import { ObjectStorageLanding } from 'src/features/ObjectStorage/ObjectStorageLanding'; + +export const objectStorageLandingLazyRoute = createLazyRoute('/object-storage')( + { + component: ObjectStorageLanding, + } +); + +export const bucketDetailLandingLazyRoute = createLazyRoute( + '/object-storage/buckets/$clusterId/$bucketName' +)({ + component: BucketDetailLanding, +}); From 9d3eb1ede782e11555a63eed366d1bf362bb3c02 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Tue, 25 Mar 2025 14:57:07 -0400 Subject: [PATCH 02/10] save progress --- .../AccessKeyLanding/AccessKeyLanding.tsx | 28 ++++----- .../BucketDetail/BucketDetail.tsx | 32 ++++------ .../ObjectStorage/BucketDetail/index.tsx | 63 ++++++------------- .../BucketLanding/OMC_BucketLanding.tsx | 48 ++++++++------ .../ObjectStorage/ObjectStorageLanding.tsx | 26 ++------ .../src/features/ObjectStorage/index.tsx | 39 ------------ .../manager/src/routes/objectStorage/index.ts | 37 ++++++----- 7 files changed, 100 insertions(+), 173 deletions(-) delete mode 100644 packages/manager/src/features/ObjectStorage/index.tsx diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx index 1c29c77af3f..039036ac6b3 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx @@ -5,6 +5,7 @@ import { } from '@linode/api-v4/lib/object-storage'; import { useAccountSettings } from '@linode/queries'; import { isFeatureEnabledV2, useErrors, useOpenClose } from '@linode/utilities'; +import { useNavigate } from '@tanstack/react-router'; import * as React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; @@ -37,22 +38,15 @@ import type { FormikBag, FormikHelpers } from 'formik'; interface Props { accessDrawerOpen: boolean; - closeAccessDrawer: () => void; isRestrictedUser: boolean; mode: MODE; - openAccessDrawer: (mode: MODE) => void; } export type FormikProps = FormikBag; export const AccessKeyLanding = (props: Props) => { - const { - accessDrawerOpen, - closeAccessDrawer, - isRestrictedUser, - mode, - openAccessDrawer, - } = props; + const { accessDrawerOpen, isRestrictedUser, mode } = props; + const navigate = useNavigate(); const pagination = usePagination(1); @@ -116,7 +110,7 @@ export const AccessKeyLanding = (props: Props) => { // "Refresh" keys to include the newly created key refetch(); - props.closeAccessDrawer(); + navigate({ to: '/object-storage/access-keys' }); displayKeysDialog.open(); // If our Redux Store says that the user doesn't have OBJ enabled, @@ -174,12 +168,12 @@ export const AccessKeyLanding = (props: Props) => { // If the new label is the same as the old one, no need to make an API // request. Just close the drawer and return early. if (values.label === keyToEdit.label) { - return closeAccessDrawer(); + return navigate({ to: '/object-storage/access-keys' }); } setSubmitting(true); - updateObjectStorageKey( + return updateObjectStorageKey( keyToEdit.id, isObjMultiClusterEnabled ? values : { label: values.label } ) @@ -189,7 +183,7 @@ export const AccessKeyLanding = (props: Props) => { // "Refresh" keys to display the newly updated key refetch(); - closeAccessDrawer(); + navigate({ to: '/object-storage/access-keys' }); // @analytics sendEditAccessKeyEvent(); @@ -250,7 +244,7 @@ export const AccessKeyLanding = (props: Props) => { ) => { setKeyToEdit(objectStorageKey); if (mode !== 'creating') { - openAccessDrawer(mode); + navigate({ to: '/object-storage/access-keys' }); } }; @@ -291,7 +285,7 @@ export const AccessKeyLanding = (props: Props) => { isRestrictedUser={props.isRestrictedUser} mode={mode} objectStorageKey={keyToEdit ? keyToEdit : undefined} - onClose={closeAccessDrawer} + onClose={() => navigate({ to: '/object-storage/access-keys' })} onSubmit={mode === 'creating' ? handleCreateKey : handleEditKey} open={accessDrawerOpen} /> @@ -300,7 +294,7 @@ export const AccessKeyLanding = (props: Props) => { isRestrictedUser={props.isRestrictedUser} mode={mode} objectStorageKey={keyToEdit ? keyToEdit : undefined} - onClose={closeAccessDrawer} + onClose={() => navigate({ to: '/object-storage/access-keys' })} onSubmit={mode === 'creating' ? handleCreateKey : handleEditKey} open={accessDrawerOpen} /> @@ -308,7 +302,7 @@ export const AccessKeyLanding = (props: Props) => { navigate({ to: '/object-storage/access-keys' })} open={mode === 'viewing' && accessDrawerOpen} /> { @@ -76,15 +67,16 @@ export const BucketDetail = (props: Props) => { * @note If `Object Storage Access Key Regions` is enabled, clusterId will actually contain * the bucket's region id */ - const match = useRouteMatch( - '/object-storage/buckets/:clusterId/:bucketName' - ); - const location = useLocation(); - const history = useHistory(); + // const match = useRouteMatch( + // '/object-storage/buckets/:clusterId/:bucketName' + // ); const { enqueueSnackbar } = useSnackbar(); - const bucketName = match?.params.bucketName || ''; - const clusterId = match?.params.clusterId || ''; - const prefix = getQueryParamFromQueryString(location.search, 'prefix'); + const { bucketName, clusterId } = useParams({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); + const { prefix = '' } = useSearch({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); const queryClient = useQueryClient(); const flags = useFlags(); diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx index b1434c1fab7..ded85fa136e 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/index.tsx @@ -1,25 +1,22 @@ import { useAccount } from '@linode/queries'; import { isFeatureEnabledV2 } from '@linode/utilities'; +import { useParams } from '@tanstack/react-router'; import * as React from 'react'; -import { matchPath } from 'react-router-dom'; import { LandingHeader } from 'src/components/LandingHeader'; import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; import { SuspenseLoader } from 'src/components/SuspenseLoader'; import { SafeTabPanel } from 'src/components/Tabs/SafeTabPanel'; -import { TabLinkList } from 'src/components/Tabs/TabLinkList'; import { TabPanels } from 'src/components/Tabs/TabPanels'; import { Tabs } from 'src/components/Tabs/Tabs'; +import { TanStackTabLinkList } from 'src/components/Tabs/TanStackTabLinkList'; import { useFlags } from 'src/hooks/useFlags'; +import { useTabs } from 'src/hooks/useTabs'; import { useObjectStorageBuckets } from 'src/queries/object-storage/queries'; import { BucketAccess } from './BucketAccess'; -import type { ObjectStorageClusterID } from '@linode/api-v4/lib/object-storage'; -import type { ComponentType, LazyExoticComponent } from 'react'; -import type { RouteComponentProps } from 'react-router-dom'; - -const ObjectList: LazyExoticComponent> = React.lazy(() => +const ObjectList = React.lazy(() => import('./BucketDetail').then((module) => ({ default: module.BucketDetail })) ); const BucketSSL = React.lazy(() => @@ -28,14 +25,10 @@ const BucketSSL = React.lazy(() => })) ); -interface MatchProps { - bucketName: string; - clusterId: ObjectStorageClusterID; -} - -type Props = RouteComponentProps; - -export const BucketDetailLanding = React.memo((props: Props) => { +export const BucketDetailLanding = React.memo(() => { + const { bucketName, clusterId } = useParams({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); const { data: account } = useAccount(); const flags = useFlags(); @@ -49,44 +42,28 @@ export const BucketDetailLanding = React.memo((props: Props) => { isObjectStorageGen2Enabled ); - const matches = (p: string) => { - return Boolean(matchPath(p, { path: props.location.pathname })); - }; - const { bucketName, clusterId } = props.match.params; - const bucket = bucketsData?.buckets.find(({ label }) => label === bucketName); const { endpoint_type } = bucket ?? {}; const isGen2Endpoint = endpoint_type === 'E2' || endpoint_type === 'E3'; - const tabs = [ + const { handleTabChange, tabIndex, tabs } = useTabs([ { - routeName: `${props.match.url}/objects`, title: 'Objects', + to: `/object-storage/buckets/$clusterId/$bucketName/objects`, }, { - routeName: `${props.match.url}/access`, title: 'Access', + to: `/object-storage/buckets/$clusterId/$bucketName/access`, }, - ...(!isGen2Endpoint - ? [ - { - routeName: `${props.match.url}/ssl`, - title: 'SSL/TLS', - }, - ] - : []), - ]; - const [index, setIndex] = React.useState( - tabs.findIndex((tab) => matches(tab.routeName)) || 0 - ); - - const handleTabChange = (index: number) => { - setIndex(index); - props.history.push(tabs[index].routeName); - }; + { + hide: !isGen2Endpoint, + title: 'SSL/TLS', + to: `/object-storage/buckets/$clusterId/$bucketName/ssl`, + }, + ]); return ( <> @@ -107,13 +84,13 @@ export const BucketDetailLanding = React.memo((props: Props) => { docsLink="https://www.linode.com/docs/platform/object-storage/" /> - - + + }> - + { return Array.from(regionMap.values()); }, [objectStorageBucketsResponse, availableStorageRegions]); + const buckets = objectStorageBucketsResponse?.buckets ?? []; + const totalUsage = sumBucketUsage(buckets); + const bucketLabel = selectedBucket ? selectedBucket.label : ''; + + const { + handleOrderChange, + order, + orderBy, + sortedData: orderedData, + } = useOrderV2({ + data: buckets, + initialRoute: { + defaultOrder: { + order: 'asc', + orderBy: 'label', + }, + from: '/object-storage/buckets', + }, + preferenceKey: 'object-storage-buckets', + }); + if (isRestrictedUser) { return ; } @@ -170,10 +191,6 @@ export const OMC_BucketLanding = (props: Props) => { ); } - const buckets = objectStorageBucketsResponse.buckets; - const totalUsage = sumBucketUsage(buckets); - const bucketLabel = selectedBucket ? selectedBucket.label : ''; - return ( { )} - - {({ data: orderedData, handleOrderChange, order, orderBy }) => { - const bucketTableProps = { - data: orderedData, - handleClickDetails, - handleClickRemove, - handleOrderChange, - order, - orderBy, - }; - return ; - }} - + {/* If there's more than one Bucket, display the total usage. */} {buckets.length > 1 ? ( export const ObjectStorageLanding = () => { const navigate = useNavigate(); - const match = useMatch({ from: '/object-storage' }); + const match = useMatch({ strict: false }); const [mode, setMode] = React.useState('creating'); const { @@ -66,18 +66,11 @@ export const ObjectStorageLanding = () => { const userHasNoBucketCreated = objectStorageBucketsResponse?.buckets.length === 0; - const openDrawer = useOpenClose(); - const { handleTabChange, tabIndex, tabs } = useTabs([ { title: 'Buckets', to: `/object-storage/buckets` }, { title: 'Access Keys', to: `/object-storage/access-keys` }, ]); - const handleOpenAccessDrawer = (mode: MODE) => { - setMode(mode); - openDrawer.open(); - }; - const objPromotionalOffers = flags.promotionalOffers?.filter((offer) => offer.features.includes('Object Storage') @@ -99,7 +92,6 @@ export const ObjectStorageLanding = () => { !areBucketsLoading && tabIndex === 0 && userHasNoBucketCreated; const isAccessKeysTab = tabIndex === 1; - const isCreateAction = match?.routeId.includes('create'); const createButtonText = isAccessKeysTab ? 'Create Access Key' @@ -107,16 +99,15 @@ export const ObjectStorageLanding = () => { const createButtonAction = () => { if (isAccessKeysTab) { - setMode('creating'); navigate({ to: '/object-storage/access-keys/create' }); - openDrawer.open(); } else { navigate({ to: '/object-storage/buckets/create' }); } }; - const isCreateBucketOpen = !isAccessKeysTab && isCreateAction; - const isCreateAccessKeyOpen = isAccessKeysTab && isCreateAction; + const isCreateBucketOpen = match.routeId === '/object-storage/buckets/create'; + const isCreateAccessKeyOpen = + match.routeId === '/object-storage/access-keys/create'; return ( @@ -169,14 +160,9 @@ export const ObjectStorageLanding = () => { { - openDrawer.close(); - navigate({ to: '/object-storage/access-keys' }); - }} - accessDrawerOpen={isCreateAccessKeyOpen || openDrawer.isOpen} + accessDrawerOpen={isCreateAccessKeyOpen} isRestrictedUser={_isRestrictedUser} mode={mode} - openAccessDrawer={handleOpenAccessDrawer} /> diff --git a/packages/manager/src/features/ObjectStorage/index.tsx b/packages/manager/src/features/ObjectStorage/index.tsx deleted file mode 100644 index a20c0dd1cd2..00000000000 --- a/packages/manager/src/features/ObjectStorage/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import * as React from 'react'; -import { Route, Switch } from 'react-router-dom'; - -import { ProductInformationBanner } from 'src/components/ProductInformationBanner/ProductInformationBanner'; -import { SuspenseLoader } from 'src/components/SuspenseLoader'; - -import type { RouteComponentProps } from 'react-router-dom'; - -const ObjectStorageLanding = React.lazy(() => - import('./ObjectStorageLanding').then((module) => ({ - default: module.ObjectStorageLanding, - })) -); - -const BucketDetail = React.lazy(() => import('./BucketDetail')); - -type CombinedProps = RouteComponentProps; - -export const ObjectStorage: React.FC = (props) => { - const path = props.match.path; - - return ( - }> - - - - - - - ); -}; - -export default ObjectStorage; diff --git a/packages/manager/src/routes/objectStorage/index.ts b/packages/manager/src/routes/objectStorage/index.ts index a872e429435..0238df938e7 100644 --- a/packages/manager/src/routes/objectStorage/index.ts +++ b/packages/manager/src/routes/objectStorage/index.ts @@ -3,6 +3,10 @@ import { createRoute } from '@tanstack/react-router'; import { rootRoute } from '../root'; import { ObjectStorageRoute } from './ObjectStorageRoute'; +export interface ObjectStorageDetailSearchParams { + prefix?: string; +} + export const objectStorageRoute = createRoute({ component: ObjectStorageRoute, getParentRoute: () => rootRoute, @@ -18,7 +22,7 @@ const objectStorageIndexRoute = createRoute({ ) ); -const objectStorageBucketsRoute = createRoute({ +const objectStorageBucketsLandingRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'buckets', }).lazy(() => @@ -27,7 +31,7 @@ const objectStorageBucketsRoute = createRoute({ ) ); -const objectStorageAccessKeysRoute = createRoute({ +const objectStorageAccessKeysLandingRoute = createRoute({ getParentRoute: () => objectStorageRoute, path: 'access-keys', }).lazy(() => @@ -37,8 +41,8 @@ const objectStorageAccessKeysRoute = createRoute({ ); const objectStorageBucketCreateRoute = createRoute({ - getParentRoute: () => objectStorageBucketsRoute, - path: 'create', + getParentRoute: () => objectStorageRoute, + path: 'buckets/create', }).lazy(() => import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute @@ -46,8 +50,8 @@ const objectStorageBucketCreateRoute = createRoute({ ); const objectStorageAccessKeyCreateRoute = createRoute({ - getParentRoute: () => objectStorageAccessKeysRoute, - path: 'create', + getParentRoute: () => objectStorageRoute, + path: 'access-keys/create', }).lazy(() => import('./objectStorageLazyRoutes').then( (m) => m.objectStorageLandingLazyRoute @@ -55,8 +59,9 @@ const objectStorageAccessKeyCreateRoute = createRoute({ ); const objectStorageBucketDetailRoute = createRoute({ - getParentRoute: () => objectStorageBucketsRoute, - path: '$clusterId/$bucketName', + getParentRoute: () => objectStorageRoute, + path: 'buckets/$clusterId/$bucketName', + validateSearch: (search: ObjectStorageDetailSearchParams) => search, }).lazy(() => import('./objectStorageLazyRoutes').then( (m) => m.bucketDetailLandingLazyRoute @@ -90,15 +95,15 @@ const objectStorageBucketSSLRoute = createRoute({ ) ); export const objectStorageRouteTree = objectStorageRoute.addChildren([ - objectStorageIndexRoute, - objectStorageBucketsRoute.addChildren([ - objectStorageBucketDetailRoute.addChildren([ - objectStorageBucketDetailObjectsRoute, - objectStorageBucketDetailAccessRoute, - objectStorageBucketSSLRoute, - ]), + objectStorageIndexRoute.addChildren([ objectStorageBucketCreateRoute, objectStorageAccessKeyCreateRoute, + objectStorageBucketsLandingRoute, + objectStorageAccessKeysLandingRoute + ]), + objectStorageBucketDetailRoute.addChildren([ + objectStorageBucketDetailObjectsRoute, + objectStorageBucketDetailAccessRoute, + objectStorageBucketSSLRoute, ]), - objectStorageAccessKeysRoute, ]); From cc79936b35c1e219a0a31005455902cce7435124 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Tue, 25 Mar 2025 16:28:24 -0400 Subject: [PATCH 03/10] save progress --- .../AccessKeyLanding/AccessKeyLanding.tsx | 28 +++++++++++-------- .../ObjectStorage/ObjectStorageLanding.tsx | 13 ++++++++- 2 files changed, 29 insertions(+), 12 deletions(-) diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx index 039036ac6b3..1c29c77af3f 100644 --- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx @@ -5,7 +5,6 @@ import { } from '@linode/api-v4/lib/object-storage'; import { useAccountSettings } from '@linode/queries'; import { isFeatureEnabledV2, useErrors, useOpenClose } from '@linode/utilities'; -import { useNavigate } from '@tanstack/react-router'; import * as React from 'react'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; @@ -38,15 +37,22 @@ import type { FormikBag, FormikHelpers } from 'formik'; interface Props { accessDrawerOpen: boolean; + closeAccessDrawer: () => void; isRestrictedUser: boolean; mode: MODE; + openAccessDrawer: (mode: MODE) => void; } export type FormikProps = FormikBag; export const AccessKeyLanding = (props: Props) => { - const { accessDrawerOpen, isRestrictedUser, mode } = props; - const navigate = useNavigate(); + const { + accessDrawerOpen, + closeAccessDrawer, + isRestrictedUser, + mode, + openAccessDrawer, + } = props; const pagination = usePagination(1); @@ -110,7 +116,7 @@ export const AccessKeyLanding = (props: Props) => { // "Refresh" keys to include the newly created key refetch(); - navigate({ to: '/object-storage/access-keys' }); + props.closeAccessDrawer(); displayKeysDialog.open(); // If our Redux Store says that the user doesn't have OBJ enabled, @@ -168,12 +174,12 @@ export const AccessKeyLanding = (props: Props) => { // If the new label is the same as the old one, no need to make an API // request. Just close the drawer and return early. if (values.label === keyToEdit.label) { - return navigate({ to: '/object-storage/access-keys' }); + return closeAccessDrawer(); } setSubmitting(true); - return updateObjectStorageKey( + updateObjectStorageKey( keyToEdit.id, isObjMultiClusterEnabled ? values : { label: values.label } ) @@ -183,7 +189,7 @@ export const AccessKeyLanding = (props: Props) => { // "Refresh" keys to display the newly updated key refetch(); - navigate({ to: '/object-storage/access-keys' }); + closeAccessDrawer(); // @analytics sendEditAccessKeyEvent(); @@ -244,7 +250,7 @@ export const AccessKeyLanding = (props: Props) => { ) => { setKeyToEdit(objectStorageKey); if (mode !== 'creating') { - navigate({ to: '/object-storage/access-keys' }); + openAccessDrawer(mode); } }; @@ -285,7 +291,7 @@ export const AccessKeyLanding = (props: Props) => { isRestrictedUser={props.isRestrictedUser} mode={mode} objectStorageKey={keyToEdit ? keyToEdit : undefined} - onClose={() => navigate({ to: '/object-storage/access-keys' })} + onClose={closeAccessDrawer} onSubmit={mode === 'creating' ? handleCreateKey : handleEditKey} open={accessDrawerOpen} /> @@ -294,7 +300,7 @@ export const AccessKeyLanding = (props: Props) => { isRestrictedUser={props.isRestrictedUser} mode={mode} objectStorageKey={keyToEdit ? keyToEdit : undefined} - onClose={() => navigate({ to: '/object-storage/access-keys' })} + onClose={closeAccessDrawer} onSubmit={mode === 'creating' ? handleCreateKey : handleEditKey} open={accessDrawerOpen} /> @@ -302,7 +308,7 @@ export const AccessKeyLanding = (props: Props) => { navigate({ to: '/object-storage/access-keys' })} + onClose={closeAccessDrawer} open={mode === 'viewing' && accessDrawerOpen} /> { ? 'Create Access Key' : 'Create Bucket'; + const openDrawer = useOpenClose(); + + const handleOpenAccessDrawer = (mode: MODE) => { + setMode(mode); + openDrawer.open(); + }; + const createButtonAction = () => { if (isAccessKeysTab) { navigate({ to: '/object-storage/access-keys/create' }); @@ -160,9 +167,13 @@ export const ObjectStorageLanding = () => { + navigate({ to: '/object-storage/access-keys' }) + } accessDrawerOpen={isCreateAccessKeyOpen} isRestrictedUser={_isRestrictedUser} mode={mode} + openAccessDrawer={handleOpenAccessDrawer} /> From d7868001edebcb784e5aa2bbd333b0219b7176a4 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Tue, 25 Mar 2025 17:03:18 -0400 Subject: [PATCH 04/10] last utils --- .../BucketDetail/BucketProperties.tsx | 10 ++--- .../BucketLanding/BucketLanding.tsx | 44 +++++++++++-------- .../BucketLanding/BucketLandingEmptyState.tsx | 7 ++- .../ObjectStorage/ObjectStorageLanding.tsx | 9 ++-- 4 files changed, 38 insertions(+), 32 deletions(-) diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx index aed134be018..c1435a5718d 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketProperties.tsx @@ -1,6 +1,5 @@ -import { getQueryParamFromQueryString } from '@linode/utilities'; +import { useSearch } from '@tanstack/react-router'; import * as React from 'react'; -import { useHistory, useLocation } from 'react-router-dom'; import { BucketRateLimitTable } from '../BucketLanding/BucketRateLimitTable'; import { BucketBreadcrumb } from './BucketBreadcrumb'; @@ -19,10 +18,9 @@ interface Props { export const BucketProperties = React.memo((props: Props) => { const { bucket } = props; const { endpoint_type, hostname, label } = bucket; - - const location = useLocation(); - const history = useHistory(); - const prefix = getQueryParamFromQueryString(location.search, 'prefix'); + const { prefix = '' } = useSearch({ + from: '/object-storage/buckets/$clusterId/$bucketName', + }); return ( <> diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx index 67d208c7ef4..74494b5e81d 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.tsx @@ -7,9 +7,9 @@ import { makeStyles } from 'tss-react/mui'; import { DocumentTitleSegment } from 'src/components/DocumentTitle'; import { Link } from 'src/components/Link'; -import OrderBy from 'src/components/OrderBy'; import { TransferDisplay } from 'src/components/TransferDisplay/TransferDisplay'; import { TypeToConfirmDialog } from 'src/components/TypeToConfirmDialog/TypeToConfirmDialog'; +import { useOrderV2 } from 'src/hooks/useOrderV2'; import { useDeleteBucketMutation, useObjectStorageBuckets, @@ -113,6 +113,23 @@ export const BucketLanding = (props: Props) => { }); }; + const { + handleOrderChange, + order, + orderBy, + sortedData: orderedData, + } = useOrderV2({ + data: objectStorageBucketsResponse?.buckets, + initialRoute: { + defaultOrder: { + order: 'asc', + orderBy: 'label', + }, + from: '/object-storage/buckets', + }, + preferenceKey: 'object-storage-buckets', + }); + const closeRemoveBucketConfirmationDialog = React.useCallback(() => { removeBucketConfirmationDialog.close(); }, [removeBucketConfirmationDialog]); @@ -164,23 +181,14 @@ export const BucketLanding = (props: Props) => { )} - - {({ data: orderedData, handleOrderChange, order, orderBy }) => { - const bucketTableProps = { - data: orderedData, - handleClickDetails, - handleClickRemove, - handleOrderChange, - order, - orderBy, - }; - return ; - }} - + {/* If there's more than one Bucket, display the total usage. */} {objectStorageBucketsResponse.buckets.length > 1 ? ( { - const history = useHistory(); - + const navigate = useNavigate(); const isBucketCreationRestricted = useRestrictedGlobalGrantCheck({ globalGrantType: 'add_buckets', }); @@ -33,7 +32,7 @@ export const BucketLandingEmptyState = () => { category: linkAnalyticsEvent.category, label: 'Create Bucket', }); - history.replace('/object-storage/buckets/create'); + navigate({ to: '/object-storage/buckets/create' }); }, tooltipText: getRestrictedResourceText({ action: 'create', diff --git a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx index 1036bd3e026..a71f03fc532 100644 --- a/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx +++ b/packages/manager/src/features/ObjectStorage/ObjectStorageLanding.tsx @@ -167,10 +167,11 @@ export const ObjectStorageLanding = () => { - navigate({ to: '/object-storage/access-keys' }) - } - accessDrawerOpen={isCreateAccessKeyOpen} + closeAccessDrawer={() => { + navigate({ to: '/object-storage/access-keys' }); + openDrawer.close(); + }} + accessDrawerOpen={isCreateAccessKeyOpen || openDrawer.isOpen} isRestrictedUser={_isRestrictedUser} mode={mode} openAccessDrawer={handleOpenAccessDrawer} From 4c2db1e29f9a523e160f621cc3c0970aed88070d Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Wed, 26 Mar 2025 09:31:24 -0400 Subject: [PATCH 05/10] Fix test --- .../BucketLanding/BucketLanding.test.tsx | 36 ++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx index 615e640db1f..3a14872baa5 100644 --- a/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketLanding/BucketLanding.test.tsx @@ -11,8 +11,37 @@ import { renderWithTheme } from 'src/utilities/testHelpers'; import { BucketLanding } from './BucketLanding'; +const queryMocks = vi.hoisted(() => ({ + useNavigate: vi.fn(() => vi.fn()), + useOrderV2: vi.fn().mockReturnValue({}), + useSearch: vi.fn(), +})); + +vi.mock('@tanstack/react-router', async () => { + const actual = await vi.importActual('@tanstack/react-router'); + return { + ...actual, + useNavigate: queryMocks.useNavigate, + useSearch: queryMocks.useSearch, + }; +}); + +vi.mock('src/hooks/useOrderV2', async () => { + const actual = await vi.importActual('src/hooks/useOrderV2'); + return { + ...actual, + useOrderV2: queryMocks.useOrderV2, + }; +}); + describe('ObjectStorageLanding', () => { - beforeAll(() => server.listen()); + beforeAll(() => { + server.listen(); + queryMocks.useSearch.mockReturnValue({ + order: 'asc', + orderBy: 'label', + }); + }); afterEach(() => server.resetHandlers()); afterAll(() => server.close()); @@ -123,6 +152,11 @@ describe('ObjectStorageLanding', () => { it('renders rows for each Bucket', async () => { const buckets = objectStorageBucketFactory.buildList(2); + queryMocks.useOrderV2.mockReturnValue({ + order: 'asc', + orderBy: 'label', + sortedData: buckets, + }); // Mock Clusters server.use( From 0423f2d270ec85a0b5e02e44e538d8f910cdb871 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Wed, 26 Mar 2025 13:43:15 -0400 Subject: [PATCH 06/10] Fix e2e s --- packages/manager/src/components/Tabs/TanStackTabLinkList.tsx | 1 + .../manager/src/features/ObjectStorage/BucketDetail/index.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx b/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx index 89701c8b0ce..5e8573b48cc 100644 --- a/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx +++ b/packages/manager/src/components/Tabs/TanStackTabLinkList.tsx @@ -25,6 +25,7 @@ export const TanStackTabLinkList = ({ noLink, tabs }: TabLinkListProps) => { { }, { - hide: !isGen2Endpoint, + hide: isGen2Endpoint, title: 'SSL/TLS', to: `/object-storage/buckets/$clusterId/$bucketName/ssl`, }, From dcba3bfbed0df35b248c86f6e81688cc0732e84e Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Thu, 27 Mar 2025 09:16:41 -0400 Subject: [PATCH 07/10] Remaining e2e --- .../objectStorage/object-storage.e2e.spec.ts | 38 +++++++++---------- .../bucket-create-multicluster.spec.ts | 4 +- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts index 71d45be1a66..225eebb5142 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts @@ -170,31 +170,31 @@ describe('object storage end-to-end tests', () => { interceptUpdateBucketAccess(bucketLabel, bucketCluster).as( 'updateBucketAccess' ); - }); - // Navigate to new bucket page, upload and delete an object. - cy.visitWithLogin(bucketAccessPage); + // Navigate to new bucket page, upload and delete an object. + cy.visitWithLogin(bucketAccessPage); - cy.wait('@getBucketAccess'); + cy.wait('@getBucketAccess'); - // Make object public, confirm it can be accessed. - cy.findByLabelText('Access Control List (ACL)') - .should('be.visible') - .should('not.have.value', 'Loading access...') - .should('have.value', 'Private') - .click(); - cy.focused().type('Public Read'); + // Make object public, confirm it can be accessed. + cy.findByLabelText('Access Control List (ACL)') + .should('be.visible') + .should('not.have.value', 'Loading access...') + .should('have.value', 'Private') + .click(); + cy.focused().type('Public Read'); - ui.autocompletePopper - .findByTitle('Public Read') - .should('be.visible') - .click(); + ui.autocompletePopper + .findByTitle('Public Read') + .should('be.visible') + .click(); - ui.button.findByTitle('Save').should('be.visible').click(); + ui.button.findByTitle('Save').should('be.visible').click(); - // TODO Confirm that outgoing API request contains expected values. - cy.wait('@updateBucketAccess'); + // TODO Confirm that outgoing API request contains expected values. + cy.wait('@updateBucketAccess'); - cy.findByText('Bucket access updated successfully.'); + cy.findByText('Bucket access updated successfully.'); + }); }); }); diff --git a/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts b/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts index 3c597b9def5..8bfd54021dd 100644 --- a/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorageMulticluster/bucket-create-multicluster.spec.ts @@ -65,9 +65,7 @@ describe('Object Storage Multicluster Bucket create', () => { cy.visitWithLogin('/object-storage'); cy.wait(['@getRegions', '@getBuckets']); - ui.entityHeader.find().within(() => { - ui.button.findByTitle('Create Bucket').should('be.visible').click(); - }); + ui.button.findByTitle('Create Bucket').should('be.visible').click(); ui.drawer .findByTitle('Create Bucket') From d63b7b874ed7f1a6a72e87f57b2f1810fc548306 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Thu, 27 Mar 2025 09:30:55 -0400 Subject: [PATCH 08/10] Added changeset: Migrate Object Storage to Tanstack Router --- .../.changeset/pr-11924-tech-stories-1743082255119.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md diff --git a/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md b/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md new file mode 100644 index 00000000000..d2c9b40cba3 --- /dev/null +++ b/packages/manager/.changeset/pr-11924-tech-stories-1743082255119.md @@ -0,0 +1,5 @@ +--- +"@linode/manager": Tech Stories +--- + +Migrate Object Storage to Tanstack Router ([#11924](https://github.com/linode/manager/pull/11924)) From 62e03a1fe475a671fa3803e30b88f98b3fee7d43 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Thu, 27 Mar 2025 13:09:19 -0400 Subject: [PATCH 09/10] last e2e failure --- .../cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts index 225eebb5142..6a014990a23 100644 --- a/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts +++ b/packages/manager/cypress/e2e/core/objectStorage/object-storage.e2e.spec.ts @@ -100,9 +100,7 @@ describe('object storage end-to-end tests', () => { cy.findByLabelText('Content is loading').should('not.exist'); }); - ui.entityHeader.find().within(() => { - ui.button.findByTitle('Create Bucket').should('be.visible').click(); - }); + ui.button.findByTitle('Create Bucket').should('be.visible').click(); ui.drawer .findByTitle('Create Bucket') From 5dc2b706b031dff68c56bde0af93cd07d9c7e4f2 Mon Sep 17 00:00:00 2001 From: Alban Bailly Date: Thu, 3 Apr 2025 14:26:26 -0400 Subject: [PATCH 10/10] feedback @coliu-akamai --- .../src/features/ObjectStorage/BucketDetail/BucketDetail.tsx | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx index 1bde76bc492..39c719acba1 100644 --- a/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx +++ b/packages/manager/src/features/ObjectStorage/BucketDetail/BucketDetail.tsx @@ -67,9 +67,6 @@ export const BucketDetail = (props: Props) => { * @note If `Object Storage Access Key Regions` is enabled, clusterId will actually contain * the bucket's region id */ - // const match = useRouteMatch( - // '/object-storage/buckets/:clusterId/:bucketName' - // ); const { enqueueSnackbar } = useSnackbar(); const { bucketName, clusterId } = useParams({ from: '/object-storage/buckets/$clusterId/$bucketName',