Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ZKUI-397: refactor bucket overview for Veeam use case #667

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/js/config.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export const XDM_FEATURE = 'XDM';
export const VEEAM_FEATURE = 'Veeam';
67 changes: 67 additions & 0 deletions src/js/mock/S3ClientMSWHandlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,43 @@ export function mockBucketOperations(
);
}

if (req.url.searchParams.has('cors')) {
return res(
ctx.xml(`
<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
</CORSConfiguration>
`),
);
}

if (req.url.searchParams.has('acl')) {
return res(
ctx.xml(`
<?xml version="1.0" encoding="UTF-8"?>
<AccessControlPolicy xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<Owner>
<ID>1234</ID>
<DisplayName>test</DisplayName>
</Owner>
<AccessControlList>
</AccessControlList>
</AccessControlPolicy>
`),
);
}

if (req.url.searchParams.has('object-lock')) {
return res(
ctx.xml(`
<?xml version="1.0" encoding="UTF-8"?>
<ObjectLockConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<ObjectLockEnabled>Enabled</ObjectLockEnabled>
</ObjectLockConfiguration>
`),
);
}

return res(ctx.status(404));
},
);
Expand Down Expand Up @@ -178,3 +215,33 @@ export const mockObjectEmpty = (bucketName: string) => {
},
);
};

export const mockGetBucketTagging = (bucketName: string) => {
return rest.get(
`${zenkoUITestConfig.zenkoEndpoint}/${bucketName}`,
(req, res, ctx) => {
if (req.url.searchParams.has('tagging')) {
return res(
ctx.xml(`
<?xml version="1.0" encoding="UTF-8"?>
<Tagging>
<TagSet>
<Tag><Key>X-Scality-Usecase</Key><Value>Veeam 12</Value></Tag>
</TagSet>
</Tagging>`),
);
}
},
);
};

export const mockGetBucketTaggingError = (bucketName: string) => {
return rest.get(
`${zenkoUITestConfig.zenkoEndpoint}/${bucketName}`,
(req, res, ctx) => {
if (req.url.searchParams.has('tagging')) {
return res(ctx.status(500));
}
},
);
};
117 changes: 94 additions & 23 deletions src/react/databrowser/buckets/details/Overview.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { ConstrainedText, Icon, Toggle, Tooltip } from '@scality/core-ui';
import {
ConstrainedText,
Icon,
Toast,
Toggle,
Tooltip,
} from '@scality/core-ui';
import { SmallerText } from '@scality/core-ui/dist/components/text/Text.component';
import { useHistory } from 'react-router-dom';
import { useDispatch, useSelector } from 'react-redux';

import type { WorkflowScheduleUnitState } from '../../../../types/stats';
import { HelpAsyncNotification } from '../../../ui-elements/Help';
import { XDM_FEATURE } from '../../../../js/config';
import { VEEAM_FEATURE, XDM_FEATURE } from '../../../../js/config';
import type { BucketInfo } from '../../../../types/s3';
import type { AppState } from '../../../../types/state';
import { useCurrentAccount } from '../../../DataServiceRoleProvider';
import { getBucketInfo, toggleBucketVersioning } from '../../../actions';
import { useChangeBucketVersionning } from '../../../next-architecture/domain/business/buckets';
import {
useBucketTagging,
useChangeBucketVersionning,
} from '../../../next-architecture/domain/business/buckets';
import { Bucket } from '../../../next-architecture/domain/entities/bucket';
import { ButtonContainer } from '../../../ui-elements/Container';
import { DeleteBucket } from '../../../ui-elements/DeleteBucket';
Expand All @@ -26,6 +35,11 @@ import {
import { useWorkflows } from '../../../workflow/Workflows';
import { useEffect, useState } from 'react';
import { Button } from '@scality/core-ui/dist/next';
import {
BUCKET_TAG_USECASE,
VEEAMVERSION11,
VEEAMVERSION12,
} from '../../../ui-elements/Veeam/VeeamConstants';

function capitalize(string: string) {
return string.toLowerCase().replace(/^\w/, (c) => {
Expand Down Expand Up @@ -88,6 +102,17 @@ function Overview({ bucket, ingestionStates }: Props) {
const features = useSelector((state: AppState) => state.auth.config.features);
const { account } = useCurrentAccount();
const [isErrorModalOpen, setIsErrorModalOpen] = useState(false);
const [bucketTaggingToast, setBucketTaggingToast] = useState(true);
const { tags } = useBucketTagging({ bucketName: bucket.name });
const VEEAM_FEATURE_FLAG_ENABLED = features.includes(VEEAM_FEATURE);
JBWatenbergScality marked this conversation as resolved.
Show resolved Hide resolved
const isVeeamBucket =
tags.status === 'success' &&
(tags.value?.[BUCKET_TAG_USECASE] === VEEAMVERSION11 ||
tags.value?.[BUCKET_TAG_USECASE] === VEEAMVERSION12) &&
VEEAM_FEATURE_FLAG_ENABLED;

const isVeeam12 =
isVeeamBucket && tags.value?.[BUCKET_TAG_USECASE] === VEEAMVERSION12;

useEffect(() => {
dispatch(getBucketInfo(bucket.name));
Expand Down Expand Up @@ -136,6 +161,14 @@ function Overview({ bucket, ingestionStates }: Props) {
: null
}
/>
<Toast
message="Encountered issues loading bucket tagging, causing uncertainty about the use-case. Please refresh the page."
open={tags.status === 'error' && bucketTaggingToast}
status="error"
onClose={() => {
setBucketTaggingToast(false);
}}
JBWatenbergScality marked this conversation as resolved.
Show resolved Hide resolved
/>
<ButtonContainer>
<EmptyBucket bucketName={bucket.name} />
<DeleteBucket bucketName={bucket.name} />
Expand Down Expand Up @@ -208,6 +241,58 @@ function Overview({ bucket, ingestionStates }: Props) {
)}
</T.Value>
</T.Row>
<T.Row>
<T.Key> Location </T.Key>
<T.Value>
{bucketInfo.locationConstraint || 'us-east-1'}
{' / '}
<small>
{locations &&
getLocationType(locations[bucketInfo.locationConstraint])}
</small>
</T.Value>
</T.Row>
{features.includes(XDM_FEATURE) && (
<T.Row>
<T.Key> Async Metadata updates </T.Key>
<T.Value>
{ingestionValue}
{isIngestion && <HelpAsyncNotification />}
</T.Value>
</T.Row>
)}
</T.GroupContent>
</T.Group>
{isVeeamBucket && (
<T.Group>
<T.GroupName> Use-case </T.GroupName>
<T.Row>
<T.Key> Use-case </T.Key>
<T.Value> Backup - {tags.value?.[BUCKET_TAG_USECASE]}</T.Value>
</T.Row>
{isVeeam12 && (
<T.Row>
<T.Key> Max repository Capacity </T.Key>
<T.GroupValues>
{/* TODO */}
<>5TB</>
<Button
variant="outline"
label="Edit"
aria-label="Edit max capacity"
icon={<Icon name="Pencil" />}
onClick={() => {
//TODO: open the modal to modify the capacity
}}
/>
</T.GroupValues>
</T.Row>
)}
</T.Group>
)}
<T.Group>
<T.GroupName> Data protection </T.GroupName>
<T.GroupContent>
{bucketInfo.objectLockConfiguration.ObjectLockEnabled ===
'Enabled' && (
<T.Row>
Expand All @@ -218,12 +303,18 @@ function Overview({ bucket, ingestionStates }: Props) {
id="edit-retention-btn"
variant="outline"
label="Edit"
aria-label="Edit default retention"
icon={<Icon name="Pencil" />}
onClick={() => {
history.push(
`/accounts/${account?.Name}/buckets/${bucket.name}/retention-setting`,
);
}}
disabled={isVeeamBucket}
tooltip={{
overlay:
'Edition is disabled as it is managed by Veeam.',
}}
/>
</T.GroupValues>
</T.Row>
Expand All @@ -235,26 +326,6 @@ function Overview({ bucket, ingestionStates }: Props) {
<T.Value>Disabled</T.Value>
</T.Row>
)}
<T.Row>
<T.Key> Location </T.Key>
<T.Value>
{bucketInfo.locationConstraint || 'us-east-1'}
{' / '}
<small>
{locations &&
getLocationType(locations[bucketInfo.locationConstraint])}
</small>
</T.Value>
</T.Row>
{features.includes(XDM_FEATURE) && (
<T.Row>
<T.Key> Async Metadata updates </T.Key>
<T.Value>
{ingestionValue}
{isIngestion && <HelpAsyncNotification />}
</T.Value>
</T.Row>
)}
</T.GroupContent>
</T.Group>
<T.Group>
Expand Down
76 changes: 68 additions & 8 deletions src/react/databrowser/buckets/details/__tests__/Overview.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import * as T from '../../../../ui-elements/TableKeyValue2';
import * as actions from '../../../../actions/s3bucket';
import {
bucketInfoResponseNoVersioning,
Expand All @@ -9,17 +8,18 @@ import {
bucketInfoResponseObjectLockDefaultRetention,
} from '../../../../../js/mock/S3Client';
import Overview from '../Overview';
import { Toggle } from '@scality/core-ui';
import { NewWrapper, zenkoUITestConfig } from '../../../../utils/testUtil';
import {
reduxMount,
reduxRender,
zenkoUITestConfig,
} from '../../../../utils/testUtil';
import { fireEvent, screen, waitFor, within } from '@testing-library/react';
fireEvent,
render,
screen,
waitFor,
within,
} from '@testing-library/react';
import Immutable from 'immutable';
import userEvent from '@testing-library/user-event';
import { renderWithRouterMatch } from '../../../../utils/testUtil';
import { debug } from 'jest-preview';

const BUCKET = {
CreationDate: 'Tue Oct 12 2020 18:38:56',
LocationConstraint: '',
Expand Down Expand Up @@ -198,6 +198,11 @@ import {
getConfigOverlay,
getStorageConsumptionMetricsHandlers,
} from '../../../../../js/mock/managementClientMSWHandlers';
import {
mockBucketOperations,
mockGetBucketTagging,
mockGetBucketTaggingError,
} from '../../../../../js/mock/S3ClientMSWHandlers';
const mockResponse =
'<VersioningConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/"><Status>Enabled</Status></VersioningConfiguration>';
const TEST_ACCOUNT =
Expand Down Expand Up @@ -235,13 +240,29 @@ const server = setupServer(
zenkoUITestConfig.managementEndpoint,
INSTANCE_ID,
),
mockBucketOperations(),
);
beforeAll(() => {
server.listen({ onUnhandledRequest: 'error' });
});
afterAll(() => server.close());
afterEach(() => server.resetHandlers());

const selectors = {
editDefaultRetentionButton: () =>
screen.getByRole('button', {
name: /edit default retention/i,
}),
bucketTaggingErrorToastCloseButton: () =>
within(screen.getByRole('status')).getByRole('button', {
name: /close/i,
}),
bucketTaggingErrorToast: () =>
within(screen.getByRole('status')).getByText(
/Encountered issues loading bucket tagging, causing uncertainty about the use-case. Please refresh the page./i,
),
};

describe('Overview', () => {
it('should call the updateBucketVersioning function when clicking on the toggle versioning button', async () => {
const useUpdateBucketVersioningMock = jest.fn();
Expand Down Expand Up @@ -273,4 +294,43 @@ describe('Overview', () => {
expect(useUpdateBucketVersioningMock).toHaveBeenCalledWith(mockResponse);
});
});

it('should display the Veeam use-case and disable the edition of default retention', async () => {
//Setup
server.use(mockGetBucketTagging(bucketName));
//Exercise
render(<Overview bucket={{ name: bucketName }} />, {
wrapper: NewWrapper(),
});
//Verify
await waitFor(() => {
expect(
screen.getByText(new RegExp(`Backup - Veeam 12`, 'i')),
).toBeInTheDocument();
});
expect(selectors.editDefaultRetentionButton()).toBeDisabled();
});

it('should show error toast when loading bucket tagging failed', async () => {
//Setup
server.use(mockGetBucketTaggingError(bucketName));
//Exercise
render(<Overview bucket={{ name: bucketName }} />, {
wrapper: NewWrapper(),
});
//Verify
await waitFor(() => {
expect(selectors.bucketTaggingErrorToast()).toBeInTheDocument();
});
//Exercise
userEvent.click(selectors.bucketTaggingErrorToastCloseButton());
//Verify
await waitFor(() => {
expect(
screen.queryByText(
/Encountered issues loading bucket tagging, causing uncertainty about the use-case. Please refresh the page./i,
),
).toBe(null);
});
});
});
Loading
Loading