diff --git a/src/js/mock/managementClientMSWHandlers.ts b/src/js/mock/managementClientMSWHandlers.ts
index f02b6a7b7..1cefd7873 100644
--- a/src/js/mock/managementClientMSWHandlers.ts
+++ b/src/js/mock/managementClientMSWHandlers.ts
@@ -144,17 +144,22 @@ export const LOCATIONS = {
name: 'ring-nick',
objectId: '99a06f79-c62c-11ec-b993-7e8a0ab79998',
},
- 'us-east-1': {
- isBuiltin: true,
- locationType: 'location-file-v1',
- name: 'us-east-1',
- objectId: '95dbedf5-9888-11ec-8565-1ac2af7d1e53',
- },
[azureblobstorage]: {
locationType: 'location-azure-v1',
name: azureblobstorage,
details: {},
},
+ 'us-east-1': {
+ details: {
+ bootstrapList: [
+ 'artesca-storage-service-hdservice-proxy.xcore.svc:18888',
+ ],
+ repoId: null,
+ },
+ locationType: 'location-scality-hdclient-v2',
+ name: 'us-east-1',
+ objectId: '22f31240-4bd3-11ee-98b3-1e5b6f897bc7',
+ },
};
export const ENDPOINTS = [
diff --git a/src/react/locations/LocationEditor.tsx b/src/react/locations/LocationEditor.tsx
index cda25f64d..791acd682 100644
--- a/src/react/locations/LocationEditor.tsx
+++ b/src/react/locations/LocationEditor.tsx
@@ -62,7 +62,8 @@ function LocationEditor() {
} = useAccountsLocationsAndEndpoints({
accountsLocationsEndpointsAdapter,
});
- const locationEditing = accountsLocationsAndEndpoints?.locations.find(
+ const locations = accountsLocationsAndEndpoints?.locations;
+ const locationEditing = locations?.find(
(location) => location.name === locationName,
);
const capabilities = useSelector(
@@ -73,9 +74,13 @@ function LocationEditor() {
convertToForm({ ...newLocationDetails(), ...locationEditing }),
);
const selectOptions = useMemo(() => {
- //@ts-expect-error fix this when you are working on it
- return selectStorageOptions(capabilities, makeLabel, !editingExisting);
- }, [capabilities, editingExisting]);
+ return selectStorageOptions(
+ capabilities,
+ locations,
+ makeLabel,
+ !editingExisting,
+ );
+ }, [capabilities, editingExisting, locations]);
useMemo(() => {
if (locationEditing) {
setLocation(convertToForm(locationEditing));
diff --git a/src/react/locations/LocationsList.tsx b/src/react/locations/LocationsList.tsx
index aeea45c1b..f758c5031 100644
--- a/src/react/locations/LocationsList.tsx
+++ b/src/react/locations/LocationsList.tsx
@@ -138,6 +138,9 @@ const ActionButtons = ({
);
};
+ const isEditButtonDisabled =
+ rowValues.isBuiltin || rowValues.type === 'location-scality-hdclient-v2';
+
return (
history.push(`/locations/${locationName}/edit`)}
type="button"
aria-label="Edit Location"
- tooltip={{
- overlay: 'Edit Location',
- placement: 'top',
- }}
- disabled={rowValues.isBuiltin}
+ tooltip={
+ isEditButtonDisabled
+ ? {
+ overlay: 'Edit Location is disabled for this location',
+ placement: 'top',
+ }
+ : {
+ overlay: 'Edit Location',
+ placement: 'top',
+ }
+ }
+ disabled={isEditButtonDisabled}
/>
}
diff --git a/src/react/locations/__tests__/LocationEditor.test.tsx b/src/react/locations/__tests__/LocationEditor.test.tsx
index 27f7e840c..d837c587f 100644
--- a/src/react/locations/__tests__/LocationEditor.test.tsx
+++ b/src/react/locations/__tests__/LocationEditor.test.tsx
@@ -3,12 +3,15 @@ import LocationEditor from '../LocationEditor';
import {
fireEvent,
+ render,
screen,
+ waitFor,
waitForElementToBeRemoved,
} from '@testing-library/react';
import { notFalsyTypeGuard } from '../../../types/typeGuards';
import {
TEST_API_BASE_URL,
+ Wrapper,
mockOffsetSize,
reduxRender,
selectClick,
@@ -17,6 +20,7 @@ import { setupServer } from 'msw/node';
import { getConfigOverlay } from '../../../js/mock/managementClientMSWHandlers';
import { INSTANCE_ID } from '../../actions/__tests__/utils/testUtil';
import userEvent from '@testing-library/user-event';
+import { rest } from 'msw';
const server = setupServer(getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID));
@@ -45,13 +49,11 @@ describe('LocationEditor', () => {
);
await selectClick(selector);
await userEvent.keyboard('{arrowup}');
-
expect(
container.querySelector('.sc-select__option--is-focused')?.textContent,
- ).toBe('Storage Service for ARTESCA');
+ ).toBe('Scality ARTESCA S3');
[
- 'Scality ARTESCA S3',
'Scality RING with S3 Connector',
'Amazon S3',
'Google Cloud Storage',
@@ -69,4 +71,43 @@ describe('LocationEditor', () => {
).toBe(locationName);
});
});
+ const selectors = {
+ loadingLocation: () => screen.getByText('Loading location...'),
+ locationType: () => screen.getByLabelText(/location type \*/i),
+ };
+
+ it('should hide the artesca storage service if it is already created', async () => {
+ //S
+ server.use(
+ rest.get(
+ `${TEST_API_BASE_URL}/api/v1/config/overlay/view/${INSTANCE_ID}`,
+ (_, res, ctx) =>
+ res(
+ ctx.json({
+ locations: {
+ 'us-east-2': {
+ details: {
+ bootstrapList: [
+ 'artesca-storage-service-hdservice-proxy.xcore.svc:18888',
+ ],
+ repoId: null,
+ },
+ locationType: 'location-scality-hdclient-v2',
+ name: 'us-east-2',
+ objectId: '22f31240-4bd3-11ee-98b3-1e5b6f897bc7',
+ },
+ },
+ }),
+ ),
+ ),
+ );
+ render(, { wrapper: Wrapper });
+ await waitForElementToBeRemoved(() => selectors.loadingLocation());
+ //E
+ selectClick(selectors.locationType());
+ //V
+ await waitFor(() => {
+ expect(screen.queryByText('Storage Service for ARTESCA')).toBeNull();
+ });
+ });
});
diff --git a/src/react/locations/__tests__/LocationList.test.tsx b/src/react/locations/__tests__/LocationList.test.tsx
new file mode 100644
index 000000000..27ed87e32
--- /dev/null
+++ b/src/react/locations/__tests__/LocationList.test.tsx
@@ -0,0 +1,65 @@
+import {
+ screen,
+ waitFor,
+ waitForElementToBeRemoved,
+ within,
+} from '@testing-library/react';
+import { setupServer } from 'msw/node';
+import { rest } from 'msw';
+import {
+ getConfigOverlay,
+ getStorageConsumptionMetricsHandlers,
+} from '../../../js/mock/managementClientMSWHandlers';
+import {
+ TEST_API_BASE_URL,
+ mockOffsetSize,
+ renderWithRouterMatch,
+ zenkoUITestConfig,
+} from '../../utils/testUtil';
+import { INSTANCE_ID } from '../../actions/__tests__/utils/testUtil';
+import { LocationsList } from '../LocationsList';
+
+const server = setupServer(
+ getConfigOverlay(TEST_API_BASE_URL, INSTANCE_ID),
+ ...getStorageConsumptionMetricsHandlers(
+ zenkoUITestConfig.managementEndpoint,
+ INSTANCE_ID,
+ ),
+ rest.get(
+ `${TEST_API_BASE_URL}/api/v1/instance/${INSTANCE_ID}/status`,
+ (req, res, ctx) => res(ctx.json({})),
+ ),
+);
+
+describe('LocationList', () => {
+ beforeAll(() => {
+ jest.setTimeout(30_000);
+ mockOffsetSize(500, 100);
+ server.listen({ onUnhandledRequest: 'error' });
+ });
+ afterEach(() => {
+ server.resetHandlers();
+ });
+ afterAll(() => {
+ server.close();
+ });
+ it('should disable the delete button for default location', async () => {
+ //S
+ renderWithRouterMatch();
+ //E
+ await waitForElementToBeRemoved(() => [
+ ...screen.queryAllByText(/Loading/i),
+ ]);
+ const defaultArtescaLocationRow = screen.getByRole('row', {
+ name: /us-east-1 Storage Service for ARTESCA/i,
+ });
+ //V
+ await waitFor(() => {
+ expect(
+ within(defaultArtescaLocationRow).getByRole('button', {
+ name: /Edit Location/i,
+ }),
+ ).toBeDisabled();
+ });
+ });
+});
diff --git a/src/react/utils/storageOptions.ts b/src/react/utils/storageOptions.ts
index e77058ce7..81457db5c 100644
--- a/src/react/utils/storageOptions.ts
+++ b/src/react/utils/storageOptions.ts
@@ -1,4 +1,4 @@
-import type { InstanceStateSnapshot } from '../../types/stats';
+import type { Capabilities, InstanceStateSnapshot } from '../../types/stats';
import type {
LabelFunction,
StorageOptionSelect,
@@ -8,7 +8,6 @@ import {
JAGUAR_S3_ENDPOINT,
JAGUAR_S3_LOCATION_KEY,
Location as LegacyLocation,
- Locations,
ORANGE_S3_ENDPOINT,
ORANGE_S3_LOCATION_KEY,
OUTSCALE_PUBLIC_S3_ENDPOINT,
@@ -20,6 +19,7 @@ import { LocationForm } from '../../types/location';
import { Location } from '../next-architecture/domain/entities/location';
import { LocationInfo } from '../next-architecture/adapters/accounts-locations/ILocationsAdapter';
import { LocationV1 } from '../../js/managementClient/api';
+
export function checkSupportsReplicationTarget(
locations: LocationInfo[],
): boolean {
@@ -104,12 +104,19 @@ export const getLocationTypeShort = (
};
export function selectStorageOptions(
- capabilities: Pick,
+ capabilities: Capabilities,
+ locations?: LocationInfo[],
labelFn?: LabelFunction,
exceptHidden = true,
): Array {
+ const hdLocation = locations?.find(
+ (l) => l.type === LocationV1.LocationTypeEnum.ScalityHdclientV2,
+ );
return Object.keys(storageOptions)
.filter((o) => {
+ if (hdLocation && o === 'location-scality-hdclient-v2') {
+ return false;
+ }
if (exceptHidden) {
const hidden = !!storageOptions[o].hidden;
diff --git a/src/react/workflow/__tests__/TransitionForm.test.tsx b/src/react/workflow/__tests__/TransitionForm.test.tsx
index 15bd25d5d..9d2989451 100644
--- a/src/react/workflow/__tests__/TransitionForm.test.tsx
+++ b/src/react/workflow/__tests__/TransitionForm.test.tsx
@@ -200,7 +200,7 @@ describe('TransitionForm', () => {
await waitFor(() =>
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -208,7 +208,7 @@ describe('TransitionForm', () => {
await userEvent.click(
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -233,7 +233,7 @@ describe('TransitionForm', () => {
await waitFor(() =>
screen.getByRole('option', {
name: new RegExp(
- `${versionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${versionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -241,7 +241,7 @@ describe('TransitionForm', () => {
await userEvent.click(
screen.getByRole('option', {
name: new RegExp(
- `${versionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${versionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -336,7 +336,7 @@ describe('TransitionForm', () => {
await waitFor(() =>
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -344,7 +344,7 @@ describe('TransitionForm', () => {
await userEvent.click(
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -384,7 +384,7 @@ describe('TransitionForm', () => {
await waitFor(() =>
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -392,7 +392,7 @@ describe('TransitionForm', () => {
await userEvent.click(
screen.getByRole('option', {
name: new RegExp(
- `${notVersionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${notVersionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
@@ -435,7 +435,7 @@ describe('TransitionForm', () => {
userEvent.click(
screen.getByRole('option', {
name: new RegExp(
- `${versionedBucket} \\(us-east-1 / Local Filesystem \\)`,
+ `${versionedBucket} \\(us-east-1 / Storage Service \\)`,
'i',
),
}),
diff --git a/src/types/stats.ts b/src/types/stats.ts
index 80b8368c5..8e9f59c33 100644
--- a/src/types/stats.ts
+++ b/src/types/stats.ts
@@ -72,22 +72,23 @@ export type MetricsUnit = {
'crr-schedule'?: CrrScheduleUnit;
'ingest-schedule'?: IngestScheduleUnit;
};
+export type 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;
+};
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 capabilities: Capabilities;
readonly latestConfigurationOverlay: ConfigurationOverlay;
readonly runningConfigurationVersion: number;
readonly lastSeen: string;