diff --git a/assets/js/common/ClusterInfoBox/ClusterInfoBox.jsx b/assets/js/common/ClusterInfoBox/ClusterInfoBox.jsx
index c2ac80be46..10f82564b4 100644
--- a/assets/js/common/ClusterInfoBox/ClusterInfoBox.jsx
+++ b/assets/js/common/ClusterInfoBox/ClusterInfoBox.jsx
@@ -1,12 +1,10 @@
import React from 'react';
-import { getClusterTypeLabel } from '@lib/model/clusters';
-
import ListView from '@common/ListView';
import ProviderLabel from '@common/ProviderLabel';
+import ClusterTypeLabel from '@common/ClusterTypeLabel';
-// eslint-disable-next-line import/prefer-default-export
-function ClusterInfoBox({ haScenario, provider }) {
+function ClusterInfoBox({ haScenario, provider, architectureType }) {
return (
(
+
+ ),
},
{
title: 'Provider',
diff --git a/assets/js/common/ClusterInfoBox/ClusterInfoBox.test.jsx b/assets/js/common/ClusterInfoBox/ClusterInfoBox.test.jsx
index e1339b3374..1a6499f0ee 100644
--- a/assets/js/common/ClusterInfoBox/ClusterInfoBox.test.jsx
+++ b/assets/js/common/ClusterInfoBox/ClusterInfoBox.test.jsx
@@ -1,6 +1,10 @@
import React from 'react';
-import { render } from '@testing-library/react';
+import { screen, render } from '@testing-library/react';
import '@testing-library/jest-dom';
+import userEvent from '@testing-library/user-event';
+
+import { renderWithRouter } from '@lib/test-utils';
+
import ClusterInfoBox from './ClusterInfoBox';
describe('Cluster Info Box', () => {
@@ -62,4 +66,32 @@ describe('Cluster Info Box', () => {
expect(getByText(haScenarioText)).toBeTruthy();
});
});
+
+ it.each([
+ { architectureType: 'classic', tooltip: 'Classic architecture' },
+ { architectureType: 'angi', tooltip: 'Angi architecture' },
+ ])(
+ 'should display architecture type icon',
+ async ({ architectureType, tooltip }) => {
+ const user = userEvent.setup();
+
+ renderWithRouter(
+
+ );
+
+ const icon = screen.getByTestId('eos-svg-component');
+
+ await user.hover(icon);
+ expect(screen.getByText(tooltip, { exact: false })).toBeInTheDocument();
+ }
+ );
+
+ it('should not display architecture type icon if architecture is unknown', () => {
+ render();
+ expect(screen.queryByTestId('eos-svg-component')).not.toBeInTheDocument();
+ });
});
diff --git a/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.jsx b/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.jsx
new file mode 100644
index 0000000000..d87e6770a1
--- /dev/null
+++ b/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.jsx
@@ -0,0 +1,56 @@
+import React from 'react';
+
+import { Link } from 'react-router-dom';
+import { EOS_INFO_OUTLINED, EOS_STAR } from 'eos-icons-react';
+
+import Tooltip from '@common/Tooltip';
+import {
+ ANGI_ARCHITECTURE,
+ CLASSIC_ARCHITECTURE,
+ getClusterTypeLabel,
+} from '@lib/model/clusters';
+
+const MIGRATION_URL =
+ 'https://www.suse.com/c/how-to-upgrade-to-saphanasr-angi/';
+const ANGI_TOOLTIP_MESSAGE = 'Angi architecture';
+const CLASSIC_TOOLTIP_MESSAGE = (
+ <>
+ Classic architecture. Recommended{' '}
+
+ migration
+ {' '}
+ to Angi architecture
+ >
+);
+
+const icons = {
+ [ANGI_ARCHITECTURE]: (
+
+
+
+ ),
+ [CLASSIC_ARCHITECTURE]: (
+
+
+
+ ),
+};
+
+function ClusterTypeLabel({ clusterType, architectureType }) {
+ return (
+
+ {icons[architectureType]}
+ {getClusterTypeLabel(clusterType)}
+
+ );
+}
+
+export default ClusterTypeLabel;
diff --git a/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.test.jsx b/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.test.jsx
new file mode 100644
index 0000000000..ede2b47624
--- /dev/null
+++ b/assets/js/common/ClusterTypeLabel/ClusterTypeLabel.test.jsx
@@ -0,0 +1,61 @@
+import React from 'react';
+import { render, screen } from '@testing-library/react';
+import userEvent from '@testing-library/user-event';
+import '@testing-library/jest-dom';
+
+import { renderWithRouter } from '@lib/test-utils';
+
+import ClusterTypeLabel from './ClusterTypeLabel';
+
+describe('ClusterTypeLabel component', () => {
+ it.each([
+ { clusterType: 'hana_scale_up', label: 'HANA Scale Up' },
+ { clusterType: 'hana_scale_out', label: 'HANA Scale Out' },
+ { clusterType: 'ascs_ers', label: 'ASCS/ERS' },
+ ])(
+ 'should display the correct $clusterType cluster type label',
+ ({ clusterType, label }) => {
+ render(
+
+ );
+ expect(screen.getByText(label)).toBeVisible();
+ }
+ );
+
+ it('should display a green star icon when the cluster uses angi architecture', async () => {
+ const user = userEvent.setup();
+
+ render(
+
+ );
+ expect(screen.getByText('HANA Scale Up')).toBeTruthy();
+
+ const icon = screen.getByTestId('eos-svg-component');
+ expect(icon.classList.toString()).toContain('fill-jungle-green-500');
+
+ await user.hover(icon);
+ expect(screen.getByText('Angi architecture')).toBeInTheDocument();
+ });
+
+ it('should display an info icon when the cluster uses classic architecture', async () => {
+ const user = userEvent.setup();
+
+ renderWithRouter(
+
+ );
+ expect(screen.getByText('HANA Scale Up')).toBeTruthy();
+
+ const icon = screen.getByTestId('eos-svg-component');
+
+ await user.hover(icon);
+ expect(
+ screen.getByText('Classic architecture', { exact: false })
+ ).toBeInTheDocument();
+ });
+});
diff --git a/assets/js/common/ClusterTypeLabel/index.js b/assets/js/common/ClusterTypeLabel/index.js
new file mode 100644
index 0000000000..1c4ac7d027
--- /dev/null
+++ b/assets/js/common/ClusterTypeLabel/index.js
@@ -0,0 +1,3 @@
+import ClusterTypeLabel from './ClusterTypeLabel';
+
+export default ClusterTypeLabel;
diff --git a/assets/js/lib/checks/env.js b/assets/js/lib/checks/env.js
index 188da93e48..25861cb707 100644
--- a/assets/js/lib/checks/env.js
+++ b/assets/js/lib/checks/env.js
@@ -6,6 +6,7 @@ export const buildEnv = ({
cluster_type,
ensa_version,
filesystem_type,
+ architecture_type,
}) => {
switch (cluster_type) {
case ASCS_ERS: {
@@ -22,6 +23,7 @@ export const buildEnv = ({
provider,
target_type,
cluster_type,
+ architecture_type,
};
}
}
diff --git a/assets/js/lib/checks/env.test.js b/assets/js/lib/checks/env.test.js
index 672567e725..4268d92352 100644
--- a/assets/js/lib/checks/env.test.js
+++ b/assets/js/lib/checks/env.test.js
@@ -1,37 +1,53 @@
import { faker } from '@faker-js/faker';
-import { has } from 'lodash';
+import { pick } from 'lodash';
import { ASCS_ERS, HANA_SCALE_UP } from '@lib/model/clusters';
import { buildEnv } from '.';
describe('buildEnv', () => {
- it('should build and env with filesystem type and ensa version for ASCS/ERS clusters', () => {
+ it('should build and env for ASCS/ERS clusters', () => {
const payload = {
cluster_type: ASCS_ERS,
provider: faker.color.rgb(),
target_type: faker.hacker.noun(),
ensa_version: faker.number.int(),
filesystem_type: faker.animal.dog(),
+ architecture_type: faker.hacker.noun(),
};
+ const expectedPayload = pick(payload, [
+ 'cluster_type',
+ 'provider',
+ 'target_type',
+ 'ensa_version',
+ 'filesystem_type',
+ ]);
+
const env = buildEnv(payload);
- expect(env).toEqual(payload);
+ expect(env).toEqual(expectedPayload);
});
- it('should build and env without filesystem type and ensa version for other clusters', () => {
+ it('should build and env for HANA clusters', () => {
const payload = {
cluster_type: HANA_SCALE_UP,
provider: faker.color.rgb(),
target_type: faker.hacker.noun(),
ensa_version: faker.number.int(),
filesystem_type: faker.animal.dog(),
+ architecture_type: faker.hacker.noun(),
};
+ const expectedPayload = pick(payload, [
+ 'cluster_type',
+ 'provider',
+ 'target_type',
+ 'architecture_type',
+ ]);
+
const env = buildEnv(payload);
- expect(has(env, 'ensa_version')).toBe(false);
- expect(has(env, 'filesystem_type')).toBe(false);
+ expect(env).toEqual(expectedPayload);
});
});
diff --git a/assets/js/lib/model/clusters.js b/assets/js/lib/model/clusters.js
index 6c78a3ff51..e9d9af0533 100644
--- a/assets/js/lib/model/clusters.js
+++ b/assets/js/lib/model/clusters.js
@@ -16,6 +16,9 @@ const clusterTypeLabels = {
export const getClusterTypeLabel = (type) =>
clusterTypeLabels[type] || 'Unknown';
+export const ANGI_ARCHITECTURE = 'angi';
+export const CLASSIC_ARCHITECTURE = 'classic';
+
export const FS_TYPE_RESOURCE_MANAGED = 'resource_managed';
export const FS_TYPE_SIMPLE_MOUNT = 'simple_mount';
export const FS_TYPE_MIXED = 'mixed_fs_types';
diff --git a/assets/js/lib/test-utils/factories/clusters.js b/assets/js/lib/test-utils/factories/clusters.js
index 5f4bab1bf3..7889b6258f 100644
--- a/assets/js/lib/test-utils/factories/clusters.js
+++ b/assets/js/lib/test-utils/factories/clusters.js
@@ -17,6 +17,9 @@ const hanaStatus = () => faker.helpers.arrayElement(['Primary', 'Failed']);
const ascsErsRole = () => faker.helpers.arrayElement(['ascs', 'ers']);
+const hanaArchitectureTypeEnum = () =>
+ faker.helpers.arrayElement(['classic', 'angi']);
+
export const sbdDevicesFactory = Factory.define(() => ({
device: faker.system.filePath(),
status: faker.helpers.arrayElement(['healthy', 'unhealthy']),
@@ -72,6 +75,7 @@ export const hanaClusterDetailsFactory = Factory.define(() => {
system_replication_mode: 'sync',
system_replication_operation_mode: 'logreplay',
maintenance_mode: false,
+ architecture_type: hanaArchitectureTypeEnum(),
};
});
diff --git a/assets/js/pages/ClusterDetails/ClusterDetailsPage.jsx b/assets/js/pages/ClusterDetails/ClusterDetailsPage.jsx
index 4902eacf11..d6a808c6ed 100644
--- a/assets/js/pages/ClusterDetails/ClusterDetailsPage.jsx
+++ b/assets/js/pages/ClusterDetails/ClusterDetailsPage.jsx
@@ -32,6 +32,7 @@ export function ClusterDetailsPage() {
const provider = get(cluster, 'provider');
const type = get(cluster, 'type');
+ const architectureType = get(cluster, 'details.architecture_type');
const catalog = useSelector(getCatalog());
@@ -51,13 +52,14 @@ export function ClusterDetailsPage() {
cluster_type: type,
ensa_version: ensaVersion,
filesystem_type: filesystemType,
+ architecture_type: architectureType,
});
if (provider && type) {
dispatch(updateCatalog(env));
dispatch(updateLastExecution(clusterID));
}
- }, [dispatch, provider, type, ensaVersion, filesystemType]);
+ }, [dispatch, provider, type, ensaVersion, filesystemType, architectureType]);
const clusterHosts = useSelector((state) =>
getClusterHosts(state, clusterID)
diff --git a/assets/js/pages/ClusterDetails/HanaClusterDetails.jsx b/assets/js/pages/ClusterDetails/HanaClusterDetails.jsx
index c9861ae9f3..b9b3bf1bdd 100644
--- a/assets/js/pages/ClusterDetails/HanaClusterDetails.jsx
+++ b/assets/js/pages/ClusterDetails/HanaClusterDetails.jsx
@@ -2,7 +2,6 @@ import React from 'react';
import { get, capitalize, sortBy } from 'lodash';
import classNames from 'classnames';
-import { getClusterTypeLabel } from '@lib/model/clusters';
import { RUNNING_STATES } from '@state/lastExecutions';
import BackButton from '@common/BackButton';
@@ -10,6 +9,7 @@ import Button from '@common/Button';
import ListView from '@common/ListView';
import PageHeader from '@common/PageHeader';
import ProviderLabel from '@common/ProviderLabel';
+import ClusterTypeLabel from '@common/ClusterTypeLabel';
import SapSystemLink from '@common/SapSystemLink';
import Tooltip from '@common/Tooltip';
import DisabledGuard from '@common/DisabledGuard';
@@ -162,7 +162,13 @@ function HanaClusterDetails({
},
{
title: 'Cluster type',
- content: getClusterTypeLabel(clusterType),
+ content: clusterType,
+ render: (content) => (
+
+ ),
},
{
title: 'Cluster maintenance',
diff --git a/assets/js/pages/ClusterDetails/HanaClusterDetails.stories.jsx b/assets/js/pages/ClusterDetails/HanaClusterDetails.stories.jsx
index 741b57d17f..5d77cb02de 100644
--- a/assets/js/pages/ClusterDetails/HanaClusterDetails.stories.jsx
+++ b/assets/js/pages/ClusterDetails/HanaClusterDetails.stories.jsx
@@ -27,11 +27,15 @@ const {
provider,
cib_last_written: cibLastWritten,
details,
-} = clusterFactory.build({ type: 'hana_scale_up' });
+} = clusterFactory.build({
+ type: 'hana_scale_up',
+ details: { architecture_type: 'classic' },
+});
const scaleOutSites = hanaClusterSiteFactory.buildList(2);
const scaleOutDetails = hanaClusterDetailsFactory.build({
+ architecture_type: 'classic',
sites: scaleOutSites,
nodes: [
hanaClusterDetailsNodesFactory.build({
@@ -213,3 +217,13 @@ export const WithNoSBDDevices = {
},
},
};
+
+export const AngiArchitecture = {
+ args: {
+ ...Hana.args,
+ details: {
+ ...Hana.args.details,
+ architecture_type: 'angi',
+ },
+ },
+};
diff --git a/assets/js/pages/ClusterDetails/HanaClusterDetails.test.jsx b/assets/js/pages/ClusterDetails/HanaClusterDetails.test.jsx
index 580fd7bb85..4aa7b28181 100644
--- a/assets/js/pages/ClusterDetails/HanaClusterDetails.test.jsx
+++ b/assets/js/pages/ClusterDetails/HanaClusterDetails.test.jsx
@@ -466,6 +466,53 @@ describe('HanaClusterDetails component', () => {
}
);
+ it.each([
+ { arch: 'angi', tooltip: 'Angi architecture' },
+ { arch: 'classic', tooltip: 'Classic architecture' },
+ ])(
+ 'should show cluster type with $arch architecture',
+ async ({ arch, tooltip }) => {
+ const user = userEvent.setup();
+
+ const {
+ clusterID,
+ clusterName,
+ cib_last_written: cibLastWritten,
+ type: clusterType,
+ sid,
+ provider,
+ details,
+ } = clusterFactory.build({
+ type: 'hana_scale_up',
+ details: { architecture_type: arch },
+ });
+
+ const hosts = hostFactory.buildList(2, { cluster_id: clusterID });
+
+ renderWithRouter(
+
+ );
+
+ const icon = screen.getByText('HANA Scale Up').children.item(0);
+ await user.hover(icon);
+ expect(screen.getByText(tooltip, { exact: false })).toBeInTheDocument();
+ }
+ );
+
describe('forbidden actions', () => {
it('should disable the check execution button when the user abilities are not compatible', async () => {
const user = userEvent.setup();
diff --git a/assets/js/pages/ClusterSettingsPage/ClusterSettingsPage.jsx b/assets/js/pages/ClusterSettingsPage/ClusterSettingsPage.jsx
index 3bcd0f28d9..0ac2fc2946 100644
--- a/assets/js/pages/ClusterSettingsPage/ClusterSettingsPage.jsx
+++ b/assets/js/pages/ClusterSettingsPage/ClusterSettingsPage.jsx
@@ -78,6 +78,7 @@ function ClusterSettingsPage() {
const provider = get(cluster, 'provider');
const type = get(cluster, 'type');
+ const architectureType = get(cluster, 'details.architecture_type');
const refreshCatalog = () => {
const env = buildEnv({
@@ -86,6 +87,7 @@ function ClusterSettingsPage() {
cluster_type: type,
ensa_version: ensaVersion,
filesystem_type: filesystemType,
+ architecture_type: architectureType,
});
dispatch(updateCatalog(env));
@@ -130,7 +132,11 @@ function ClusterSettingsPage() {
onStartExecution={requestChecksExecution}
/>
{catalogBanner[provider]}
-
+
+
);
case TARGET_HOST:
return (