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

Add repo validation, enable branch selection #68

Merged
merged 9 commits into from
Sep 8, 2024
395 changes: 290 additions & 105 deletions package-lock.json

Large diffs are not rendered by default.

4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"dependencies": {
"@jupyterhub/binderhub-client": "0.4.0",
"configurable-http-proxy": "^4.6.1",
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did you mean to remove this @oliverroick ?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Weird, I thought this wasn't part of the dependencies, but it looks like it is. I'll revert the change.

"configurable-http-proxy": "^4.6.2",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-select": "^5.7.4",
Expand Down Expand Up @@ -48,13 +48,15 @@
"identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
"react-test-renderer": "^18.3.1",
"style-loader": "^3.3.2",
"webpack": "^5.6.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.9.3"
},
"jest": {
"automock": false,
"testEnvironment": "jsdom",
"testMatch": [
"<rootDir>/src/**/*.test.js"
Expand Down
18 changes: 18 additions & 0 deletions setupTests.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,22 @@
import { jest } from "@jest/globals";
import "@testing-library/jest-dom";
import fetchMock from "jest-fetch-mock";
fetchMock.enableMocks();

HTMLCanvasElement.prototype.getContext = () => {};
Object.defineProperty(window, "matchMedia", {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: false,
media: query,
onchange: null,
addListener: jest.fn(), // Deprecated
removeListener: jest.fn(), // Deprecated
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});

window.profileList = [
{
Expand Down
100 changes: 79 additions & 21 deletions src/ImageBuilder.jsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { useEffect, useState } from "react";
import { useEffect, useState, useRef } from "react";
import Select from "react-select";
import useRepositoryField from "./hooks/useRepositoryField";
import useRefField from "./hooks/useRefField";

async function buildImage(repo, ref, term, fitAddon, onImageBuilt) {
const { BinderRepository } = await import("@jupyterhub/binderhub-client");
Expand Down Expand Up @@ -86,23 +89,33 @@ function ImageLogs({ setTerm, setFitAddon }) {
</div>
);
}

export function ImageBuilder({ name }) {
const [repo, setRepo] = useState("");
const { repo, repoId, repoFieldProps, repoError, repoIsValidating } =
useRepositoryField();
const { ref, refError, refFieldProps, refIsLoading } = useRefField(repoId);
const repoFieldRef = useRef();
const branchFieldRef = useRef();

const [customImage, setCustomImage] = useState("");
const [error, setError] = useState("");

// FIXME: Allow users to actually configure this
const [ref, _] = useState("HEAD"); // eslint-disable-line no-unused-vars
const [term, setTerm] = useState(null);
const [fitAddon, setFitAddon] = useState(null);

const handleBuildStart = async () => {
if (!repo) {
setError("Provide a Github repository.");
repoFieldRef.current.focus();
repoFieldRef.current.blur();
return;
}

if (!ref) {
branchFieldRef.current.focus();
branchFieldRef.current.blur();
return;
}

await buildImage(repo, ref, term, fitAddon, (imageName) => {
await buildImage(repoId, ref, term, fitAddon, (imageName) => {
setCustomImage(imageName);
term.write(
"\nImage has been built! Click the start button to launch your server",
Expand All @@ -116,28 +129,73 @@ export function ImageBuilder({ name }) {
// don't generate the hidden input that posts the built image out.
return (
<>
<div className={`profile-option-container ${error ? "has-error" : ""}`}>
<div className="profile-option-container">
<div className="profile-option-label-container">
<b>Provider</b>
</div>
<div className="profile-option-control-container">GitHub</div>
</div>

<div
className={`profile-option-container ${repoError ? "has-error" : ""}`}
>
<div className="profile-option-label-container">
<label htmlFor="github-repo">GitHub Repository</label>
<label htmlFor="repo">Repository</label>
</div>
<div className="profile-option-control-container">
<input
id="github-repo"
id="repo"
type="text"
value={repo}
onChange={(e) => setRepo(e.target.value)}
ref={repoFieldRef}
{...repoFieldProps}
aria-invalid={!!repoError}
/>
{error && <div className="profile-option-control-error">{error}</div>}
<button
type="button"
className="btn btn-jupyter pull-right"
onClick={handleBuildStart}
>
Build image
</button>
{repoIsValidating && (
<div className="profile-option-control-info">
Validating repository...
</div>
)}
{repoError && (
<div className="profile-option-control-error">{repoError}</div>
)}
</div>
<input name={name} type="hidden" value={customImage} />
</div>

<div
className={`profile-option-container ${repoError ? "has-error" : ""}`}
>
<div className="profile-option-label-container">
<label>Git Ref</label>
</div>
<div className="profile-option-control-container">
<Select
aria-label="Git Ref"
ref={branchFieldRef}
{...refFieldProps}
aria-invalid={!!refError}
isDisabled={!refFieldProps.options}
/>
{refIsLoading && !refIsLoading && (
<div className="profile-option-control-info">
Loading Git ref options...
</div>
)}
{refError && (
<div className="profile-option-control-error">{refError}</div>
)}
</div>
</div>

<div className="right-button">
<button
type="button"
className="btn btn-jupyter"
onClick={handleBuildStart}
>
Build image
</button>
</div>
<input name={name} type="hidden" value={customImage} />
<ImageLogs setFitAddon={setFitAddon} setTerm={setTerm} />
</>
);
Expand Down
Loading
Loading