Skip to content

Commit

Permalink
Merge pull request #6139 from rhamilto/add-workers-to-graph
Browse files Browse the repository at this point in the history
Continue to show Workers progress bar if not 100% when CV is updated
  • Loading branch information
openshift-merge-robot committed Jul 29, 2020
2 parents ea81fdc + 483af9b commit e2141df
Show file tree
Hide file tree
Showing 4 changed files with 156 additions and 65 deletions.
1 change: 1 addition & 0 deletions frontend/.eslintrc.js
Expand Up @@ -6,6 +6,7 @@ module.exports = {
browser: true,
es6: true,
jasmine: true,
jest: true,
},
extends: [
'eslint:recommended',
Expand Down
6 changes: 6 additions & 0 deletions frontend/__tests__/components/cluster-settings.spec.tsx
Expand Up @@ -2,6 +2,7 @@ import * as React from 'react';
import { Link } from 'react-router-dom';
import { shallow, mount, ShallowWrapper } from 'enzyme';

import { useK8sWatchResource } from '@console/internal/components/utils/k8s-watch-hook';
import { clusterVersionProps } from '../../__mocks__/clusterVersinMock';
import {
ClusterSettingsPage,
Expand All @@ -17,12 +18,17 @@ import { GlobalConfigPage } from '../../public/components/cluster-settings/globa
import { Firehose, HorizontalNav, ResourceLink, Timestamp } from '../../public/components/utils';
import { AddCircleOIcon } from '@patternfly/react-icons';

jest.mock('@console/internal/components/utils/k8s-watch-hook', () => ({
useK8sWatchResource: jest.fn(),
}));

describe('Cluster Settings page', () => {
let wrapper: ShallowWrapper<any>;
const match = { url: '/settings/cluster', params: {}, isExact: true, path: '/settings/cluster' };

beforeEach(() => {
wrapper = shallow(<ClusterSettingsPage match={match} />);
(useK8sWatchResource as jest.Mock).mockReturnValueOnce([[], true]);
});

it('should render ClusterSettingsPage component', () => {
Expand Down
205 changes: 140 additions & 65 deletions frontend/public/components/cluster-settings/cluster-settings.tsx
Expand Up @@ -13,8 +13,12 @@ import {
import { Link } from 'react-router-dom';
import { HashLink } from 'react-router-hash-link';

import { AddCircleOIcon, SyncAltIcon, PencilAltIcon } from '@patternfly/react-icons';
import { BlueInfoCircleIcon } from '@console/shared/src/components/status';
import {
AddCircleOIcon,
OutlinedQuestionCircleIcon,
PencilAltIcon,
SyncAltIcon,
} from '@patternfly/react-icons';
import { removeQueryArgument } from '@console/internal/components/utils/router';

import { ClusterOperatorPage } from './cluster-operator';
Expand Down Expand Up @@ -450,6 +454,68 @@ const ChannelVersionDot: React.FC<ChannelVersionDotProps> = ({ current, version
);
};

const UpdatesBar: React.FC<UpdatesBarProps> = ({ children }) => {
return <div className="co-cluster-settings__updates-bar">{children}</div>;
};

const UpdatesGroup: React.FC<UpdatesGroupProps> = ({ children, divided }) => {
return (
<div
className={classNames('co-cluster-settings__updates-group', {
'co-cluster-settings__updates-group--divided': divided,
})}
>
{children}
</div>
);
};

const UpdatesProgress: React.FC<UpdatesProgressProps> = ({ children }) => {
return <div className="co-cluster-settings__updates-progress">{children}</div>;
};

const UpdatesType: React.FC<UpdatesTypeProps> = ({ children }) => {
return <div className="co-cluster-settings__updates-type">{children}</div>;
};

const WorkerNodes: React.FC<WorkerNodesProps> = ({
percentWorkerNodes,
totalWorkerNodes,
updatedWorkerNodes,
}) => {
return (
<UpdatesGroup divided>
<UpdatesType>
<Link to="/k8s/cluster/nodes?rowFilter-node-role=worker">Worker Nodes</Link>
<Popover
bodyContent={
<>
Worker nodes may continue to update after the update of master nodes and operators are
complete.
</>
}
>
<Button
variant="plain"
aria-label="Help"
className="co-help-popover-button co-help-popover-button--space-l"
>
<OutlinedQuestionCircleIcon />
</Button>
</Popover>
</UpdatesType>
<UpdatesBar>
<Progress
title={`${updatedWorkerNodes} of ${totalWorkerNodes}`}
value={!_.isNaN(percentWorkerNodes) ? percentWorkerNodes : null}
size={ProgressSize.sm}
variant={percentWorkerNodes === 100 ? ProgressVariant.success : null}
/>
</UpdatesBar>
</UpdatesGroup>
);
};

const UpdatesGraph: React.FC<UpdatesGraphProps> = ({ cv }) => {
const availableUpdates = getSortedUpdates(cv);
const lastVersion = getLastCompletedUpdate(cv);
Expand Down Expand Up @@ -512,26 +578,6 @@ const UpdatesGraph: React.FC<UpdatesGraphProps> = ({ cv }) => {
);
};

const UpdatesBar: React.FC<UpdatesBarProps> = ({ children }) => {
return <div className="co-cluster-settings__updates-bar">{children}</div>;
};

const UpdatesGroup: React.FC<UpdatesGroupProps> = ({ children, divided }) => {
return (
<div
className={classNames('co-cluster-settings__updates-group', {
'co-cluster-settings__updates-group--divided': divided,
})}
>
{children}
</div>
);
};

const UpdatesType: React.FC<UpdatesTypeProps> = ({ children }) => {
return <div className="co-cluster-settings__updates-type">{children}</div>;
};

const ClusterOperatorsResource: WatchK8sResource = {
isList: true,
kind: referenceForModel(ClusterOperatorModel),
Expand All @@ -542,13 +588,16 @@ const MachineConfigPoolsResource: WatchK8sResource = {
kind: referenceForModel(MachineConfigPoolModel),
};

const UpdateProgress: React.FC<UpdateProgressProps> = ({ cv }) => {
const UpdateInProgress: React.FC<UpdateInProgressProps> = ({
desiredVersion,
machineConfigPools,
percentWorkerNodes,
workerMachinePoolConfig,
totalWorkerNodes,
updatedWorkerNodes,
updateStartedTime,
}) => {
const [clusterOperators] = useK8sWatchResource<ClusterOperator[]>(ClusterOperatorsResource);
const [machineConfigPools] = useK8sWatchResource<MachineConfigPoolKind[]>(
MachineConfigPoolsResource,
);
const desiredVersion = getDesiredClusterVersion(cv);
const updateStartedTime = getStartedTimeForCVDesiredVersion(cv, desiredVersion);
const totalOperatorsCount = clusterOperators?.length || 0;
const updatedOperatorsCount = getUpdatedOperatorsCount(clusterOperators, desiredVersion);
const percentOperators = calculatePercentage(updatedOperatorsCount, totalOperatorsCount);
Expand All @@ -560,17 +609,9 @@ const UpdateProgress: React.FC<UpdateProgressProps> = ({ cv }) => {
? masterMachinePoolConfig?.status?.updatedMachineCount
: 0;
const percentMasterNodes = calculatePercentage(updatedMasterNodes, totalMasterNodes);
const workerMachinePoolConfig = getMCPByName(machineConfigPools, 'worker');
const workerMachinePoolConfigUpdatingTime = getUpdatingTimeForMCP(workerMachinePoolConfig);
const totalWorkerNodes = workerMachinePoolConfig?.status?.machineCount || 0;
const updatedWorkerNodes =
workerMachinePoolConfigUpdatingTime > updateStartedTime
? workerMachinePoolConfig?.status?.updatedMachineCount
: 0;
const percentWorkerNodes = calculatePercentage(updatedWorkerNodes, totalWorkerNodes);

return (
<div className="co-cluster-settings__updates-progress">
<UpdatesProgress>
<UpdatesGroup>
<UpdatesType>
<Link to="/settings/cluster/clusteroperators">Cluster Operators</Link>
Expand Down Expand Up @@ -600,31 +641,13 @@ const UpdateProgress: React.FC<UpdateProgressProps> = ({ cv }) => {
</UpdatesGroup>
)}
{workerMachinePoolConfig && (
<UpdatesGroup divided>
<UpdatesType>
<Link to="/k8s/cluster/nodes?rowFilter-node-role=worker">Worker Nodes</Link>
<Tooltip
content={
<>
Worker nodes will continue to update after the update of master nodes and
operators are complete.
</>
}
>
<BlueInfoCircleIcon className="co-icon-space-l" />
</Tooltip>
</UpdatesType>
<UpdatesBar>
<Progress
title={`${updatedWorkerNodes} of ${totalWorkerNodes}`}
value={!_.isNaN(percentWorkerNodes) ? percentWorkerNodes : null}
size={ProgressSize.sm}
variant={percentWorkerNodes === 100 ? ProgressVariant.success : null}
/>
</UpdatesBar>
</UpdatesGroup>
<WorkerNodes
percentWorkerNodes={percentWorkerNodes}
totalWorkerNodes={totalWorkerNodes}
updatedWorkerNodes={updatedWorkerNodes}
/>
)}
</div>
</UpdatesProgress>
);
};

Expand All @@ -645,6 +668,19 @@ export const ClusterVersionDetailsTable: React.FC<ClusterVersionDetailsTableProp
verb: 'patch',
name: cv.metadata.name,
});
const [machineConfigPools] = useK8sWatchResource<MachineConfigPoolKind[]>(
MachineConfigPoolsResource,
);
const desiredVersion = getDesiredClusterVersion(cv);
const updateStartedTime = getStartedTimeForCVDesiredVersion(cv, desiredVersion);
const workerMachinePoolConfig = getMCPByName(machineConfigPools, 'worker');
const workerMachinePoolConfigUpdatingTime = getUpdatingTimeForMCP(workerMachinePoolConfig);
const totalWorkerNodes = workerMachinePoolConfig?.status?.machineCount || 0;
const updatedWorkerNodes =
workerMachinePoolConfigUpdatingTime > updateStartedTime
? workerMachinePoolConfig?.status?.updatedMachineCount
: 0;
const percentWorkerNodes = calculatePercentage(updatedWorkerNodes, totalWorkerNodes);
if (new URLSearchParams(window.location.search).has('showVersions')) {
clusterUpdateModal({ cv })
.then(() => removeQueryArgument('showVersions'))
Expand Down Expand Up @@ -695,10 +731,33 @@ export const ClusterVersionDetailsTable: React.FC<ClusterVersionDetailsTableProp
</div>
</div>
{(status === ClusterUpdateStatus.UpToDate ||
status === ClusterUpdateStatus.UpdatesAvailable) && <UpdatesGraph cv={cv} />}
status === ClusterUpdateStatus.UpdatesAvailable) && (
<>
<UpdatesGraph cv={cv} />
{workerMachinePoolConfig && percentWorkerNodes < 100 && (
<UpdatesProgress>
<WorkerNodes
percentWorkerNodes={percentWorkerNodes}
totalWorkerNodes={totalWorkerNodes}
updatedWorkerNodes={updatedWorkerNodes}
/>
</UpdatesProgress>
)}
</>
)}
{(status === ClusterUpdateStatus.Failing ||
status === ClusterUpdateStatus.UpdatingAndFailing ||
status === ClusterUpdateStatus.Updating) && <UpdateProgress cv={cv} />}
status === ClusterUpdateStatus.Updating) && (
<UpdateInProgress
desiredVersion={desiredVersion}
machineConfigPools={machineConfigPools}
updateStartedTime={updateStartedTime}
workerMachinePoolConfig={workerMachinePoolConfig}
percentWorkerNodes={percentWorkerNodes}
totalWorkerNodes={totalWorkerNodes}
updatedWorkerNodes={updatedWorkerNodes}
/>
)}
</div>
</div>
</div>
Expand Down Expand Up @@ -922,12 +981,28 @@ type UpdatesGroupProps = {
divided?: boolean;
};

type UpdatesProgressProps = {
children: React.ReactNode;
};

type UpdatesTypeProps = {
children: React.ReactNode;
};

type UpdateProgressProps = {
cv: ClusterVersionKind;
type WorkerNodesProps = {
percentWorkerNodes: number;
totalWorkerNodes: number;
updatedWorkerNodes: number;
};

type UpdateInProgressProps = {
desiredVersion: string;
machineConfigPools: MachineConfigPoolKind[];
percentWorkerNodes: number;
workerMachinePoolConfig: MachineConfigPoolKind;
totalWorkerNodes: number;
updatedWorkerNodes: number;
updateStartedTime: string;
};

type ClusterVersionDetailsTableProps = {
Expand Down
9 changes: 9 additions & 0 deletions frontend/public/style/_common.scss
Expand Up @@ -184,6 +184,15 @@ $co-external-link-padding-right: 15px;
margin-bottom: 20px;
}

// PatternFly lacks the ability to have a plain button variant without padding
.co-help-popover-button {
padding: 0 !important;

&--space-l {
margin-left: 0.35em;
}
}

.co-help-text {
color: $color-pf-black-600;
}
Expand Down

0 comments on commit e2141df

Please sign in to comment.