Skip to content

Commit

Permalink
Implement catalog source form and list page
Browse files Browse the repository at this point in the history
  • Loading branch information
TheRealJon committed Oct 18, 2019
1 parent bb544eb commit 3c984d0
Show file tree
Hide file tree
Showing 13 changed files with 737 additions and 10 deletions.
@@ -1,23 +1,45 @@
import * as React from 'react';
import * as _ from 'lodash';
import * as classNames from 'classnames';
import { match } from 'react-router-dom';
import { CreateYAML } from '@console/internal/components/create-yaml';
import { ListPageProps } from '@console/internal/components/monitoring';
import { sortable } from '@patternfly/react-table';
import { withFallback } from '@console/internal/components/utils/error-boundary';
import {
K8sResourceKind,
referenceForModel,
K8sKind,
k8sPatch,
} from '@console/internal/module/k8s';
import {
SectionHeading,
Firehose,
MsgBox,
LoadingBox,
Kebab,
LoadingBox,
MsgBox,
navFactory,
ResourceKebab,
ResourceLink,
SectionHeading,
asAccessReview,
KebabOption,
} from '@console/internal/components/utils';
import { withFallback } from '@console/internal/components/utils/error-boundary';
import { CreateYAML } from '@console/internal/components/create-yaml';
import { referenceForModel, K8sResourceKind } from '@console/internal/module/k8s';
import { DetailsPage } from '@console/internal/components/factory';
import {
DetailsPage,
Table,
TableRow,
TableData,
TableProps,
TableRowProps,
MultiListPage,
} from '@console/internal/components/factory';
import { ConfigMapModel } from '@console/internal/models';
import {
SubscriptionModel,
CatalogSourceModel,
PackageManifestModel,
OperatorGroupModel,
OperatorHubModel,
} from '../models';
import {
CatalogSourceKind,
Expand All @@ -27,6 +49,54 @@ import {
} from '../types';
import { requireOperatorGroup } from './operator-group';
import { PackageManifestList } from './package-manifest';
import { deleteCatalogSourceModal } from './modals/delete-catalog-source-modal';
import { disableDefaultSourceModal } from './modals/disable-default-source-modal';
import { OperatorHubKind } from './operator-hub';

const catalogSourceModelReference = referenceForModel(CatalogSourceModel);
const DEFAULT_SOURCE_NAMESPACE = 'openshift-marketplace';

const deleteModal = (kind: K8sKind, catalogSource: CatalogSourceKind): KebabOption => ({
...Kebab.factory.Delete(kind, catalogSource),
callback: () => deleteCatalogSourceModal({ kind, resource: catalogSource }),
});

const disableSourceModal = (
kind: K8sKind,
operatorHub: OperatorHubKind,
sourceName: string,
): KebabOption => ({
label: 'Disable',
callback: () => disableDefaultSourceModal({ kind, operatorHub, sourceName }),
accessReview: asAccessReview(kind, operatorHub, 'patch'),
});

const enableSource = (
kind: K8sKind,
operatorHub: OperatorHubKind,
sourceName: string,
): KebabOption => ({
label: 'Enable',
callback: () => {
const currentSources = _.get(operatorHub, 'spec.sources', []);
const patch = [
{
op: _.isEmpty(currentSources) ? 'add' : 'replace',
path: '/spec/sources',
value: _.filter(currentSources, (source) => source.name !== sourceName),
},
];
return k8sPatch(kind, operatorHub, patch);
},
accessReview: asAccessReview(kind, operatorHub, 'patch'),
});

const DefaultSourceKebab = ({ kind, operatorHub, sourceName, sourceDisabled }) => {
const options = sourceDisabled
? [enableSource(kind, operatorHub, sourceName)]
: [disableSourceModal(kind, operatorHub, sourceName)];
return <Kebab options={options} />;
};

export const CatalogSourceDetails: React.SFC<CatalogSourceDetailsProps> = ({
obj,
Expand Down Expand Up @@ -165,6 +235,238 @@ export const CreateSubscriptionYAML: React.SFC<CreateSubscriptionYAMLProps> = (p
);
};

const tableColumnClasses = [
classNames('col-lg-3', 'col-md-3', 'col-sm-4', 'col-xs-6'),
classNames('col-lg-2', 'col-md-3', 'col-sm-4', 'col-xs-6'),
classNames('col-lg-2', 'col-md-3', 'col-sm-4', 'hidden-xs'),
classNames('col-lg-3', 'col-md-3', 'hidden-sm', 'hidden-xs'),
classNames('col-lg-2', 'hidden-md', 'hidden-sm', 'hidden-xs'),
Kebab.columnClass,
];

const CatalogSourceHeader = () => {
return [
{
title: 'Name',
sortField: 'name',
transforms: [sortable],
props: { className: tableColumnClasses[0] },
},
{
title: 'Publisher',
sortField: 'publisher',
transforms: [sortable],
props: { className: tableColumnClasses[1] },
},
{
title: 'Availability',
sortField: 'availability',
transforms: [sortable],
props: { className: tableColumnClasses[2] },
},
{
title: 'Endpoint',
sortField: 'endpoint',
transforms: [sortable],
props: { className: tableColumnClasses[3] },
},
{
title: '# of Operators',
sortField: 'operatorCount',
transforms: [sortable],
props: { className: tableColumnClasses[4] },
},
{
title: '',
props: { className: tableColumnClasses[5] },
},
];
};

const getEndpoint = (catalogSource) => {
if (catalogSource.spec.configmap) {
return (
<ResourceLink
kind={referenceForModel(ConfigMapModel)}
name={catalogSource.spec.configmap}
namespace={catalogSource.metadata.namespace}
/>
);
}
return catalogSource.spec.image || catalogSource.spec.address;
};

const getOperatorCount = (catalogSource, packageManifests) =>
_.filter(packageManifests, {
status: {
catalogSource: catalogSource.metadata.name,
catalogSourceNamespace: catalogSource.metadata.namespace,
},
}).length;

const CatalogSourceTableRow: React.FC<CatalogSourceTableRowProps> = ({
obj: {
availability = '-',
disabled = false,
endpoint = '-',
isDefault = false,
name,
operatorCount = 0,
operatorHub,
publisher = '-',
source,
},
index,
key,
style,
}) => {
return (
<TableRow
className={disabled && 'catalog-source__table-row--disabled'}
id={source ? source.metadata.uid : index}
index={index}
style={style}
trKey={key}
>
<TableData className={tableColumnClasses[0]}>
{source ? (
<ResourceLink
kind={catalogSourceModelReference}
name={source.metadata.name}
namespace={source.metadata.namespace}
title={source.metadata.name}
/>
) : (
name
)}
</TableData>
<TableData className={tableColumnClasses[1]}>{publisher || '-'}</TableData>
<TableData className={tableColumnClasses[2]}>{availability}</TableData>
<TableData className={tableColumnClasses[3]}>{endpoint || '-'}</TableData>
<TableData className={tableColumnClasses[4]}>{operatorCount || '-'}</TableData>
<TableData className={tableColumnClasses[5]}>
{isDefault ? (
<DefaultSourceKebab
kind={OperatorHubModel}
operatorHub={operatorHub}
sourceName={name}
sourceDisabled={disabled}
/>
) : (
<ResourceKebab
actions={[Kebab.factory.Edit, deleteModal]}
kind={catalogSourceModelReference}
resource={source}
/>
)}
</TableData>
</TableRow>
);
};

const CatalogSourceList: React.FC<TableProps> = (props) => (
<Table
{...props}
aria-label="Catalog Sources"
Header={CatalogSourceHeader}
Row={CatalogSourceTableRow}
/>
);

const flatten = ({
catalogSources,
operatorHub,
packageManifests,
}: FlattenArgType): CatalogSourceTableRowObj[] => {
const defaultSources = _.map(operatorHub.status.sources, (defaultSource) => {
const catalogSource = _.find(catalogSources.data, {
metadata: { name: defaultSource.name, namespace: DEFAULT_SOURCE_NAMESPACE },
});
const catalogSourceExists = !_.isEmpty(catalogSource);
return {
availability: !catalogSourceExists ? 'Disabled' : 'Cluster wide',
disabled: !catalogSourceExists,
isDefault: true,
name: defaultSource.name,
namespace: DEFAULT_SOURCE_NAMESPACE,
operatorHub,
...(catalogSourceExists &&
!defaultSource.disabled && {
source: catalogSource,
endpoint: getEndpoint(catalogSource),
operatorCount: getOperatorCount(catalogSource, packageManifests.data),
publisher: catalogSource.spec.publisher,
}),
};
});

const customSources = _.map(catalogSources.data, (source) => ({
availability:
source.metadata.namespace === DEFAULT_SOURCE_NAMESPACE
? 'Cluster wide'
: source.metadata.namespace,
endpoint: getEndpoint(source),
name: source.metadata.name,
namespace: source.metadata.namespace,
operatorCount: getOperatorCount(source, packageManifests.data),
operatorHub,
publisher: source.spec.publisher,
source,
}));

return _.unionWith(
defaultSources,
customSources,
(a, b) => a.name === b.name && a.namespace === b.namespace,
);
};

export const CatalogSourceListPage: React.FC<CatalogSourceListPageProps> = (props) => (
<MultiListPage
{...props}
canCreate
createAccessReview={{ model: CatalogSourceModel }}
createButtonText="Create Catalog Source"
createProps={{ to: `/k8s/cluster/${referenceForModel(CatalogSourceModel)}/~new` }}
flatten={(data) => flatten({ operatorHub: props.obj, ...data })}
ListComponent={CatalogSourceList}
resources={[
{
isList: true,
kind: referenceForModel(PackageManifestModel),
prop: 'packageManifests',
},
{
isList: true,
kind: catalogSourceModelReference,
prop: 'catalogSources',
},
]}
/>
);

type FlattenArgType = {
catalogSources: { data: CatalogSourceKind[] };
packageManifests: { data: PackageManifestKind[] };
operatorHub: OperatorHubKind;
};
type CatalogSourceListPageProps = { obj: K8sResourceKind } & ListPageProps;
type CatalogSourceTableRowObj = {
availability?: React.ReactNode;
disabled?: boolean;
endpoint?: string;
isDefault?: boolean;
name?: string;
publisher?: string;
operatorCount?: number;
operatorHub: OperatorHubKind;
source?: CatalogSourceKind;
};
type CatalogSourceTableRowProps = {
obj: CatalogSourceTableRowObj;
key: string;
} & TableRowProps;

export type CatalogSourceDetailsProps = {
obj: CatalogSourceKind;
subscriptions: SubscriptionKind[];
Expand Down

0 comments on commit 3c984d0

Please sign in to comment.