Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

1495 5 app repos for namespace only #1503

Merged
merged 6 commits into from
Feb 9, 2020
Merged
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
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