Skip to content

Commit

Permalink
Show the list of operators installed (#1613)
Browse files Browse the repository at this point in the history
  • Loading branch information
Andres Martinez Gotor authored Mar 31, 2020
1 parent 5ba52be commit 3865aab
Show file tree
Hide file tree
Showing 6 changed files with 178 additions and 23 deletions.
71 changes: 69 additions & 2 deletions dashboard/src/components/OperatorList/OperatorList.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { shallow } from "enzyme";
import * as React from "react";
import { IPackageManifest } from "shared/types";
import itBehavesLike from "../../shared/specs";
import { CardGrid } from "../Card";
import { ErrorSelector } from "../ErrorAlert";
import InfoCard from "../InfoCard";
import OLMNotFound from "./OLMNotFound";
Expand All @@ -14,6 +15,8 @@ const defaultProps: IOperatorListProps = {
operators: [],
namespace: "default",
getOperators: jest.fn(),
getCSVs: jest.fn(),
csvs: [],
};

const sampleOperator = {
Expand All @@ -24,8 +27,11 @@ const sampleOperator = {
provider: {
name: "kubeapps",
},
defaultChannel: "alpha",
channels: [
{
name: "alpha",
currentCSV: "kubeapps-operator",
currentCSVDesc: {
version: "1.0.0",
annotations: {
Expand All @@ -37,6 +43,26 @@ const sampleOperator = {
},
} as IPackageManifest;

const sampleCSV = {
metadata: { name: "kubeapps-operator" },
spec: {
icon: [{}],
provider: {
name: "kubeapps",
},
customresourcedefinitions: {
owned: [
{
name: "foo.kubeapps.com",
version: "v1alpha1",
kind: "Foo",
resources: [{ kind: "Deployment" }],
},
],
},
},
} as any;

itBehavesLike("aLoadingComponent", {
component: OperatorList,
props: { ...defaultProps, isFetching: true },
Expand All @@ -49,6 +75,17 @@ it("call the OLM check and render the NotFound message if not found", () => {
expect(wrapper.find(OLMNotFound)).toExist();
});

it("re-request operators if the namespace changes", () => {
const getOperators = jest.fn();
const getCSVs = jest.fn();
const wrapper = shallow(
<OperatorList {...defaultProps} getOperators={getOperators} getCSVs={getCSVs} />,
);
wrapper.setProps({ namespace: "other" });
expect(getOperators).toHaveBeenCalledTimes(2);
expect(getCSVs).toHaveBeenCalledTimes(2);
});

it("renders an error if exists", () => {
const wrapper = shallow(
<OperatorList {...defaultProps} isOLMInstalled={true} error={new Error("Boom!")} />,
Expand All @@ -75,11 +112,41 @@ it("skips the error if the OLM is not installed", () => {
expect(wrapper.find(OLMNotFound)).toExist();
});

it("render the operator list if the OLM is installed", () => {
it("render the operator list with installed operators", () => {
const wrapper = shallow(
<OperatorList {...defaultProps} isOLMInstalled={true} operators={[sampleOperator]} />,
<OperatorList
{...defaultProps}
isOLMInstalled={true}
operators={[sampleOperator]}
csvs={[sampleCSV]}
/>,
);
expect(wrapper.find(OLMNotFound)).not.toExist();
expect(wrapper.find(InfoCard)).toExist();
// The section "Available operators" should be empty since all the ops are installed
expect(wrapper.find("h3").filterWhere(c => c.text() === "Installed")).toExist();
expect(
wrapper
.find(CardGrid)
.last()
.children(),
).not.toExist();
expect(wrapper).toMatchSnapshot();
});

it("render the operator list without installed operators", () => {
const wrapper = shallow(
<OperatorList {...defaultProps} isOLMInstalled={true} operators={[sampleOperator]} csvs={[]} />,
);
expect(wrapper.find(OLMNotFound)).not.toExist();
expect(wrapper.find(InfoCard)).toExist();
// The section "Available operators" should not be empty since the operator is not installed
expect(wrapper.find("h3").filterWhere(c => c.text() === "Installed")).not.toExist();
expect(
wrapper
.find(CardGrid)
.last()
.children(),
).toExist();
expect(wrapper).toMatchSnapshot();
});
81 changes: 63 additions & 18 deletions dashboard/src/components/OperatorList/OperatorList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import * as React from "react";

import { IPackageManifest } from "shared/types";
import { api } from "../../shared/url";
import { IClusterServiceVersion, IPackageManifest } from "shared/types";
import { api, app } from "../../shared/url";
import { CardGrid } from "../Card";
import { ErrorSelector, MessageAlert } from "../ErrorAlert";
import InfoCard from "../InfoCard";
Expand All @@ -17,12 +17,22 @@ export interface IOperatorListProps {
getOperators: (namespace: string) => Promise<void>;
operators: IPackageManifest[];
error?: Error;
getCSVs: (namespace: string) => Promise<IClusterServiceVersion[]>;
csvs: IClusterServiceVersion[];
}

class OperatorList extends React.Component<IOperatorListProps> {
public componentDidMount() {
this.props.checkOLMInstalled();
this.props.getOperators(this.props.namespace);
this.props.getCSVs(this.props.namespace);
}

public componentDidUpdate(prevProps: IOperatorListProps) {
if (prevProps.namespace !== this.props.namespace) {
this.props.getOperators(this.props.namespace);
this.props.getCSVs(this.props.namespace);
}
}

public render() {
Expand Down Expand Up @@ -51,7 +61,7 @@ class OperatorList extends React.Component<IOperatorListProps> {
}

private renderOperators() {
const { operators, error } = this.props;
const { operators, error, csvs } = this.props;
if (error) {
return (
<ErrorSelector
Expand All @@ -62,22 +72,57 @@ class OperatorList extends React.Component<IOperatorListProps> {
/>
);
}
const csvNames = csvs.map(csv => csv.metadata.name);
const installedOperators: IPackageManifest[] = [];
const availableOperators: IPackageManifest[] = [];
operators.forEach(operator => {
const defaultChannel = operator.status.defaultChannel;
const channel = operator.status.channels.find(ch => ch.name === defaultChannel);
if (csvNames.some(csvName => csvName === channel?.currentCSV)) {
installedOperators.push(operator);
} else {
availableOperators.push(operator);
}
});
return (
<CardGrid>
{operators.map(operator => {
return (
<InfoCard
key={operator.metadata.name}
link={`/operators/ns/${this.props.namespace}/${operator.metadata.name}`}
title={operator.metadata.name}
icon={api.operators.operatorIcon(this.props.namespace, operator.metadata.name)}
info={`v${operator.status.channels[0].currentCSVDesc.version}`}
tag1Content={operator.status.channels[0].currentCSVDesc.annotations.categories}
tag2Content={operator.status.provider.name}
/>
);
})}
</CardGrid>
<>
{installedOperators.length > 0 && (
<>
<h3>Installed</h3>
<CardGrid>
{installedOperators.map(operator => {
return (
<InfoCard
key={operator.metadata.name}
link={app.operators.view(this.props.namespace, operator.metadata.name)}
title={operator.metadata.name}
icon={api.operators.operatorIcon(this.props.namespace, operator.metadata.name)}
info={`v${operator.status.channels[0].currentCSVDesc.version}`}
tag1Content={operator.status.channels[0].currentCSVDesc.annotations.categories}
tag2Content={operator.status.provider.name}
/>
);
})}
</CardGrid>
</>
)}
<h3>Available Operators</h3>
<CardGrid>
{availableOperators.map(operator => {
return (
<InfoCard
key={operator.metadata.name}
link={app.operators.view(this.props.namespace, operator.metadata.name)}
title={operator.metadata.name}
icon={api.operators.operatorIcon(this.props.namespace, operator.metadata.name)}
info={`v${operator.status.channels[0].currentCSVDesc.version}`}
tag1Content={operator.status.channels[0].currentCSVDesc.annotations.categories}
tag2Content={operator.status.provider.name}
/>
);
})}
</CardGrid>
</>
);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ exports[`loading spinner matches the snapshot 1`] = `
</div>
`;

exports[`render the operator list if the OLM is installed 1`] = `
exports[`render the operator list with installed operators 1`] = `
<div>
<PageHeader>
<h1>
Expand All @@ -58,6 +58,44 @@ exports[`render the operator list if the OLM is installed 1`] = `
loaded={true}
type={0}
>
<h3>
Installed
</h3>
<CardGrid>
<InfoCard
icon="api/v1/namespaces/default/operator/foo/logo"
info="v1.0.0"
key="foo"
link="/operators/ns/default/foo"
tag1Content="security"
tag2Content="kubeapps"
title="foo"
/>
</CardGrid>
<h3>
Available Operators
</h3>
<CardGrid />
</LoadingWrapper>
</main>
</div>
`;

exports[`render the operator list without installed operators 1`] = `
<div>
<PageHeader>
<h1>
Operators
</h1>
</PageHeader>
<main>
<LoadingWrapper
loaded={true}
type={0}
>
<h3>
Available Operators
</h3>
<CardGrid>
<InfoCard
icon="api/v1/namespaces/default/operator/foo/logo"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,13 +17,15 @@ function mapStateToProps(
isOLMInstalled: operators.isOLMInstalled,
operators: operators.operators,
error: operators.errors.fetch,
csvs: operators.csvs,
};
}

function mapDispatchToProps(dispatch: ThunkDispatch<IStoreState, null, Action>) {
return {
checkOLMInstalled: () => dispatch(actions.operators.checkOLMInstalled()),
getOperators: (namespace: string) => dispatch(actions.operators.getOperators(namespace)),
getCSVs: (namespace: string) => dispatch(actions.operators.getCSVs(namespace)),
};
}

Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/reducers/operators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,9 +93,9 @@ const catalogReducer = (
case getType(operators.receiveCustomResource):
return { ...state, isFetching: false, resource: action.payload };
case LOCATION_CHANGE:
return { ...initialState };
return { ...initialState, isOLMInstalled: state.isOLMInstalled };
case getType(actions.namespace.setNamespace):
return { ...initialState };
return { ...initialState, isOLMInstalled: state.isOLMInstalled };
default:
return { ...state };
}
Expand Down
3 changes: 3 additions & 0 deletions dashboard/src/shared/url.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ export const app = {
version: (cv: IChartVersion) =>
`/charts/${cv.relationships.chart.data.repo.name}/${cv.relationships.chart.data.name}/versions/${cv.attributes.version}`,
},
operators: {
view: (namespace: string, name: string) => `/operators/ns/${namespace}/${name}`,
},
operatorInstances: {
update: (namespace: string, csvName: string, crdName: string, instanceName: string) =>
`/operators-instances/ns/${namespace}/${csvName}/${crdName}/update/${instanceName}`,
Expand Down

0 comments on commit 3865aab

Please sign in to comment.