Skip to content

Commit 992b0bf

Browse files
authored
chore: pull in latest repo-review (#558)
Signed-off-by: Henry Schreiner <[email protected]>
1 parent ebc85cf commit 992b0bf

File tree

3 files changed

+186
-23
lines changed

3 files changed

+186
-23
lines changed

docs/_includes/head_custom.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
{%- if page.interactive_repo_review %}
44

55
<script
6-
src="https://cdn.jsdelivr.net/pyodide/v0.27.1/full/pyodide.js"
6+
src="https://cdn.jsdelivr.net/pyodide/v0.27.3/full/pyodide.js"
77
crossorigin
88
></script>
99

docs/_includes/interactive_repo_review.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
"repo-review~=0.12.1",
2424
"sp-repo-review==2025.01.22",
2525
"validate-pyproject-schema-store==2025.02.03",
26-
"validate-pyproject[all]~=0.22.0",
26+
"validate-pyproject[all]~=0.23.0",
2727
]}
2828
/>,
2929
);

docs/assets/js/webapp.js

Lines changed: 184 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
const DEFAULT_MSG =
2-
"Enter a GitHub repo and branch to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
2+
"Enter a GitHub repo and branch/tag to review. Runs Python entirely in your browser using WebAssembly. Built with React, MaterialUI, and Pyodide.";
33

44
const urlParams = new URLSearchParams(window.location.search);
55
const baseurl = window.location.pathname;
@@ -154,6 +154,39 @@ function Results(props) {
154154
);
155155
}
156156

157+
async function fetchRepoRefs(repo) {
158+
if (!repo) return { branches: [], tags: [] };
159+
try {
160+
// Fetch both branches and tags from GitHub API
161+
const [branchesResponse, tagsResponse] = await Promise.all([
162+
fetch(`https://api.github.com/repos/${repo}/branches`),
163+
fetch(`https://api.github.com/repos/${repo}/tags`),
164+
]);
165+
166+
if (!branchesResponse.ok || !tagsResponse.ok) {
167+
console.error("Error fetching repo data");
168+
return { branches: [], tags: [] };
169+
}
170+
171+
const branches = await branchesResponse.json();
172+
const tags = await tagsResponse.json();
173+
174+
return {
175+
branches: branches.map((branch) => ({
176+
name: branch.name,
177+
type: "branch",
178+
})),
179+
tags: tags.map((tag) => ({
180+
name: tag.name,
181+
type: "tag",
182+
})),
183+
};
184+
} catch (error) {
185+
console.error("Error fetching repo references:", error);
186+
return { branches: [], tags: [] };
187+
}
188+
}
189+
157190
async function prepare_pyodide(deps) {
158191
const deps_str = deps.map((i) => `"${i}"`).join(", ");
159192
const pyodide = await loadPyodide();
@@ -196,28 +229,58 @@ class App extends React.Component {
196229
this.state = {
197230
results: [],
198231
repo: urlParams.get("repo") || "",
199-
branch: urlParams.get("branch") || "",
232+
ref: urlParams.get("ref") || "",
233+
refType: urlParams.get("refType") || "branch",
234+
refs: { branches: [], tags: [] },
200235
msg: `<p>${DEFAULT_MSG}</p><h4>Packages:</h4> ${deps_str}`,
201236
progress: false,
237+
loadingRefs: false,
202238
err_msg: "",
203239
skip_reason: "",
204240
url: "",
205241
};
206242
this.pyodide_promise = prepare_pyodide(props.deps);
243+
this.refInputDebounce = null;
244+
}
245+
246+
async fetchRepoReferences(repo) {
247+
if (!repo) return;
248+
249+
this.setState({ loadingRefs: true });
250+
const refs = await fetchRepoRefs(repo);
251+
this.setState({
252+
refs: refs,
253+
loadingRefs: false,
254+
});
255+
}
256+
257+
handleRepoChange(repo) {
258+
this.setState({ repo });
259+
260+
// debounce the API call to avoid too many requests
261+
clearTimeout(this.refInputDebounce);
262+
this.refInputDebounce = setTimeout(() => {
263+
this.fetchRepoReferences(repo);
264+
}, 500);
265+
}
266+
267+
handleRefChange(ref, refType) {
268+
this.setState({ ref, refType });
207269
}
208270

209271
handleCompute() {
210-
if (!this.state.repo || !this.state.branch) {
272+
if (!this.state.repo || !this.state.ref) {
211273
this.setState({ results: [], msg: DEFAULT_MSG });
212274
window.history.replaceState(null, "", baseurl);
213275
alert(
214-
`Please enter a repo (${this.state.repo}) and branch (${this.state.branch})`,
276+
`Please enter a repo (${this.state.repo}) and branch/tag (${this.state.ref})`,
215277
);
216278
return;
217279
}
218280
const local_params = new URLSearchParams({
219281
repo: this.state.repo,
220-
branch: this.state.branch,
282+
ref: this.state.ref,
283+
refType: this.state.refType,
221284
});
222285
window.history.replaceState(null, "", `${baseurl}?${local_params}`);
223286
this.setState({
@@ -234,7 +297,7 @@ class App extends React.Component {
234297
from repo_review.ghpath import GHPath
235298
from dataclasses import replace
236299
237-
package = GHPath(repo="${state.repo}", branch="${state.branch}")
300+
package = GHPath(repo="${state.repo}", branch="${state.ref}")
238301
families, checks = process(package)
239302
240303
for v in families.values():
@@ -249,7 +312,7 @@ class App extends React.Component {
249312
this.setState({
250313
msg: DEFAULT_MSG,
251314
progress: false,
252-
err_msg: "Invalid repository or branch. Please try again.",
315+
err_msg: "Invalid repository or branch/tag. Please try again.",
253316
});
254317
return;
255318
}
@@ -288,7 +351,7 @@ class App extends React.Component {
288351
this.setState({
289352
results: results,
290353
families: families,
291-
msg: `Results for ${state.repo}@${state.branch}`,
354+
msg: `Results for ${state.repo}@${state.ref} (${state.refType})`,
292355
progress: false,
293356
err_msg: "",
294357
url: "",
@@ -300,13 +363,78 @@ class App extends React.Component {
300363
}
301364

302365
componentDidMount() {
303-
if (urlParams.get("repo") && urlParams.get("branch")) {
304-
this.handleCompute();
366+
if (urlParams.get("repo")) {
367+
this.fetchRepoReferences(urlParams.get("repo"));
368+
369+
if (urlParams.get("ref")) {
370+
this.handleCompute();
371+
}
305372
}
306373
}
307374

308375
render() {
309-
const common_branches = ["main", "master", "develop", "stable"];
376+
const priorityBranches = ["HEAD", "main", "master", "develop", "stable"];
377+
const branchMap = new Map(
378+
this.state.refs.branches.map((branch) => [branch.name, branch]),
379+
);
380+
381+
let availableOptions = [];
382+
383+
// If no repo is entered or API hasn't returned any branches/tags yet,
384+
// show all five priority branches.
385+
if (
386+
this.state.repo === "" ||
387+
(this.state.refs.branches.length === 0 &&
388+
this.state.refs.tags.length === 0)
389+
) {
390+
availableOptions = [
391+
{ label: "HEAD (default branch)", value: "HEAD", type: "branch" },
392+
{ label: "main (branch)", value: "main", type: "branch" },
393+
{ label: "master (branch)", value: "master", type: "branch" },
394+
{ label: "develop (branch)", value: "develop", type: "branch" },
395+
{ label: "stable (branch)", value: "stable", type: "branch" },
396+
];
397+
} else {
398+
const prioritizedBranches = [
399+
{ label: "HEAD (default branch)", value: "HEAD", type: "branch" },
400+
];
401+
402+
priorityBranches.slice(1).forEach((branchName) => {
403+
if (branchMap.has(branchName)) {
404+
prioritizedBranches.push({
405+
label: `${branchName} (branch)`,
406+
value: branchName,
407+
type: "branch",
408+
});
409+
// Remove from map so it doesn't get added twice.
410+
branchMap.delete(branchName);
411+
}
412+
});
413+
414+
const otherBranches = [];
415+
branchMap.forEach((branch) => {
416+
otherBranches.push({
417+
label: `${branch.name} (branch)`,
418+
value: branch.name,
419+
type: "branch",
420+
});
421+
});
422+
otherBranches.sort((a, b) => a.value.localeCompare(b.value));
423+
424+
const tagOptions = this.state.refs.tags.map((tag) => ({
425+
label: `${tag.name} (tag)`,
426+
value: tag.name,
427+
type: "tag",
428+
}));
429+
tagOptions.sort((a, b) => a.value.localeCompare(b.value));
430+
431+
availableOptions = [
432+
...prioritizedBranches,
433+
...otherBranches,
434+
...tagOptions,
435+
];
436+
}
437+
310438
return (
311439
<MyThemeProvider>
312440
<MaterialUI.CssBaseline />
@@ -326,29 +454,64 @@ class App extends React.Component {
326454
autoFocus={true}
327455
onKeyDown={(e) => {
328456
if (e.keyCode === 13)
329-
document.getElementById("branch-select").focus();
457+
document.getElementById("ref-select").focus();
330458
}}
331-
onInput={(e) => this.setState({ repo: e.target.value })}
459+
onInput={(e) => this.handleRepoChange(e.target.value)}
332460
defaultValue={urlParams.get("repo")}
333461
sx={{ flexGrow: 3 }}
334462
/>
335463
<MaterialUI.Autocomplete
336464
disablePortal
337-
id="branch-select"
338-
options={common_branches}
465+
id="ref-select"
466+
options={availableOptions}
467+
loading={this.state.loadingRefs}
339468
freeSolo={true}
340469
onKeyDown={(e) => {
341470
if (e.keyCode === 13) this.handleCompute();
342471
}}
343-
onInputChange={(e, value) => this.setState({ branch: value })}
344-
defaultValue={urlParams.get("branch")}
472+
getOptionLabel={(option) =>
473+
typeof option === "string" ? option : option.label
474+
}
475+
renderOption={(props, option) => (
476+
<li {...props}>{option.label}</li>
477+
)}
478+
onInputChange={(e, value) => {
479+
// If the user enters free text, treat it as a branch
480+
if (typeof value === "string") {
481+
this.handleRefChange(value, "branch");
482+
}
483+
}}
484+
onChange={(e, option) => {
485+
if (option) {
486+
if (typeof option === "object") {
487+
this.handleRefChange(option.value, option.type);
488+
} else {
489+
this.handleRefChange(option, "branch");
490+
}
491+
}
492+
}}
493+
defaultValue={urlParams.get("ref")}
345494
renderInput={(params) => (
346495
<MaterialUI.TextField
347496
{...params}
348-
label="Branch"
497+
label="Branch/Tag"
349498
variant="outlined"
350-
helperText="e.g. main"
351-
sx={{ flexGrow: 2, minWidth: 130 }}
499+
helperText="e.g. HEAD, main, or v1.0.0"
500+
sx={{ flexGrow: 2, minWidth: 200 }}
501+
InputProps={{
502+
...params.InputProps,
503+
endAdornment: (
504+
<React.Fragment>
505+
{this.state.loadingRefs ? (
506+
<MaterialUI.CircularProgress
507+
color="inherit"
508+
size={20}
509+
/>
510+
) : null}
511+
{params.InputProps.endAdornment}
512+
</React.Fragment>
513+
),
514+
}}
352515
/>
353516
)}
354517
/>
@@ -358,7 +521,7 @@ class App extends React.Component {
358521
variant="contained"
359522
size="large"
360523
disabled={
361-
this.state.progress || !this.state.repo || !this.state.branch
524+
this.state.progress || !this.state.repo || !this.state.ref
362525
}
363526
>
364527
<MaterialUI.Icon>start</MaterialUI.Icon>

0 commit comments

Comments
 (0)