Skip to content

Commit

Permalink
Bug 1666231 - Update Cluster Settings page for ClusterVersion API cha…
Browse files Browse the repository at this point in the history
…nges

Change "Current Version" label to "Desired Version," which is more
accurate. Add an "Update History" table so that it's possible to see the
last completed update version.

https://bugzilla.redhat.com/show_bug.cgi?id=1666231
  • Loading branch information
spadgett committed Feb 5, 2019
1 parent dfed75a commit bfad801
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 61 deletions.
147 changes: 91 additions & 56 deletions frontend/public/components/cluster-settings/cluster-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,20 @@ import { Helmet } from 'react-helmet';
import { Button } from 'patternfly-react';
import { Link } from 'react-router-dom';

import { Firehose, HorizontalNav, ResourceLink, resourcePathFromModel } from '../utils';
import { K8sResourceKind, referenceForModel } from '../../module/k8s';
import { ClusterVersionKind, K8sResourceKind, referenceForModel } from '../../module/k8s';
import { ClusterAutoscalerModel, ClusterVersionModel } from '../../models';
import { ClusterOperatorPage } from './cluster-operator';
import { clusterUpdateModal } from '../modals';
import { GlobalConfigPage } from './global-config';
import {
EmptyBox,
Firehose,
HorizontalNav,
ResourceLink,
resourcePathFromModel,
SectionHeading,
Timestamp,
} from '../utils';

enum ClusterUpdateStatus {
UpToDate = 'Up to Date',
Expand All @@ -26,20 +34,19 @@ enum ClusterUpdateStatus {
const clusterAutoscalerReference = referenceForModel(ClusterAutoscalerModel);
const clusterVersionReference = referenceForModel(ClusterVersionModel);

export const getAvailableClusterUpdates = (cv) => {
export const getAvailableClusterUpdates = (cv: ClusterVersionKind) => {
return _.get(cv, 'status.availableUpdates');
};

export const getCurrentClusterVersion = (cv) => {
export const getDesiredClusterVersion = (cv: ClusterVersionKind) => {
return _.get(cv, 'status.desired.version');
};


const launchUpdateModal = (cv) => {
const launchUpdateModal = (cv: ClusterVersionKind) => {
clusterUpdateModal({cv});
};

const getClusterUpdateStatus = (cv: K8sResourceKind): ClusterUpdateStatus => {
const getClusterUpdateStatus = (cv: ClusterVersionKind): ClusterUpdateStatus => {
const conditions = _.get(cv, 'status.conditions', []);
const isFailingCondition = _.find(conditions, { type: 'Failing', status: 'True' });
if (isFailingCondition) {
Expand Down Expand Up @@ -111,63 +118,91 @@ const UpdateStatus: React.SFC<UpdateStatusProps> = ({cv}) => {
</React.Fragment>;
};

const CurrentVersion: React.SFC<CurrentVersionProps> = ({cv}) => {
const currentVersion = getCurrentClusterVersion(cv);
return currentVersion || <React.Fragment><i className="pficon pficon-warning-triangle-o" aria-hidden="true" />&nbsp;Unknown</React.Fragment>;
const DesiredVersion: React.SFC<DesiredVersionProps> = ({cv}) => {
const version = getDesiredClusterVersion(cv);
return version
? <React.Fragment>{version}</React.Fragment>
: <React.Fragment><i className="pficon pficon-warning-triangle-o" aria-hidden="true" />&nbsp;Unknown</React.Fragment>;
};

const ClusterVersionDetailsTable: React.SFC<ClusterVersionDetailsTableProps> = ({obj: cv, autoscalers}) => {
const conditions = _.get(cv, 'status.conditions', []);
const { history, conditions } = cv.status;
const status = getClusterUpdateStatus(cv);
const retrievedUpdatesFailedCondition = _.find(conditions, { type: 'RetrievedUpdates', status: 'False' });
const isFailingCondition = _.find(conditions, { type: 'Failing', status: 'True' });

return <div className="co-m-pane__body">
<div className="co-m-pane__body-group">
{ status === ClusterUpdateStatus.Updating && <UpdateInProgressAlert /> }
{ status === ClusterUpdateStatus.UpdatesAvailable && <UpdatesAvailableAlert cv={cv} /> }
{ isFailingCondition && <FailedConditionAlert message="Update is failing." condition={isFailingCondition} /> }
{ retrievedUpdatesFailedCondition && <FailedConditionAlert message="Could not retrieve updates." condition={retrievedUpdatesFailedCondition} /> }
<div className="co-detail-table">
<div className="co-detail-table__row row">
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Channel</dt>
<dd>{cv.spec.channel}</dd>
</dl>
</div>
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Update Status</dt>
<dd><UpdateStatus cv={cv} /></dd>
</dl>
</div>
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Current Version</dt>
<dd><CurrentVersion cv={cv} /></dd>
</dl>
return <React.Fragment>
<div className="co-m-pane__body">
<div className="co-m-pane__body-group">
{ status === ClusterUpdateStatus.Updating && <UpdateInProgressAlert /> }
{ status === ClusterUpdateStatus.UpdatesAvailable && <UpdatesAvailableAlert cv={cv} /> }
{ isFailingCondition && <FailedConditionAlert message="Update is failing." condition={isFailingCondition} /> }
{ retrievedUpdatesFailedCondition && <FailedConditionAlert message="Could not retrieve updates." condition={retrievedUpdatesFailedCondition} /> }
<div className="co-detail-table">
<div className="co-detail-table__row row">
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Channel</dt>
<dd>{cv.spec.channel}</dd>
</dl>
</div>
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Update Status</dt>
<dd><UpdateStatus cv={cv} /></dd>
</dl>
</div>
<div className="co-detail-table__section">
<dl className="co-m-pane__details">
<dt className="co-detail-table__section-header">Desired Version</dt>
<dd><DesiredVersion cv={cv} /></dd>
</dl>
</div>
</div>
</div>
</div>
<div className="co-m-pane__body-group">
<dl className="co-m-pane__details">
<dt>Cluster ID</dt>
<dd className="co-break-all">{cv.spec.clusterID}</dd>
<dt>Desired Release Image</dt>
<dd className="co-break-all">{_.get(cv, 'status.desired.image') || '-'}</dd>
<dt>Cluster Autoscaler</dt>
<dd>
{_.isEmpty(autoscalers)
? <Link to={`${resourcePathFromModel(ClusterAutoscalerModel)}/new`}>
<i className="pficon pficon-add-circle-o" aria-hidden="true" /> Create Autoscaler
</Link>
: autoscalers.map(autoscaler => <div key={autoscaler.metadata.uid}><ResourceLink kind={clusterAutoscalerReference} name={autoscaler.metadata.name} /></div>)}
</dd>
</dl>
</div>
</div>
<div className="co-m-pane__body-group">
<dl className="co-m-pane__details">
<dt>Cluster ID</dt>
<dd className="co-break-all">{cv.spec.clusterID}</dd>
<dt>Current Payload</dt>
<dd className="co-break-all">{_.get(cv, 'status.desired.payload') || '-'}</dd>
<dt>Cluster Autoscaler</dt>
<dd>
{_.isEmpty(autoscalers)
? <Link to={`${resourcePathFromModel(ClusterAutoscalerModel)}/new`}>
<i className="pficon pficon-add-circle-o" aria-hidden="true" /> Create Autoscaler
</Link>
: autoscalers.map(autoscaler => <div key={autoscaler.metadata.uid}><ResourceLink kind={clusterAutoscalerReference} name={autoscaler.metadata.name} /></div>)}
</dd>
</dl>
<div className="co-m-pane__body">
<SectionHeading text="Update History" />
<div className="co-table-container">
<table className="table">
<thead>
<tr>
<th>Version</th>
<th>State</th>
<th>Started</th>
<th>Completed</th>
</tr>
</thead>
<tbody>
{_.isEmpty(history) && <EmptyBox label="History" />}
{history.map((update, i) => <tr key={i}>
<td className="co-break-all">{update.version || '-'}</td>
<td>{update.state || '-'}</td>
<td><Timestamp timestamp={update.startedTime} /></td>
<td>{update.completionTime ? <Timestamp timestamp={update.completionTime} /> : '-'}</td>
</tr>)}
</tbody>
</table>
</div>
</div>
</div>;
</React.Fragment>;
};

const ClusterOperatorTabPage: React.SFC = () => <ClusterOperatorPage autoFocus={false} showTitle={false} />;
Expand Down Expand Up @@ -207,15 +242,15 @@ export const ClusterSettingsPage: React.SFC<ClusterSettingsPageProps> = ({match}
};

type UpdateStatusProps = {
cv: K8sResourceKind;
cv: ClusterVersionKind;
};

type CurrentVersionProps = {
cv: K8sResourceKind;
type DesiredVersionProps = {
cv: ClusterVersionKind;
};

type ClusterVersionDetailsTableProps = {
obj: K8sResourceKind;
obj: ClusterVersionKind;
autoscalers: K8sResourceKind[];
};

Expand Down
8 changes: 4 additions & 4 deletions frontend/public/components/modals/cluster-update-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import * as React from 'react';

import { createModalLauncher, ModalTitle, ModalBody, ModalSubmitFooter, ModalComponentProps } from '../factory/modal';
import { Dropdown, PromiseComponent, ExternalLink } from '../utils';
import { k8sPatch, K8sResourceKind } from '../../module/k8s';
import { ClusterVersionKind, k8sPatch } from '../../module/k8s';
import { ClusterVersionModel } from '../../models';
import { getAvailableClusterUpdates, getCurrentClusterVersion } from '../cluster-settings/cluster-settings';
import { getAvailableClusterUpdates, getDesiredClusterVersion } from '../cluster-settings/cluster-settings';

class ClusterUpdateModal extends PromiseComponent {
readonly state: ClusterUpdateModalState;
Expand Down Expand Up @@ -38,7 +38,7 @@ class ClusterUpdateModal extends PromiseComponent {
const {cv} = this.props;
const {selectedVersion} = this.state;
const availableUpdates = getAvailableClusterUpdates(cv);
const currentVersion = getCurrentClusterVersion(cv);
const currentVersion = getDesiredClusterVersion(cv);
const dropdownItems = _.map(availableUpdates, 'version');
const dropdownTitle = _.get(availableUpdates[selectedVersion], 'version');
return <form onSubmit={this._submit} name="form" className="modal-content">
Expand Down Expand Up @@ -71,7 +71,7 @@ class ClusterUpdateModal extends PromiseComponent {
export const clusterUpdateModal = createModalLauncher(ClusterUpdateModal);

type ClusterUpdateModalProps = {
cv: K8sResourceKind;
cv: ClusterVersionKind;
} & ModalComponentProps;

type ClusterUpdateModalState = {
Expand Down
3 changes: 2 additions & 1 deletion frontend/public/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
PrometheusModel,
SelfSubjectAccessReviewModel,
} from './models';
import { ClusterVersionKind } from './module/k8s';
import { k8sBasePath, referenceForModel } from './module/k8s/k8s';
import { k8sCreate } from './module/k8s/resource';
import { types } from './module/k8s/k8s-actions';
Expand Down Expand Up @@ -105,7 +106,7 @@ const detectOpenShift = dispatch => coFetchJSON(openshiftPath)
const clusterVersionPath = `${k8sBasePath}/apis/config.openshift.io/v1/clusterversions/version`;
const detectClusterVersion = dispatch => coFetchJSON(clusterVersionPath)
.then(
clusterVersion => {
(clusterVersion: ClusterVersionKind) => {
const hasClusterVersion = !_.isEmpty(clusterVersion);
setFlag(dispatch, FLAGS.CLUSTER_VERSION, hasClusterVersion);

Expand Down
28 changes: 28 additions & 0 deletions frontend/public/module/k8s/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,34 @@ export type MachineConfigPoolKind = {
status: MachineConfigPoolStatus;
} & K8sResourceKind;

type ClusterUpdate = {
image: string;
version: string;
};

type UpdateHistory = {
state: 'Completed' | 'Partial';
startedTime: string;
completionTime: string;
version: string;
image: string;
};

export type ClusterVersionKind = {
spec: {
channel: string;
clusterID: string;
desiredUpdate: ClusterUpdate;
upstream: string;
};
status: {
availableUpdates: ClusterUpdate[];
conditions: any[];
desired: ClusterUpdate;
history: UpdateHistory[];
};
} & K8sResourceKind;

export type K8sKind = {
abbr: string;
kind: string;
Expand Down

0 comments on commit bfad801

Please sign in to comment.