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

Angi architecture frontend #2766

Merged
merged 3 commits into from
Jul 11, 2024
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
14 changes: 9 additions & 5 deletions assets/js/common/ClusterInfoBox/ClusterInfoBox.jsx
Original file line number Diff line number Diff line change
@@ -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 (
<div className="tn-cluster-details w-full my-6 mr-4 bg-white shadow rounded-lg px-8 py-4">
<ListView
Expand All @@ -16,7 +14,13 @@ function ClusterInfoBox({ haScenario, provider }) {
data={[
{
title: 'HA Scenario',
content: getClusterTypeLabel(haScenario),
content: haScenario,
render: (content) => (
<ClusterTypeLabel
clusterType={content}
architectureType={architectureType}
/>
),
},
{
title: 'Provider',
Expand Down
34 changes: 33 additions & 1 deletion assets/js/common/ClusterInfoBox/ClusterInfoBox.test.jsx
Original file line number Diff line number Diff line change
@@ -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', () => {
Expand Down Expand Up @@ -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(
<ClusterInfoBox
haScenario="hana_scale_up"
provider="azure"
architectureType={architectureType}
/>
);

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(<ClusterInfoBox haScenario="ascs_ers" provider="azure" />);
expect(screen.queryByTestId('eos-svg-component')).not.toBeInTheDocument();
});
});
56 changes: 56 additions & 0 deletions assets/js/common/ClusterTypeLabel/ClusterTypeLabel.jsx
Original file line number Diff line number Diff line change
@@ -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{' '}
<Link
to={MIGRATION_URL}
className="text-jungle-green-500 hover:opacity-75"
target="_blank"
>
migration
</Link>{' '}
to Angi architecture
</>
);

const icons = {
[ANGI_ARCHITECTURE]: (
<Tooltip content={ANGI_TOOLTIP_MESSAGE} place="bottom">
<EOS_STAR className="mr-1 fill-jungle-green-500" />
</Tooltip>
),
[CLASSIC_ARCHITECTURE]: (
<Tooltip
content={CLASSIC_TOOLTIP_MESSAGE}
place="bottom"
className="whitespace-pre"
>
<EOS_INFO_OUTLINED className="mr-1" />
</Tooltip>
),
};

function ClusterTypeLabel({ clusterType, architectureType }) {
return (
<span className="group flex items-center relative">
{icons[architectureType]}
{getClusterTypeLabel(clusterType)}
</span>
);
}

export default ClusterTypeLabel;
61 changes: 61 additions & 0 deletions assets/js/common/ClusterTypeLabel/ClusterTypeLabel.test.jsx
Original file line number Diff line number Diff line change
@@ -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(
<ClusterTypeLabel
clusterType={clusterType}
architectureType="classic"
/>
);
expect(screen.getByText(label)).toBeVisible();
}
);

it('should display a green star icon when the cluster uses angi architecture', async () => {
const user = userEvent.setup();

render(
<ClusterTypeLabel clusterType="hana_scale_up" architectureType="angi" />
);
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(
<ClusterTypeLabel
clusterType="hana_scale_up"
architectureType="classic"
/>
);
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();
});
});
3 changes: 3 additions & 0 deletions assets/js/common/ClusterTypeLabel/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ClusterTypeLabel from './ClusterTypeLabel';

export default ClusterTypeLabel;
2 changes: 2 additions & 0 deletions assets/js/lib/checks/env.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export const buildEnv = ({
cluster_type,
ensa_version,
filesystem_type,
architecture_type,
}) => {
switch (cluster_type) {
case ASCS_ERS: {
Expand All @@ -22,6 +23,7 @@ export const buildEnv = ({
provider,
target_type,
cluster_type,
architecture_type,
};
}
}
Expand Down
28 changes: 22 additions & 6 deletions assets/js/lib/checks/env.test.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
3 changes: 3 additions & 0 deletions assets/js/lib/model/clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';
4 changes: 4 additions & 0 deletions assets/js/lib/test-utils/factories/clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -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']),
Expand Down Expand Up @@ -72,6 +75,7 @@ export const hanaClusterDetailsFactory = Factory.define(() => {
system_replication_mode: 'sync',
system_replication_operation_mode: 'logreplay',
maintenance_mode: false,
architecture_type: hanaArchitectureTypeEnum(),
};
});

Expand Down
4 changes: 3 additions & 1 deletion assets/js/pages/ClusterDetails/ClusterDetailsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -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)
Expand Down
10 changes: 8 additions & 2 deletions assets/js/pages/ClusterDetails/HanaClusterDetails.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@ 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';
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';
Expand Down Expand Up @@ -162,7 +162,13 @@ function HanaClusterDetails({
},
{
title: 'Cluster type',
content: getClusterTypeLabel(clusterType),
content: clusterType,
render: (content) => (
<ClusterTypeLabel
clusterType={content}
architectureType={details.architecture_type}
/>
),
},
{
title: 'Cluster maintenance',
Expand Down
16 changes: 15 additions & 1 deletion assets/js/pages/ClusterDetails/HanaClusterDetails.stories.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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({
Expand Down Expand Up @@ -213,3 +217,13 @@ export const WithNoSBDDevices = {
},
},
};

export const AngiArchitecture = {
args: {
...Hana.args,
details: {
...Hana.args.details,
architecture_type: 'angi',
},
},
};
Loading
Loading