Skip to content

Commit

Permalink
Display cluster details by type (#1456)
Browse files Browse the repository at this point in the history
* Update ClusterLink for ascs/ers cluster types

* Rename ClusterDetails to HanaClusterDetails

* Choose between details view depending on the type

* Add basic test to see correct cluster type is used

* Update HANA storybook story to match changes

* Move cluster types constant to model lib
  • Loading branch information
arbulu89 authored May 25, 2023
1 parent 7c9a623 commit 05ba92c
Show file tree
Hide file tree
Showing 8 changed files with 150 additions and 66 deletions.
65 changes: 31 additions & 34 deletions assets/js/components/ClusterDetails/ClusterDetailsPage.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,13 @@ import React, { useEffect } from 'react';
import { useNavigate, useParams } from 'react-router-dom';
import { useSelector, useDispatch } from 'react-redux';

import {
getCluster,
getClusterHosts,
getClusterHostIDs,
} from '@state/selectors/cluster';
import { getCluster, getClusterHosts } from '@state/selectors/cluster';
import {
updateLastExecution,
executionRequested,
} from '@state/actions/lastExecutions';
import { getLastExecution } from '@state/selectors/lastExecutions';
import ClusterDetails from './ClusterDetails';
import HanaClusterDetails from './HanaClusterDetails';
import { getClusterName } from '../ClusterLink';

export function ClusterDetailsPage() {
Expand All @@ -23,7 +19,6 @@ export function ClusterDetailsPage() {

const dispatch = useDispatch();
const lastExecution = useSelector(getLastExecution(clusterID));
const hosts = useSelector(getClusterHostIDs(clusterID));
useEffect(() => {
dispatch(updateLastExecution(clusterID));
}, [dispatch]);
Expand All @@ -34,33 +29,35 @@ export function ClusterDetailsPage() {
return <div>Loading...</div>;
}

const renderedNodes = cluster.details?.nodes?.map((node) => ({
...node,
...clusterHosts.find(({ hostname }) => hostname === node.name),
}));

const hasSelectedChecks = cluster.selected_checks.length > 0;

return (
<ClusterDetails
clusterID={clusterID}
clusterName={getClusterName(cluster)}
selectedChecks={cluster.selected_checks}
hasSelectedChecks={hasSelectedChecks}
hosts={hosts}
clusterType={cluster.type}
cibLastWritten={cluster.cib_last_written}
sid={cluster.sid}
provider={cluster.provider}
clusterNodes={renderedNodes}
details={cluster.details}
lastExecution={lastExecution}
onStartExecution={(_, hostList, checks, navigateFunction) =>
dispatch(
executionRequested(clusterID, hostList, checks, navigateFunction)
)
}
navigate={navigate}
/>
);
switch (cluster.type) {
case 'hana_scale_up':
case 'hana_scale_out':
return (
<HanaClusterDetails
clusterID={clusterID}
clusterName={getClusterName(cluster)}
selectedChecks={cluster.selected_checks}
hasSelectedChecks={hasSelectedChecks}
hosts={clusterHosts}
clusterType={cluster.type}
cibLastWritten={cluster.cib_last_written}
sid={cluster.sid}
provider={cluster.provider}
details={cluster.details}
lastExecution={lastExecution}
onStartExecution={(_, hostList, checks, navigateFunction) =>
dispatch(
executionRequested(clusterID, hostList, checks, navigateFunction)
)
}
navigate={navigate}
/>
);
case 'ascs_ers':
return <div>ASCS/ERS</div>;
default:
return <div>Unknown cluster type</div>;
}
}
41 changes: 41 additions & 0 deletions assets/js/components/ClusterDetails/ClusterDetailsPage.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import React from 'react';

import { screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { withState, renderWithRouterMatch } from '@lib/test-utils';

import { clusterFactory } from '@lib/test-utils/factories';

import { ClusterDetailsPage } from './ClusterDetailsPage';

describe('ClusterDetails ClusterDetailsPage component', () => {
it.each([
{ type: 'hana_scale_up', label: 'HANA scale-up' },
{ type: 'ascs_ers', label: 'ASCS/ERS' },
{ type: 'unknwon', label: 'Unknown cluster type' },
])(
'should display the $type details based on cluster type',
({ type, label }) => {
const cluster = clusterFactory.build({ type });
const initialState = {
clustersList: { clusters: [cluster] },
hostsList: { hosts: [] },
lastExecutions: {
[cluster.id]: { data: null, loading: false, error: null },
},
};

const [statefulClusterDetailsPage, _] = withState(
<ClusterDetailsPage />,
initialState
);

renderWithRouterMatch(statefulClusterDetailsPage, {
path: 'clusters/:clusterID',
route: `/clusters/${cluster.id}`,
});

expect(screen.getByText(label)).toBeInTheDocument();
}
);
});
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ const siteDetailsConfig = {
],
};

function ClusterDetails({
function HanaClusterDetails({
clusterID,
clusterName,
selectedChecks,
Expand All @@ -64,12 +64,16 @@ function ClusterDetails({
cibLastWritten,
sid,
provider,
clusterNodes,
details,
lastExecution,
onStartExecution,
navigate,
}) {
const enrichedNodes = details?.nodes?.map((node) => ({
...node,
...hosts.find(({ hostname }) => hostname === node.name),
}));

return (
<div>
<BackButton url="/clusters">Back to Clusters</BackButton>
Expand Down Expand Up @@ -106,7 +110,7 @@ function ClusterDetails({
cssClasses="flex rounded relative ml-0.5 disabled:bg-slate-50 disabled:text-slate-500 disabled:border-gray-400"
clusterId={clusterID}
disabled={!hasSelectedChecks}
hosts={hosts}
hosts={hosts.map(({ id }) => id)}
checks={selectedChecks}
onStartExecution={onStartExecution}
>
Expand Down Expand Up @@ -197,7 +201,7 @@ function ClusterDetails({
<Table
className="tn-site-table"
config={siteDetailsConfig}
data={clusterNodes.filter(({ site }) => site === siteName)}
data={enrichedNodes.filter(({ site }) => site === siteName)}
/>
</div>
))}
Expand All @@ -207,4 +211,4 @@ function ClusterDetails({
);
}

export default ClusterDetails;
export default HanaClusterDetails;
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
hostFactory,
} from '@lib/test-utils/factories';

import ClusterDetails from './ClusterDetails';
import HanaClusterDetails from './HanaClusterDetails';

const {
id: clusterID,
Expand All @@ -34,14 +34,9 @@ const hosts = [
hostFactory.build({ hostname: details.nodes[1].name }),
];

const clusterNodes = details.nodes.map((node) => ({
...node,
...hosts.find(({ hostname }) => hostname === node.name),
}));

export default {
title: 'ClusterDetails',
components: ClusterDetails,
title: 'HanaClusterDetails',
components: HanaClusterDetails,
decorators: [
(Story) => (
<MemoryRouter>
Expand All @@ -63,20 +58,19 @@ export const Hana = {
clusterName,
selectedChecks,
hasSelectedChecks: true,
hosts: hosts.map(({ id: hostID }) => hostID),
hosts,
clusterType,
cibLastWritten,
sid,
provider,
clusterNodes,
details,
lastExecution,
onStartExecution: () => {},
navigate: () => {},
},
render: (args) => (
<ContainerWrapper>
<ClusterDetails {...args} />
<HanaClusterDetails {...args} />
</ContainerWrapper>
),
};
4 changes: 3 additions & 1 deletion assets/js/components/ClusterLink.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import React from 'react';
import { Link } from 'react-router-dom';
import classNames from 'classnames';

import { CLUSTER_TYPES } from '@lib/model';

export const getClusterName = (cluster) => cluster?.name || cluster?.id;

function ClusterLink({ cluster }) {
Expand All @@ -11,7 +13,7 @@ function ClusterLink({ cluster }) {
'truncate w-32 inline-block align-middle'
);

if (cluster?.type === 'hana_scale_up' || cluster?.type === 'hana_scale_out') {
if (CLUSTER_TYPES.includes(cluster?.type)) {
return (
<Link
className="text-jungle-green-500 hover:opacity-75"
Expand Down
42 changes: 42 additions & 0 deletions assets/js/components/ClusterLink.test.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from 'react';

import { screen } from '@testing-library/react';
import '@testing-library/jest-dom';
import { renderWithRouter } from '@lib/test-utils';

import { clusterFactory } from '@lib/test-utils/factories';

import ClusterLink from './ClusterLink';

describe('ClusterLink component', () => {
it.each(['hana_scale_up', 'hana_scale_out', 'ascs_ers'])(
'should render a link if the cluster type is %s',
(type) => {
const cluster = clusterFactory.build({ type });

renderWithRouter(<ClusterLink cluster={cluster} />);

const link = screen.getByRole('link');

expect(link).toHaveTextContent(cluster.name);
expect(link).toHaveAttribute('href', `/clusters/${cluster.id}`);
}
);

it('should show a simple text if the cluster type is unknown', () => {
const cluster = clusterFactory.build({ type: 'unknown' });

renderWithRouter(<ClusterLink cluster={cluster} />);

expect(screen.queryByRole('link')).not.toBeInTheDocument();
expect(screen.getByText(cluster.name)).toBeInTheDocument();
});

it('should show the cluster ID if the name is not available', () => {
const cluster = clusterFactory.build({ name: '' });

renderWithRouter(<ClusterLink cluster={cluster} />);

expect(screen.getByText(cluster.id)).toBeInTheDocument();
});
});
2 changes: 2 additions & 0 deletions assets/js/lib/model/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export const isValidTargetType = (targetType) =>

export const UNKNOWN_PROVIDER = 'unknown';
export const VMWARE_PROVIDER = 'vmware';

export const CLUSTER_TYPES = ['hana_scale_up', 'hana_scale_out', 'ascs_ers'];
32 changes: 17 additions & 15 deletions assets/js/lib/test-utils/factories/clusters.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,11 @@ const clusterTypeEnum = () =>

const hanaStatus = () => faker.helpers.arrayElement(['Primary', 'Failed']);

export const sbdDevicesFactory = Factory.define(() => ({
device: faker.system.filePath(),
status: faker.helpers.arrayElement(['healthy', 'unhealthy']),
}));

export const clusterResourceFactory = Factory.define(() => ({
id: faker.datatype.uuid(),
role: faker.animal.bear(),
Expand All @@ -18,7 +23,7 @@ export const clusterResourceFactory = Factory.define(() => ({
fail_count: faker.datatype.number(),
}));

export const clusterDetailsNodesFactory = Factory.define(() => ({
export const hanaClusterDetailsNodesFactory = Factory.define(() => ({
name: faker.animal.dog(),
site: faker.address.city(),
virtual_ip: faker.internet.ip(),
Expand All @@ -33,12 +38,18 @@ export const clusterDetailsNodesFactory = Factory.define(() => ({
resources: clusterResourceFactory.buildList(5),
}));

export const sbdDevicesFactory = Factory.define(() => ({
device: faker.system.filePath(),
status: faker.helpers.arrayElement(['healthy', 'unhealthy']),
export const hanaClusterDetailsFactory = Factory.define(() => ({
fencing_type: 'external/sbd',
nodes: hanaClusterDetailsNodesFactory.buildList(2),
sbd_devices: sbdDevicesFactory.buildList(3),
secondary_sync_state: 'SOK',
sr_health_state: '4',
stopped_resources: clusterResourceFactory.buildList(2),
system_replication_mode: 'sync',
system_replication_operation_mode: 'logreplay',
}));

export const clusterFactory = Factory.define(({ sequence }) => ({
export const clusterFactory = Factory.define(({ sequence, params }) => ({
id: faker.datatype.uuid(),
name: `${faker.name.firstName()}_${sequence}`,
sid: faker.random.alphaNumeric(3, { casing: 'upper' }),
Expand All @@ -49,14 +60,5 @@ export const clusterFactory = Factory.define(({ sequence }) => ({
selected_checks: [],
provider: cloudProviderEnum(),
cib_last_written: day(faker.date.recent()).format(),
details: {
fencing_type: 'external/sbd',
nodes: clusterDetailsNodesFactory.buildList(2),
sbd_devices: sbdDevicesFactory.buildList(3),
secondary_sync_state: 'SOK',
sr_health_state: '4',
stopped_resources: clusterResourceFactory.buildList(2),
system_replication_mode: 'sync',
system_replication_operation_mode: 'logreplay',
},
details: hanaClusterDetailsFactory.build(params.details),
}));

0 comments on commit 05ba92c

Please sign in to comment.