Skip to content

Commit

Permalink
1495 5 app repos for namespace only (#1503)
Browse files Browse the repository at this point in the history
* Initial displaying of app repos per namespace.

* Refetch on namespace change. Use kubeapps namespace for _all

* Add info to help with change.

* Update snapshot

* Update to display app repos across all namespaces when All Namespaces selected.

* Remove confusing sentence.
  • Loading branch information
absoludity authored Feb 9, 2020
1 parent 9cac48d commit c399f08
Show file tree
Hide file tree
Showing 13 changed files with 257 additions and 34 deletions.
25 changes: 23 additions & 2 deletions dashboard/src/actions/repos.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ const mockStore = configureMockStore([thunk]);

let store: any;
const appRepo = { spec: { resyncRequests: 10000 } };
const kubeappsNamespace = "kubeapps-namespace";

beforeEach(() => {
store = mockStore({ config: { namespace: "my-namespace" } });
store = mockStore({ config: { namespace: kubeappsNamespace } });
AppRepository.list = jest.fn().mockImplementationOnce(() => {
return { items: { foo: "bar" } };
});
Expand Down Expand Up @@ -85,6 +86,7 @@ describe("deleteRepo", () => {
const expectedActions = [
{
type: getType(repoActions.requestRepos),
payload: kubeappsNamespace,
},
{
type: getType(repoActions.receiveRepos),
Expand Down Expand Up @@ -148,10 +150,28 @@ describe("resyncRepo", () => {
});

describe("fetchRepos", () => {
const namespace = "default";
it("dispatches requestRepos and receivedRepos if no error", async () => {
const expectedActions = [
{
type: getType(repoActions.requestRepos),
payload: namespace,
},
{
type: getType(repoActions.receiveRepos),
payload: { foo: "bar" },
},
];

await store.dispatch(repoActions.fetchRepos(namespace));
expect(store.getActions()).toEqual(expectedActions);
});

it("defaults to apprepos in kubeapps own namespace if none specified", async () => {
const expectedActions = [
{
type: getType(repoActions.requestRepos),
payload: kubeappsNamespace,
},
{
type: getType(repoActions.receiveRepos),
Expand All @@ -171,14 +191,15 @@ describe("fetchRepos", () => {
const expectedActions = [
{
type: getType(repoActions.requestRepos),
payload: namespace,
},
{
type: getType(repoActions.errorRepos),
payload: { err: new Error("Boom!"), op: "fetch" },
},
];

await store.dispatch(repoActions.fetchRepos());
await store.dispatch(repoActions.fetchRepos(namespace));
expect(store.getActions()).toEqual(expectedActions);
});
});
Expand Down
22 changes: 16 additions & 6 deletions dashboard/src/actions/repos.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ export const addedRepo = createAction("ADDED_REPO", resolve => {
return (added: IAppRepository) => resolve(added);
});

export const requestRepos = createAction("REQUEST_REPOS");
export const requestRepos = createAction("REQUEST_REPOS", resolve => {
return (namespace: string) => resolve(namespace);
});
export const receiveRepos = createAction("RECEIVE_REPOS", resolve => {
return (repos: IAppRepository[]) => resolve(repos);
});
Expand Down Expand Up @@ -107,13 +109,21 @@ export const resyncAllRepos = (
};
};

export const fetchRepos = (): ThunkAction<Promise<void>, IStoreState, null, AppReposAction> => {
// fetchRepos fetches the AppRepositories in a specified namespace, defaulting to those
// in Kubeapps' own namespace for backwards compatibility.
export const fetchRepos = (
namespace = "",
): ThunkAction<Promise<void>, IStoreState, null, AppReposAction> => {
return async (dispatch, getState) => {
dispatch(requestRepos());
try {
const {
config: { namespace },
} = getState();
// Default to the kubeapps' namespace for existing call-sites until we
// need to explicitly get repos for a specific namespace as well as
// the global app repos from kubeapps' namespace.
if (namespace === "") {
const { config } = getState();
namespace = config.namespace;
}
dispatch(requestRepos(namespace));
const repos = await AppRepository.list(namespace);
dispatch(receiveRepos(repos.items));
} catch (e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { AppRepoForm } from "./AppRepoForm";

const defaultProps = {
install: jest.fn(),
kubeappsNamespace: "kubeapps",
namespace: "kubeapps",
};

it("should open a modal with the repository form", () => {
Expand Down
4 changes: 2 additions & 2 deletions dashboard/src/components/Config/AppRepoList/AppRepoButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ interface IAppRepoAddButtonProps {
syncJobPodTemplate: string,
) => Promise<boolean>;
redirectTo?: string;
kubeappsNamespace: string;
namespace: string;
}
interface IAppRepoAddButtonState {
lastSubmittedName: string;
Expand Down Expand Up @@ -63,7 +63,7 @@ export class AppRepoAddButton extends React.Component<
error={this.props.error}
defaultRequiredRBACRoles={{ create: RequiredRBACRoles }}
action="create"
namespace={this.props.kubeappsNamespace}
namespace={this.props.namespace}
resource={`App Repository ${this.state.lastSubmittedName}`}
/>
)}
Expand Down
80 changes: 80 additions & 0 deletions dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { shallow } from "enzyme";
import * as React from "react";

import AppRepoList from "./AppRepoList";

const defaultNamespace = "default-namespace";

const defaultProps = {
errors: {},
repos: [],
fetchRepos: jest.fn(),
deleteRepo: jest.fn(),
resyncRepo: jest.fn(),
resyncAllRepos: jest.fn(),
install: jest.fn(),
namespace: defaultNamespace,
displayReposPerNamespaceMsg: false,
};

describe("AppRepoList", () => {
it("fetches repos for a namespace when mounted", () => {
const props = {
...defaultProps,
fetchRepos: jest.fn(),
};

shallow(<AppRepoList {...props} />);

expect(props.fetchRepos).toHaveBeenCalledWith(defaultNamespace);
});

it("refetches repos when updating after a fetch error is cleared", () => {
const props = {
...defaultProps,
errors: { fetch: new Error("Bang!") },
fetchRepos: jest.fn(),
};

const wrapper = shallow(<AppRepoList {...props} />);
wrapper.setProps({
...props,
errors: {},
});

expect(props.fetchRepos).toHaveBeenCalledTimes(2);
expect(props.fetchRepos).toHaveBeenLastCalledWith(defaultNamespace);
});

it("refetches repos when the namespace changes", () => {
const props = {
...defaultProps,
fetchRepos: jest.fn(),
};
const differentNamespace = "different-namespace";

const wrapper = shallow(<AppRepoList {...props} />);
wrapper.setProps({
...props,
namespace: differentNamespace,
});

expect(props.fetchRepos).toHaveBeenCalledTimes(2);
expect(props.fetchRepos).toHaveBeenLastCalledWith(differentNamespace);
});

it("does not refetch otherwise", () => {
const props = {
...defaultProps,
fetchRepos: jest.fn(),
};

const wrapper = shallow(<AppRepoList {...props} />);
wrapper.setProps({
...props,
errors: { fetch: new Error("Bang!") },
});

expect(props.fetchRepos).toHaveBeenCalledTimes(1);
});
});
52 changes: 37 additions & 15 deletions dashboard/src/components/Config/AppRepoList/AppRepoList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import * as React from "react";

import { definedNamespaces } from "../../../shared/Namespace";
import { IAppRepository, IRBACRole } from "../../../shared/types";
import ErrorSelector from "../../ErrorAlert/ErrorSelector";
import { ErrorSelector, MessageAlert } from "../../ErrorAlert";
import { AppRepoAddButton } from "./AppRepoButton";
import { AppRepoListItem } from "./AppRepoListItem";
import { AppRepoRefreshAllButton } from "./AppRepoRefreshAllButton";
Expand All @@ -14,7 +15,7 @@ export interface IAppRepoListProps {
update?: Error;
};
repos: IAppRepository[];
fetchRepos: () => void;
fetchRepos: (namespace: string) => void;
deleteRepo: (name: string) => Promise<boolean>;
resyncRepo: (name: string) => void;
resyncAllRepos: (names: string[]) => void;
Expand All @@ -25,7 +26,8 @@ export interface IAppRepoListProps {
customCA: string,
syncJobPodTemplate: string,
) => Promise<boolean>;
kubeappsNamespace: string;
namespace: string;
displayReposPerNamespaceMsg: boolean;
}

const RequiredRBACRoles: { [s: string]: IRBACRole[] } = {
Expand Down Expand Up @@ -54,17 +56,18 @@ const RequiredRBACRoles: { [s: string]: IRBACRole[] } = {

class AppRepoList extends React.Component<IAppRepoListProps> {
public componentDidMount() {
this.props.fetchRepos();
this.props.fetchRepos(this.props.namespace);
}

public componentDidUpdate(prevProps: IAppRepoListProps) {
const {
errors: { fetch },
fetchRepos,
namespace,
} = this.props;
// refetch if error removed due to location change
if (prevProps.errors.fetch && !fetch) {
fetchRepos();
// refetch if namespace changes or if error removed due to location change
if (prevProps.namespace !== namespace || (prevProps.errors.fetch && !fetch)) {
fetchRepos(namespace);
}
}

Expand All @@ -76,8 +79,10 @@ class AppRepoList extends React.Component<IAppRepoListProps> {
deleteRepo,
resyncRepo,
resyncAllRepos,
kubeappsNamespace,
namespace,
displayReposPerNamespaceMsg,
} = this.props;
const renderNamespace = namespace === definedNamespaces.all;
return (
<div className="app-repo-list">
<h1>App Repositories</h1>
Expand All @@ -88,6 +93,7 @@ class AppRepoList extends React.Component<IAppRepoListProps> {
<thead>
<tr>
<th>Repo</th>
{renderNamespace && <th>Namespace</th>}
<th>URL</th>
<th>Actions</th>
</tr>
Expand All @@ -99,20 +105,36 @@ class AppRepoList extends React.Component<IAppRepoListProps> {
deleteRepo={deleteRepo}
resyncRepo={resyncRepo}
repo={repo}
renderNamespace={renderNamespace}
/>
))}
</tbody>
</table>
<AppRepoAddButton
error={errors.create}
install={install}
kubeappsNamespace={kubeappsNamespace}
/>
<AppRepoAddButton error={errors.create} install={install} namespace={namespace} />
<AppRepoRefreshAllButton
resyncAllRepos={resyncAllRepos}
repos={repos}
kubeappsNamespace={kubeappsNamespace}
namespace={namespace}
/>
{displayReposPerNamespaceMsg && (
<MessageAlert header="Looking for other app repositories?">
<div>
<p className="margin-v-normal">
You can view App Repositories across all namespaces by selecting "All Namespaces"
above, if you have permission to view App Repositories cluster-wide.
</p>
<p className="margin-v-normal">
Kubeapps now enables you to create App Repositories in your own namespace that will
be available in your own namespace and, in the future, optionally available in other
namespaces to which you have access. You can read more information in the{" "}
<a href="https://github.com/kubeapps/kubeapps/blob/master/docs/user/private-app-repository.md#per-namespace-app-repositories">
Private App Repository docs
</a>
.
</p>
</div>
</MessageAlert>
)}
</div>
);
}
Expand All @@ -123,7 +145,7 @@ class AppRepoList extends React.Component<IAppRepoListProps> {
error={this.props.errors[action]}
defaultRequiredRBACRoles={RequiredRBACRoles}
action={action}
namespace={this.props.kubeappsNamespace}
namespace={this.props.namespace}
resource="App Repositories"
/>
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import ConfirmDialog from "../../ConfirmDialog";

interface IAppRepoListItemProps {
repo: IAppRepository;
renderNamespace: boolean;
deleteRepo: (name: string) => Promise<boolean>;
resyncRepo: (name: string) => void;
}
Expand All @@ -20,12 +21,13 @@ export class AppRepoListItem extends React.Component<IAppRepoListItemProps, IApp
};

public render() {
const { repo } = this.props;
const { renderNamespace, repo } = this.props;
return (
<tr key={repo.metadata.name}>
<td>
<Link to={`/catalog/${repo.metadata.name}`}>{repo.metadata.name}</Link>
</td>
{renderNamespace && <td>{repo.metadata.namespace}</td>}
<td>{repo.spec && repo.spec.url}</td>
<td>
<ConfirmDialog
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "./AppRepo.css";
interface IAppRepoRefreshAllButtonProps {
resyncAllRepos: (names: string[]) => void;
repos: IAppRepository[];
kubeappsNamespace: string;
namespace: string;
}

export class AppRepoRefreshAllButton extends React.Component<IAppRepoRefreshAllButtonProps> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
exports[`should open a modal with the repository form 1`] = `
<AppRepoAddButton
install={[Function]}
kubeappsNamespace="kubeapps"
namespace="kubeapps"
>
<button
className="button button-primary"
Expand Down
Loading

0 comments on commit c399f08

Please sign in to comment.