Skip to content

Commit

Permalink
Improve visibility of Operator installation status
Browse files Browse the repository at this point in the history
Added install page logic to OperatorHub install states, disabled install button when operator is installing, and added a message.

Fixes https://issues.redhat.com/browse/CONSOLE-2805
  • Loading branch information
rebeccaalpert committed Jul 2, 2021
1 parent 7b1eaa7 commit c26fc75
Show file tree
Hide file tree
Showing 7 changed files with 128 additions and 43 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,12 @@
"Deep Insights": "Deep Insights",
"Auto Pilot": "Auto Pilot",
"Checked": "Checked",
"Installed Operator": "Installed Operator",
"Version <1>{{installedVersion}}</1> of this Operator has been installed on the cluster.": "Version <1>{{installedVersion}}</1> of this Operator has been installed on the cluster.",
"This Operator has been installed on the cluster.": "This Operator has been installed on the cluster.",
"View it here.": "View it here.",
"Installing Operator": "Installing Operator",
"This Operator is being installed on the cluster.": "This Operator is being installed on the cluster.",
"Community Operator": "Community Operator",
"This is a community provided Operator. These are Operators which have not been vetted or verified by Red Hat. Community Operators should be used with caution because their stability is unknown. Red Hat provides no support for community Operators.": "This is a community provided Operator. These are Operators which have not been vetted or verified by Red Hat. Community Operators should be used with caution because their stability is unknown. Red Hat provides no support for community Operators.",
"Marketplace Operator": "Marketplace Operator",
Expand Down Expand Up @@ -289,7 +292,6 @@
"ready for use": "ready for use",
"The Operator has installed successfully. Create the required custom resource to be able to use this Operator.": "The Operator has installed successfully. Create the required custom resource to be able to use this Operator.",
"View Operator": "View Operator",
"Installing Operator": "Installing Operator",
"The Operator is being installed. This may take a few minutes.": "The Operator is being installed. This may take a few minutes.",
"Once the Operator is installed the required custom resource will be available for creation.": "Once the Operator is installed the required custom resource will be available for creation.",
"Installing...": "Installing...",
Expand Down
6 changes: 4 additions & 2 deletions frontend/packages/operator-lifecycle-manager/mocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -764,9 +764,9 @@ export const dummyPackageManifest = {
export const operatorHubListPageProps = {
loaded: true,
loadError: null,
operatorGroup: { loaded: false },
operatorGroups: { loaded: false },
catalogSourceConfig: { loaded: false },
packageManifest: {
packageManifests: {
loaded: true,
data: [
amqPackageManifest,
Expand All @@ -776,6 +776,8 @@ export const operatorHubListPageProps = {
svcatPackageManifest,
] as PackageManifestKind[],
},
installPlans: null,
clusterServiceVersions: null,
};

export const operatorHubTileViewPageProps = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ const InstalledHintBlock: React.FC<OperatorHubItemDetailsHintBlockProps> = ({
: `${nsPath}/subscriptions/${subscription.metadata.name ?? ''}`;
const installedVersion = installedCSV?.spec?.version;
return (
<HintBlock className="co-catalog-page__hint" title="Installed Operator">
<HintBlock className="co-catalog-page__hint" title={t('olm~Installed Operator')}>
<p>
{installedVersion !== latestVersion ? (
<span>
Expand All @@ -100,9 +100,46 @@ const InstalledHintBlock: React.FC<OperatorHubItemDetailsHintBlockProps> = ({
);
};

const InstallingHintBlock: React.FC<OperatorHubItemDetailsHintBlockProps> = ({
namespace,
subscription,
}) => {
const { t } = useTranslation();
const [installedCSV] = useK8sWatchResource<ClusterServiceVersionKind>(
subscription?.status?.installedCSV
? {
kind: referenceForModel(ClusterServiceVersionModel),
name: subscription?.status?.installedCSV,
namespace: subscription?.metadata?.namespace,
isList: false,
namespaced: true,
}
: null,
);
const nsPath = `/k8s/${namespace ? `ns/${namespace}` : 'all-namespaces'}`;
const to = installedCSV
? `${nsPath}/clusterserviceversions/${installedCSV?.metadata?.name}/subscription`
: `${nsPath}/subscriptions/${subscription.metadata.name ?? ''}`;
return (
<HintBlock className="co-catalog-page__hint" title={t('olm~Installing Operator')}>
<p>
<span>
<Trans ns="olm">This Operator is being installed on the cluster.</Trans>
</span>
&nbsp;
<Link to={to}>{t('olm~View it here.')}</Link>
</p>
</HintBlock>
);
};

const OperatorHubItemDetailsHintBlock: React.FC<OperatorHubItemDetailsHintBlockProps> = (props) => {
const { t } = useTranslation();
const { installed, catalogSource } = props;
const { installed, isInstalling, catalogSource } = props;
if (isInstalling) {
return <InstallingHintBlock {...props} />;
}

if (installed) {
return <InstalledHintBlock {...props} />;
}
Expand Down Expand Up @@ -162,6 +199,7 @@ export const OperatorHubItemDetails: React.FC<OperatorHubItemDetailsProps> = ({
description,
infraFeatures,
installed,
isInstalling,
longDescription,
marketplaceSupportWorkflow,
provider,
Expand Down Expand Up @@ -260,6 +298,7 @@ export const OperatorHubItemDetails: React.FC<OperatorHubItemDetailsProps> = ({
<div className="co-catalog-page__overlay-description">
<OperatorHubItemDetailsHintBlock
installed={installed}
isInstalling={isInstalling}
latestVersion={version}
namespace={namespace}
catalogSource={catalogSource}
Expand All @@ -279,6 +318,7 @@ OperatorHubItemDetails.defaultProps = {
};
type OperatorHubItemDetailsHintBlockProps = {
installed: boolean;
isInstalling: boolean;
latestVersion: string;
namespace: string;
catalogSource: string;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -345,6 +345,7 @@ const OperatorHubTile: React.FC<OperatorHubTileProps> = ({ item, onClick }) => {
alt=""
/>
);

return (
<CatalogTile
className="co-catalog-tile"
Expand All @@ -356,7 +357,7 @@ const OperatorHubTile: React.FC<OperatorHubTileProps> = ({ item, onClick }) => {
description={description}
onClick={() => onClick(item)}
footer={
installed ? (
installed && !item.isInstalling ? (
<span>
<GreenCheckCircleIcon /> {t('olm~Installed')}
</span>
Expand Down Expand Up @@ -522,6 +523,7 @@ export const OperatorHubTileView: React.FC<OperatorHubTileViewProps> = (props) =
'pf-c-button',
{ 'pf-m-secondary': remoteWorkflowUrl },
{ 'pf-m-primary': !remoteWorkflowUrl },
{ 'pf-m-disabled': detailsItem.isInstalling },
'co-catalog-page__overlay-action',
)}
data-test-id="operator-install-btn"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,19 @@ import { withFallback } from '@console/shared/src/components/error/error-boundar
import { parseJSONAnnotation } from '@console/shared/src/utils/annotations';
import { iconFor } from '..';
import { OPERATOR_TYPE_ANNOTATION, NON_STANDALONE_ANNOTATION_VALUE } from '../../const';
import { PackageManifestModel, OperatorGroupModel, SubscriptionModel } from '../../models';
import { PackageManifestKind, OperatorGroupKind, SubscriptionKind } from '../../types';
import { installedFor, subscriptionFor } from '../operator-group';
import {
ClusterServiceVersionModel,
PackageManifestModel,
OperatorGroupModel,
SubscriptionModel,
} from '../../models';
import {
ClusterServiceVersionKind,
PackageManifestKind,
OperatorGroupKind,
SubscriptionKind,
} from '../../types';
import { subscriptionFor } from '../operator-group';
import { OperatorHubTileView } from './operator-hub-items';
import { getCatalogSourceDisplayName } from './operator-hub-utils';
import {
Expand All @@ -37,18 +47,26 @@ const ANNOTATIONS_WITH_JSON = [
OperatorHubCSVAnnotationKey.validSubscription,
];

const clusterServiceVersionFor = (
clusterServiceVersions: ClusterServiceVersionKind[],
csvName: string,
): ClusterServiceVersionKind => {
return clusterServiceVersions?.find((csv) => csv.metadata.name === csvName);
};

export const OperatorHubList: React.FC<OperatorHubListProps> = ({
loaded,
loadError,
marketplacePackageManifest,
marketplacePackageManifests,
namespace,
operatorGroup,
packageManifest,
subscription,
operatorGroups,
packageManifests,
subscriptions,
clusterServiceVersions,
}) => {
const { t } = useTranslation();
const items: OperatorHubItem[] = React.useMemo(() => {
return [...(marketplacePackageManifest?.data ?? []), ...(packageManifest?.data ?? [])]
return [...(marketplacePackageManifests?.data ?? []), ...(packageManifests?.data ?? [])]
.filter((pkg) => {
const { channels, defaultChannel } = pkg.status ?? {};
// if a package does not have status.defaultChannel, exclude it so the app doesn't fail
Expand Down Expand Up @@ -97,23 +115,33 @@ export const OperatorHubList: React.FC<OperatorHubListProps> = ({
[OperatorHubCSVAnnotationKey.supportWorkflow]: marketplaceSupportWorkflow,
} = currentCSVAnnotations;

const subscription =
loaded &&
subscriptionFor(subscriptions?.data)(operatorGroups?.data)(pkg.status.packageName)(
namespace,
);

const clusterServiceVersion =
loaded &&
clusterServiceVersionFor(
clusterServiceVersions?.data,
subscription?.status?.currentCSV,
);

const installed = loaded && clusterServiceVersion?.status?.phase === 'Succeeded';

return {
obj: pkg,
kind: PackageManifestModel.kind,
name: currentCSVDesc?.displayName ?? pkg.metadata.name,
uid: `${pkg.metadata.name}-${pkg.status.catalogSource}-${pkg.status.catalogSourceNamespace}`,
installed: installedFor(subscription.data)(operatorGroup.data)(pkg.status.packageName)(
namespace,
),
subscription: subscriptionFor(subscription.data)(operatorGroup.data)(
pkg.status.packageName,
)(namespace),
// FIXME: Just use `installed`
installState: installedFor(subscription.data)(operatorGroup.data)(
pkg.status.packageName,
)(namespace)
? InstalledState.Installed
: InstalledState.NotInstalled,
installed,
isInstalling:
loaded &&
!_.isNil(subscription) &&
clusterServiceVersion?.status?.phase !== 'Succeeded',
subscription,
installState: installed ? InstalledState.Installed : InstalledState.NotInstalled,
imgUrl: iconFor(pkg),
description: currentCSVAnnotations.description || currentCSVDesc.description,
longDescription: currentCSVDesc.description || currentCSVAnnotations.description,
Expand Down Expand Up @@ -143,11 +171,13 @@ export const OperatorHubList: React.FC<OperatorHubListProps> = ({
},
);
}, [
marketplacePackageManifest,
clusterServiceVersions,
loaded,
marketplacePackageManifests,
namespace,
operatorGroup.data,
packageManifest,
subscription.data,
operatorGroups,
packageManifests,
subscriptions,
]);

const uniqueItems = _.uniqBy(items, 'uid');
Expand Down Expand Up @@ -215,14 +245,14 @@ export const OperatorHubPage = withFallback(
{
isList: true,
kind: referenceForModel(OperatorGroupModel),
prop: 'operatorGroup',
prop: 'operatorGroups',
},
{
isList: true,
kind: referenceForModel(PackageManifestModel),
namespace: props.match.params.ns,
selector: { 'openshift-marketplace': 'true' },
prop: 'marketplacePackageManifest',
prop: 'marketplacePackageManifests',
},
{
isList: true,
Expand All @@ -232,12 +262,19 @@ export const OperatorHubPage = withFallback(
{ key: 'opsrc-owner-name', operator: 'DoesNotExist' },
{ key: 'csc-owner-name', operator: 'DoesNotExist' },
]),
prop: 'packageManifest',
prop: 'packageManifests',
},
{
isList: true,
kind: referenceForModel(SubscriptionModel),
prop: 'subscription',
prop: 'subscriptions',
},
{
kind: referenceForModel(ClusterServiceVersionModel),
namespaced: true,
isList: true,
namespace: props.match.params.ns,
prop: 'clusterServiceVersions',
},
]}
>
Expand All @@ -258,12 +295,13 @@ export type OperatorHubPageProps = {

export type OperatorHubListProps = {
namespace?: string;
operatorGroup: { loaded: boolean; data?: OperatorGroupKind[] };
packageManifest: { loaded: boolean; data?: PackageManifestKind[] };
marketplacePackageManifest: { loaded: boolean; data?: PackageManifestKind[] };
subscription: { loaded: boolean; data?: SubscriptionKind[] };
operatorGroups: { loaded: boolean; data?: OperatorGroupKind[] };
packageManifests: { loaded: boolean; data?: PackageManifestKind[] };
marketplacePackageManifests: { loaded: boolean; data?: PackageManifestKind[] };
subscriptions: { loaded: boolean; data?: SubscriptionKind[] };
loaded: boolean;
loadError?: string;
clusterServiceVersions: { loaded: boolean; data?: ClusterServiceVersionKind[] };
};

OperatorHubList.displayName = 'OperatorHubList';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ xdescribe('[https://issues.redhat.com/browse/CONSOLE-2136] OperatorHubList', ()
<MemoryRouter>
<OperatorHubList
{...operatorHubListPageProps}
marketplacePackageManifest={null}
subscription={{ loaded: false, data: [] }}
marketplacePackageManifests={null}
subscriptions={{ loaded: false, data: [] }}
/>
</MemoryRouter>,
);
Expand All @@ -60,7 +60,7 @@ xdescribe('[https://issues.redhat.com/browse/CONSOLE-2136] OperatorHubList', ()
it('renders amq-streams tile with correct props', () => {
const tiles = wrapper.find<any>(CatalogTile);
const amqTileProps = tiles.at(0).props();
const amqPackageManifest = operatorHubListPageProps.packageManifest.data[0];
const amqPackageManifest = operatorHubListPageProps.packageManifests.data[0];

expect(amqTileProps.title).toEqual(
amqPackageManifest.status.channels[0].currentCSVDesc.displayName,
Expand All @@ -82,7 +82,7 @@ xdescribe('[https://issues.redhat.com/browse/CONSOLE-2136] OperatorHubList', ()
it('renders prometheus tile with correct props', () => {
const tiles = wrapper.find<any>(CatalogTile);
const prometheusTileProps = tiles.at(3).props(); // Sorting makes this 3
const prometheusPackageManifest = operatorHubListPageProps.packageManifest.data[3];
const prometheusPackageManifest = operatorHubListPageProps.packageManifests.data[3];

expect(prometheusTileProps.title).toEqual(
prometheusPackageManifest.status.channels[0].currentCSVDesc.displayName,
Expand All @@ -109,7 +109,7 @@ xdescribe('[https://issues.redhat.com/browse/CONSOLE-2136] OperatorHubList', ()
expect(details.exists()).toBe(true);

const modalItem = details.at(0).props().item;
const amqPackageManifest = operatorHubListPageProps.packageManifest.data[0];
const amqPackageManifest = operatorHubListPageProps.packageManifests.data[0];

expect(modalItem.name).toEqual(
amqPackageManifest.status.channels[0].currentCSVDesc.displayName,
Expand Down
1 change: 1 addition & 0 deletions frontend/packages/operator-lifecycle-manager/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,7 @@ export type SubscriptionKind = {
installPlanRef?: ObjectReference;
state?: SubscriptionState;
lastUpdated?: string;
currentCSV?: string;
};
} & K8sResourceCommon;

Expand Down

0 comments on commit c26fc75

Please sign in to comment.