From e6e736025aca95bd53e1c60e5edfdd01cb464c0f Mon Sep 17 00:00:00 2001 From: YanJin Date: Thu, 6 Jul 2023 16:29:02 +0200 Subject: [PATCH 1/6] ZKUI-370: Disable the versioning for Microsoft Azure Blob and Google Cloud Storage --- .../databrowser/buckets/BucketCreate.tsx | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/src/react/databrowser/buckets/BucketCreate.tsx b/src/react/databrowser/buckets/BucketCreate.tsx index 4058b2fd9..f919badb4 100644 --- a/src/react/databrowser/buckets/BucketCreate.tsx +++ b/src/react/databrowser/buckets/BucketCreate.tsx @@ -102,6 +102,9 @@ function BucketCreate() { isIngestLocation(locations[watchLocationName], capabilities), [watchLocationName, locations, capabilities], ); + const isLocationAzureOrGcpSelected = + locations?.[watchLocationName]?.locationType === 'location-azure-v1' || + locations?.[watchLocationName]?.locationType === 'location-gcp-v1'; const clearServerError = () => { if (hasError) { @@ -395,7 +398,12 @@ function BucketCreate() { render={({ field: { onChange, value: isVersioning } }) => { return ( ) => onChange(e.target.checked) } @@ -407,10 +415,16 @@ function BucketCreate() { }} /> } - disabled={isObjectLockEnabled || isAsyncNotification} + disabled={ + isObjectLockEnabled || + isAsyncNotification || + isLocationAzureOrGcpSelected + } helpErrorPosition="bottom" help={ - isObjectLockEnabled || isAsyncNotification + isLocationAzureOrGcpSelected + ? 'Selected Storage Location does not support versioning.' + : isObjectLockEnabled || isAsyncNotification ? `Automatically activated when ${isObjectLockEnabled ? 'Object-lock' : ''} ${isObjectLockEnabled && isAsyncNotification ? 'or' : ''} @@ -420,7 +434,9 @@ function BucketCreate() { } /> - + ); From 7d03caa833443bec7aecd8eec86ec14fe73c54ea Mon Sep 17 00:00:00 2001 From: YanJin Date: Thu, 6 Jul 2023 16:30:25 +0200 Subject: [PATCH 2/6] ZKUI-370: Fix the object-lock can not toggle off after toggling on --- .../buckets/ObjectLockRetentionSettings.tsx | 6 +++-- .../buckets/__tests__/BucketCreate.test.tsx | 27 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx index 1a18643a0..b07cecc32 100644 --- a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx +++ b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx @@ -26,8 +26,10 @@ export const objectLockRetentionSettingsValidationRules = { }; export default function ObjectLockRetentionSettings({ + isLocationAzureOrGcpSelected, isEditRetentionSetting = false, }: { + isLocationAzureOrGcpSelected: boolean; isEditRetentionSetting?: boolean; }) { const { @@ -40,7 +42,7 @@ export default function ObjectLockRetentionSettings({ const isDefaultRetentionEnabled = watch('isDefaultRetentionEnabled'); const isObjectLockEnabled = watch('isObjectLockEnabled'); const matchVersioning = (checked: boolean) => { - if (checked) { + if (checked && !isLocationAzureOrGcpSelected) { setValue('isVersioning', true); } }; @@ -64,10 +66,10 @@ export default function ObjectLockRetentionSettings({ onChange(e.target.checked); matchVersioning(e.target.checked); }} + id="isObjectLockEnabled" placeholder="isObjectLockEnabled" label={isObjectLockEnabled ? 'Enabled' : 'Disabled'} toggle={isObjectLockEnabled} - disabled={isObjectLockEnabled} /> ); }} diff --git a/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx b/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx index 9539b091c..a21facbac 100644 --- a/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx +++ b/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx @@ -214,4 +214,31 @@ describe('BucketCreate', () => { screen.queryByRole('option', { name: new RegExp(coldLocation, 'i') }), ).toHaveAttribute('aria-disabled', 'true'); }); + it('should disable versioning for Microsoft Azure Blob Storage', () => { + const azureblobstorage = 'azureblobstorage'; + reduxRender(, { + configuration: { + latest: { + locations: { + [azureblobstorage]: { + locationType: 'location-azure-v1', + name: azureblobstorage, + details: {}, + }, + }, + }, + }, + }); + userEvent.click(screen.getByText('Location Name')); + userEvent.click( + screen.getByRole('option', { name: new RegExp(azureblobstorage, 'i') }), + ); + //V + expect(screen.getByLabelText('Versioning')).toBeDisabled(); + //E + userEvent.click(screen.getByLabelText('Object-lock')); + //Verify the versioning is off even though set object-lock for Azure Blob Storage + expect(screen.getByLabelText('Versioning')).toBeDisabled(); + expect(screen.getByLabelText('Versioning')).not.toBeChecked(); + }); }); From a83a4c7a7bc488b77d67e2f92598cee9352862af Mon Sep 17 00:00:00 2001 From: YanJin Date: Fri, 7 Jul 2023 18:00:36 +0200 Subject: [PATCH 3/6] ZKUI-370: Replace Toggle by Checkbox and refactor the testing --- .../databrowser/buckets/BucketCreate.tsx | 86 +++++------- .../buckets/ObjectLockRetentionSettings.tsx | 56 +++----- .../buckets/__tests__/BucketCreate.test.tsx | 129 ++++++++---------- src/types/stats.ts | 14 +- 4 files changed, 126 insertions(+), 159 deletions(-) diff --git a/src/react/databrowser/buckets/BucketCreate.tsx b/src/react/databrowser/buckets/BucketCreate.tsx index f919badb4..0a1fbf3be 100644 --- a/src/react/databrowser/buckets/BucketCreate.tsx +++ b/src/react/databrowser/buckets/BucketCreate.tsx @@ -1,11 +1,11 @@ import { Banner, + Checkbox, Form, FormGroup, FormSection, Icon, Stack, - Toggle, } from '@scality/core-ui'; import { Controller, FormProvider, useForm } from 'react-hook-form'; import { ChangeEvent, useMemo, useRef } from 'react'; @@ -75,6 +75,7 @@ function BucketCreate() { const { isValid, errors } = formState; const isObjectLockEnabled = watch('isObjectLockEnabled'); const isAsyncNotification = watch('isAsyncNotification'); + const isVersioning = watch('isVersioning'); const watchLocationName = watch('locationName'); const dispatch = useDispatch(); const hasError = useSelector( @@ -102,9 +103,13 @@ function BucketCreate() { isIngestLocation(locations[watchLocationName], capabilities), [watchLocationName, locations, capabilities], ); - const isLocationAzureOrGcpSelected = - locations?.[watchLocationName]?.locationType === 'location-azure-v1' || - locations?.[watchLocationName]?.locationType === 'location-gcp-v1'; + + const isLocationAzureOrGcpSelected = (locationName: string) => + locations?.[locationName]?.locationType === 'location-azure-v1' || + locations?.[locationName]?.locationType === 'location-gcp-v1'; + + const isWatchedLocationAzureOrGCPSelected = + isLocationAzureOrGcpSelected(watchLocationName); const clearServerError = () => { if (hasError) { @@ -308,6 +313,11 @@ function BucketCreate() { // Note: when changing location we make sure // to reset isAsyncNotification toggle to false. setValue('isAsyncNotification', false); + if (isLocationAzureOrGcpSelected(value)) { + setValue('isVersioning', false); + } else if (isObjectLockEnabled) { + setValue('isVersioning', true); + } }} placeholder="Location Name" value={locationName} @@ -348,27 +358,15 @@ function BucketCreate() { 'Enabling Async Metadata updates automatically activates Versioning for the bucket, and you won’t be able to suspend Versioning.' } content={ - { - return ( - <> - ) => { - onChange(e.target.checked); - matchVersioning(e.target.checked); - }} - label={isAsyncNotification ? 'Enabled' : 'Disabled'} - toggle={isAsyncNotification} - placeholder="isAsyncNotification" - /> - - ); - }} + ) { + matchVersioning(e.target.checked); + }, + })} /> } /> @@ -392,37 +390,25 @@ function BucketCreate() { } content={ - { - return ( - ) => - onChange(e.target.checked) - } - placeholder="Versioning" - label={isVersioning ? 'Active' : 'Inactive'} - toggle={isVersioning} - /> - ); - }} + } disabled={ isObjectLockEnabled || isAsyncNotification || - isLocationAzureOrGcpSelected + isWatchedLocationAzureOrGCPSelected } helpErrorPosition="bottom" help={ - isLocationAzureOrGcpSelected + isWatchedLocationAzureOrGCPSelected ? 'Selected Storage Location does not support versioning.' : isObjectLockEnabled || isAsyncNotification ? `Automatically activated when @@ -430,12 +416,12 @@ function BucketCreate() { ${isObjectLockEnabled && isAsyncNotification ? 'or' : ''} ${isAsyncNotification ? 'Async Metadata updates' : ''} is Enabled` - : 'Automatically activated when Object-lock is Active.' + : '' } /> diff --git a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx index b07cecc32..53d79552c 100644 --- a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx +++ b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx @@ -1,7 +1,13 @@ import { ChangeEvent } from 'react'; import { Controller, useFormContext } from 'react-hook-form'; import Joi from '@hapi/joi'; -import { FormGroup, FormSection, Stack, Text, Toggle } from '@scality/core-ui'; +import { + Checkbox, + FormGroup, + FormSection, + Stack, + Text, +} from '@scality/core-ui'; import { Input, Select } from '@scality/core-ui/dist/next'; import { convertRemToPixels } from '@scality/core-ui/dist/components/tablev2/TableUtils'; @@ -55,24 +61,15 @@ export default function ObjectLockRetentionSettings({ id="isObjectLockEnabled" label="Object-lock" content={ - { - return ( - ) => { - onChange(e.target.checked); - matchVersioning(e.target.checked); - }} - id="isObjectLockEnabled" - placeholder="isObjectLockEnabled" - label={isObjectLockEnabled ? 'Enabled' : 'Disabled'} - toggle={isObjectLockEnabled} - /> - ); - }} + ) { + matchVersioning(e.target.checked); + }, + })} /> } labelHelpTooltip={ @@ -140,23 +137,10 @@ export default function ObjectLockRetentionSettings({ ) } content={ - { - return ( - ) => - onChange(e.target.checked) - } - placeholder="isDefaultRetentionEnabled" - label={isDefaultRetentionEnabled ? 'Active' : 'Inactive'} - toggle={isDefaultRetentionEnabled} - /> - ); - }} + } /> diff --git a/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx b/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx index a21facbac..37c18d417 100644 --- a/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx +++ b/src/react/databrowser/buckets/__tests__/BucketCreate.test.tsx @@ -1,9 +1,10 @@ import router from 'react-router'; import BucketCreate, { bucketErrorMessage } from '../BucketCreate'; -import { reduxMountAct, reduxRender } from '../../../utils/testUtil'; +import { reduxRender } from '../../../utils/testUtil'; import { XDM_FEATURE } from '../../../../js/config'; -import { screen, act } from '@testing-library/react'; +import { screen, act, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; +import { LOCATIONS } from '../../../../js/mock/managementClientMSWHandlers'; describe('BucketCreate', () => { const errorMessage = 'This is an error test message'; @@ -12,29 +13,22 @@ describe('BucketCreate', () => { accountName: 'accountName', }); }); - - it('should render BucketCreate component with no error banner', async () => { - const component = await reduxMountAct(); - expect(component.find('#zk-error-banner')).toHaveLength(0); - // react-hook-form is set to validate on change. - // UseForm hook will update state multiple time and component - // will re-render as state updates. - // This will cause state updates to occur even when we haven't fired an action. - // However my test is only interested in the earlier state. - // The errors were due to state that updated after the test finished. - // The hook will clean up properly if unmounted - component.unmount(); - }); - it('should render BucketCreate component with an error banner', async () => { - const component = await reduxMountAct(, { + const selectors = { + objectlock: () => screen.getByLabelText(/object-lock/i), + versioning: () => screen.getByLabelText(/versioning/i), + asyncMetadata: () => screen.getByLabelText(/async metadata updates/i), + locationSelect: () => screen.getByLabelText(/Storage Service Location/i), + ringLocationOption: () => + screen.getByRole('option', { name: /ring-nick/i }), + }; + it('should render BucketCreate component with an error banner', () => { + reduxRender(, { uiErrors: { errorMsg: errorMessage, errorType: 'byComponent', }, }); - expect(component.find('#zk-error-banner')).toHaveLength(1); - expect(component.find('#zk-error-banner').text()).toContain(errorMessage); - component.unmount(); + expect(screen.getByText('Error')).toBeInTheDocument(); }); // TESTING INPUT NAME: // 1) empty name input @@ -133,62 +127,36 @@ describe('BucketCreate', () => { } }); }); - it('should toggle versioning and disable it when enabling object lock', async () => { - const component = await reduxMountAct(); - await act(async () => { - const input = component.find('input#name'); - input.getDOMNode().value = 'test'; - input.getDOMNode().dispatchEvent(new Event('input')); - const objectLockEnabled = component.find( - 'input[placeholder="isObjectLockEnabled"]', - ); - objectLockEnabled.simulate('change', { - target: { - checked: true, - }, - }); - }); - expect( - component.find('input[placeholder="Versioning"]').getDOMNode().checked, - ).toBe(true); - expect( - component.find('input[placeholder="Versioning"]').getDOMNode().disabled, - ).toBe(true); - component.unmount(); + it('should set versioning and disable it while enabling object lock', () => { + //S + reduxRender(); + //E + userEvent.click(selectors.objectlock()); + //V + expect(selectors.versioning()).toBeChecked(); + expect(selectors.versioning()).toBeDisabled(); }); - it('should toggle versioning and disable it when enabling Async Metadata updates', async () => { - const component = await reduxMountAct(, { - auth: { - config: { - features: [XDM_FEATURE], - }, + it('should set versioning and disable it while enabling Async Metadata updates for RING', async () => { + //S + reduxRender(, { + configuration: { latest: { locations: LOCATIONS } }, + auth: { config: { features: [XDM_FEATURE] } }, + instanceStatus: { + latest: { state: { capabilities: { s3cIngestLocation: true } } }, }, }); - await act(async () => { - const input = component.find('input#name'); - input.getDOMNode().value = 'test'; - input.getDOMNode().dispatchEvent(new Event('input')); - const isAsyncNotification = component.find( - 'input[placeholder="isAsyncNotification"]', - ); - isAsyncNotification.simulate('change', { - target: { - checked: true, - }, - }); - }); - expect( - component.find('input[placeholder="Versioning"]').getDOMNode().checked, - ).toBe(true); - expect( - component.find('input[placeholder="Versioning"]').getDOMNode().disabled, - ).toBe(true); - component.unmount(); + //E + userEvent.click(selectors.locationSelect()); + userEvent.click(selectors.ringLocationOption()); + userEvent.click(selectors.asyncMetadata()); + //V + await waitFor(() => expect(selectors.versioning()).toBeChecked()); + expect(selectors.versioning()).toBeDisabled(); }); - it('should disable cold location as a source storage location when creating a bucket', async () => { + it('should disable cold location as a source storage location while creating a bucket', () => { const coldLocation = 'europe25-myroom-cold'; - //E - await reduxRender(, { + //S + reduxRender(, { configuration: { latest: { locations: { @@ -208,13 +176,15 @@ describe('BucketCreate', () => { }, }, }); - await userEvent.click(screen.getByText('Location Name')); + //E + userEvent.click(screen.getByText('Location Name')); //V expect( screen.queryByRole('option', { name: new RegExp(coldLocation, 'i') }), ).toHaveAttribute('aria-disabled', 'true'); }); it('should disable versioning for Microsoft Azure Blob Storage', () => { + //S const azureblobstorage = 'azureblobstorage'; reduxRender(, { configuration: { @@ -229,6 +199,7 @@ describe('BucketCreate', () => { }, }, }); + //E userEvent.click(screen.getByText('Location Name')); userEvent.click( screen.getByRole('option', { name: new RegExp(azureblobstorage, 'i') }), @@ -241,4 +212,18 @@ describe('BucketCreate', () => { expect(screen.getByLabelText('Versioning')).toBeDisabled(); expect(screen.getByLabelText('Versioning')).not.toBeChecked(); }); + it('should be able to remove object-lock after setting it', () => { + //S + reduxRender(); + //E + userEvent.click(selectors.objectlock()); + //V + expect(selectors.objectlock()).toBeChecked(); + expect(selectors.objectlock()).toBeEnabled(); + //E + userEvent.click(selectors.objectlock()); + //V + expect(selectors.objectlock()).not.toBeChecked(); + expect(selectors.objectlock()).toBeEnabled(); + }); }); diff --git a/src/types/stats.ts b/src/types/stats.ts index e648136cb..f166954c0 100644 --- a/src/types/stats.ts +++ b/src/types/stats.ts @@ -75,10 +75,22 @@ export type MetricsUnit = { }; export type InstanceStateSnapshot = { readonly capabilities: { + readonly locationTypeCephRadosGW: boolean; + readonly locationTypeDigitalOcean: boolean; + readonly locationTypeHyperdriveV2: boolean; + readonly locationTypeLocal: boolean; + readonly locationTypeNFS: boolean; + readonly locationTypeS3Custom: boolean; + readonly locationTypeSproxyd: boolean; + readonly managedLifecycle: boolean; + readonly managedLifecycleTransition: boolean; + readonly preferredReadLocation: boolean; + readonly s3cIngestLocation: boolean; readonly secureChannel: boolean; + readonly secureChannelOptimizedPath: boolean; }; readonly latestConfigurationOverlay: ConfigurationOverlay; readonly runningConfigurationVersion: number; readonly lastSeen: string; readonly serverVersion: string; -}; \ No newline at end of file +}; From e89956bd17b12c09d37a396b7cd5d9c4f86ef3f5 Mon Sep 17 00:00:00 2001 From: YanJin Date: Tue, 11 Jul 2023 15:28:22 +0200 Subject: [PATCH 4/6] ZKUI-370: update core-ui to 0.89.0 --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1bc22de22..e1be786dd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -19,7 +19,7 @@ "@hookform/resolvers": "^2.8.8", "@js-temporal/polyfill": "^0.4.3", "@monaco-editor/react": "^4.4.5", - "@scality/core-ui": "github:scality/core-ui#0.86.0", + "@scality/core-ui": "github:scality/core-ui#0.89.0", "@types/react-table": "^7.7.10", "@types/react-virtualized": "^9.21.20", "@types/react-window": "^1.8.5", @@ -3214,8 +3214,8 @@ } }, "node_modules/@scality/core-ui": { - "version": "0.86.0", - "resolved": "git+ssh://git@github.com/scality/core-ui.git#626a9b8075c6bca508f7400e90774b8223ec710b", + "version": "0.89.0", + "resolved": "git+ssh://git@github.com/scality/core-ui.git#39aff18eae79efcd2c1e97565d32e09787bba44b", "license": "SEE LICENSE IN LICENSE", "dependencies": { "@floating-ui/dom": "^0.1.10" @@ -27462,8 +27462,8 @@ } }, "@scality/core-ui": { - "version": "git+ssh://git@github.com/scality/core-ui.git#626a9b8075c6bca508f7400e90774b8223ec710b", - "from": "@scality/core-ui@github:scality/core-ui#0.86.0", + "version": "git+ssh://git@github.com/scality/core-ui.git#39aff18eae79efcd2c1e97565d32e09787bba44b", + "from": "@scality/core-ui@github:scality/core-ui#0.89.0", "requires": { "@floating-ui/dom": "^0.1.10" } diff --git a/package.json b/package.json index ce132cffc..cb7a57e59 100644 --- a/package.json +++ b/package.json @@ -78,7 +78,7 @@ "@hookform/resolvers": "^2.8.8", "@js-temporal/polyfill": "^0.4.3", "@monaco-editor/react": "^4.4.5", - "@scality/core-ui": "github:scality/core-ui#0.86.0", + "@scality/core-ui": "github:scality/core-ui#0.89.0", "@types/react-table": "^7.7.10", "@types/react-virtualized": "^9.21.20", "@types/react-window": "^1.8.5", From d101909fecda5d2db9274eb861b3cb749141aa73 Mon Sep 17 00:00:00 2001 From: YanJin Date: Wed, 12 Jul 2023 13:50:24 +0200 Subject: [PATCH 5/6] ZKUI-370: Refactor the test for objectlock retention setting --- .../ObjectLockRetentionSettings.test.tsx | 110 ++++++++++-------- .../buckets/ObjectLockRetentionSettings.tsx | 9 +- 2 files changed, 67 insertions(+), 52 deletions(-) diff --git a/src/react/databrowser/buckets/ObjectLockRetentionSettings.test.tsx b/src/react/databrowser/buckets/ObjectLockRetentionSettings.test.tsx index 76a23397c..aeb2c23b2 100644 --- a/src/react/databrowser/buckets/ObjectLockRetentionSettings.test.tsx +++ b/src/react/databrowser/buckets/ObjectLockRetentionSettings.test.tsx @@ -1,10 +1,25 @@ import { FormProvider, useForm } from 'react-hook-form'; import ObjectLockRetentionSettings from './ObjectLockRetentionSettings'; -import { reduxMountAct } from '../../utils/testUtil'; -import { act } from 'react-dom/test-utils'; +import { reduxRender } from '../../utils/testUtil'; +import { fireEvent, screen } from '@testing-library/react'; import { useEffect } from 'react'; +import userEvent from '@testing-library/user-event'; +import { notFalsyTypeGuard } from '../../../types/typeGuards'; + describe('ObjectLockRetentionSettings', () => { - it('should enable retention settings when default retention is checked', async () => { + const selectors = { + defaultRetention: () => screen.getByLabelText(/Default Retention/i), + governanceRetentionMode: () => screen.getByLabelText(/Governance/i), + complianceRetentionMode: () => screen.getByLabelText(/Compliance/i), + retentionPeriodInput: () => screen.getByLabelText(/retention period/i), + retentionPeriodDaysOption: () => + screen.getByRole('option', { name: /days/i }), + retentionPeriodYearsOption: () => + screen.getByRole('option', { name: /years/i }), + objectlock: () => screen.getByLabelText(/object-lock/i), + }; + it('should enable retention settings when default retention is checked', () => { + //S const Form = () => { const methods = useForm({ defaultValues: { @@ -18,32 +33,50 @@ describe('ObjectLockRetentionSettings', () => { ); }; - const component = await reduxMountAct(
); + const { + component: { container }, + } = reduxRender(); + //E + userEvent.click(selectors.defaultRetention()); + //V + expect(selectors.governanceRetentionMode()).toBeEnabled(); + expect(selectors.complianceRetentionMode()).toBeEnabled(); + expect(selectors.retentionPeriodInput()).toBeEnabled(); + userEvent.click(selectors.retentionPeriodInput()); - const objectLockDefaultRetentionEnabled = component.find( - 'input[placeholder="isDefaultRetentionEnabled"]', + const selector = notFalsyTypeGuard( + container.querySelector('.sc-select__control'), ); - - await act(async () => { - objectLockDefaultRetentionEnabled.simulate('change', { - target: { - checked: true, + fireEvent.keyDown(selector, { key: 'ArrowDown', which: 40, keyCode: 40 }); + expect(selectors.retentionPeriodYearsOption()).toBeInTheDocument(); + expect(selectors.retentionPeriodDaysOption()).toBeInTheDocument(); + }); + it('should disable the object-lock while editing on bucket that was created with object-lock enabled and should disable retention setting since retention is not active', () => { + //S + const Form = () => { + const methods = useForm({ + defaultValues: { + isObjectLockEnabled: true, + isDefaultRetentionEnabled: false, }, }); - }); - expect( - component.find('input[value="GOVERNANCE"]').getDOMNode().disabled, - ).toBe(false); - expect( - component.find('input[value="COMPLIANCE"]').getDOMNode().disabled, - ).toBe(false); - expect( - component.find('input[name="retentionPeriod"]').getDOMNode().disabled, - ).toBe(false); - expect( - component.find('input[id="retentionPeriodFrequencyChoice"]').getDOMNode() - .disabled, - ).toBe(false); + return ( + + + + ); + }; + reduxRender(); + //E+V + expect(selectors.objectlock()).toBeChecked(); + expect(selectors.objectlock()).toBeDisabled(); + expect(selectors.defaultRetention()).not.toBeChecked(); + expect(selectors.defaultRetention()).toBeEnabled(); + // The retention setting should be disabled since default retention is not enabled + expect(selectors.governanceRetentionMode()).not.toBeChecked(); + expect(selectors.governanceRetentionMode()).toBeDisabled(); + expect(selectors.complianceRetentionMode()).not.toBeChecked(); + expect(selectors.complianceRetentionMode()).toBeDisabled(); }); it('should display retention period error message', async () => { const Form = () => { @@ -60,32 +93,13 @@ describe('ObjectLockRetentionSettings', () => { return ( - + ); }; - const component = await reduxMountAct(); - await act(async () => { - const objectLockDefaultRetentionEnabled = component.find( - 'input[placeholder="isDefaultRetentionEnabled"]', - ); - objectLockDefaultRetentionEnabled.simulate('change', { - target: { - checked: true, - }, - }); - }); - expect( - component - .find( - `#${component - .find('input[name="retentionPeriod"]') - .getDOMNode() - .getAttribute('aria-describedby')}`, - ) - .first() - .text(), - ).toContain('Expected error'); + reduxRender(); + userEvent.click(selectors.defaultRetention()); + expect(screen.getByText(/expected error/i)).toBeInTheDocument(); }); }); diff --git a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx index 53d79552c..75154e94c 100644 --- a/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx +++ b/src/react/databrowser/buckets/ObjectLockRetentionSettings.tsx @@ -32,10 +32,10 @@ export const objectLockRetentionSettingsValidationRules = { }; export default function ObjectLockRetentionSettings({ - isLocationAzureOrGcpSelected, + isLocationAzureOrGcpSelected = false, isEditRetentionSetting = false, }: { - isLocationAzureOrGcpSelected: boolean; + isLocationAzureOrGcpSelected?: boolean; isEditRetentionSetting?: boolean; }) { const { @@ -63,8 +63,9 @@ export default function ObjectLockRetentionSettings({ content={ ) { matchVersioning(e.target.checked); @@ -138,7 +139,7 @@ export default function ObjectLockRetentionSettings({ } content={ From 000b95905b172adc04ffa9e3947efd5d7720001d Mon Sep 17 00:00:00 2001 From: YanJin Date: Wed, 12 Jul 2023 18:33:41 +0200 Subject: [PATCH 6/6] ZKUI-370: Unable to enable the versioning for the bucket hosted on Azure or GCP --- src/js/mock/S3Client.ts | 15 ++++ .../databrowser/buckets/details/Overview.tsx | 61 +++++++++----- .../details/__tests__/Overview.test.tsx | 79 +++++++++++++------ 3 files changed, 113 insertions(+), 42 deletions(-) diff --git a/src/js/mock/S3Client.ts b/src/js/mock/S3Client.ts index 418f9342a..6daab4a0f 100644 --- a/src/js/mock/S3Client.ts +++ b/src/js/mock/S3Client.ts @@ -157,6 +157,21 @@ export const bucketInfoResponseObjectLockDefaultRetention: BucketInfo = { }, }, }; + +export const bucketInfoResponseVersioningDisabled: BucketInfo = { + name: bucketName, + policy: false, + owner: ownerName, + aclGrantees: 0, + cors: false, + versioning: 'Disabled', + isVersioning: false, + public: false, + locationConstraint: 'azure-blob', + objectLockConfiguration: { + ObjectLockEnabled: 'Disabled', + }, +}; export class MockS3Client implements S3ClientInterface { listBucketsWithLocation() { return Promise.resolve({ diff --git a/src/react/databrowser/buckets/details/Overview.tsx b/src/react/databrowser/buckets/details/Overview.tsx index 7d6214fc6..64ffadfaa 100644 --- a/src/react/databrowser/buckets/details/Overview.tsx +++ b/src/react/databrowser/buckets/details/Overview.tsx @@ -13,7 +13,7 @@ import { ButtonContainer } from '../../../ui-elements/Container'; import DeleteConfirmation from '../../../ui-elements/DeleteConfirmation'; import type { BucketInfo } from '../../../../types/s3'; import { CellLink, TableContainer } from '../../../ui-elements/Table'; -import { Icon, Toggle } from '@scality/core-ui'; +import { Icon, Toggle, Tooltip } from '@scality/core-ui'; import { getLocationType, getLocationIngestionState, @@ -94,7 +94,7 @@ function Overview({ bucket, ingestionStates }: Props) { ); const bucketInfo = useSelector((state: AppState) => state.s3.bucketInfo); const locations = useSelector( - (state: AppState) => state.configuration.latest.locations, + (state: AppState) => state.configuration.latest?.locations, ); const loading = useSelector( (state: AppState) => state.networkActivity.counter > 0, @@ -149,7 +149,10 @@ 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'; return ( {bucketInfo.objectLockConfiguration.ObjectLockEnabled === 'Disabled' && ( - - dispatch( - toggleBucketVersioning( - bucket.name, - !bucketInfo.isVersioning, - ), + + 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, + ), + ) + } + /> + )} {bucketInfo.objectLockConfiguration.ObjectLockEnabled === 'Enabled' && ( diff --git a/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx b/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx index 40dc61f32..99b71ecae 100644 --- a/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx +++ b/src/react/databrowser/buckets/details/__tests__/Overview.test.tsx @@ -3,13 +3,17 @@ import * as actions from '../../../../actions/s3bucket'; import { bucketInfoResponseNoVersioning, bucketInfoResponseVersioning, + bucketInfoResponseVersioningDisabled, bucketInfoResponseObjectLockNoDefaultRetention, bucketName, bucketInfoResponseObjectLockDefaultRetention, } from '../../../../../js/mock/S3Client'; import Overview from '../Overview'; import { Toggle } from '@scality/core-ui'; -import { reduxMount } from '../../../../utils/testUtil'; +import { reduxMount, reduxRender } from '../../../../utils/testUtil'; +import { screen, waitFor, within } from '@testing-library/react'; +import Immutable from 'immutable'; +import userEvent from '@testing-library/user-event'; const BUCKET = { CreationDate: 'Tue Oct 12 2020 18:38:56', LocationConstraint: '', @@ -17,20 +21,24 @@ const BUCKET = { }; const TEST_STATE = { uiBuckets: { - showDelete: '', + showDelete: false, }, configuration: { latest: { - locations: [ - { - 'us-east-1': { - isBuiltin: true, - locationType: 'location-file-v1', - name: 'us-east-1', - objectId: '1060b13c-d805-11ea-a59c-a0999b105a5f', - }, + 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: { @@ -38,10 +46,11 @@ const TEST_STATE = { }, networkActivity: { counter: 0, + messages: Immutable.List(), }, }; //TODO: Those tests are testing implementation details based on child component names. We should refactor them. -describe.skip('Overview', () => { +describe('Overview', () => { it('should render Overview component', () => { const { component } = reduxMount(, { ...TEST_STATE, @@ -157,23 +166,49 @@ describe.skip('Overview', () => { 'Governance - 5 days', ); }); - it('should trigger deleteBucket function when approving clicking on delete button when modal popup', () => { + it.skip('should trigger deleteBucket function when approving clicking on delete button when modal popup', async () => { const deleteBucketMock = jest.spyOn(actions, 'deleteBucket'); - const { component } = reduxMount(, { + reduxRender(, { ...TEST_STATE, ...{ - uiBuckets: { - showDelete: bucketName, - }, s3: { bucketInfo: bucketInfoResponseVersioning, }, }, }); - const deleteButton = component.find( - 'button.delete-confirmation-delete-button', - ); - deleteButton.simulate('click'); - expect(deleteBucketMock).toHaveBeenCalledTimes(1); + const deleteButton = screen.getByRole('button', { name: /delete bucket/i }); + userEvent.click(deleteButton); + await waitFor(() => { + expect( + screen.getByRole('dialog', { name: /confirmation/i }), + ).toBeVisible(); + }); + const confirmationDialog = screen.getByRole('dialog', { + name: /confirmation/i, + }); + const confirmDeleteButton = within(confirmationDialog).getByRole('button', { + name: /delete/i, + }); + userEvent.click(confirmDeleteButton); + expect(deleteBucketMock).toHaveBeenCalledWith(bucketName); + }); + it('should disable the versioning toogle for Azure Blob Storage', async () => { + //S + reduxRender(, { + ...TEST_STATE, + ...{ s3: { bucketInfo: bucketInfoResponseVersioningDisabled } }, + }); + await waitFor(() => { + expect( + screen.getByRole('checkbox', { + name: /inactive/i, + }), + ).toBeInTheDocument(); + }); + const versioningToggleItem = screen.getByRole('checkbox', { + name: /inactive/i, + }); + //V + expect(versioningToggleItem).toHaveAttribute('disabled'); }); });