Skip to content

Commit

Permalink
Merge pull request #1955 from timbrel/search-pull-requests
Browse files Browse the repository at this point in the history
Implement `query` parameter when searching for PR's
  • Loading branch information
kaste authored Nov 27, 2024
2 parents cf2e695 + 8f3f6a3 commit 0bd255f
Show file tree
Hide file tree
Showing 3 changed files with 69 additions and 14 deletions.
7 changes: 6 additions & 1 deletion Default.sublime-commands
Original file line number Diff line number Diff line change
Expand Up @@ -125,9 +125,14 @@
"command": "gs_github_open_repo"
},
{
"caption": "github: review pull request",
"caption": "github: review pull request (show: all)",
"command": "gs_github_pull_request"
},
{
"caption": "github: review pull request (show: mine)",
"command": "gs_github_pull_request",
"args": { "query": "involves:@me" }
},
{
"caption": "github: set remote for integration",
"command": "gs_github_configure_remote"
Expand Down
33 changes: 27 additions & 6 deletions github/commands/pull_request.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,12 @@
from ...common import interwebs
from ...common import util
from ...core.commands.push import gs_push_to_branch_name
from ...core.fns import filter_
from ...core.ui_mixins.quick_panel import show_paginated_panel
from ...core.ui_mixins.input_panel import show_single_line_input_panel
from ...core.view import replace_view_content
from GitSavvy.core.base_commands import GsWindowCommand
from GitSavvy.core.runtime import on_worker
from GitSavvy.core.runtime import on_worker, run_as_future


__all__ = (
Expand All @@ -27,18 +28,29 @@
class gs_github_pull_request(GsWindowCommand, git_mixins.GithubRemotesMixin):

"""
Display open pull requests on the base repo. When a pull request is selected,
Display pull requests on the base repo. When a pull request is selected,
allow the user to 1) checkout the PR as detached HEAD, 2) checkout the PR as
a local branch, 3) view the PR's diff, or 4) open the PR in the browser.
By default, all "open" pull requests are displayed. This can be customized
using the `query` arg which is of the same query format as in the Web UI of
Github. Note that "repo:", "type:", and "state:" are prefilled if omitted.
"""

@on_worker
def run(self):
def run(self, query=""):
self.remotes = self.get_remotes()
self.base_remote_name = self.get_integrated_remote_name(self.remotes)
self.base_remote_url = self.remotes[self.base_remote_name]
self.base_remote = github.parse_remote(self.base_remote_url)
self.pull_requests = github.get_pull_requests(self.base_remote)
self.base_remote = repository = github.parse_remote(self.base_remote_url)

query_ = " ".join(filter_((
f"repo:{repository.owner}/{repository.repo}" if "repo:" not in query else None,
"type:pr" if "type:" not in query else None,
"state:open" if "state:" not in query else None,
query.strip()
)))
self.pull_requests = github.search_pull_requests(self.base_remote, query_)

pp = show_paginated_panel(
self.pull_requests,
Expand Down Expand Up @@ -67,7 +79,7 @@ def on_select_pr(self, pr):
if not pr:
return

self.pr = pr
self.pr_ = run_as_future(github.get_pull_request, pr["number"], self.base_remote)
self.window.show_quick_panel(
["Checkout as detached HEAD.",
"Checkout as local branch.",
Expand All @@ -81,6 +93,15 @@ def on_select_action(self, idx):
if idx == -1:
return

# Note that the request starts in `on_select_pr`. So the actual wait time includes the
# time we wait for the user to take action.
timeout = 4.0
try:
self.pr = self.pr_.result(timeout)
except TimeoutError:
self.window.status_message(f"Timeout: could not fetch the PR details within {timeout} seconds.")
return

owner = self.pr["head"]["repo"]["owner"]["login"]

if owner == self.base_remote.owner:
Expand Down
43 changes: 36 additions & 7 deletions github/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,9 @@ def get_api_fqdn(github_repo):
return True, github_repo.fqdn


def github_api_url(api_url_template, repository, **kwargs):
def github_api_url(
api_url_template: str, repository: GitHubRepo, **kwargs: dict[str, str]
) -> tuple[str, str]:
"""
Construct a github URL to query using the given url template string,
and a github.GitHubRepo instance, and optionally query parameters
Expand Down Expand Up @@ -151,7 +153,7 @@ def validate_response(response, method="GET"):
action=action, payload=response.payload))


def query_github(api_url_template, github_repo):
def query_github(api_url_template: str, github_repo: GitHubRepo):
"""
Takes a URL template that takes `owner` and `repo` template variables
and as a GitHub repo object. Do a GET for the provided URL and return
Expand All @@ -169,13 +171,16 @@ def query_github(api_url_template, github_repo):
get_repo_data = partial(query_github, "/repos/{owner}/{repo}")


def iteratively_query_github(api_url_template, github_repo):
def iteratively_query_github(
api_url_template: str, github_repo: GitHubRepo, query: dict = {}, yield_: str = None
):
"""
Like `query_github` but return a generator by repeatedly
iterating until no link to next page.
"""
fqdn, path = github_api_url(api_url_template, github_repo,
per_page=GITHUB_PER_PAGE_MAX)
default_query = {"per_page": GITHUB_PER_PAGE_MAX}
query_ = {**default_query, **query}
fqdn, path = github_api_url(api_url_template, github_repo, **query_)
auth = (github_repo.token, "x-oauth-basic") if github_repo.token else None

response = None
Expand All @@ -198,8 +203,10 @@ def iteratively_query_github(api_url_template, github_repo):
validate_response(response)

if response.payload:
for item in response.payload:
yield item
if yield_:
yield from response.payload[yield_]
else:
yield from response.payload
else:
break

Expand All @@ -210,6 +217,28 @@ def iteratively_query_github(api_url_template, github_repo):
get_pull_requests = partial(iteratively_query_github, "/repos/{owner}/{repo}/pulls")


def search_pull_requests(repository: GitHubRepo, q: str):
return iteratively_query_github("/search/issues", repository, query={"q": q}, yield_="items")


def get_pull_request(nr: str | int, github_repo: GitHubRepo):
return get_from_github(f"/repos/{{owner}}/{{repo}}/pulls/{nr}", github_repo)


def get_from_github(api_url_template: str, github_repo: GitHubRepo):
fqdn, path = github_api_url(api_url_template, github_repo)
auth = (github_repo.token, "x-oauth-basic") if github_repo.token else None

response = interwebs.get(
fqdn, 443, path, https=True, auth=auth,
headers={
"Accept": "application/vnd.github+json",
"X-GitHub-Api-Version": "2022-11-28"
})
validate_response(response)
return response.payload


def post_to_github(api_url_template, github_repo, payload=None):
"""
Takes a URL template that takes `owner` and `repo` template variables
Expand Down

0 comments on commit 0bd255f

Please sign in to comment.