Skip to content

Commit

Permalink
Update url.app.apps.{get|upgrade} with required cluster arg. (#1833)
Browse files Browse the repository at this point in the history
Also adds
* More info when deployment fails on CI.
* Restrict mongodb dependency to ~7.14 rather than >7.14 (as 8 is not currently compatible)
  • Loading branch information
absoludity committed Jul 1, 2020
1 parent 9c14870 commit f810c08
Show file tree
Hide file tree
Showing 18 changed files with 217 additions and 143 deletions.
8 changes: 4 additions & 4 deletions chart/kubeapps/requirements.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
dependencies:
- name: mongodb
repository: https://charts.bitnami.com/bitnami
version: 7.11.1
version: 7.14.8
- name: postgresql
repository: https://charts.bitnami.com/bitnami
version: 8.9.1
digest: sha256:bec1eb6964d077d775be68be65a04a46a7244d426091b6179acf2df9076a76f2
generated: "2020-04-22T10:21:48.840081009Z"
version: 8.10.11
digest: sha256:ab1c99273fd342dce2879dece3c38eb0d525a1df870758e873ca293cdd857a25
generated: "2020-07-01T14:11:29.916518817+10:00"
2 changes: 1 addition & 1 deletion chart/kubeapps/requirements.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
dependencies:
- name: mongodb
version: "> 7.10.2"
version: "~ 7.14.2"
repository: https://charts.bitnami.com/bitnami
condition: mongodb.enabled
- name: postgresql
Expand Down
3 changes: 2 additions & 1 deletion dashboard/src/components/AppList/AppList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ class AppList extends React.Component<IAppListProps, IAppListState> {
public appListItems() {
const {
apps: { listOverview },
cluster,
customResources,
} = this.props;
const filteredReleases = this.filteredReleases(listOverview || [], this.state.filter);
Expand All @@ -148,7 +149,7 @@ class AppList extends React.Component<IAppListProps, IAppListState> {
<div>
<CardGrid>
{filteredReleases.map(r => {
return <AppListItem key={r.releaseName} app={r} />;
return <AppListItem key={r.releaseName} app={r} cluster={cluster} />;
})}
{filteredCRs.map(r => {
const csv = this.props.csvs.find(c =>
Expand Down
146 changes: 66 additions & 80 deletions dashboard/src/components/AppList/AppListItem.test.tsx
Original file line number Diff line number Diff line change
@@ -1,25 +1,26 @@
import { shallow } from "enzyme";
import * as React from "react";
import { Link } from "react-router-dom";
import { IAppOverview } from "../../shared/types";
import * as url from "../../shared/url";
import InfoCard from "../InfoCard";
import AppListItem from "./AppListItem";
import AppListItem, { IAppListItemProps } from "./AppListItem";

const defaultProps = {
app: {
namespace: "default",
releaseName: "foo",
status: "DEPLOYED",
version: "1.0.0",
chart: "myapp",
chartMetadata: {
appVersion: "1.0.0",
},
},
cluster: "default",
} as IAppListItemProps;

it("renders an app item", () => {
const wrapper = shallow(
<AppListItem
app={
{
namespace: "default",
releaseName: "foo",
status: "DEPLOYED",
version: "1.0.0",
chart: "myapp",
} as IAppOverview
}
/>,
);
const wrapper = shallow(<AppListItem {...defaultProps} />);
const card = wrapper.find(InfoCard).shallow();
expect(
card
Expand All @@ -32,88 +33,73 @@ it("renders an app item", () => {
.find(Link)
.at(0)
.props().to,
).toBe(url.app.apps.get("foo", "default"));
).toBe(url.app.apps.get("default", "default", "foo"));
expect(card.find(".type-color-light-blue").text()).toBe("myapp v1.0.0");
expect(card.find(".deployed").exists()).toBe(true);
expect(card.find(".ListItem__content__info_tag-1").text()).toBe("default");
expect(card.find(".ListItem__content__info_tag-2").text()).toBe("deployed");
});

it("should set a banner if there are chart updates available", () => {
const wrapper = shallow(
<AppListItem
app={
{
namespace: "default",
releaseName: "foo",
status: "DEPLOYED",
version: "1.0.0",
chart: "myapp",
chartMetadata: {
appVersion: "1.1.0",
},
updateInfo: {
upToDate: false,
chartLatestVersion: "1.1.0",
appLatestVersion: "1.1.0",
repository: { name: "", url: "" },
},
} as IAppOverview
}
/>,
);
const props = {
...defaultProps,
app: {
...defaultProps.app,
chartMetadata: {
appVersion: "1.1.0",
},
updateInfo: {
upToDate: false,
chartLatestVersion: "1.1.0",
appLatestVersion: "1.1.0",
repository: { name: "", url: "" },
},
},
} as IAppListItemProps;
const wrapper = shallow(<AppListItem {...props} />);
const card = wrapper.find(InfoCard);
expect(card.prop("banner")).toBe("Update available");
});

it("should set a banner if there are app updates available", () => {
const wrapper = shallow(
<AppListItem
app={
{
namespace: "default",
releaseName: "foo",
status: "DEPLOYED",
version: "1.0.0",
chart: "myapp",
chartMetadata: {
appVersion: "1.0.0",
},
updateInfo: {
upToDate: false,
chartLatestVersion: "1.1.0",
appLatestVersion: "1.1.0",
repository: { name: "", url: "" },
},
} as IAppOverview
}
/>,
);
const props = {
...defaultProps,
app: {
...defaultProps.app,
chartMetadata: {
appVersion: "1.0.0",
},
updateInfo: {
upToDate: false,
chartLatestVersion: "1.0.0",
appLatestVersion: "1.1.0",
repository: { name: "", url: "" },
},
},
} as IAppListItemProps;
const wrapper = shallow(<AppListItem {...props} />);
const card = wrapper.find(InfoCard);
expect(card.prop("banner")).toBe("Update available");
});

it("should not set a banner if there are errors in the update info", () => {
const wrapper = shallow(
<AppListItem
app={
{
namespace: "default",
releaseName: "foo",
status: "DEPLOYED",
version: "1.0.0",
chart: "myapp",
updateInfo: {
error: new Error("Boom!"),
upToDate: false,
chartLatestVersion: "",
appLatestVersion: "",
repository: { name: "", url: "" },
},
} as IAppOverview
}
/>,
);
const props = {
...defaultProps,
app: {
...defaultProps.app,
chartMetadata: {
appVersion: "1.0.0",
},
updateInfo: {
upToDate: false,
error: new Error("Boom!"),
chartLatestVersion: "1.0.0",
appLatestVersion: "1.1.0",
repository: { name: "", url: "" },
},
},
} as IAppListItemProps;
const wrapper = shallow(<AppListItem {...props} />);
const card = wrapper.find(InfoCard);
expect(card.prop("banner")).toBe(undefined);
});
7 changes: 4 additions & 3 deletions dashboard/src/components/AppList/AppListItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,14 @@ import * as url from "../../shared/url";
import InfoCard from "../InfoCard";
import "./AppListItem.css";

interface IAppListItemProps {
export interface IAppListItemProps {
app: IAppOverview;
cluster: string;
}

class AppListItem extends React.Component<IAppListItemProps> {
public render() {
const { app } = this.props;
const { app, cluster } = this.props;
const icon = app.icon ? app.icon : placeholder;
const banner =
app.updateInfo && !app.updateInfo.error && !app.updateInfo.upToDate
Expand All @@ -21,7 +22,7 @@ class AppListItem extends React.Component<IAppListItemProps> {
return (
<InfoCard
key={app.releaseName}
link={url.app.apps.get(app.releaseName, app.namespace)}
link={url.app.apps.get(cluster, app.namespace, app.releaseName)}
title={app.releaseName}
icon={icon}
info={`${app.chart} v${app.version || "-"}`}
Expand Down
84 changes: 59 additions & 25 deletions dashboard/src/components/AppView/AppControls/AppControls.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,46 @@ import { mount, shallow } from "enzyme";
import context from "jest-plugin-context";
import * as React from "react";
import Modal from "react-modal";
import { Redirect } from "react-router";
import { Provider } from "react-redux";
import configureMockStore, { MockStore } from "redux-mock-store";
import thunk from "redux-thunk";

import { IRelease } from "shared/types";
import { IStoreState } from "shared/types";
import RollbackButtonContainer from "../../../containers/RollbackButtonContainer";
import { hapi } from "../../../shared/hapi/release";
import itBehavesLike from "../../../shared/specs";
import * as url from "../../../shared/url";
import ConfirmDialog from "../../ConfirmDialog";

import { IRelease } from "shared/types";
import AppControls, { IAppControlsProps } from "./AppControls";
import UpgradeButton from "./UpgradeButton";

const mockStore = configureMockStore([thunk]);

// TODO(absoludity): As we move to function components with (redux) hooks we'll need to
// be including state in tests, so we may want to put things like initialState
// and a generalized getWrapper in a test helpers or similar package?
const initialState = {
apps: {},
auth: {},
catalog: {},
charts: {},
config: {},
kube: {},
clusters: {
currentCluster: "default-cluster",
},
repos: {},
operators: {},
} as IStoreState;

const getWrapper = (store: MockStore, props: IAppControlsProps) =>
mount(
<Provider store={store}>
<AppControls {...props} />
</Provider>,
);

const namespace = "bar";
const defaultProps = {
cluster: "default",
Expand All @@ -21,54 +50,59 @@ const defaultProps = {
push: jest.fn(),
} as IAppControlsProps;

it("calls delete function when clicking the button", done => {
const props = { ...defaultProps, deleteApp: jest.fn().mockReturnValue(true) };
const wrapper = shallow(<AppControls {...props} />);
const button = wrapper
.find(".AppControls")
.children()
.find(".button-danger");
it("calls delete function without purge when clicking the button", done => {
const store = mockStore(initialState);
const push = jest.fn();
const deleteApp = jest.fn().mockReturnValue(true);
const props = {
...defaultProps,
deleteApp,
push,
};
const wrapper = getWrapper(store, props);
const appControls = wrapper.find(AppControls);
const button = appControls.children().find(".button-danger");
expect(button.exists()).toBe(true);
expect(button.text()).toBe("Delete");
button.simulate("click");

const confirm = wrapper
.find(".AppControls")
.children()
.find(ConfirmDialog);
const confirm = appControls.children().find(ConfirmDialog);
expect(confirm.exists()).toBe(true);
confirm.props().onConfirm(); // Simulate confirmation

expect(wrapper.state("deleting")).toBe(true);
expect(appControls.state("deleting")).toBe(true);

// Wait for the async action to finish
setTimeout(() => {
wrapper.update();
const redirect = wrapper.find(Redirect);
expect(redirect.props()).toMatchObject({
to: url.app.apps.list(defaultProps.cluster, namespace),
} as any);
expect(push.mock.calls.length).toBe(1);
expect(push.mock.calls[0]).toEqual([url.app.apps.list(defaultProps.cluster, namespace)]);
done();
}, 1);
expect(deleteApp).toHaveBeenCalledWith(false);
});

it("calls delete function with additional purge", () => {
// Return "false" to avoid redirect when mounting
const deleteApp = jest.fn().mockReturnValue(false);
const props = { ...defaultProps, deleteApp };
// mount() is necessary to render the Modal
const wrapper = mount(<AppControls {...props} />);
const store = mockStore(initialState);
const wrapper = getWrapper(store, props);
Modal.setAppElement(document.createElement("div"));
wrapper.setState({ modalIsOpen: true });
wrapper.update();
const appControls = wrapper.find(AppControls);
const button = appControls.children().find(".button-danger");
expect(button.exists()).toBe(true);
expect(button.text()).toBe("Delete");
button.simulate("click");

// Check that the checkbox changes the AppControls state
const confirm = wrapper.find(ConfirmDialog);
expect(confirm.exists()).toBe(true);
const checkbox = wrapper.find('input[type="checkbox"]');
expect(checkbox.exists()).toBe(true);
expect(wrapper.state("purge")).toBe(false);
expect(appControls.state("purge")).toBe(false);
checkbox.simulate("change");
expect(wrapper.state("purge")).toBe(true);
expect(appControls.state("purge")).toBe(true);

// Check that the "purge" state is forwarded to deleteApp
confirm.props().onConfirm(); // Simulate confirmation
Expand Down
3 changes: 1 addition & 2 deletions dashboard/src/components/AppView/AppControls/AppControls.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { RouterAction } from "connected-react-router";
import * as React from "react";
import { Redirect } from "react-router";

import { IRelease } from "shared/types";
import RollbackButtonContainer from "../../../containers/RollbackButtonContainer";
Expand Down Expand Up @@ -85,7 +84,7 @@ class AppControls extends React.Component<IAppControlsProps, IAppControlsState>
)
}
/>
{this.state.redirectToAppList && <Redirect to={url.app.apps.list(cluster, namespace)} />}
{this.state.redirectToAppList && push(url.app.apps.list(cluster, namespace))}
</div>
);
}
Expand Down

0 comments on commit f810c08

Please sign in to comment.