diff --git a/packages/manager/.changeset/pr-10189-upcoming-features-1707838240012.md b/packages/manager/.changeset/pr-10189-upcoming-features-1707838240012.md
new file mode 100644
index 00000000000..9f90805efdc
--- /dev/null
+++ b/packages/manager/.changeset/pr-10189-upcoming-features-1707838240012.md
@@ -0,0 +1,5 @@
+---
+"@linode/manager": Upcoming Features
+---
+
+Handle errors gracefully when OBJ Multi-Cluster feature flag is enabled without MSW ([#10189](https://github.com/linode/manager/pull/10189))
diff --git a/packages/manager/src/components/AccountActivation/AccountActivationError.tsx b/packages/manager/src/components/AccountActivation/AccountActivationError.tsx
index ad56e0fcc4b..c5e76f969f5 100644
--- a/packages/manager/src/components/AccountActivation/AccountActivationError.tsx
+++ b/packages/manager/src/components/AccountActivation/AccountActivationError.tsx
@@ -23,10 +23,12 @@ const AccountActivationError = (props: CombinedProps) => {
return (
- {getAPIErrorOrDefault(
- props.errors,
- 'Your account is not yet activated. Please reach out to support@linode.com for more information'
- )[0].reason}
+ {
+ getAPIErrorOrDefault(
+ props.errors,
+ 'Your account is not yet activated. Please reach out to support@linode.com for more information'
+ )[0].reason
+ }
);
};
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
index 38d48b1d327..639b5a6d6ac 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyLanding.tsx
@@ -30,8 +30,8 @@ import { AccessKeyDrawer } from './AccessKeyDrawer';
import { AccessKeyTable } from './AccessKeyTable/AccessKeyTable';
import { OMC_AccessKeyDrawer } from './OMC_AccessKeyDrawer';
import { RevokeAccessKeyDialog } from './RevokeAccessKeyDialog';
-import ViewPermissionsDrawer from './ViewPermissionsDrawer';
import { MODE, OpenAccessDrawer } from './types';
+import ViewPermissionsDrawer from './ViewPermissionsDrawer';
interface Props {
accessDrawerOpen: boolean;
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
index 70b112fc15b..241f0b43d77 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableBody.tsx
@@ -1,15 +1,16 @@
-import React from 'react';
import {
ObjectStorageKey,
RegionS3EndpointAndID,
} from '@linode/api-v4/lib/object-storage';
import { APIError } from '@linode/api-v4/lib/types';
+import React from 'react';
import { TableRowEmpty } from 'src/components/TableRowEmpty/TableRowEmpty';
import { TableRowError } from 'src/components/TableRowError/TableRowError';
import { TableRowLoading } from 'src/components/TableRowLoading/TableRowLoading';
-import { AccessKeyTableRows } from './AccessKeyTableRows';
+
import { OpenAccessDrawer } from '../types';
+import { AccessKeyTableRow } from './AccessKeyTableRow';
type Props = {
data: ObjectStorageKey[] | undefined;
@@ -52,13 +53,18 @@ export const AccessKeyTableBody = ({
return (
<>
{data && data.length > 0 ? (
-
+ <>
+ {data.map((eachKey: ObjectStorageKey, index) => (
+
+ ))}
+ >
) : (
)}
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx
new file mode 100644
index 00000000000..828570409dd
--- /dev/null
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRow.tsx
@@ -0,0 +1,84 @@
+import {
+ ObjectStorageKey,
+ RegionS3EndpointAndID,
+} from '@linode/api-v4/lib/object-storage';
+import { styled } from '@mui/material/styles';
+import React from 'react';
+
+import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
+import { TableCell } from 'src/components/TableCell';
+import { TableRow } from 'src/components/TableRow';
+import { Typography } from 'src/components/Typography';
+import { useAccountManagement } from 'src/hooks/useAccountManagement';
+import { useFlags } from 'src/hooks/useFlags';
+import { isFeatureEnabled } from 'src/utilities/accountCapabilities';
+
+import { OpenAccessDrawer } from '../types';
+import { AccessKeyActionMenu } from './AccessKeyActionMenu';
+import { HostNameTableCell } from './HostNameTableCell';
+
+type Props = {
+ openDrawer: OpenAccessDrawer;
+ openRevokeDialog: (storageKeyData: ObjectStorageKey) => void;
+ setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
+ setShowHostNamesDrawers: (show: boolean) => void;
+ storageKeyData: ObjectStorageKey;
+};
+
+export const AccessKeyTableRow = ({
+ openDrawer,
+ openRevokeDialog,
+ setHostNames,
+ setShowHostNamesDrawers,
+ storageKeyData,
+}: Props) => {
+ const { account } = useAccountManagement();
+ const flags = useFlags();
+
+ const isObjMultiClusterEnabled = isFeatureEnabled(
+ 'Object Storage Access Key Regions',
+ Boolean(flags.objMultiCluster),
+ account?.capabilities ?? []
+ );
+
+ return (
+
+
+
+ {storageKeyData.label}
+
+
+
+
+ {storageKeyData.access_key}
+
+
+
+ {isObjMultiClusterEnabled && (
+
+ )}
+
+
+
+
+
+ );
+};
+
+const StyledCopyIcon = styled(CopyTooltip)(({ theme }) => ({
+ '& svg': {
+ height: 12,
+ top: 1,
+ width: 12,
+ },
+ marginLeft: theme.spacing(),
+}));
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRows.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRows.tsx
deleted file mode 100644
index e07d3083434..00000000000
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/AccessKeyTableRows.tsx
+++ /dev/null
@@ -1,109 +0,0 @@
-import {
- ObjectStorageKey,
- RegionS3EndpointAndID,
-} from '@linode/api-v4/lib/object-storage';
-
-import { styled } from '@mui/material/styles';
-import React from 'react';
-
-import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
-import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
-
-import { TableCell } from 'src/components/TableCell';
-
-import { TableRow } from 'src/components/TableRow';
-import { Typography } from 'src/components/Typography';
-import { useAccountManagement } from 'src/hooks/useAccountManagement';
-import { useFlags } from 'src/hooks/useFlags';
-import { useRegionsQuery } from 'src/queries/regions';
-import { isFeatureEnabled } from 'src/utilities/accountCapabilities';
-import { getRegionsByRegionId } from 'src/utilities/regions';
-
-import { AccessKeyActionMenu } from './AccessKeyActionMenu';
-import { OpenAccessDrawer } from '../types';
-
-type Props = {
- objectStorageKeys: ObjectStorageKey[];
- openDrawer: OpenAccessDrawer;
- openRevokeDialog: (objectStorageKey: ObjectStorageKey) => void;
- setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
- setShowHostNamesDrawers: (show: boolean) => void;
-};
-
-export const AccessKeyTableRows = ({
- objectStorageKeys,
- openDrawer,
- openRevokeDialog,
- setHostNames,
- setShowHostNamesDrawers,
-}: Props) => {
- const { account } = useAccountManagement();
- const { data: regionsData } = useRegionsQuery();
- const flags = useFlags();
-
- const regionsLookup = regionsData && getRegionsByRegionId(regionsData);
-
- const isObjMultiClusterEnabled = isFeatureEnabled(
- 'Object Storage Access Key Regions',
- Boolean(flags.objMultiCluster),
- account?.capabilities ?? []
- );
-
- return (
- <>
- {objectStorageKeys.map((eachKey: ObjectStorageKey) => (
-
-
-
- {eachKey.label}
-
-
-
-
- {eachKey.access_key}
-
-
-
- {isObjMultiClusterEnabled && regionsLookup && (
-
- {`${regionsLookup[eachKey?.regions[0]?.id].label}: ${
- eachKey?.regions[0]?.s3_endpoint
- } `}
- {eachKey?.regions?.length === 1 && (
-
- )}
- {eachKey.regions.length > 1 && (
- {
- setHostNames(eachKey.regions);
- setShowHostNamesDrawers(true);
- }}
- type="button"
- >
- and {eachKey.regions.length - 1} more...
-
- )}
-
- )}
-
-
-
-
- ))}
- >
- );
-};
-
-const StyledCopyIcon = styled(CopyTooltip)(({ theme }) => ({
- '& svg': {
- height: 12,
- top: 1,
- width: 12,
- },
- marginLeft: theme.spacing(),
-}));
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx
new file mode 100644
index 00000000000..e1ece753526
--- /dev/null
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/AccessKeyTable/HostNameTableCell.tsx
@@ -0,0 +1,65 @@
+import {
+ ObjectStorageKey,
+ RegionS3EndpointAndID,
+} from '@linode/api-v4/lib/object-storage';
+import { styled } from '@mui/material/styles';
+import React from 'react';
+
+import { StyledLinkButton } from 'src/components/Button/StyledLinkButton';
+import { CopyTooltip } from 'src/components/CopyTooltip/CopyTooltip';
+import { TableCell } from 'src/components/TableCell';
+import { useRegionsQuery } from 'src/queries/regions';
+import { getRegionsByRegionId } from 'src/utilities/regions';
+
+type Props = {
+ setHostNames: (hostNames: RegionS3EndpointAndID[]) => void;
+ setShowHostNamesDrawers: (show: boolean) => void;
+ storageKeyData: ObjectStorageKey;
+};
+
+export const HostNameTableCell = ({
+ setHostNames,
+ setShowHostNamesDrawers,
+ storageKeyData,
+}: Props) => {
+ const { data: regionsData } = useRegionsQuery();
+
+ const regionsLookup = regionsData && getRegionsByRegionId(regionsData);
+
+ const { regions } = storageKeyData;
+
+ if (!regionsLookup || !regionsData || !regions) {
+ return ;
+ }
+
+ return (
+
+ {`${regionsLookup[storageKeyData.regions[0].id].label}: ${
+ storageKeyData?.regions[0]?.s3_endpoint
+ } `}
+ {storageKeyData?.regions?.length === 1 && (
+
+ )}
+ {storageKeyData.regions.length > 1 && (
+ {
+ setHostNames(storageKeyData.regions);
+ setShowHostNamesDrawers(true);
+ }}
+ type="button"
+ >
+ and {storageKeyData.regions.length - 1} more...
+
+ )}
+
+ );
+};
+
+const StyledCopyIcon = styled(CopyTooltip)(({ theme }) => ({
+ '& svg': {
+ height: 12,
+ top: 1,
+ width: 12,
+ },
+ marginLeft: theme.spacing(),
+}));
diff --git a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx
index 6231553e59a..c65e752c2a2 100644
--- a/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx
+++ b/packages/manager/src/features/ObjectStorage/AccessKeyLanding/OMC_AccessKeyDrawer.tsx
@@ -174,10 +174,12 @@ export const OMC_AccessKeyDrawer = (props: AccessKeyDrawerProps) => {
: updateObjectStorageKeysSchema,
});
+ // @TODO OBJ Multicluster: The objectStorageKey check is a temporary fix to handle error cases when the feature flag is enabled without Mock Service Worker (MSW). This can be removed during the feature flag cleanup.
const isSaveDisabled =
isRestrictedUser ||
(mode !== 'creating' &&
objectStorageKey &&
+ objectStorageKey?.regions?.length > 0 &&
!hasLabelOrRegionsChanged(formik.values, objectStorageKey));
const beforeSubmit = () => {
diff --git a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx
index b54025501cb..bd597a4a468 100644
--- a/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx
+++ b/packages/manager/src/features/ObjectStorage/BucketLanding/CreateBucketDrawer.test.tsx
@@ -92,7 +92,10 @@ describe('CreateBucketDrawer', () => {
getByTestId,
} = renderWithTheme(, { queryClient });
- await userEvent.type(getByLabelText('Label', { exact: false }), 'my-test-bucket');
+ await userEvent.type(
+ getByLabelText('Label', { exact: false }),
+ 'my-test-bucket'
+ );
// We must waitFor because we need to load region and cluster data from the API
await waitFor(() =>
diff --git a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
index aed649b7381..f49b977a3a0 100644
--- a/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
+++ b/packages/manager/src/features/Profile/SecretTokenDialog/SecretTokenDialog.tsx
@@ -76,46 +76,61 @@ export const SecretTokenDialog = (props: Props) => {
spacingTop={8}
variant="warning"
/>
- {isObjMultiClusterEnabled && (
-
-
- `${regionsLookup?.[region.id]?.label}: ${
- region.s3_endpoint
- }`
- )
- .join('\n') ?? ''
- }
- />
-
- )}
- {isObjMultiClusterEnabled && (
- ({
- backgroundColor: theme.bg.main,
- border: `1px solid ${theme.color.grey3}`,
- borderColor: theme.name === 'light' ? '#ccc' : '#222',
- })}
- >
- {objectStorageKey?.regions.map((region, index) => (
- 0 && (
+
+
+ `${regionsLookup?.[region.id]?.label}: ${
+ region.s3_endpoint
+ }`
+ )
+ .join('\n') ?? ''
+ }
/>
- ))}
-
- )}
+
+ )}
+ {/* @TODO OBJ Multicluster: The objectStorageKey check is a temporary fix
+ to handle error cases when the feature flag is enabled without Mock
+ Service Worker (MSW). This can be removed during the feature flag cleanup. */}
+ {isObjMultiClusterEnabled &&
+ objectStorageKey &&
+ objectStorageKey?.regions?.length > 0 && (
+ ({
+ '.copyIcon': {
+ marginRight: 0,
+ paddingRight: 0,
+ },
+ backgroundColor: theme.bg.main,
+ border: `1px solid ${theme.color.grey3}`,
+ borderColor: theme.name === 'light' ? '#ccc' : '#222',
+ padding: theme.spacing(1),
+ })}
+ >
+ {objectStorageKey?.regions.map((region, index) => (
+
+ ))}
+
+ )}
{objectStorageKey ? (
<>