Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Form to add an App Repository (#1954)
- Loading branch information
Andres Martinez Gotor
committed
Aug 21, 2020
1 parent
3a81b45
commit 024cb3b
Showing
12 changed files
with
1,200 additions
and
51 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
132 changes: 132 additions & 0 deletions
132
dashboard/src/components/Config/AppRepoList/AppRepoAddDockerCreds.v2.test.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,132 @@ | ||
import actions from "actions"; | ||
import { CdsButton } from "components/Clarity/clarity"; | ||
import { shallow } from "enzyme"; | ||
import * as React from "react"; | ||
import { act } from "react-dom/test-utils"; | ||
import * as ReactRedux from "react-redux"; | ||
import { ISecret } from "shared/types"; | ||
import AppRepoAddDockerCreds from "./AppRepoAddDockerCreds.v2"; | ||
|
||
const secret1 = { | ||
metadata: { | ||
name: "foo", | ||
}, | ||
} as ISecret; | ||
const secret2 = { | ||
metadata: { | ||
name: "bar", | ||
}, | ||
} as ISecret; | ||
const defaultProps = { | ||
imagePullSecrets: [], | ||
togglePullSecret: jest.fn(), | ||
selectedImagePullSecrets: {}, | ||
namespace: "default", | ||
}; | ||
|
||
let spyOnUseDispatch: jest.SpyInstance; | ||
const kubeaActions = { ...actions.kube }; | ||
beforeEach(() => { | ||
actions.repos = { | ||
...actions.repos, | ||
createDockerRegistrySecret: jest.fn(), | ||
}; | ||
const mockDispatch = jest.fn(r => r); | ||
spyOnUseDispatch = jest.spyOn(ReactRedux, "useDispatch").mockReturnValue(mockDispatch); | ||
}); | ||
|
||
afterEach(() => { | ||
actions.kube = { ...kubeaActions }; | ||
spyOnUseDispatch.mockRestore(); | ||
}); | ||
|
||
it("shows an info message if there are no secrets", () => { | ||
const wrapper = shallow(<AppRepoAddDockerCreds {...defaultProps} />); | ||
expect(wrapper.text()).toContain("No existing credentials found"); | ||
}); | ||
|
||
it("shows the list of available pull secrets", () => { | ||
const wrapper = shallow( | ||
<AppRepoAddDockerCreds {...defaultProps} imagePullSecrets={[secret1, secret2]} />, | ||
); | ||
expect(wrapper.text()).toContain(secret1.metadata.name); | ||
expect(wrapper.text()).toContain(secret2.metadata.name); | ||
}); | ||
|
||
it("select secrets", () => { | ||
const wrapper = shallow( | ||
<AppRepoAddDockerCreds | ||
{...defaultProps} | ||
imagePullSecrets={[secret1, secret2]} | ||
selectedImagePullSecrets={{ [secret1.metadata.name]: true }} | ||
/>, | ||
); | ||
const totalCheckbox = wrapper.find("input").filterWhere(i => i.prop("type") === "checkbox"); | ||
expect(totalCheckbox.length).toBe(2); | ||
|
||
const selectedCheckbox = totalCheckbox.filterWhere(i => i.prop("checked") === true); | ||
expect(selectedCheckbox.length).toBe(1); | ||
}); | ||
|
||
it("renders the form to create a registry secret", () => { | ||
const wrapper = shallow(<AppRepoAddDockerCreds {...defaultProps} />); | ||
|
||
expect(wrapper.text()).not.toContain("Secret Name"); | ||
|
||
const button = wrapper.find(CdsButton).filterWhere(b => b.html().includes("Add new")); | ||
act(() => { | ||
(button.prop("onClick") as any)(); | ||
}); | ||
wrapper.update(); | ||
|
||
expect(wrapper.text()).toContain("Secret Name"); | ||
}); | ||
|
||
it("submits the new secret", async () => { | ||
const createDockerRegistrySecret = jest.fn().mockReturnValue(true); | ||
actions.repos = { | ||
...actions.repos, | ||
createDockerRegistrySecret, | ||
}; | ||
const wrapper = shallow(<AppRepoAddDockerCreds {...defaultProps} />); | ||
// Open form | ||
const button = wrapper.find(CdsButton).filterWhere(b => b.html().includes("Add new")); | ||
act(() => { | ||
(button.prop("onClick") as any)(); | ||
}); | ||
wrapper.update(); | ||
|
||
const secretName = "repo-1"; | ||
const user = "foo"; | ||
const password = "pass"; | ||
const email = "foo@bar.com"; | ||
const server = "docker.io"; | ||
|
||
wrapper | ||
.find("#kubeapps-docker-cred-secret-name") | ||
.simulate("change", { target: { value: secretName } }); | ||
wrapper.find("#kubeapps-docker-cred-server").simulate("change", { target: { value: server } }); | ||
wrapper.find("#kubeapps-docker-cred-username").simulate("change", { target: { value: user } }); | ||
wrapper | ||
.find("#kubeapps-docker-cred-password") | ||
.simulate("change", { target: { value: password } }); | ||
wrapper.find("#kubeapps-docker-cred-email").simulate("change", { target: { value: email } }); | ||
wrapper.update(); | ||
|
||
const submit = wrapper.find(CdsButton).filterWhere(b => b.html().includes("Submit")); | ||
await act(async () => { | ||
await (submit.prop("onClick") as () => Promise<any>)(); | ||
}); | ||
wrapper.update(); | ||
|
||
expect(createDockerRegistrySecret).toHaveBeenCalledWith( | ||
secretName, | ||
user, | ||
password, | ||
email, | ||
server, | ||
defaultProps.namespace, | ||
); | ||
// There should be a new item with the secret | ||
expect(wrapper.find("#app-repo-secret-repo-1")).toExist(); | ||
}); |
205 changes: 205 additions & 0 deletions
205
dashboard/src/components/Config/AppRepoList/AppRepoAddDockerCreds.v2.tsx
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,205 @@ | ||
import React, { useState } from "react"; | ||
|
||
import actions from "actions"; | ||
import { CdsButton } from "components/Clarity/clarity"; | ||
import { useDispatch } from "react-redux"; | ||
import { Action } from "redux"; | ||
import { ThunkDispatch } from "redux-thunk"; | ||
import { ISecret, IStoreState } from "../../../shared/types"; | ||
|
||
interface IAppRepoFormProps { | ||
imagePullSecrets: ISecret[]; | ||
togglePullSecret: (imagePullSecret: string) => () => void; | ||
selectedImagePullSecrets: { [key: string]: boolean }; | ||
namespace: string; | ||
} | ||
|
||
export function AppRepoAddDockerCreds({ | ||
imagePullSecrets, | ||
togglePullSecret, | ||
selectedImagePullSecrets, | ||
namespace, | ||
}: IAppRepoFormProps) { | ||
const dispatch: ThunkDispatch<IStoreState, null, Action> = useDispatch(); | ||
const [secretName, setSecretName] = useState(""); | ||
const [user, setUser] = useState(""); | ||
const [password, setPassword] = useState(""); | ||
const [email, setEmail] = useState(""); | ||
const [server, setServer] = useState(""); | ||
const [showSecretSubForm, setShowSecretSubForm] = useState(false); | ||
const [creating, setCreating] = useState(false); | ||
const [currentImagePullSecrets, setCurrentImagePullSecrets] = useState(imagePullSecrets); | ||
|
||
const handleUserChange = (e: React.ChangeEvent<HTMLInputElement>) => setUser(e.target.value); | ||
const handleSecretNameChange = (e: React.ChangeEvent<HTMLInputElement>) => | ||
setSecretName(e.target.value); | ||
const handlePasswordChange = (e: React.ChangeEvent<HTMLInputElement>) => | ||
setPassword(e.target.value); | ||
const handleEmailChange = (e: React.ChangeEvent<HTMLInputElement>) => setEmail(e.target.value); | ||
const handleServerChange = (e: React.ChangeEvent<HTMLInputElement>) => setServer(e.target.value); | ||
const toggleCredSubForm = () => setShowSecretSubForm(!showSecretSubForm); | ||
|
||
const handleInstallClick = async () => { | ||
setCreating(true); | ||
const success = await dispatch( | ||
actions.repos.createDockerRegistrySecret( | ||
secretName, | ||
user, | ||
password, | ||
email, | ||
server, | ||
namespace, | ||
), | ||
); | ||
setCreating(false); | ||
if (success) { | ||
// Re-fetching secrets cause a re-render and the modal to be closed, | ||
// using local state to avoid that. | ||
setCurrentImagePullSecrets( | ||
currentImagePullSecrets.concat({ metadata: { name: secretName, namespace } } as ISecret), | ||
); | ||
setUser(""); | ||
setSecretName(""); | ||
setPassword(""); | ||
setEmail(""); | ||
setServer(""); | ||
setShowSecretSubForm(false); | ||
} | ||
}; | ||
|
||
return ( | ||
<div className="clr-form-columns"> | ||
{currentImagePullSecrets.length > 0 ? ( | ||
currentImagePullSecrets.map(secret => { | ||
return ( | ||
<div key={secret.metadata.name} className="clr-checkbox-wrapper"> | ||
<label | ||
className="clr-control-label clr-control-label-checkbox" | ||
htmlFor={`app-repo-secret-${secret.metadata.name}`} | ||
key={secret.metadata.name} | ||
> | ||
<input | ||
id={`app-repo-secret-${secret.metadata.name}`} | ||
type="checkbox" | ||
onChange={togglePullSecret(secret.metadata.name)} | ||
checked={selectedImagePullSecrets[secret.metadata.name] || false} | ||
/> | ||
<span>{secret.metadata.name}</span> | ||
</label> | ||
</div> | ||
); | ||
}) | ||
) : ( | ||
<label className="clr-control-label">No existing credentials found.</label> | ||
)} | ||
{showSecretSubForm && ( | ||
<div className="secondary-input"> | ||
<label className="clr-control-label">New Docker Registry Credentials</label> | ||
<div className="clr-form-separator-sm"> | ||
<label htmlFor="kubeapps-docker-cred-secret-name" className="clr-control-label"> | ||
Secret Name | ||
</label> | ||
<div className="clr-control-container"> | ||
<div className="clr-input-wrapper"> | ||
<input | ||
id="kubeapps-docker-cred-secret-name" | ||
className="clr-input" | ||
value={secretName} | ||
onChange={handleSecretNameChange} | ||
placeholder="Secret" | ||
required={true} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="clr-form-control"> | ||
<label className="clr-control-label" htmlFor="kubeapps-docker-cred-server"> | ||
Server | ||
</label> | ||
<div className="clr-control-container"> | ||
<div className="clr-input-wrapper"> | ||
<input | ||
id="kubeapps-docker-cred-server" | ||
value={server} | ||
className="clr-input" | ||
onChange={handleServerChange} | ||
placeholder="https://index.docker.io/v1/" | ||
required={true} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="clr-form-control"> | ||
<label className="clr-control-label" htmlFor="kubeapps-docker-cred-username"> | ||
Username | ||
</label> | ||
<div className="clr-control-container"> | ||
<div className="clr-input-wrapper"> | ||
<input | ||
id="kubeapps-docker-cred-username" | ||
className="clr-input" | ||
value={user} | ||
onChange={handleUserChange} | ||
placeholder="Username" | ||
required={true} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="clr-form-control"> | ||
<label className="clr-control-label" htmlFor="kubeapps-docker-cred-password"> | ||
Password | ||
</label> | ||
<div className="clr-control-container"> | ||
<div className="clr-input-wrapper"> | ||
<input | ||
type="password" | ||
id="kubeapps-docker-cred-password" | ||
className="clr-input" | ||
value={password} | ||
onChange={handlePasswordChange} | ||
placeholder="Password" | ||
required={true} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="clr-form-control"> | ||
<label className="clr-control-label" htmlFor="kubeapps-docker-cred-email"> | ||
</label> | ||
<div className="clr-control-container"> | ||
<div className="clr-input-wrapper"> | ||
<input | ||
id="kubeapps-docker-cred-email" | ||
className="clr-input" | ||
value={email} | ||
onChange={handleEmailChange} | ||
placeholder="user@example.com" | ||
required={true} | ||
/> | ||
</div> | ||
</div> | ||
</div> | ||
<div className="clr-form-separator"> | ||
<CdsButton type="button" disabled={creating} onClick={handleInstallClick}> | ||
{creating ? "Creating..." : "Submit"} | ||
</CdsButton> | ||
<CdsButton onClick={toggleCredSubForm} type="button" action="outline"> | ||
Cancel | ||
</CdsButton> | ||
</div> | ||
</div> | ||
)} | ||
{!showSecretSubForm && ( | ||
<div className="clr-form-separator-sm"> | ||
<CdsButton onClick={toggleCredSubForm} type="button" size="sm"> | ||
Add new credentials | ||
</CdsButton> | ||
</div> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
export default AppRepoAddDockerCreds; |
6 changes: 6 additions & 0 deletions
6
dashboard/src/components/Config/AppRepoList/AppRepoButton.v2.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
.modal-close { | ||
position: fixed; | ||
margin-left: 39.5rem; | ||
margin-top: -1.8rem !important; | ||
cursor: pointer; | ||
} |
Oops, something went wrong.