From 16eda686491eba865561a61c4aed3fb85b1e4a9f Mon Sep 17 00:00:00 2001 From: Michael Nelson Date: Thu, 6 Feb 2020 00:58:46 +0000 Subject: [PATCH 1/6] Initial displaying of app repos per namespace. --- dashboard/src/actions/repos.test.tsx | 25 +++++++++- dashboard/src/actions/repos.ts | 22 ++++++--- .../Config/AppRepoList/AppRepoButton.test.tsx | 2 +- .../Config/AppRepoList/AppRepoButton.tsx | 4 +- .../Config/AppRepoList/AppRepoList.test.tsx | 47 ++++++++++++++++++ .../Config/AppRepoList/AppRepoList.tsx | 21 ++++---- .../AppRepoList/AppRepoRefreshAllButton.tsx | 2 +- .../RepoListContainer.test.tsx | 48 +++++++++++++++++++ .../RepoListContainer/RepoListContainer.ts | 12 +++-- 9 files changed, 155 insertions(+), 28 deletions(-) create mode 100644 dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx create mode 100644 dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx diff --git a/dashboard/src/actions/repos.test.tsx b/dashboard/src/actions/repos.test.tsx index 8256f9762e2..14bdece01bc 100644 --- a/dashboard/src/actions/repos.test.tsx +++ b/dashboard/src/actions/repos.test.tsx @@ -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" } }; }); @@ -85,6 +86,7 @@ describe("deleteRepo", () => { const expectedActions = [ { type: getType(repoActions.requestRepos), + payload: kubeappsNamespace, }, { type: getType(repoActions.receiveRepos), @@ -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), @@ -171,6 +191,7 @@ describe("fetchRepos", () => { const expectedActions = [ { type: getType(repoActions.requestRepos), + payload: namespace, }, { type: getType(repoActions.errorRepos), @@ -178,7 +199,7 @@ describe("fetchRepos", () => { }, ]; - await store.dispatch(repoActions.fetchRepos()); + await store.dispatch(repoActions.fetchRepos(namespace)); expect(store.getActions()).toEqual(expectedActions); }); }); diff --git a/dashboard/src/actions/repos.ts b/dashboard/src/actions/repos.ts index bb8fba2e4ab..2b72a50550f 100644 --- a/dashboard/src/actions/repos.ts +++ b/dashboard/src/actions/repos.ts @@ -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); }); @@ -107,13 +109,21 @@ export const resyncAllRepos = ( }; }; -export const fetchRepos = (): ThunkAction, 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, 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) { diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoButton.test.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoButton.test.tsx index 63f45e42211..508d68de31d 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoButton.test.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoButton.test.tsx @@ -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", () => { diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoButton.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoButton.tsx index 196b0473231..fd84d58e5f9 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoButton.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoButton.tsx @@ -30,7 +30,7 @@ interface IAppRepoAddButtonProps { syncJobPodTemplate: string, ) => Promise; redirectTo?: string; - kubeappsNamespace: string; + namespace: string; } interface IAppRepoAddButtonState { lastSubmittedName: string; @@ -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}`} /> )} diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx new file mode 100644 index 00000000000..641fb54988b --- /dev/null +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx @@ -0,0 +1,47 @@ +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, +}; + +describe("AppRepoList", () => { + it("fetches repos for a namespace when mounted", () => { + const props = { + ...defaultProps, + fetchRepos: jest.fn(), + }; + + shallow(); + + expect(props.fetchRepos).toHaveBeenCalledWith(defaultNamespace); + }); + + it("fetches repos when updating after a fetch error is cleared", () => { + const props = { + ...defaultProps, + errors: { fetch: new Error("Bang!") }, + fetchRepos: jest.fn(), + }; + + const wrapper = shallow(); + wrapper.setProps({ + ...props, + errors: {}, + }); + + expect(props.fetchRepos).toHaveBeenCalledTimes(2); + expect(props.fetchRepos).toHaveBeenLastCalledWith(defaultNamespace); + }); +}); diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx index b2199d18596..8fabe928437 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx @@ -14,7 +14,7 @@ export interface IAppRepoListProps { update?: Error; }; repos: IAppRepository[]; - fetchRepos: () => void; + fetchRepos: (namespace: string) => void; deleteRepo: (name: string) => Promise; resyncRepo: (name: string) => void; resyncAllRepos: (names: string[]) => void; @@ -25,7 +25,7 @@ export interface IAppRepoListProps { customCA: string, syncJobPodTemplate: string, ) => Promise; - kubeappsNamespace: string; + namespace: string; } const RequiredRBACRoles: { [s: string]: IRBACRole[] } = { @@ -54,17 +54,18 @@ const RequiredRBACRoles: { [s: string]: IRBACRole[] } = { class AppRepoList extends React.Component { 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(); + fetchRepos(namespace); } } @@ -76,7 +77,7 @@ class AppRepoList extends React.Component { deleteRepo, resyncRepo, resyncAllRepos, - kubeappsNamespace, + namespace, } = this.props; return (
@@ -103,15 +104,11 @@ class AppRepoList extends React.Component { ))} - +
); @@ -123,7 +120,7 @@ class AppRepoList extends React.Component { error={this.props.errors[action]} defaultRequiredRBACRoles={RequiredRBACRoles} action={action} - namespace={this.props.kubeappsNamespace} + namespace={this.props.namespace} resource="App Repositories" /> ); diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoRefreshAllButton.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoRefreshAllButton.tsx index 38c786eefab..8aa7e836c1b 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoRefreshAllButton.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoRefreshAllButton.tsx @@ -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 { diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx new file mode 100644 index 00000000000..6af7bed2bf9 --- /dev/null +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx @@ -0,0 +1,48 @@ +import { shallow } from "enzyme"; +import * as React from "react"; +import configureMockStore from "redux-mock-store"; +import thunk from "redux-thunk"; + +import RepoListContainer from "."; + +const mockStore = configureMockStore([thunk]); +const currentNamespace = "current-namespace"; +const kubeappsNamespace = "kubeapps-namespace"; + +const defaultState = { + config: { + featureFlags: { reposPerNamespace: false }, + namespace: kubeappsNamespace, + }, + namespace: { current: currentNamespace }, + repos: {}, +}; + +describe("RepoListContainer props", () => { + it("uses kubeapps namespace when reposPerNamespace false", () => { + const store = mockStore(defaultState); + const wrapper = shallow(); + + const component = wrapper.find("AppRepoList"); + + expect(component).toHaveProp({ + namespace: kubeappsNamespace, + }); + }); + + it("uses the current namespace when reposPerNamespace is true", () => { + const store = mockStore({ + ...defaultState, + config: { + featureFlags: { reposPerNamespace: true }, + }, + }); + const wrapper = shallow(); + + const component = wrapper.find("AppRepoList"); + + expect(component).toHaveProp({ + namespace: currentNamespace, + }); + }); +}); diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts index 72f57d3f995..8bf4288c7bf 100644 --- a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts @@ -6,10 +6,14 @@ import actions from "../../actions"; import AppRepoList from "../../components/Config/AppRepoList"; import { IStoreState } from "../../shared/types"; -function mapStateToProps({ repos, config }: IStoreState) { +function mapStateToProps({ config, namespace, repos }: IStoreState) { + let repoNamespace = config.namespace; + if (config.featureFlags.reposPerNamespace) { + repoNamespace = namespace.current; + } return { errors: repos.errors, - kubeappsNamespace: config.namespace, + namespace: repoNamespace, repos: repos.repos, }; } @@ -19,8 +23,8 @@ function mapDispatchToProps(dispatch: ThunkDispatch) deleteRepo: async (name: string) => { return dispatch(actions.repos.deleteRepo(name)); }, - fetchRepos: async () => { - return dispatch(actions.repos.fetchRepos()); + fetchRepos: async (namespace: string) => { + return dispatch(actions.repos.fetchRepos(namespace)); }, install: async ( name: string, From 445055fdb3d9c6c493ae2f55cf66e6af1ec51728 Mon Sep 17 00:00:00 2001 From: Michael Nelson Date: Thu, 6 Feb 2020 01:23:59 +0000 Subject: [PATCH 2/6] Refetch on namespace change. Use kubeapps namespace for _all --- .../Config/AppRepoList/AppRepoList.test.tsx | 34 ++++++++++++++++++- .../Config/AppRepoList/AppRepoList.tsx | 4 +-- .../RepoListContainer.test.tsx | 19 +++++++++++ .../RepoListContainer/RepoListContainer.ts | 3 +- 4 files changed, 56 insertions(+), 4 deletions(-) diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx index 641fb54988b..ff3ed132490 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx @@ -28,7 +28,7 @@ describe("AppRepoList", () => { expect(props.fetchRepos).toHaveBeenCalledWith(defaultNamespace); }); - it("fetches repos when updating after a fetch error is cleared", () => { + it("refetches repos when updating after a fetch error is cleared", () => { const props = { ...defaultProps, errors: { fetch: new Error("Bang!") }, @@ -44,4 +44,36 @@ describe("AppRepoList", () => { 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(); + 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(); + wrapper.setProps({ + ...props, + errors: { fetch: new Error("Bang!") }, + }); + + expect(props.fetchRepos).toHaveBeenCalledTimes(1); + }); }); diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx index 8fabe928437..ead4475efd5 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx @@ -63,8 +63,8 @@ class AppRepoList extends React.Component { fetchRepos, namespace, } = this.props; - // refetch if error removed due to location change - if (prevProps.errors.fetch && !fetch) { + // refetch if namespace changes or if error removed due to location change + if (prevProps.namespace !== namespace || (prevProps.errors.fetch && !fetch)) { fetchRepos(namespace); } } diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx index 6af7bed2bf9..e828f5813e5 100644 --- a/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx @@ -4,6 +4,7 @@ import configureMockStore from "redux-mock-store"; import thunk from "redux-thunk"; import RepoListContainer from "."; +import { definedNamespaces } from "../../shared/Namespace"; const mockStore = configureMockStore([thunk]); const currentNamespace = "current-namespace"; @@ -45,4 +46,22 @@ describe("RepoListContainer props", () => { namespace: currentNamespace, }); }); + + it("uses kubeapps namespace when reposPerNamespace is true and _all namespaces selected", () => { + const store = mockStore({ + ...defaultState, + config: { + featureFlags: { reposPerNamespace: true }, + namespace: kubeappsNamespace, + }, + namespace: { current: definedNamespaces.all }, + }); + const wrapper = shallow(); + + const component = wrapper.find("AppRepoList"); + + expect(component).toHaveProp({ + namespace: kubeappsNamespace, + }); + }); }); diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts index 8bf4288c7bf..33f3cecdc1d 100644 --- a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts @@ -4,11 +4,12 @@ import { ThunkDispatch } from "redux-thunk"; import actions from "../../actions"; import AppRepoList from "../../components/Config/AppRepoList"; +import { definedNamespaces } from "../../shared/Namespace"; import { IStoreState } from "../../shared/types"; function mapStateToProps({ config, namespace, repos }: IStoreState) { let repoNamespace = config.namespace; - if (config.featureFlags.reposPerNamespace) { + if (config.featureFlags.reposPerNamespace && namespace.current !== definedNamespaces.all) { repoNamespace = namespace.current; } return { From ec5bb258ca539cce7edad6346c044cdfac0daae5 Mon Sep 17 00:00:00 2001 From: Michael Nelson Date: Thu, 6 Feb 2020 02:17:00 +0000 Subject: [PATCH 3/6] Add info to help with change. --- .../Config/AppRepoList/AppRepoList.test.tsx | 1 + .../Config/AppRepoList/AppRepoList.tsx | 22 ++++++++++++++++++- .../RepoListContainer.test.tsx | 4 ++++ .../RepoListContainer/RepoListContainer.ts | 3 +++ docs/user/private-app-repository.md | 6 +++++ 5 files changed, 35 insertions(+), 1 deletion(-) diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx index ff3ed132490..36472e93314 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.test.tsx @@ -14,6 +14,7 @@ const defaultProps = { resyncAllRepos: jest.fn(), install: jest.fn(), namespace: defaultNamespace, + displayReposPerNamespaceMsg: false, }; describe("AppRepoList", () => { diff --git a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx index ead4475efd5..306939b1889 100644 --- a/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx +++ b/dashboard/src/components/Config/AppRepoList/AppRepoList.tsx @@ -1,7 +1,7 @@ import * as React from "react"; 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"; @@ -26,6 +26,7 @@ export interface IAppRepoListProps { syncJobPodTemplate: string, ) => Promise; namespace: string; + displayReposPerNamespaceMsg: boolean; } const RequiredRBACRoles: { [s: string]: IRBACRole[] } = { @@ -78,6 +79,7 @@ class AppRepoList extends React.Component { resyncRepo, resyncAllRepos, namespace, + displayReposPerNamespaceMsg, } = this.props; return (
@@ -110,6 +112,24 @@ class AppRepoList extends React.Component { repos={repos} namespace={namespace} /> + {displayReposPerNamespaceMsg && ( + +
+

+ You can view site-wide App Repositories by selecting "All Namespaces" above, if you + have permission to view or edit App Repositories available to all namespaces. +

+

+ Kubeapps now enables you to create App Repositories in your own namespace. You can + read more information in the{" "} + + Private App Repository docs + + . +

+
+
+ )}
); } diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx index e828f5813e5..7ebd6b8558a 100644 --- a/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.test.tsx @@ -17,6 +17,7 @@ const defaultState = { }, namespace: { current: currentNamespace }, repos: {}, + displayReposPerNamespaceMsg: false, }; describe("RepoListContainer props", () => { @@ -28,6 +29,7 @@ describe("RepoListContainer props", () => { expect(component).toHaveProp({ namespace: kubeappsNamespace, + displayReposPerNamespaceMsg: false, }); }); @@ -44,6 +46,7 @@ describe("RepoListContainer props", () => { expect(component).toHaveProp({ namespace: currentNamespace, + displayReposPerNamespaceMsg: true, }); }); @@ -62,6 +65,7 @@ describe("RepoListContainer props", () => { expect(component).toHaveProp({ namespace: kubeappsNamespace, + displayReposPerNamespaceMsg: false, }); }); }); diff --git a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts index 33f3cecdc1d..b7cd41e3197 100644 --- a/dashboard/src/containers/RepoListContainer/RepoListContainer.ts +++ b/dashboard/src/containers/RepoListContainer/RepoListContainer.ts @@ -9,13 +9,16 @@ import { IStoreState } from "../../shared/types"; function mapStateToProps({ config, namespace, repos }: IStoreState) { let repoNamespace = config.namespace; + let displayReposPerNamespaceMsg = false; if (config.featureFlags.reposPerNamespace && namespace.current !== definedNamespaces.all) { repoNamespace = namespace.current; + displayReposPerNamespaceMsg = true; } return { errors: repos.errors, namespace: repoNamespace, repos: repos.repos, + displayReposPerNamespaceMsg, }; } diff --git a/docs/user/private-app-repository.md b/docs/user/private-app-repository.md index 6e9b765af9e..c6b3f2d2f05 100644 --- a/docs/user/private-app-repository.md +++ b/docs/user/private-app-repository.md @@ -199,3 +199,9 @@ spec: ``` The above will generate a Pod with the label `my-repo: isPrivate` and the environment variable `FOO=BAR`. + +## Per Namespace App Repositories + +There is work in progress to support AppRepositories per namespace in Kubeapps, rather than sharing access to AppRepositories in Kubeapps' own namespace. Details about the design can be read on the [design document](https://docs.google.com/document/d/1YEeKC6nPLoq4oaxs9v8_UsmxrRfWxB6KCyqrh2-Q8x0/edit?ts=5e2adf87). + +More information will be added once it is available for general use. From 512a1a54da8454d1b7fce327b7968795224548e0 Mon Sep 17 00:00:00 2001 From: Michael Nelson Date: Thu, 6 Feb 2020 02:34:21 +0000 Subject: [PATCH 4/6] Update snapshot --- .../AppRepoList/__snapshots__/AppRepoButton.test.tsx.snap | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dashboard/src/components/Config/AppRepoList/__snapshots__/AppRepoButton.test.tsx.snap b/dashboard/src/components/Config/AppRepoList/__snapshots__/AppRepoButton.test.tsx.snap index 3df0b8096a4..5dfa929afc7 100644 --- a/dashboard/src/components/Config/AppRepoList/__snapshots__/AppRepoButton.test.tsx.snap +++ b/dashboard/src/components/Config/AppRepoList/__snapshots__/AppRepoButton.test.tsx.snap @@ -3,7 +3,7 @@ exports[`should open a modal with the repository form 1`] = `