Skip to content

Commit

Permalink
Bug 1666231 - Update Cluster Settings page for ClusterVersino 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 Jan 17, 2019
1 parent 30d327f commit e9188c8
Show file tree
Hide file tree
Showing 4 changed files with 129 additions and 63 deletions.
154 changes: 96 additions & 58 deletions frontend/public/components/cluster-settings/cluster-settings.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,21 @@ 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,
Overflow,
ResourceLink,
resourcePathFromModel,
SectionHeading,
Timestamp,
} from '../utils';

enum ClusterUpdateStatus {
UpToDate = 'Up to Date',
Expand All @@ -31,7 +40,7 @@ const MOCK_CLUSTER_UPDATE = localStorage.getItem('MOCK_CLUSTER_UPDATE');
const clusterAutoscalerReference = referenceForModel(ClusterAutoscalerModel);
const clusterVersionReference = referenceForModel(ClusterVersionModel);

export const getAvailableClusterUpdates = (cv) => {
export const getAvailableClusterUpdates = (cv: ClusterVersionKind) => {
// TODO - REMOVE MOCK CODE
if (MOCK_CLUSTER_UPDATE) {
return [{ version: '4.0.0', payload: 'openshift/release:v4.0.0' }];
Expand All @@ -41,16 +50,15 @@ export const getAvailableClusterUpdates = (cv) => {
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 => {
// TODO Remove this line and uncomment the rest of this function once update feature is available
return ClusterUpdateStatus.FeatureUnavailable;

Expand Down Expand Up @@ -112,7 +120,7 @@ const UpdatesAvailableAlert = ({cv}) => <div className="alert alert-info">
</div>;

const FeatureUnavailableAlert = () => <div className="alert alert-info">
<i className="pficon pficon-info" aria-hidden="true"></i>
<i className="pficon pficon-info" aria-hidden="true" />
Cluster updates are not supported in beta 1.
</div>;

Expand All @@ -136,13 +144,15 @@ 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);

// TODO - REMOVE MOCK CODE
Expand All @@ -152,53 +162,81 @@ const ClusterVersionDetailsTable: React.SFC<ClusterVersionDetailsTableProps> = (
// END MOCK CODE


return <div className="co-m-pane__body">
<div className="co-m-pane__body-group">
{ status === ClusterUpdateStatus.Updating && <UpdateInProgressAlert /> }
{ status === ClusterUpdateStatus.UpdatesAvailable && <UpdatesAvailableAlert cv={cv} /> }
{ status === ClusterUpdateStatus.FeatureUnavailable && <FeatureUnavailableAlert /> }
{ 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} /> }
{ status === ClusterUpdateStatus.FeatureUnavailable && <FeatureUnavailableAlert /> }
{ 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 Payload</dt>
<dd className="co-break-all">{_.get(cv, 'status.history[0].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>
</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>
<th>Payload</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><Timestamp timestamp={update.completionTime} /></td>
<td><Overflow value={update.payload} /></td>
</tr>)}
</tbody>
</table>
</div>
</div>
</div>;
</React.Fragment>;
};

const ClusterOperatorTabPage: React.SFC = () => <ClusterOperatorPage autoFocus={false} showTitle={false} />;
Expand Down Expand Up @@ -238,15 +276,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">
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 @@ -104,7 +105,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
27 changes: 27 additions & 0 deletions frontend/public/module/k8s/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,33 @@ export type MachineDeploymentKind = {
};
} & K8sResourceKind;

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

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

export type ClusterVersionKind = {
spec: {
channel: string;
clusterID: string;
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 e9188c8

Please sign in to comment.