diff --git a/python/robomp/src/manual_triage.py b/python/robomp/src/manual_triage.py index 08c9c425d..ac6559bb2 100644 --- a/python/robomp/src/manual_triage.py +++ b/python/robomp/src/manual_triage.py @@ -14,6 +14,11 @@ from robomp.db import INACTIVE_EVENT_STATES, Database, EventRow, issue_key from robomp.github_backend import GitHubBackend _ISSUE_REF = re.compile(r"^(?P[^/\s]+)/(?P[^#\s]+)#(?P\d+)$") +_ISSUE_URL = re.compile( + r"^(?:https?://)?(?:www\.)?github\.com/" + r"(?P[^/\s]+)/(?P[^/\s]+)/issues/(?P\d+)" + r"(?:[/?#].*)?$" +) class InvalidIssueRef(ValueError): @@ -44,10 +49,13 @@ class ManualTriageTimeout(TimeoutError): def parse_issue_ref(ref: str) -> tuple[str, int]: - """Parse `owner/repo#NN` into `("owner/repo", NN)`.""" - match = _ISSUE_REF.match(ref.strip()) + """Parse `owner/repo#NN` or a github issue url into `("owner/repo", NN)`.""" + cleaned = ref.strip() + match = _ISSUE_REF.match(cleaned) or _ISSUE_URL.match(cleaned) if match is None: - raise InvalidIssueRef(f"expected owner/repo#NN, got {ref!r}") + raise InvalidIssueRef( + f"expected owner/repo#NN or https://github.com/owner/repo/issues/NN, got {ref!r}" + ) return f"{match.group('owner')}/{match.group('repo')}", int(match.group("number")) diff --git a/python/robomp/tests/test_server.py b/python/robomp/tests/test_server.py index 2e7d284e1..e5b31fb05 100644 --- a/python/robomp/tests/test_server.py +++ b/python/robomp/tests/test_server.py @@ -271,8 +271,32 @@ def test_parse_issue_ref_accepts_owner_repo_hash_number() -> None: assert parse_issue_ref(" octo/widget#42 ") == ("octo/widget", 42) +def test_parse_issue_ref_accepts_github_issue_urls() -> None: + cases = ( + "https://github.com/can1357/oh-my-pi/issues/1348", + "http://github.com/can1357/oh-my-pi/issues/1348", + "github.com/can1357/oh-my-pi/issues/1348", + "https://www.github.com/can1357/oh-my-pi/issues/1348", + "https://github.com/can1357/oh-my-pi/issues/1348/", + "https://github.com/can1357/oh-my-pi/issues/1348?foo=bar", + "https://github.com/can1357/oh-my-pi/issues/1348#issuecomment-99", + " https://github.com/can1357/oh-my-pi/issues/1348 ", + ) + for case in cases: + assert parse_issue_ref(case) == ("can1357/oh-my-pi", 1348), case + + def test_parse_issue_ref_rejects_garbage() -> None: - for bad in ("widget#1", "octo/widget", "octo/widget#abc", "octo widget#1", ""): + for bad in ( + "widget#1", + "octo/widget", + "octo/widget#abc", + "octo widget#1", + "", + "https://github.com/octo/widget/pull/1", + "https://github.com/octo/widget/issues/", + "https://gitlab.com/octo/widget/issues/1", + ): with pytest.raises(InvalidIssueRef): parse_issue_ref(bad) diff --git a/python/robomp/web/src/components/Trigger.tsx b/python/robomp/web/src/components/Trigger.tsx index 3e5ff8206..a8fb9563a 100644 --- a/python/robomp/web/src/components/Trigger.tsx +++ b/python/robomp/web/src/components/Trigger.tsx @@ -16,7 +16,7 @@ export function Trigger(): JSX.Element { const validate = (): string | null => { const value = issue().trim(); - if (!value) return "enter owner/repo#NN"; + if (!value) return "enter owner/repo#NN or github issue url"; return null; }; @@ -33,7 +33,7 @@ export function Trigger(): JSX.Element { }; return ( - owner/repo#NN}> + owner/repo#NN or issue url}> setIssue(ev.currentTarget.value)}