From ad8d0ee1d7a3c55e117c0b8381a93bace3d69792 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Mon, 20 Nov 2023 16:35:53 +0100 Subject: [PATCH 01/26] Refactor bucket overview --- src/js/mock/S3Client.ts | 3 +- src/js/mock/managementClientMSWHandlers.ts | 2 +- src/react/databrowser/buckets/BucketList.tsx | 6 +- src/react/databrowser/buckets/Buckets.tsx | 3 - .../databrowser/buckets/details/Overview.tsx | 187 ++++++++++-------- .../details/__tests__/Overview.test.tsx | 20 +- src/react/utils/storageOptions.ts | 11 +- 7 files changed, 123 insertions(+), 109 deletions(-) diff --git a/src/js/mock/S3Client.ts b/src/js/mock/S3Client.ts index 6daab4a0f..4220c444b 100644 --- a/src/js/mock/S3Client.ts +++ b/src/js/mock/S3Client.ts @@ -10,6 +10,7 @@ import type { } from '../../types/s3'; import type { AWSError } from '../../types/aws'; import { addTrailingSlash } from '../../react/utils'; +import { azureblobstorage } from './managementClientMSWHandlers'; export const ownerName = 'bart'; export const bucketName = 'bucket'; export const fileName = 'file'; @@ -167,7 +168,7 @@ export const bucketInfoResponseVersioningDisabled: BucketInfo = { versioning: 'Disabled', isVersioning: false, public: false, - locationConstraint: 'azure-blob', + locationConstraint: azureblobstorage, objectLockConfiguration: { ObjectLockEnabled: 'Disabled', }, diff --git a/src/js/mock/managementClientMSWHandlers.ts b/src/js/mock/managementClientMSWHandlers.ts index afa85cf3f..0e9fb686a 100644 --- a/src/js/mock/managementClientMSWHandlers.ts +++ b/src/js/mock/managementClientMSWHandlers.ts @@ -10,7 +10,7 @@ import { LatestUsedCapacity } from '../../react/next-architecture/domain/entitie export const ACCOUNT_ID = '718643629313'; export const BUCKET_NAME = 'test-bucket'; -const azureblobstorage = 'azureblobstorage'; +export const azureblobstorage = 'azureblobstorage'; export const TRANSITION_WORKFLOW_CURRENT_ID = '0d55a1d7-349c-4e79-932b-b502bcc45a8f'; diff --git a/src/react/databrowser/buckets/BucketList.tsx b/src/react/databrowser/buckets/BucketList.tsx index 7080eb496..9c9ae58a4 100644 --- a/src/react/databrowser/buckets/BucketList.tsx +++ b/src/react/databrowser/buckets/BucketList.tsx @@ -5,7 +5,7 @@ import { useMemo } from 'react'; import { useHistory, useParams } from 'react-router-dom'; import { CoreUIColumn } from 'react-table'; import { XDM_FEATURE } from '../../../js/config'; -import type { LocationName, Locations } from '../../../types/config'; +import type { LocationName } from '../../../types/config'; import type { WorkflowScheduleUnitState } from '../../../types/stats'; import { useCurrentAccount } from '../../DataServiceRoleProvider'; import { useBucketLatestUsedCapacity } from '../../next-architecture/domain/business/buckets'; @@ -23,7 +23,6 @@ import { BucketLocationNameAndType } from '../../workflow/SourceBucketOption'; const SEARCH_QUERY_PARAM = 'search'; type Props = { - locations: Locations; buckets: Bucket[]; selectedBucketName: string | null | undefined; ingestionStates: WorkflowScheduleUnitState | null | undefined; @@ -31,7 +30,6 @@ type Props = { export default function BucketList({ selectedBucketName, buckets, - locations, ingestionStates, }: Props) { const { accountName } = useParams<{ accountName: string }>(); @@ -141,7 +139,7 @@ export default function BucketList({ }, }); return columns; - }, [locations, ingestionStates, features, isStorageManager]); + }, [ingestionStates, features, isStorageManager]); const selectedId = useMemo(() => { if (buckets) { diff --git a/src/react/databrowser/buckets/Buckets.tsx b/src/react/databrowser/buckets/Buckets.tsx index 274f12100..9aae6c010 100644 --- a/src/react/databrowser/buckets/Buckets.tsx +++ b/src/react/databrowser/buckets/Buckets.tsx @@ -19,9 +19,6 @@ export default function Buckets() { const metricsAdapter = useMetricsAdapter(); const { buckets } = useListBucketsForCurrentAccount({ metricsAdapter }); - const locations = useSelector( - (state: AppState) => state.configuration.latest.locations, - ); const ingestionStates = useSelector( (state: AppState) => state.instanceStatus.latest.metrics?.['ingest-schedule']?.states, diff --git a/src/react/databrowser/buckets/details/Overview.tsx b/src/react/databrowser/buckets/details/Overview.tsx index bcf205ff2..212a1fc4c 100644 --- a/src/react/databrowser/buckets/details/Overview.tsx +++ b/src/react/databrowser/buckets/details/Overview.tsx @@ -40,6 +40,9 @@ import { VEEAMVERSION11, VEEAMVERSION12, } from '../../../ui-elements/Veeam/VeeamConstants'; +import { useAccountsLocationsAndEndpoints } from '../../../next-architecture/domain/business/accounts'; +import { useAccountsLocationsEndpointsAdapter } from '../../../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider'; +import { LocationV1 } from '../../../../js/managementClient/api'; function capitalize(string: string) { return string.toLowerCase().replace(/^\w/, (c) => { @@ -88,16 +91,113 @@ const workflowAttachedError = (count: number, bucketName: string) => ( ); +function VersionningValue({ bucketInfo }: { bucketInfo: BucketInfo }) { + const dispatch = useDispatch(); + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status: locationsStatus } = + useAccountsLocationsAndEndpoints({ accountsLocationsEndpointsAdapter }); + const locationType = accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === bucketInfo.locationConstraint, + )?.type; + const isBucketHostedOnAzureOrGCP = + locationType === LocationV1.LocationTypeEnum.AzureV1 || + locationType === LocationV1.LocationTypeEnum.GcpV1; + const { mutate: changeBucketVersionning } = useChangeBucketVersionning(); + const updateBucketVersioning = (isVersioning: boolean) => { + changeBucketVersionning({ + Bucket: bucketInfo.name, + VersioningConfiguration: { + Status: isVersioning ? 'Enabled' : 'Disabled', + }, + }); + }; + + return ( + + {bucketInfo.objectLockConfiguration.ObjectLockEnabled === 'Disabled' && ( + + Enabling versioning is not possible due to the bucket being + hosted on Microsoft Azure. + + ) : locationType === LocationV1.LocationTypeEnum.GcpV1 ? ( + <> + Enabling versioning is not possible due to the bucket being + hosted on Google Cloud. + + ) : ( + <> + ) + } + > + { + dispatch( + toggleBucketVersioning( + bucketInfo.name, + !bucketInfo.isVersioning, + ), + ); + updateBucketVersioning(!bucketInfo.isVersioning); + }} + /> + + )} + {bucketInfo.objectLockConfiguration.ObjectLockEnabled === 'Enabled' && ( + <> + Enabled +
+ + Versioning cannot be suspended because Object-lock is enabled for + this bucket. + + + )} +
+ ); +} + +function LocationType({ location: locationName }: { location: string }) { + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status: locationsStatus } = + useAccountsLocationsAndEndpoints({ accountsLocationsEndpointsAdapter }); + const locationObject = accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === locationName, + ); + if (locationsStatus === 'loading' || locationsStatus === 'idle') { + return Loading...; + } + if (locationsStatus === 'error') { + //Todo convert to tooltip ? + return An error occurred while fetching locations; + } + if (!locationObject) { + return Location not found: {locationName}; + } + return {getLocationType(locationObject)}; +} + function Overview({ bucket, ingestionStates }: Props) { const history = useHistory(); const dispatch = useDispatch(); const bucketInfo = useSelector((state: AppState) => state.s3.bucketInfo); - const locations = useSelector( - (state: AppState) => state.configuration.latest?.locations, - ); - const loading = useSelector( - (state: AppState) => state.networkActivity.counter > 0, - ); const workflowsQuery = useWorkflows([bucket.name]); const features = useSelector((state: AppState) => state.auth.config.features); const { account } = useCurrentAccount(); @@ -118,8 +218,6 @@ function Overview({ bucket, ingestionStates }: Props) { dispatch(getBucketInfo(bucket.name)); }, [dispatch, bucket.name]); - const { mutate: changeBucketVersionning } = useChangeBucketVersionning(); - const workflows = workflowsQuery.data; const attachedWorkflowsCount = (workflows?.expirations?.length || 0) + @@ -134,19 +232,6 @@ function Overview({ bucket, ingestionStates }: Props) { ingestionStates, bucketInfo.locationConstraint || 'us-east-1', ); - const locationType = - locations && locations[bucketInfo.locationConstraint]?.locationType; - const isBucketHostedOnAzureOrGCP = - locationType === 'location-azure-v1' || locationType === 'location-gcp-v1'; - - const updateBucketVersioning = (isVersioning: boolean) => { - changeBucketVersionning({ - Bucket: bucketInfo.name, - VersioningConfiguration: { - Status: isVersioning ? 'Enabled' : 'Disabled', - }, - }); - }; return ( @@ -186,70 +271,14 @@ function Overview({ bucket, ingestionStates }: Props) { Versioning - - {bucketInfo.objectLockConfiguration.ObjectLockEnabled === - 'Disabled' && ( - - Enabling versioning is not possible due to the - bucket being hosted on Microsoft Azure. - - ) : locationType === 'location-gcp-v1' ? ( - <> - Enabling versioning is not possible due to the - bucket being hosted on Google Cloud. - - ) : ( - <> - ) - } - > - { - dispatch( - toggleBucketVersioning( - bucket.name, - !bucketInfo.isVersioning, - ), - ); - updateBucketVersioning(!bucketInfo.isVersioning); - }} - /> - - )} - {bucketInfo.objectLockConfiguration.ObjectLockEnabled === - 'Enabled' && ( - <> - Enabled -
- - Versioning cannot be suspended because Object-lock is - enabled for this bucket. - - - )} -
+
Location {bucketInfo.locationConstraint || 'us-east-1'} {' / '} - - {locations && - getLocationType(locations[bucketInfo.locationConstraint])} - + {features.includes(XDM_FEATURE) && ( diff --git a/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx b/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx index 657268b2f..b8e26df0c 100644 --- a/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx +++ b/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx @@ -14,6 +14,7 @@ import { render, screen, waitFor, + waitForElementToBeRemoved, within, } from '@testing-library/react'; import Immutable from 'immutable'; @@ -29,24 +30,6 @@ const TEST_STATE = { uiBuckets: { showDelete: false, }, - configuration: { - latest: { - locations: { - 'us-east-1': { - isBuiltin: true, - locationType: 'location-file-v1', - name: 'us-east-1', - objectId: '1060b13c-d805-11ea-a59c-a0999b105a5f', - }, - - 'azure-blob': { - locationType: 'location-azure-v1', - name: 'azure-blob', - objectId: '1060b13c-d806-11ea-a59c-a0999b105a5f', - }, - }, - }, - }, workflow: { replications: [], }, @@ -164,6 +147,7 @@ describe('Overview', () => { ...TEST_STATE, ...{ s3: { bucketInfo: bucketInfoResponseVersioningDisabled } }, }); + await waitForElementToBeRemoved(() => screen.getByText(/loading/i)); await waitFor(() => { expect( screen.getByRole('checkbox', { diff --git a/src/react/utils/storageOptions.ts b/src/react/utils/storageOptions.ts index e7817130b..f0855bd0c 100644 --- a/src/react/utils/storageOptions.ts +++ b/src/react/utils/storageOptions.ts @@ -18,6 +18,7 @@ import { } from '../../types/config'; import { LocationForm } from '../../types/location'; import { Location } from '../next-architecture/domain/entities/location'; +import { LocationInfo } from '../next-architecture/adapters/accounts-locations/ILocationsAdapter'; export function checkSupportsReplicationTarget(locations: Locations): boolean { return Object.keys(locations).some( (l) => @@ -45,7 +46,11 @@ export function checkIfExternalLocation(locations: Locations): boolean { * @returns a string which represent a locationType */ export const getLocationTypeKey = ( - location: LocationForm | LegacyLocation | Omit, + location: + | LocationInfo + | LocationForm + | LegacyLocation + | Omit, ) => { if (location) { if ( @@ -75,7 +80,7 @@ export const getLocationTypeKey = ( }; const selectStorageLocationFromLocationType = ( - location: LegacyLocation | Omit, + location: LegacyLocation | Omit | LocationInfo, ) => { const locationTypeKey = getLocationTypeKey(location); if (locationTypeKey !== '') { @@ -86,7 +91,7 @@ const selectStorageLocationFromLocationType = ( }; export const getLocationType = ( - location: LegacyLocation | Omit, + location: LegacyLocation | Omit | LocationInfo, ) => { const storageLocation = selectStorageLocationFromLocationType(location); return storageLocation?.name ?? ''; From 8edfa57557fcd52c4126bf623340e3662e4e8119 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Mon, 20 Nov 2023 16:47:56 +0100 Subject: [PATCH 02/26] Refactor object listing --- .../databrowser/objects/ObjectListTable.tsx | 30 +++++++++++++++---- .../objects/__tests__/Objects.test.tsx | 26 +++++----------- 2 files changed, 32 insertions(+), 24 deletions(-) diff --git a/src/react/databrowser/objects/ObjectListTable.tsx b/src/react/databrowser/objects/ObjectListTable.tsx index b6d401c92..9c5ca0e65 100644 --- a/src/react/databrowser/objects/ObjectListTable.tsx +++ b/src/react/databrowser/objects/ObjectListTable.tsx @@ -30,6 +30,8 @@ import MemoRow, { createItemData } from './ObjectRow'; import { CenterredSecondaryText } from '../../account/iamAttachment/AttachmentTable'; import { useS3Client } from '../../next-architecture/ui/S3ClientProvider'; import { parseRestore } from '../../reducers/s3'; +import { useAccountsLocationsAndEndpoints } from '../../next-architecture/domain/business/accounts'; +import { useAccountsLocationsEndpointsAdapter } from '../../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider'; export const Icon = styled.i` margin-right: ${spacing.sp4}; @@ -66,9 +68,6 @@ export default function ObjectListTable({ const loading = useSelector( (state: AppState) => state.networkActivity.counter > 0, ); - const locations = useSelector( - (state: AppState) => state.configuration.latest.locations, - ); const objectsLength = objects.size; const isToggledFull = toggled.size > 0 && toggled.size === objectsLength; const isItemLoaded = useCallback( @@ -181,7 +180,17 @@ export default function ObjectListTable({ const restore = parseRestore(headObject?.Restore); const storageClass = original.storageClass; - const isObjectInColdStorage = !!locations[storageClass]?.isCold; + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status } = + useAccountsLocationsAndEndpoints({ + accountsLocationsEndpointsAdapter, + }); + const isObjectInColdStorage = + status === 'success' && + accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === storageClass, + )?.isCold; if (original.isFolder) { return ( @@ -292,9 +301,20 @@ export default function ObjectListTable({ accessor: 'storageClass', width: 20, Cell({ value: storageClass }: { value: string }) { + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status } = + useAccountsLocationsAndEndpoints({ + accountsLocationsEndpointsAdapter, + }); + const isObjectInColdStorage = + status === 'success' && + accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === storageClass, + )?.isCold; return (
- {locations.storageClass?.isCold ? : ''}{' '} + {isObjectInColdStorage ? : ''}{' '} {storageClass === 'STANDARD' ? 'default' : storageClass}
); diff --git a/src/react/databrowser/objects/__tests__/Objects.test.tsx b/src/react/databrowser/objects/__tests__/Objects.test.tsx index b6cd562e2..423a8ffe5 100644 --- a/src/react/databrowser/objects/__tests__/Objects.test.tsx +++ b/src/react/databrowser/objects/__tests__/Objects.test.tsx @@ -7,11 +7,14 @@ import { } from '../../../utils/testUtil'; import Objects from '../Objects'; import { screen, waitFor } from '@testing-library/react'; +import { getConfigOverlay } from '../../../../js/mock/managementClientMSWHandlers'; +import { INSTANCE_ID } from '../../../actions/__tests__/utils/testUtil'; const BUCKET_NAME = 'bucket'; const COLD_OBJECT_KEY = 'my-cold-image.jpg'; const OBJECT_IN_DEFAULT_LOCATION = 'object-key'; const server = setupServer( + getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID), //HANDLERS TO GET BUCKET INFO //get bucket cors rest.get(`${TEST_API_BASE_URL}/${BUCKET_NAME}`, (req, res, ctx) => { @@ -214,25 +217,10 @@ afterAll(() => server.close()); describe('Objects', () => { it('should remove the link to download for the object store in cold storage', async () => { //S - renderWithRouterMatch( - , - { - path: '/accounts/:accountName/buckets/:bucketName/objects', - route: `/accounts/renard/buckets/${BUCKET_NAME}/objects`, - }, - { - configuration: { - latest: { - locations: { - 'europe25-myroom-cold': { - name: 'europe25-myroom-cold', - isCold: true, - }, - }, - }, - }, - }, - ); + renderWithRouterMatch(, { + path: '/accounts/:accountName/buckets/:bucketName/objects', + route: `/accounts/renard/buckets/${BUCKET_NAME}/objects`, + }); //E await waitFor(() => { expect(screen.getByText(/storage location/i)).toBeInTheDocument(); From c5c226e10da3ab1c11fa8922d5581e35becd8c80 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Mon, 20 Nov 2023 16:54:06 +0100 Subject: [PATCH 03/26] Refactor metadata edition --- .../databrowser/objects/details/Metadata.tsx | 25 ++++++++++++------- .../details/__tests__/Metadata.test.tsx | 18 +++++-------- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/react/databrowser/objects/details/Metadata.tsx b/src/react/databrowser/objects/details/Metadata.tsx index b27789384..f72a5a87d 100644 --- a/src/react/databrowser/objects/details/Metadata.tsx +++ b/src/react/databrowser/objects/details/Metadata.tsx @@ -27,11 +27,12 @@ import type { } from '../../../../types/s3'; import { LIST_OBJECT_VERSIONS_S3_TYPE } from '../../../utils/s3'; import { putObjectMetadata } from '../../../actions'; -import { useDispatch, useSelector } from 'react-redux'; +import { useDispatch } from 'react-redux'; import { useForm, useFieldArray, Controller } from 'react-hook-form'; import { useEffect } from 'react'; import { Form, Icon } from '@scality/core-ui'; -import { AppState } from '../../../../types/state'; +import { useAccountsLocationsEndpointsAdapter } from '../../../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider'; +import { useAccountsLocationsAndEndpoints } from '../../../next-architecture/domain/business/accounts'; const userMetadataOption = { value: AMZ_META, label: AMZ_META, @@ -99,14 +100,20 @@ function Metadata({ storageClass, }: Props) { const dispatch = useDispatch(); - const locations = useSelector( - (state: AppState) => state.configuration.latest?.locations, - ); - const isObjectStoredColdStorage = storageClass - ? locations?.[storageClass]?.isCold - : false; + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status } = + useAccountsLocationsAndEndpoints({ + accountsLocationsEndpointsAdapter, + }); + const isObjectInColdStorage = + status === 'success' && + accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === storageClass, + )?.isCold; + const isMetadataEditionDisabled = - listType === LIST_OBJECT_VERSIONS_S3_TYPE || isObjectStoredColdStorage; + listType === LIST_OBJECT_VERSIONS_S3_TYPE || isObjectInColdStorage; const EMPTY_ITEM = { key: '', diff --git a/src/react/databrowser/objects/details/__tests__/Metadata.test.tsx b/src/react/databrowser/objects/details/__tests__/Metadata.test.tsx index 84c60e54b..af9dd0375 100644 --- a/src/react/databrowser/objects/details/__tests__/Metadata.test.tsx +++ b/src/react/databrowser/objects/details/__tests__/Metadata.test.tsx @@ -1,6 +1,9 @@ import { METADATA_SYSTEM_TYPE, METADATA_USER_TYPE } from '../../../../utils'; import Metadata from '../Metadata'; -import { OBJECT_METADATA } from '../../../../actions/__tests__/utils/testUtil'; +import { + INSTANCE_ID, + OBJECT_METADATA, +} from '../../../../actions/__tests__/utils/testUtil'; import { reduxMount, reduxRender, @@ -10,8 +13,9 @@ import { fireEvent, screen, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import { rest } from 'msw'; import { setupServer } from 'msw/node'; +import { getConfigOverlay } from '../../../../../js/mock/managementClientMSWHandlers'; -const server = setupServer(); +const server = setupServer(getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID)); beforeAll(() => { server.listen({ onUnhandledRequest: 'error' }); @@ -36,16 +40,6 @@ describe('Metadata', () => { access_token: '', }, }, - configuration: { - latest: { - endpoints: [], - locations: { - [COLD_LOCATION]: { - isCold: true, - }, - }, - }, - }, }; afterEach(() => { From 85549364a736f6fbcb6d84d14c4ae8c60c4a504e Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Tue, 21 Nov 2023 10:57:20 +0100 Subject: [PATCH 04/26] Refactor object properties --- .../objects/details/Properties.tsx | 29 +++++-- .../details/__tests__/Properties.test.tsx | 79 ++++++------------- 2 files changed, 46 insertions(+), 62 deletions(-) diff --git a/src/react/databrowser/objects/details/Properties.tsx b/src/react/databrowser/objects/details/Properties.tsx index 10b9d104d..bd6fc9bf6 100644 --- a/src/react/databrowser/objects/details/Properties.tsx +++ b/src/react/databrowser/objects/details/Properties.tsx @@ -24,6 +24,8 @@ import { Button } from '@scality/core-ui/dist/next'; import { ColdStorageIconLabel } from '../../../ui-elements/ColdStorageIcon'; import ObjectRestorationButtonAndModal from './ObjectRestorationButtonAndModal'; import { useBucketDefaultRetention } from '../../../next-architecture/domain/business/buckets'; +import { useAccountsLocationsEndpointsAdapter } from '../../../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider'; +import { useAccountsLocationsAndEndpoints } from '../../../next-architecture/domain/business/accounts'; type Props = { objectMetadata: ObjectMetadata; @@ -39,11 +41,18 @@ function Properties({ objectMetadata }: Props) { const loading = useSelector( (state: AppState) => state.networkActivity.counter > 0, ); - const locations = useSelector( - (state: AppState) => state.configuration.latest.locations, - ); - const location = - objectMetadata.storageClass && locations[objectMetadata.storageClass]; + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status: checkColdLocationStatus } = + useAccountsLocationsAndEndpoints({ + accountsLocationsEndpointsAdapter, + }); + const isObjectInColdStorage = + checkColdLocationStatus === 'success' && + accountsLocationsAndEndpoints?.locations.find( + (location) => location.name === objectMetadata.storageClass, + )?.isCold; + const prefixWithSlash = usePrefixWithSlash(); const isLegalHoldEnabled = objectMetadata.isLegalHoldEnabled; //Display Legal Hold when the Bucket is versioned and object-lock enabled. @@ -128,7 +137,15 @@ function Properties({ objectMetadata }: Props) { Location {objectMetadata.storageClass || 'default'} - {(location?.isCold || objectMetadata.restore?.expiryDate) && ( + {checkColdLocationStatus === 'idle' || + (checkColdLocationStatus === 'loading' && ( + + Temperature + Loading location information... + + ))} + {(isObjectInColdStorage || + objectMetadata.restore?.expiryDate) && ( Temperature diff --git a/src/react/databrowser/objects/details/__tests__/Properties.test.tsx b/src/react/databrowser/objects/details/__tests__/Properties.test.tsx index 72cd39f4a..82403486d 100644 --- a/src/react/databrowser/objects/details/__tests__/Properties.test.tsx +++ b/src/react/databrowser/objects/details/__tests__/Properties.test.tsx @@ -1,13 +1,21 @@ import { rest } from 'msw'; import { setupServer } from 'msw/node'; -import { screen, waitFor } from '@testing-library/react'; +import { + screen, + waitFor, + waitForElementToBeRemoved, +} from '@testing-library/react'; -import { OBJECT_METADATA } from '../../../../actions/__tests__/utils/testUtil'; +import { + INSTANCE_ID, + OBJECT_METADATA, +} from '../../../../actions/__tests__/utils/testUtil'; import { TEST_API_BASE_URL, renderWithRouterMatch, } from '../../../../utils/testUtil'; import Properties from '../Properties'; +import { getConfigOverlay } from '../../../../../js/mock/managementClientMSWHandlers'; const renderProperties = ( component: React.ReactNode = , @@ -25,6 +33,7 @@ const renderProperties = ( //Mock getObjectLockConfiguration for bucket 'bucket' const server = setupServer( + getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID), rest.get(`${TEST_API_BASE_URL}/bucket?object-lock`, (req, res, ctx) => { return res( ctx.xml(` @@ -175,27 +184,13 @@ describe('Properties', () => { versioning: 'Enabled', }, }, - configuration: { - latest: { - locations: { - ['europe25-myroom-cold']: { - locationType: 'location-dmf-v1', - name: 'europe25-myroom-cold', - isCold: true, - details: { - endpoint: 'ws://tape.myroom.europe25.cnes:8181', - repoId: ['repoId'], - nsId: 'nsId', - username: 'username', - password: 'password', - }, - }, - }, - }, - }, }, ); + await waitForElementToBeRemoved(() => + screen.getByText('Loading location information...'), + ); + const labelsValues = [ { label: 'Location', value: 'europe25-myroom-cold' }, { label: 'Temperature', value: 'Cold' }, @@ -232,27 +227,13 @@ describe('Properties', () => { versioning: 'Enabled', }, }, - configuration: { - latest: { - locations: { - ['europe25-myroom-cold']: { - locationType: 'location-dmf-v1', - name: 'europe25-myroom-cold', - isCold: true, - details: { - endpoint: 'ws://tape.myroom.europe25.cnes:8181', - repoId: ['repoId'], - nsId: 'nsId', - username: 'username', - password: 'password', - }, - }, - }, - }, - }, }, ); + await waitForElementToBeRemoved(() => + screen.getByText('Loading location information...'), + ); + expect(screen.getByText('Temperature')).toBeInTheDocument(); expect(screen.getByText('Temperature').parentElement).toHaveTextContent( 'Restoration in progress...', @@ -296,27 +277,13 @@ describe('Properties', () => { versioning: 'Enabled', }, }, - configuration: { - latest: { - locations: { - ['europe25-myroom-cold']: { - locationType: 'location-dmf-v1', - name: 'europe25-myroom-cold', - isCold: true, - details: { - endpoint: 'ws://tape.myroom.europe25.cnes:8181', - repoId: ['repoId'], - nsId: 'nsId', - username: 'username', - password: 'password', - }, - }, - }, - }, - }, }, ); + await waitForElementToBeRemoved(() => + screen.getByText('Loading location information...'), + ); + //V expect(screen.getByText('Temperature')).toBeInTheDocument(); expect(screen.getByText('Temperature').parentElement).toHaveTextContent( From ea627911f091a755ffdbd76959cde3b006524237 Mon Sep 17 00:00:00 2001 From: Jean-Baptiste WATENBERG Date: Tue, 21 Nov 2023 11:32:22 +0100 Subject: [PATCH 05/26] Refactor endpoint creation --- src/react/endpoint/EndpointCreate.tsx | 92 +++++++++++-------- .../__tests__/EndpointCreate.test.tsx | 24 ++++- src/react/locations/utils.tsx | 3 +- 3 files changed, 76 insertions(+), 43 deletions(-) diff --git a/src/react/endpoint/EndpointCreate.tsx b/src/react/endpoint/EndpointCreate.tsx index 46c7689c7..93b13b6fc 100644 --- a/src/react/endpoint/EndpointCreate.tsx +++ b/src/react/endpoint/EndpointCreate.tsx @@ -17,6 +17,8 @@ import { joiResolver } from '@hookform/resolvers/joi'; import { useHistory } from 'react-router-dom'; import { useOutsideClick } from '../utils/hooks'; import { renderLocation } from '../locations/utils'; +import { useAccountsLocationsEndpointsAdapter } from '../next-architecture/ui/AccountsLocationsEndpointsAdapterProvider'; +import { useAccountsLocationsAndEndpoints } from '../next-architecture/domain/business/accounts'; const schema = Joi.object({ hostname: Joi.string().label('Hostname').required().min(3), @@ -28,10 +30,14 @@ function EndpointCreate() { register, handleSubmit, control, - formState: { errors }, + formState: { errors, isValid, isDirty }, } = useForm({ - mode: 'all', + mode: 'onChange', resolver: joiResolver(schema), + defaultValues: { + hostname: '', + locationName: 'us-east-1', + }, }); const history = useHistory(); const dispatch = useDispatch(); @@ -42,12 +48,13 @@ function EndpointCreate() { const errorMessage = useSelector( (state: AppState) => state.uiErrors.errorMsg, ); - const loading = useSelector( - (state: AppState) => state.networkActivity.counter > 0, - ); - const locations = useSelector( - (state: AppState) => state.configuration.latest?.locations, - ); + const accountsLocationsEndpointsAdapter = + useAccountsLocationsEndpointsAdapter(); + const { accountsLocationsAndEndpoints, status } = + useAccountsLocationsAndEndpoints({ + accountsLocationsEndpointsAdapter, + }); + const loading = status === 'idle' || status === 'loading'; const clearServerError = () => { if (hasError) { @@ -83,7 +90,6 @@ function EndpointCreate() { rightActions={