Skip to content

Commit

Permalink
UI: autodetect pasted URL
Browse files Browse the repository at this point in the history
  • Loading branch information
manics committed Dec 11, 2023
1 parent 819bba8 commit 70d6b5b
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 1 deletion.
6 changes: 6 additions & 0 deletions binderhub/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ def generate_config(self):
"repo_providers"
].items():
config[repo_provider_class_alias] = repo_provider_class.labels
config[repo_provider_class_alias][
"display_name"
] = repo_provider_class.display_name
config[repo_provider_class_alias][
"regex_detect"
] = repo_provider_class.regex_detect
return config

async def get(self):
Expand Down
36 changes: 36 additions & 0 deletions binderhub/repoproviders.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,17 @@ class RepoProvider(LoggingConfigurable):
config=True,
)

regex_detect = List(
[],
help="""
List of JavaScript regexes to detect repository parameters from URLs.
Regexes must contain named groups for `repo`, and optional named groups
for `ref`, `filepath`, `urlpath`.
Note named groups in Javascript are not the same as in Python.
""",
)

unresolved_ref = Unicode()

git_credentials = Unicode(
Expand Down Expand Up @@ -192,6 +203,15 @@ def is_valid_sha1(sha1):
class FakeProvider(RepoProvider):
"""Fake provider for local testing of the UI"""

name = Unicode("Fake")

display_name = "Fake GitHub"

regex_detect = [
r"^https://github\.com/(?<repo>[^/]+/[^/]+)(/blob/(?<ref>[^/]+)(/(?<filepath>.+))?)?$",
r"^https://github\.com/(?<repo>[^/]+/[^/]+)(/tree/(?<ref>[^/]+)(/(?<urlpath>.+))?)?$",
]

labels = {
"text": "Fake Provider",
"tag_text": "Fake Ref",
Expand Down Expand Up @@ -627,6 +647,13 @@ def _default_git_credentials(self):
return rf"username=binderhub\npassword={self.private_token}"
return ""

# Gitlab repos can be nested under projects
_regex_detect_base = r"^https://gitlab\.com/(?<repo>[^/]+/[^/]+(/[^/-][^/]+)*)"
regex_detect = [
_regex_detect_base + r"(/-/blob/(?<ref>[^/]+)(/(?<filepath>.+))?)?$",
_regex_detect_base + r"(/-/tree/(?<ref>[^/]+)(/(?<urlpath>.+))?)?$",
]

labels = {
"text": "GitLab.com repository or URL",
"tag_text": "Git ref (branch, tag, or commit)",
Expand Down Expand Up @@ -780,6 +807,11 @@ def _default_git_credentials(self):
return rf"username={self.access_token}\npassword=x-oauth-basic"
return ""

regex_detect = [
r"^https://github\.com/(?<repo>[^/]+/[^/]+)(/blob/(?<ref>[^/]+)(/(?<filepath>.+))?)?$",
r"^https://github\.com/(?<repo>[^/]+/[^/]+)(/tree/(?<ref>[^/]+)(/(?<urlpath>.+))?)?$",
]

labels = {
"text": "GitHub repository name or URL",
"tag_text": "Git ref (branch, tag, or commit)",
Expand Down Expand Up @@ -973,6 +1005,10 @@ class GistRepoProvider(GitHubRepoProvider):
help="Flag for allowing usages of secret Gists. The default behavior is to disallow secret gists.",
)

regex_detect = [
r"^https://gist\.github\.com/(?<repo>[^/]+/[^/]+)(/(?<ref>[^/]+))?$"
]

labels = {
"text": "Gist ID (username/gistId) or URL",
"tag_text": "Git commit SHA",
Expand Down
35 changes: 34 additions & 1 deletion binderhub/static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { BinderRepository } from "@jupyterhub/binderhub-client";
import { updatePathText } from "./src/path";
import { nextHelpText } from "./src/loading";
import { updateFavicon } from "./src/favicon";
import { detect } from "./src/autodetect";

import "xterm/css/xterm.css";

Expand Down Expand Up @@ -166,7 +167,39 @@ function indexMain() {
updatePathText();
updateRepoText(BASE_URL);

$("#repository").on("keyup paste change", function () {
// If the user pastes a URL into the repository field try to autodetect
// In all other cases don't do anything to avoid overwriting the user's input
$("#repository").on("paste", function (ev) {
const event = ev.originalEvent;
const pastedText = (event.clipboardData || window.clipboardData)
?.getData("text")
.trim();
if (pastedText) {
const fields = detect(pastedText);
console.log(fields);
if (fields) {
$("#provider_prefix-selected").text(fields.display_name);
$("#provider_prefix").val(fields.provider);
$("#repository").val(fields.repo);
if (fields.ref) {
$("#ref").val(fields.ref);
}
if (fields.path) {
$("#filepath").val(fields.path);
$("#url-or-file-selected").text(
fields.pathType === "filepath" ? "File" : "URL",
);
}
updatePathText();
event.preventDefault();
updateRepoText(BASE_URL);
}
}
// else autodetect failed, let the paste go through
updateUrls(BADGE_BASE_URL);
});

$("#repository").on("keyup change", function () {
updateUrls(BADGE_BASE_URL);
});

Expand Down
27 changes: 27 additions & 0 deletions binderhub/static/js/src/autodetect.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { getConfigDict } from "./repo";

export function detect(text) {
// Assumes configDict was already loaded by another module
const configDict = getConfigDict();
for (const provider in configDict) {
const regex_detect = configDict[provider].regex_detect || [];
for (const regex of regex_detect) {
const m = text.match(regex);
if (m?.groups.repo) {
return {
provider: provider,
display_name: configDict[provider].display_name,
repo: m.groups.repo,
ref: m.groups.ref,
path: m.groups.filepath || m.groups.urlpath || null,
pathType: m.groups.filepath
? "filepath"
: m.groups.urlpath
? "urlpath"
: null,
};
}
}
}
return null;
}
4 changes: 4 additions & 0 deletions binderhub/static/js/src/repo.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,7 @@ export function updateRepoText(baseUrl) {
setLabels();
}
}

export function getConfigDict() {
return configDict;
}

0 comments on commit 70d6b5b

Please sign in to comment.