Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ import { Link } from 'react-router-dom';
import { safeLoad, safeDump } from 'js-yaml';
import Spy = jasmine.Spy;

import { CatalogSourceDetails, CatalogSourceDetailsProps, CatalogSourceDetailsPage, CatalogSourceDetailsPageProps, PackageHeader, PackageHeaderProps, PackageRow, PackageRowProps, PackageList, PackageListProps, CreateSubscriptionYAML, CreateSubscriptionYAMLProps } from '../../../public/components/cloud-services/catalog-source';
import { CatalogSourceDetails, CatalogSourceFirehose, CatalogSourceDetailsProps, CatalogSourceDetailsPage, CatalogSourceDetailsPageProps, PackageHeader, PackageHeaderProps, PackageRow, PackageRowProps, PackageList, PackageListProps, CreateSubscriptionYAML, CreateSubscriptionYAMLProps, CreateSubscriptionYAMLFirehose } from '../../../public/components/cloud-services/catalog-source';
import { ClusterServiceVersionLogo, SubscriptionKind } from '../../../public/components/cloud-services';
import { referenceForModel } from '../../../public/module/k8s';
import { SubscriptionModel, CatalogSourceModel } from '../../../public/models';
import { SubscriptionModel } from '../../../public/models';
import { ListHeader, ColHead, List } from '../../../public/components/factory';
import { Firehose, NavTitle, LoadingBox, ErrorBoundary } from '../../../public/components/utils';
import { CreateYAML } from '../../../public/components/create-yaml';
Expand Down Expand Up @@ -159,27 +158,18 @@ describe(CatalogSourceDetailsPage.displayName, () => {
expect(wrapper.find(NavTitle).props().title).toEqual('Open Cloud Services');
});

it('creates a `Firehose` component for the necessary resources', () => {
expect(wrapper.find<any>(Firehose).props().resources).toEqual([{
kind: referenceForModel(CatalogSourceModel),
name: 'tectonic-ocs',
namespace: 'tectonic-system',
isList: false,
prop: 'catalogSource',
}, {
kind: referenceForModel(SubscriptionModel),
isList: true,
prop: 'subscription',
}, {
kind: 'ConfigMap',
isList: false,
name: 'tectonic-ocs',
namespace: 'tectonic-system',
prop: 'configMap'}]);
});

it('renders `CatalogSourceDetails` component', () => {
expect(wrapper.find(Firehose).find(CatalogSourceDetails).exists()).toBe(true);
it('renders `CatalogSourceDetails` component wrapped in a custom `Firehose`', () => {
const configMap = {loaded: false, loadError: null, data: null};
const subscription = {loaded: false, loadError: null, data: null};
const catalogSource = {loaded: false, loadError: null, data: null};

const render = wrapper.find(CatalogSourceFirehose).props().render;
wrapper = shallow(<div>{render({configMap, subscription, catalogSource})}</div>);

expect(wrapper.find(CatalogSourceDetails).props().ns).toEqual('default');
expect(wrapper.find(CatalogSourceDetails).props().configMap).toEqual(configMap);
expect(wrapper.find(CatalogSourceDetails).props().subscription).toEqual(subscription);
expect(wrapper.find(CatalogSourceDetails).props().catalogSource).toEqual(catalogSource);
});
});

Expand All @@ -193,23 +183,25 @@ describe(CreateSubscriptionYAML.displayName, () => {
wrapper = shallow(<CreateSubscriptionYAML match={{isExact: true, url: '', path: '', params: {ns: 'default', pkgName: testPackage.packageName}}} />);
});

it('renders a `Firehose` for the catalog ConfigMap', () => {
expect(wrapper.find<any>(Firehose).props().resources).toEqual([
it('renders a custom `Firehose` for the catalog ConfigMap', () => {
expect(wrapper.find(CreateSubscriptionYAMLFirehose).shallow().find(Firehose).props().resources).toEqual([
{kind: 'ConfigMap', name: 'tectonic-ocs', namespace: 'tectonic-system', isList: false, prop: 'ConfigMap'}
]);
});

it('renders YAML editor component wrapped by an error boundary component', () => {
wrapper = wrapper.setProps({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}} as any);
const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render;
wrapper = shallow(<div>{render({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}})}</div>);

expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).exists()).toBe(true);
expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(CreateYAML).exists()).toBe(true);
expect(wrapper.childAt(0).dive().find(ErrorBoundary).exists()).toBe(true);
expect(wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(CreateYAML).exists()).toBe(true);
});

it('registers example YAML templates using the package default channel', () => {
wrapper = wrapper.setProps({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}} as any);
const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render;
wrapper = shallow(<div>{render({ConfigMap: {loaded: true, data: {data: {packages: safeDump([testPackage])}}}})}</div>);

wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive();
wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive();
const subTemplate: SubscriptionKind = safeLoad(registerTemplateSpy.calls.argsFor(0)[1]);

expect(registerTemplateSpy.calls.count()).toEqual(1);
Expand All @@ -221,9 +213,10 @@ describe(CreateSubscriptionYAML.displayName, () => {
});

it('does not render YAML editor component if ConfigMap has not loaded yet', () => {
wrapper = wrapper.setProps({ConfigMap: {loaded: false}} as any);
const render = wrapper.find(CreateSubscriptionYAMLFirehose).props().render;
wrapper = shallow(<div>{render({ConfigMap: {loaded: false}})}</div>);

expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(false);
expect(wrapper.find(Firehose).childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(LoadingBox).exists()).toBe(true);
expect(wrapper.childAt(0).dive().find(CreateYAML).exists()).toBe(false);
expect(wrapper.childAt(0).dive().find(ErrorBoundary).childAt(0).dive().find(LoadingBox).exists()).toBe(true);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,7 @@ describe(ClusterServiceVersionPrometheusGraph.displayName, () => {
});

describe('ResourcesList', () => {

it('uses the resources defined in the CSV', () => {
const kindObj: K8sKind = {
abbr: '',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,41 +25,45 @@ describe(CreateCRDYAML.displayName, () => {
});

it('renders a `Firehose` for the ClusterServiceVersion', () => {
expect(wrapper.find<any>(Firehose).props().resources).toEqual([
expect(wrapper.shallow().find(Firehose).props().resources).toEqual([
{kind: referenceForModel(ClusterServiceVersionModel), name: 'example', namespace: 'default', isList: false, prop: 'ClusterServiceVersion'}
]);
});

it('renders YAML editor component', () => {
wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}} as any);
const render = wrapper.find('CreateCRDYAMLFirehose').prop<Function>('render');
wrapper = shallow(<div>{render({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}})}</div>);

expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(true);
expect(wrapper.childAt(0).shallow().find(CreateYAML).exists()).toBe(true);
});

it('registers example YAML templates using annotations on the ClusterServiceVersion', () => {
let data = _.cloneDeep(testClusterServiceVersion);
data.metadata.annotations = {'alm-examples': JSON.stringify([testResourceInstance])};
wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data}} as any);
const render = wrapper.find('CreateCRDYAMLFirehose').prop<Function>('render');
wrapper = shallow(<div>{render({ClusterServiceVersion: {loaded: true, data}})}</div>);

wrapper.find(Firehose).childAt(0).dive();
wrapper.childAt(0).dive();

expect(registerTemplateSpy.calls.count()).toEqual(1);
expect(registerTemplateSpy.calls.argsFor(0)[1]).toEqual(safeDump(testResourceInstance));
});

it('registers fallback example YAML template if annotations not present on ClusterServiceVersion', () => {
wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}} as any);
const render = wrapper.find('CreateCRDYAMLFirehose').prop<Function>('render');
wrapper = shallow(<div>{render({ClusterServiceVersion: {loaded: true, data: _.cloneDeep(testClusterServiceVersion)}})}</div>);

wrapper.find(Firehose).childAt(0).dive();
wrapper.childAt(0).dive();

expect(registerTemplateSpy.calls.count()).toEqual(1);
expect(registerTemplateSpy.calls.argsFor(0)[1]).not.toEqual(safeDump(testResourceInstance));
});

it('does not render YAML editor component if ClusterServiceVersion has not loaded yet', () => {
wrapper = wrapper.setProps({ClusterServiceVersion: {loaded: false}} as any);
const render = wrapper.find('CreateCRDYAMLFirehose').prop<Function>('render');
wrapper = shallow(<div>{render({ClusterServiceVersion: {loaded: false}})}</div>);

expect(wrapper.find(Firehose).childAt(0).dive().find(CreateYAML).exists()).toBe(false);
expect(wrapper.find(Firehose).childAt(0).dive().find(LoadingBox).exists()).toBe(true);
expect(wrapper.childAt(0).dive().find(CreateYAML).exists()).toBe(false);
expect(wrapper.childAt(0).dive().find(LoadingBox).exists()).toBe(true);
});
});
43 changes: 43 additions & 0 deletions frontend/__tests__/components/utils/firehose.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/* eslint-disable no-unused-vars, no-undef */

import * as React from 'react';
import { shallow } from 'enzyme';
import * as _ from 'lodash-es';

import { Firehose, FirehoseResource, firehoseFor } from '../../../public/components/utils';

describe('firehoseFor', () => {
type ComponentProps = {pods: any, namespace: string};
let resources: {pods: FirehoseResource};
const Component: React.SFC<ComponentProps> = (props) => <span>{props.pods.loaded}</span>;

beforeEach(() => {
resources = {
pods: {kind: 'Pod', isList: true},
};
});

it('returns a component which accepts a render callback', (done) => {
const FirehosedComponent = firehoseFor(resources);
const render = () => {
done();
return <div />;
};

shallow(<FirehosedComponent render={render} />).childAt(0).dive();
});

it('wraps render callback component in a `Firehose`', () => {
const FirehosedComponent = firehoseFor(resources);
const wrapper = shallow(<FirehosedComponent render={(props) => <Component namespace="default" pods={props.pods} />} />);

expect(wrapper.find(Firehose).childAt(0).shallow().find(Component).exists()).toBe(true);
});

it('passes `resources` to `Firehose` component', () => {
const FirehosedComponent = firehoseFor(resources);
const wrapper = shallow(<FirehosedComponent render={(props) => <Component namespace="default" pods={props.pods} />} />);

expect(wrapper.find(Firehose).props().resources).toEqual(_.map(resources, (res, prop) => ({...res, prop})));
});
});
51 changes: 19 additions & 32 deletions frontend/public/components/cloud-services/catalog-source.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { match, Link } from 'react-router-dom';
import { Helmet } from 'react-helmet';
import { safeLoad } from 'js-yaml';

import { NavTitle, Firehose, MsgBox, LoadingBox, withFallback } from '../utils';
import { NavTitle, MsgBox, LoadingBox, withFallback, firehoseFor } from '../utils';
import { CreateYAML } from '../create-yaml';
import { ClusterServiceVersionLogo, SubscriptionKind, CatalogSourceKind, ClusterServiceVersionKind, Package } from './index';
import { SubscriptionModel, CatalogSourceModel } from '../../models';
Expand Down Expand Up @@ -97,34 +97,26 @@ export const CatalogSourceDetails: React.SFC<CatalogSourceDetailsProps> = ({cata
: <div />;
};

export const CatalogSourceFirehose = firehoseFor({
catalogSource: {kind: referenceForModel(CatalogSourceModel), name: 'tectonic-ocs', namespace: 'tectonic-system', isList: false},
subscription: {kind: referenceForModel(SubscriptionModel), isList: true},
configMap: {kind: 'ConfigMap', isList: false, name: 'tectonic-ocs', namespace: 'tectonic-system'},
});

export const CatalogSourceDetailsPage: React.SFC<CatalogSourceDetailsPageProps> = (props) => <div>
<Helmet>
<title>Open Cloud Services</title>
</Helmet>
<NavTitle detail={true} title="Open Cloud Services" />
<Firehose
{...props}
resources={[{
kind: referenceForModel(CatalogSourceModel),
name: 'tectonic-ocs',
namespace: 'tectonic-system',
isList: false,
prop: 'catalogSource',
}, {
kind: referenceForModel(SubscriptionModel),
isList: true,
prop: 'subscription',
}, {
kind: 'ConfigMap',
isList: false,
name: 'tectonic-ocs',
namespace: 'tectonic-system',
prop: 'configMap'}]}>
{/* FIXME(alecmerdler): Hack because `Firehose` injects props without TypeScript knowing about it */}
<CatalogSourceDetails {...props as any} ns={props.match.params.ns} />
</Firehose>
<CatalogSourceFirehose render={({configMap, subscription, catalogSource}) =>
<CatalogSourceDetails ns={props.match.params.ns} catalogSource={catalogSource} configMap={configMap} subscription={subscription} />
} />
</div>;

export const CreateSubscriptionYAMLFirehose = firehoseFor({
ConfigMap: {kind: 'ConfigMap', isList: false, name: 'tectonic-ocs', namespace: 'tectonic-system'}
});

export const CreateSubscriptionYAML: React.SFC<CreateSubscriptionYAMLProps> = (props) => {
type CreateProps = {ConfigMap: {loaded: boolean, data: K8sResourceKind}};
const Create = withFallback<CreateProps>((createProps) => {
Expand All @@ -150,16 +142,9 @@ export const CreateSubscriptionYAML: React.SFC<CreateSubscriptionYAMLProps> = (p
return <LoadingBox />;
}, () => <MsgBox title="Package Not Found" detail="Cannot create a Subscription to a non-existent package." />);

return <Firehose resources={[{
kind: 'ConfigMap',
isList: false,
name: 'tectonic-ocs',
namespace: 'tectonic-system',
prop: 'ConfigMap'
}]}>
{/* FIXME(alecmerdler): Hack because `Firehose` injects props without TypeScript knowing about it */}
<Create {...props as any} />
</Firehose>;
return <CreateSubscriptionYAMLFirehose render={({ConfigMap}) =>
<Create ConfigMap={ConfigMap} />
} />;
};

export type PackageHeaderProps = {
Expand Down Expand Up @@ -203,3 +188,5 @@ PackageList.displayName = 'PackageList';
CatalogSourceDetails.displayName = 'CatalogSourceDetails';
CatalogSourceDetailsPage.displayName = 'CatalogSourceDetailPage';
CreateSubscriptionYAML.displayName = 'CreateSubscriptionYAML';
CatalogSourceFirehose.displayName = 'CatalogSourceFirehose';
CreateSubscriptionYAMLFirehose.displayName = 'CreateSubscriptionYAMLFirehose';
25 changes: 15 additions & 10 deletions frontend/public/components/cloud-services/create-crd-yaml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { safeDump } from 'js-yaml';
import { match } from 'react-router-dom';
import * as _ from 'lodash-es';

import { Firehose, LoadingBox } from '../utils';
import { firehoseFor, LoadingBox } from '../utils';
import { CreateYAML } from '../create-yaml';
import { referenceForModel, K8sResourceKind, K8sResourceKindReference, kindForReference, apiVersionForReference } from '../../module/k8s';
import { ClusterServiceVersionModel } from '../../models';
Expand All @@ -19,6 +19,17 @@ import { ocsTemplates } from './ocs-templates';
export const CreateCRDYAML: React.SFC<CreateCRDYAMLProps> = (props) => {
const annotationKey = 'alm-examples';

const CreateCRDYAMLFirehose = firehoseFor({
ClusterServiceVersion: {
kind: referenceForModel(ClusterServiceVersionModel),
name: props.match.params.appName,
namespace: props.match.params.ns,
isList: false,
prop: 'ClusterServiceVersion'
}
});
CreateCRDYAMLFirehose.displayName = 'CreateCRDYAMLFirehose';

const Create = (createProps: {ClusterServiceVersion: {loaded: boolean, data: ClusterServiceVersionKind}}) => {
if (createProps.ClusterServiceVersion.loaded && createProps.ClusterServiceVersion.data) {
try {
Expand All @@ -34,15 +45,9 @@ export const CreateCRDYAML: React.SFC<CreateCRDYAMLProps> = (props) => {
return <LoadingBox />;
};

return <Firehose resources={[{
kind: referenceForModel(ClusterServiceVersionModel),
name: props.match.params.appName,
namespace: props.match.params.ns,
isList: false,
prop: 'ClusterServiceVersion'
}]}>
<Create {...props as any} />
</Firehose>;
return <CreateCRDYAMLFirehose render={({ClusterServiceVersion}) =>
<Create ClusterServiceVersion={ClusterServiceVersion} />
} />;
};

export type CreateCRDYAMLProps = {
Expand Down
13 changes: 9 additions & 4 deletions frontend/public/components/create-yaml.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { safeLoad } from 'js-yaml';
import { TEMPLATES } from '../yaml-templates';
import { connectToPlural } from '../kinds';
import { AsyncComponent } from './utils/async';
import { Firehose, LoadingBox } from './utils';
import { LoadingBox, firehoseFor } from './utils';
import { K8sKind, apiVersionForModel } from '../module/k8s';
import { ErrorPage404 } from './error';
import { ClusterServiceVersionModel } from '../models';
Expand Down Expand Up @@ -51,9 +51,14 @@ export const CreateYAML = connectToPlural((props: CreateYAMLProps) => {

export const EditYAMLPage: React.SFC<EditYAMLPageProps> = (props) => {
const Wrapper = (wrapperProps) => <AsyncComponent {...wrapperProps} obj={wrapperProps.obj.data} loader={() => import('./edit-yaml').then(c => c.EditYAML)} create={false} showHeader={true} />;
return <Firehose resources={[{kind: props.kind, name: props.match.params.name, namespace: props.match.params.ns, isList: false, prop: 'obj'}]}>
<Wrapper />
</Firehose>;
const EditYAMLPageFirehose = firehoseFor({
obj: {kind: props.kind, name: props.match.params.name, namespace: props.match.params.ns, isList: false},
});
EditYAMLPageFirehose.displayName = 'EditYAMLPageFirehose';

return <EditYAMLPageFirehose render={({obj}) =>
<Wrapper obj={obj} />
} />;
};

/* eslint-disable no-undef */
Expand Down
Loading