lazy-github 0.6.1__tar.gz → 0.6.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {lazy_github-0.6.1 → lazy_github-0.6.2}/PKG-INFO +1 -1
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/cli.py +15 -3
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/bindings.py +7 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/backends/cli.py +1 -1
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/issues.py +8 -0
- lazy_github-0.6.2/lazy_github/lib/github/reactions.py +41 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/messages.py +11 -3
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/pr_drafts.py +1 -1
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/models/github.py +42 -1
- lazy_github-0.6.2/lazy_github/ui/screens/lookup_issue.py +89 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/primary.py +4 -4
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/conversations.py +89 -10
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/issues.py +11 -3
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/pull_requests.py +105 -29
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/workflows.py +0 -2
- lazy_github-0.6.2/lazy_github/version.py +1 -0
- lazy_github-0.6.2/lint.sh +6 -0
- lazy_github-0.6.1/lazy_github/version.py +0 -1
- lazy_github-0.6.1/lint.sh +0 -6
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.devcontainer/devcontainer.json +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.github/workflows/code-checks.yaml +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.github/workflows/publish.yaml +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.gitignore +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.pre-commit-config.yaml +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.quicklinks/README.md +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.quicklinks/config.lua +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/.quicklinks/queries/python/patterns/textual_imports.scm +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/LICENSE +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/README.md +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/flake.lock +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/flake.nix +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/gh-lazy +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/images/lazy-github-conversation-ui.svg +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/images/lazy-github-settings-ui.png +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/__main__.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/cache.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/config.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/constants.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/context.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/debug.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/diff_parser.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/git_cli.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/auth.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/backends/hishel.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/backends/protocol.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/branches.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/checks.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/client.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/notifications.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/pull_requests.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/repositories.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/users.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/github/workflows.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/lib/logging.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/app.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/auth.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/confirm.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/create_or_edit_pull_request.py +1 -1
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/debug.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/edit_issue.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/lookup_pull_request.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/lookup_repository.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/new_comment.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/new_issue.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/notifications.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/settings.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/trigger_workflow.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/command_log.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/common.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/diff_viewer.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/info.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/repositories.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/split_diff_viewer.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/widgets/workflow_run_details.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/pyproject.toml +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/start.sh +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/tests/__init__.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/tests/test_cache.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/tests/test_config.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/tests/test_settings_ui.py +0 -0
- {lazy_github-0.6.1 → lazy_github-0.6.2}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: lazy-github
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.2
|
|
4
4
|
Summary: A terminal UI for interacting with Github
|
|
5
5
|
Author-email: "Chris (Gizmo)" <gizmo385@users.noreply.github.com>
|
|
6
6
|
Maintainer-email: "Chris (Gizmo)" <gizmo385@users.noreply.github.com>
|
|
@@ -55,10 +55,14 @@ def clear_auth():
|
|
|
55
55
|
|
|
56
56
|
|
|
57
57
|
@cli.command
|
|
58
|
-
|
|
58
|
+
@click.option("--no-confirm", is_flag=True, default=False, help="Don't ask for confirmation")
|
|
59
|
+
def clear_config(no_confirm: bool):
|
|
59
60
|
"""Reset the user's settings"""
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
if no_confirm or click.confirm("Confirm deletion your LazyGithub settings"):
|
|
62
|
+
_CONFIG_FILE_LOCATION.unlink(missing_ok=True)
|
|
63
|
+
print("Your settings have been cleared")
|
|
64
|
+
else:
|
|
65
|
+
print("Canceling")
|
|
62
66
|
|
|
63
67
|
|
|
64
68
|
@cli.command
|
|
@@ -80,3 +84,11 @@ def clear_cache(no_confirm: bool):
|
|
|
80
84
|
def debug():
|
|
81
85
|
"""Outputs LazyGithub debug info"""
|
|
82
86
|
print(collect_debug_info())
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
@cli.command
|
|
90
|
+
def version():
|
|
91
|
+
"""Prints the current version"""
|
|
92
|
+
from lazy_github.version import VERSION
|
|
93
|
+
|
|
94
|
+
print(f"LazyGithub version {VERSION}")
|
|
@@ -38,6 +38,13 @@ class LazyGithubBindings:
|
|
|
38
38
|
"I", "open_issue", "New Issue", id="issue.new", tooltip="Open a new issue in the current repository"
|
|
39
39
|
)
|
|
40
40
|
EDIT_ISSUE = Binding("E", "edit_issue", "Edit Issue", id="issue.edit", tooltip="Edit the selected issue")
|
|
41
|
+
LOOKUP_ISSUE = Binding(
|
|
42
|
+
"O",
|
|
43
|
+
"lookup_issue",
|
|
44
|
+
"Lookup Issue",
|
|
45
|
+
id="issue.lookup",
|
|
46
|
+
tooltip="Lookup an issue in the current repository",
|
|
47
|
+
)
|
|
41
48
|
|
|
42
49
|
# Pull request actions
|
|
43
50
|
OPEN_PULL_REQUEST = Binding(
|
|
@@ -101,7 +101,7 @@ def _create_request_body_tempfile(body: bytes) -> tempfile._TemporaryFileWrapper
|
|
|
101
101
|
_TEMPORARY_JSON_BODY_DIRECTORY.mkdir(parents=True, exist_ok=True)
|
|
102
102
|
if sys.version_info.minor > 11:
|
|
103
103
|
# delete_on_close parameter added in Python 3.12
|
|
104
|
-
temp = tempfile.NamedTemporaryFile(delete=False, delete_on_close=False, dir=_TEMPORARY_JSON_BODY_DIRECTORY)
|
|
104
|
+
temp = tempfile.NamedTemporaryFile(delete=False, delete_on_close=False, dir=_TEMPORARY_JSON_BODY_DIRECTORY)
|
|
105
105
|
else:
|
|
106
106
|
temp = tempfile.NamedTemporaryFile(delete=False, dir=_TEMPORARY_JSON_BODY_DIRECTORY)
|
|
107
107
|
temp.write(body)
|
|
@@ -7,6 +7,14 @@ from lazy_github.models.github import Issue, IssueComment, PartialPullRequest, R
|
|
|
7
7
|
DEFAULT_PAGE_SIZE = 30
|
|
8
8
|
|
|
9
9
|
|
|
10
|
+
async def get_issue_by_number(repo: Repository, issue_number: int) -> Issue:
|
|
11
|
+
"""Looks up a single issue by number in the given repository"""
|
|
12
|
+
url = f"/repos/{repo.owner.login}/{repo.name}/issues/{issue_number}"
|
|
13
|
+
response = await LazyGithubContext.client.get(url, headers=github_headers())
|
|
14
|
+
response.raise_for_status()
|
|
15
|
+
return Issue(**response.json(), repo=repo)
|
|
16
|
+
|
|
17
|
+
|
|
10
18
|
class UpdateIssuePayload(TypedDict):
|
|
11
19
|
title: str | None
|
|
12
20
|
body: str | None
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from lazy_github.lib.context import LazyGithubContext, github_headers
|
|
4
|
+
from lazy_github.models.github import Issue, IssueComment, Reaction, ReactionSet, ReactionType, Repository, User
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _build_reaction_set(response: Any) -> ReactionSet:
|
|
8
|
+
response.raise_for_status()
|
|
9
|
+
reaction_users: dict[ReactionType, list[User]] = {}
|
|
10
|
+
reaction_counts: dict[ReactionType, int] = {}
|
|
11
|
+
|
|
12
|
+
for raw_reaction in response.json():
|
|
13
|
+
reaction_type = ReactionType.from_github(raw_reaction["content"])
|
|
14
|
+
reaction_users.setdefault(reaction_type, [])
|
|
15
|
+
reaction_counts.setdefault(reaction_type, 0)
|
|
16
|
+
|
|
17
|
+
user = User(**raw_reaction["user"])
|
|
18
|
+
reaction_users[reaction_type].append(user)
|
|
19
|
+
reaction_counts[reaction_type] += 1
|
|
20
|
+
|
|
21
|
+
return ReactionSet(reaction_users=reaction_users, reaction_counts=reaction_counts)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def list_reactions_on_comment(repo: Repository, comment: IssueComment) -> ReactionSet:
|
|
25
|
+
url = f"/repos/{repo.full_name}/issues/comments/{comment.id}/reactions"
|
|
26
|
+
response = await LazyGithubContext.client.get(url, headers=github_headers())
|
|
27
|
+
return _build_reaction_set(response)
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
async def add_reaction_on_comment(repo: Repository, comment: IssueComment, reaction: ReactionType) -> Reaction:
|
|
31
|
+
url = f"/repos/{repo.full_name}/issues/comments/{comment.id}/reactions"
|
|
32
|
+
body = {"content": reaction.name.lower()}
|
|
33
|
+
response = await LazyGithubContext.client.post(url, headers=github_headers(), json=body)
|
|
34
|
+
response.raise_for_status()
|
|
35
|
+
return Reaction(**response.json())
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def list_reactions_on_issue(repo: Repository, issue: Issue) -> ReactionSet:
|
|
39
|
+
url = f"/repos/{repo.full_name}/issues/{issue.number}/reactions"
|
|
40
|
+
response = await LazyGithubContext.client.get(url, headers=github_headers())
|
|
41
|
+
return _build_reaction_set(response)
|
|
@@ -9,18 +9,26 @@ from lazy_github.models.github import (
|
|
|
9
9
|
IssueComment,
|
|
10
10
|
Notification,
|
|
11
11
|
PartialPullRequest,
|
|
12
|
+
ReactionSet,
|
|
12
13
|
Repository,
|
|
13
14
|
Review,
|
|
14
15
|
WorkflowRun,
|
|
15
16
|
)
|
|
16
17
|
|
|
17
18
|
|
|
18
|
-
class
|
|
19
|
-
"""A message sent when reviews for a pull request have been loaded"""
|
|
19
|
+
class ReviewsAndCommentsLoaded(Message):
|
|
20
|
+
"""A message sent when reviews and comments for a pull request have been loaded"""
|
|
20
21
|
|
|
21
|
-
def __init__(self, reviews: list[Review]) -> None:
|
|
22
|
+
def __init__(self, reviews: list[Review], comments: list[IssueComment]) -> None:
|
|
22
23
|
super().__init__()
|
|
23
24
|
self.reviews = reviews
|
|
25
|
+
self.comments = comments
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class CommentReactionsLoaded(Message):
|
|
29
|
+
def __init__(self, reactions: dict[int, ReactionSet]) -> None:
|
|
30
|
+
super().__init__()
|
|
31
|
+
self.reactions = reactions
|
|
24
32
|
|
|
25
33
|
|
|
26
34
|
class RepoSelected(Message):
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
from datetime import datetime
|
|
2
|
-
from enum import StrEnum
|
|
2
|
+
from enum import Enum, StrEnum
|
|
3
3
|
|
|
4
4
|
from pydantic import BaseModel, Field
|
|
5
5
|
|
|
@@ -302,3 +302,44 @@ class CheckStatus(BaseModel):
|
|
|
302
302
|
class CombinedCheckStatus(BaseModel):
|
|
303
303
|
state: CheckStatusState
|
|
304
304
|
statuses: list[CheckStatus]
|
|
305
|
+
|
|
306
|
+
|
|
307
|
+
class ReactionType(Enum):
|
|
308
|
+
def __init__(self, value: str, emoji: str) -> None:
|
|
309
|
+
super().__init__()
|
|
310
|
+
self._value = value
|
|
311
|
+
self.emoji = emoji
|
|
312
|
+
|
|
313
|
+
@classmethod
|
|
314
|
+
def from_github(cls, github_content: str) -> "ReactionType":
|
|
315
|
+
try:
|
|
316
|
+
return cls[github_content.upper()]
|
|
317
|
+
except KeyError:
|
|
318
|
+
if github_content == "+1":
|
|
319
|
+
return ReactionType.THUMBS_UP
|
|
320
|
+
elif github_content == "-1":
|
|
321
|
+
return ReactionType.THUMBS_DOWN
|
|
322
|
+
else:
|
|
323
|
+
raise ValueError(f"Invalid reaction string: {github_content}")
|
|
324
|
+
|
|
325
|
+
THUMBS_UP = ("+1", "👍")
|
|
326
|
+
THUMBS_DOWN = ("-1", "👎")
|
|
327
|
+
LAUGH = ("laugh", "😄")
|
|
328
|
+
CONFUSED = ("confused", "😕")
|
|
329
|
+
HEART = ("heart", "❤️")
|
|
330
|
+
HOORAY = ("hooray", "🎉")
|
|
331
|
+
ROCKET = ("rocket", "🚀")
|
|
332
|
+
EYES = ("eyes", "👀")
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
class Reaction(BaseModel):
|
|
336
|
+
reaction_type: ReactionType
|
|
337
|
+
user: User
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
class ReactionSet(BaseModel):
|
|
341
|
+
reaction_users: dict[ReactionType, list[User]]
|
|
342
|
+
reaction_counts: dict[ReactionType, int]
|
|
343
|
+
|
|
344
|
+
def __bool__(self) -> bool:
|
|
345
|
+
return bool(self.reaction_counts) or bool(self.reaction_users)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
from textual import on
|
|
2
|
+
from textual.app import ComposeResult
|
|
3
|
+
from textual.containers import Container, Horizontal
|
|
4
|
+
from textual.content import Content
|
|
5
|
+
from textual.screen import ModalScreen
|
|
6
|
+
from textual.widgets import Button, Input, Label, Markdown, Rule
|
|
7
|
+
|
|
8
|
+
from lazy_github.lib.bindings import LazyGithubBindings
|
|
9
|
+
from lazy_github.lib.context import LazyGithubContext
|
|
10
|
+
from lazy_github.lib.github.backends.protocol import GithubApiRequestFailed
|
|
11
|
+
from lazy_github.lib.github.issues import get_issue_by_number
|
|
12
|
+
from lazy_github.models.github import Issue
|
|
13
|
+
from lazy_github.ui.widgets.common import LazyGithubFooter
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class LookupIssueButtons(Horizontal):
|
|
17
|
+
DEFAULT_CSS = """
|
|
18
|
+
LookupIssueButtons {
|
|
19
|
+
align: center middle;
|
|
20
|
+
height: auto;
|
|
21
|
+
width: 100%;
|
|
22
|
+
}
|
|
23
|
+
Button {
|
|
24
|
+
margin: 1;
|
|
25
|
+
}
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
def compose(self) -> ComposeResult:
|
|
29
|
+
yield Button("Open", id="lookup", variant="success")
|
|
30
|
+
yield Button("Cancel", id="cancel", variant="error")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class LookupIssueContainer(Container):
|
|
34
|
+
DEFAULT_CSS = """
|
|
35
|
+
LookupIssueContainer {
|
|
36
|
+
align: center middle;
|
|
37
|
+
}
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def compose(self) -> ComposeResult:
|
|
41
|
+
yield Markdown("# Search for an issue by number:")
|
|
42
|
+
yield Label(Content.from_markup("[bold]Issue Number:[/bold]"))
|
|
43
|
+
yield Input(
|
|
44
|
+
id="issue_number",
|
|
45
|
+
placeholder="Issue number",
|
|
46
|
+
type="number",
|
|
47
|
+
)
|
|
48
|
+
yield Rule()
|
|
49
|
+
yield LookupIssueButtons()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class LookupIssueModal(ModalScreen[Issue | None]):
|
|
53
|
+
DEFAULT_CSS = """
|
|
54
|
+
LookupIssueModal {
|
|
55
|
+
align: center middle;
|
|
56
|
+
content-align: center middle;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
LookupIssueContainer {
|
|
60
|
+
width: 60;
|
|
61
|
+
max-height: 25;
|
|
62
|
+
border: thick $background 80%;
|
|
63
|
+
background: $surface-lighten-3;
|
|
64
|
+
}
|
|
65
|
+
"""
|
|
66
|
+
|
|
67
|
+
BINDINGS = [LazyGithubBindings.SUBMIT_DIALOG, LazyGithubBindings.CLOSE_DIALOG]
|
|
68
|
+
|
|
69
|
+
def compose(self) -> ComposeResult:
|
|
70
|
+
yield LookupIssueContainer()
|
|
71
|
+
yield LazyGithubFooter()
|
|
72
|
+
|
|
73
|
+
@on(Button.Pressed, "#lookup")
|
|
74
|
+
async def action_submit(self) -> None:
|
|
75
|
+
assert LazyGithubContext.current_repo is not None, "Current repo is missing!"
|
|
76
|
+
|
|
77
|
+
try:
|
|
78
|
+
issue_number = int(self.query_one("#issue_number", Input).value)
|
|
79
|
+
issue = await get_issue_by_number(LazyGithubContext.current_repo, issue_number)
|
|
80
|
+
except ValueError:
|
|
81
|
+
self.notify("Must enter a valid issue number!", title="Invalid Issue Number", severity="error")
|
|
82
|
+
except GithubApiRequestFailed:
|
|
83
|
+
self.notify("Could not find issue!", title="Unknown Issue", severity="error")
|
|
84
|
+
else:
|
|
85
|
+
self.dismiss(issue)
|
|
86
|
+
|
|
87
|
+
@on(Button.Pressed, "#cancel")
|
|
88
|
+
async def action_close(self) -> None:
|
|
89
|
+
self.dismiss(None)
|
|
@@ -30,7 +30,7 @@ from lazy_github.lib.messages import (
|
|
|
30
30
|
IssueSelected,
|
|
31
31
|
PullRequestSelected,
|
|
32
32
|
RepoSelected,
|
|
33
|
-
|
|
33
|
+
ReviewsAndCommentsLoaded,
|
|
34
34
|
WorkflowRunSelected,
|
|
35
35
|
)
|
|
36
36
|
from lazy_github.models.github import Issue, PartialPullRequest, Repository, WorkflowRun
|
|
@@ -333,11 +333,11 @@ class MainViewPane(Container):
|
|
|
333
333
|
if focus_run_details:
|
|
334
334
|
tabbed_content.children[0].focus()
|
|
335
335
|
|
|
336
|
-
@on(
|
|
337
|
-
async def handle_reviews_loaded(self, message:
|
|
336
|
+
@on(ReviewsAndCommentsLoaded)
|
|
337
|
+
async def handle_reviews_loaded(self, message: ReviewsAndCommentsLoaded) -> None:
|
|
338
338
|
try:
|
|
339
339
|
overview_pane = self.query_one(PrOverviewTabPane)
|
|
340
|
-
except WrongType
|
|
340
|
+
except (WrongType, NoMatches):
|
|
341
341
|
pass
|
|
342
342
|
else:
|
|
343
343
|
overview_pane.add_reviews(message.reviews)
|
|
@@ -2,15 +2,77 @@ from textual import work
|
|
|
2
2
|
from textual.app import ComposeResult
|
|
3
3
|
from textual.containers import Container
|
|
4
4
|
from textual.content import Content
|
|
5
|
-
from textual.widgets import Collapsible, Label, Markdown
|
|
5
|
+
from textual.widgets import Collapsible, Label, ListItem, ListView, Markdown, Static
|
|
6
6
|
|
|
7
7
|
from lazy_github.lib.bindings import LazyGithubBindings
|
|
8
8
|
from lazy_github.lib.github.pull_requests import ReviewCommentNode
|
|
9
9
|
from lazy_github.lib.messages import NewCommentCreated
|
|
10
|
-
from lazy_github.models.github import
|
|
10
|
+
from lazy_github.models.github import (
|
|
11
|
+
FullPullRequest,
|
|
12
|
+
Issue,
|
|
13
|
+
IssueComment,
|
|
14
|
+
ReactionSet,
|
|
15
|
+
Review,
|
|
16
|
+
ReviewComment,
|
|
17
|
+
ReviewState,
|
|
18
|
+
)
|
|
11
19
|
from lazy_github.ui.screens.new_comment import NewCommentModal
|
|
12
20
|
|
|
13
21
|
|
|
22
|
+
class ReactionsDisplay(Container):
|
|
23
|
+
DEFAULT_CSS = """
|
|
24
|
+
ReactionsDisplay {
|
|
25
|
+
height: auto;
|
|
26
|
+
}
|
|
27
|
+
Collapsible {
|
|
28
|
+
height: auto;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
ListView {
|
|
32
|
+
height: auto;
|
|
33
|
+
}
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, item_id: str | int, id: str | None = None) -> None:
|
|
37
|
+
super().__init__(id=id)
|
|
38
|
+
self.item_id = item_id
|
|
39
|
+
|
|
40
|
+
@property
|
|
41
|
+
def reactions_list(self) -> ListView:
|
|
42
|
+
return self.query_one(f"#reactions_list_{self.item_id}", ListView)
|
|
43
|
+
|
|
44
|
+
@property
|
|
45
|
+
def collapsible_reactions(self) -> Collapsible:
|
|
46
|
+
return self.query_one(f"#collapsible_reactions_{self.item_id}", Collapsible)
|
|
47
|
+
|
|
48
|
+
def compose(self) -> ComposeResult:
|
|
49
|
+
with Collapsible(title="Reactions...", id=f"collapsible_reactions_{self.item_id}", collapsed=True):
|
|
50
|
+
yield ListView(id=f"reactions_list_{self.item_id}")
|
|
51
|
+
|
|
52
|
+
def on_mount(self) -> None:
|
|
53
|
+
self.loading = True
|
|
54
|
+
|
|
55
|
+
async def set_reactions(self, reactions: ReactionSet) -> None:
|
|
56
|
+
self.loading = True
|
|
57
|
+
await self.reactions_list.clear()
|
|
58
|
+
summary_strings = [f"{rt.emoji} {count}" for rt, count in reactions.reaction_counts.items() if count]
|
|
59
|
+
|
|
60
|
+
for reaction_type, users in reactions.reaction_users.items():
|
|
61
|
+
if not users:
|
|
62
|
+
continue
|
|
63
|
+
elif len(users) > 3:
|
|
64
|
+
users_string = f"{users[0].login}, {users[1].login}, {users[2].login}, and {len(users) - 3} more"
|
|
65
|
+
else:
|
|
66
|
+
users_string = ", ".join(u.login for u in users)
|
|
67
|
+
|
|
68
|
+
reaction_label = f"{reaction_type.emoji}: {users_string}"
|
|
69
|
+
self.reactions_list.append(ListItem(Label(Content.from_markup(reaction_label))))
|
|
70
|
+
|
|
71
|
+
self.loading = False
|
|
72
|
+
self.collapsible_reactions.title = " | ".join(summary_strings)
|
|
73
|
+
self.collapsible_reactions.display = bool(summary_strings)
|
|
74
|
+
|
|
75
|
+
|
|
14
76
|
class IssueCommentContainer(Container, can_focus=True):
|
|
15
77
|
DEFAULT_CSS = """
|
|
16
78
|
IssueCommentContainer {
|
|
@@ -60,6 +122,15 @@ class IssueCommentContainer(Container, can_focus=True):
|
|
|
60
122
|
def action_reply_to_individual_comment(self) -> None:
|
|
61
123
|
self.reply_to_comment_flow()
|
|
62
124
|
|
|
125
|
+
async def add_reaction_display(self, reactions: ReactionSet) -> None:
|
|
126
|
+
# Add the reactions to the bottom of each comment display
|
|
127
|
+
if not reactions:
|
|
128
|
+
return
|
|
129
|
+
|
|
130
|
+
rd = ReactionsDisplay(self.comment.id)
|
|
131
|
+
await self.children[-1].mount(rd)
|
|
132
|
+
await rd.set_reactions(reactions)
|
|
133
|
+
|
|
63
134
|
|
|
64
135
|
class ReviewConversation(Container):
|
|
65
136
|
DEFAULT_CSS = """
|
|
@@ -86,12 +157,16 @@ class ReviewConversation(Container):
|
|
|
86
157
|
yield IssueCommentContainer(self.pr, comment)
|
|
87
158
|
|
|
88
159
|
|
|
89
|
-
class ReviewContainer(
|
|
160
|
+
class ReviewContainer(Container):
|
|
90
161
|
DEFAULT_CSS = """
|
|
91
162
|
ReviewContainer {
|
|
92
163
|
height: auto;
|
|
93
164
|
}
|
|
94
165
|
|
|
166
|
+
Collapsible {
|
|
167
|
+
height: auto;
|
|
168
|
+
}
|
|
169
|
+
|
|
95
170
|
ReviewContainer:focus-within {
|
|
96
171
|
border: solid $success-lighten-3;
|
|
97
172
|
}
|
|
@@ -111,15 +186,19 @@ class ReviewContainer(Collapsible, can_focus=True):
|
|
|
111
186
|
review_state_text = "[red]Changes Requested[/red]"
|
|
112
187
|
else:
|
|
113
188
|
review_state_text = self.review.state.title()
|
|
189
|
+
review_summary = f"Review from {self.review.user.login} ({review_state_text})"
|
|
114
190
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
191
|
+
if self.review.body or self.review.comments:
|
|
192
|
+
with Collapsible(title=review_summary, collapsed=self.review.state == ReviewState.DISMISSED):
|
|
193
|
+
if self.review.body:
|
|
194
|
+
yield Markdown(self.review.body)
|
|
119
195
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
196
|
+
for comment in self.review.comments:
|
|
197
|
+
if comment_node := self.hierarchy.get(comment.id):
|
|
198
|
+
yield ReviewConversation(self.pr, comment_node)
|
|
199
|
+
else:
|
|
200
|
+
with Collapsible(title=review_summary, collapsed=True):
|
|
201
|
+
yield Static("No additional review content")
|
|
123
202
|
|
|
124
203
|
def action_reply_to_review(self) -> None:
|
|
125
204
|
self.app.push_screen(NewCommentModal(self.pr.repo, self.pr, self.review))
|
|
@@ -15,6 +15,7 @@ from lazy_github.lib.logging import lg
|
|
|
15
15
|
from lazy_github.lib.messages import IssuesAndPullRequestsFetched, IssueSelected, NewCommentCreated
|
|
16
16
|
from lazy_github.models.github import Issue, IssueState, PartialPullRequest, Repository
|
|
17
17
|
from lazy_github.ui.screens.edit_issue import EditIssueModal
|
|
18
|
+
from lazy_github.ui.screens.lookup_issue import LookupIssueModal
|
|
18
19
|
from lazy_github.ui.screens.new_comment import NewCommentModal
|
|
19
20
|
from lazy_github.ui.widgets.common import LazilyLoadedDataTable, LazyGithubContainer, TableRow
|
|
20
21
|
from lazy_github.ui.widgets.conversations import IssueCommentContainer
|
|
@@ -25,7 +26,7 @@ def issue_to_cell(issue: Issue) -> TableRow:
|
|
|
25
26
|
|
|
26
27
|
|
|
27
28
|
class IssuesContainer(LazyGithubContainer):
|
|
28
|
-
BINDINGS = [LazyGithubBindings.EDIT_ISSUE]
|
|
29
|
+
BINDINGS = [LazyGithubBindings.LOOKUP_ISSUE, LazyGithubBindings.EDIT_ISSUE]
|
|
29
30
|
|
|
30
31
|
issues: Dict[int, Issue] = {}
|
|
31
32
|
status_column_index = -1
|
|
@@ -80,8 +81,6 @@ class IssuesContainer(LazyGithubContainer):
|
|
|
80
81
|
self.number_column_index = self.table.get_column_index("number")
|
|
81
82
|
self.title_column_index = self.table.get_column_index("title")
|
|
82
83
|
|
|
83
|
-
self.searchable_table.loading = True
|
|
84
|
-
|
|
85
84
|
def load_cached_issues_for_repo(self, repo: Repository) -> None:
|
|
86
85
|
self.searchable_table.loading = True
|
|
87
86
|
self.searchable_table.initialize_from_cache(repo, Issue)
|
|
@@ -112,6 +111,15 @@ class IssuesContainer(LazyGithubContainer):
|
|
|
112
111
|
async def action_edit_issue(self) -> None:
|
|
113
112
|
self.trigger_edit_issue_flow()
|
|
114
113
|
|
|
114
|
+
@work
|
|
115
|
+
async def action_lookup_issue(self) -> None:
|
|
116
|
+
if issue := await self.app.push_screen_wait(LookupIssueModal()):
|
|
117
|
+
if not self.searchable_table.item_in_table(issue):
|
|
118
|
+
self.searchable_table.add_item(issue)
|
|
119
|
+
|
|
120
|
+
self.post_message(IssueSelected(issue))
|
|
121
|
+
lg.info(f"Looked up Issue #{issue.number}")
|
|
122
|
+
|
|
115
123
|
@on(DataTable.RowSelected, "#issues_table")
|
|
116
124
|
async def issue_selected(self) -> None:
|
|
117
125
|
issue = await self.get_selected_issue()
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from typing import Any, Coroutine
|
|
3
|
+
|
|
1
4
|
from httpx import HTTPStatusError
|
|
2
5
|
from textual import on, work
|
|
3
6
|
from textual.app import ComposeResult
|
|
@@ -28,16 +31,20 @@ from lazy_github.lib.github.pull_requests import (
|
|
|
28
31
|
merge_pull_request,
|
|
29
32
|
reconstruct_review_conversation_hierarchy,
|
|
30
33
|
)
|
|
34
|
+
from lazy_github.lib.github.reactions import list_reactions_on_comment, list_reactions_on_issue
|
|
31
35
|
from lazy_github.lib.logging import lg
|
|
32
36
|
from lazy_github.lib.messages import (
|
|
37
|
+
CommentReactionsLoaded,
|
|
33
38
|
IssuesAndPullRequestsFetched,
|
|
34
39
|
PullRequestSelected,
|
|
35
|
-
|
|
40
|
+
ReviewsAndCommentsLoaded,
|
|
36
41
|
)
|
|
37
42
|
from lazy_github.models.github import (
|
|
38
43
|
CheckStatus,
|
|
39
44
|
FullPullRequest,
|
|
45
|
+
IssueComment,
|
|
40
46
|
PartialPullRequest,
|
|
47
|
+
ReactionSet,
|
|
41
48
|
Repository,
|
|
42
49
|
Review,
|
|
43
50
|
ReviewState,
|
|
@@ -52,7 +59,7 @@ from lazy_github.ui.widgets.common import (
|
|
|
52
59
|
LazyGithubContainer,
|
|
53
60
|
TableRow,
|
|
54
61
|
)
|
|
55
|
-
from lazy_github.ui.widgets.conversations import IssueCommentContainer, ReviewContainer
|
|
62
|
+
from lazy_github.ui.widgets.conversations import IssueCommentContainer, ReactionsDisplay, ReviewContainer
|
|
56
63
|
from lazy_github.ui.widgets.diff_viewer import DiffViewerContainer
|
|
57
64
|
|
|
58
65
|
|
|
@@ -145,8 +152,6 @@ class PullRequestsContainer(LazyGithubContainer):
|
|
|
145
152
|
self.number_column_index = self.table.get_column_index("number")
|
|
146
153
|
self.title_column_index = self.table.get_column_index("title")
|
|
147
154
|
|
|
148
|
-
self.searchable_table.loading = True
|
|
149
|
-
|
|
150
155
|
async def on_issues_and_pull_requests_fetched(self, message: IssuesAndPullRequestsFetched) -> None:
|
|
151
156
|
message.stop()
|
|
152
157
|
|
|
@@ -197,6 +202,24 @@ class PrOverviewTabPane(TabPane):
|
|
|
197
202
|
super().__init__("Overview", id="overview_pane")
|
|
198
203
|
self.pr = pr
|
|
199
204
|
|
|
205
|
+
@property
|
|
206
|
+
def collapsible_status_checks(self) -> Collapsible:
|
|
207
|
+
return self.query_one("#collapsible_status_checks", Collapsible)
|
|
208
|
+
|
|
209
|
+
@property
|
|
210
|
+
def collapsible_reviews(self) -> Collapsible:
|
|
211
|
+
return self.query_one("#collapsible_reviews", Collapsible)
|
|
212
|
+
|
|
213
|
+
@property
|
|
214
|
+
def pr_reactions(self) -> ReactionsDisplay:
|
|
215
|
+
return self.query_one("#pr_reactions", ReactionsDisplay)
|
|
216
|
+
|
|
217
|
+
def on_mount(self) -> None:
|
|
218
|
+
self.collapsible_status_checks.loading = True
|
|
219
|
+
self.collapsible_reviews.loading = True
|
|
220
|
+
self.load_checks()
|
|
221
|
+
self.load_reactions()
|
|
222
|
+
|
|
200
223
|
def _status_check_to_label(self, status: CheckStatus) -> str:
|
|
201
224
|
status_summary = status.state.to_display()
|
|
202
225
|
return f"{status_summary} {status.context} - {status.description}"
|
|
@@ -280,30 +303,24 @@ class PrOverviewTabPane(TabPane):
|
|
|
280
303
|
merged_date = self.pr.merged_at.strftime("%c")
|
|
281
304
|
date_text += f" • Merged on {merged_date}"
|
|
282
305
|
yield Label(Content.from_markup(date_text))
|
|
283
|
-
yield Rule()
|
|
284
306
|
|
|
285
|
-
|
|
307
|
+
yield ReactionsDisplay(self.pr.id, id="pr_reactions")
|
|
308
|
+
|
|
286
309
|
with Collapsible(title="Status Checks: ...", id="collapsible_status_checks"):
|
|
287
310
|
yield ListView(id="status_checks_list")
|
|
288
311
|
|
|
289
|
-
# This is where we'll store information about the PR reviews
|
|
290
312
|
with Collapsible(title="Reviews: ...", id="collapsible_reviews"):
|
|
291
313
|
yield ListView(id="reviews_list")
|
|
292
314
|
|
|
293
315
|
yield Rule()
|
|
294
316
|
yield Markdown(self.pr.body)
|
|
295
317
|
|
|
296
|
-
def on_mount(self) -> None:
|
|
297
|
-
self.query_one("#collapsible_status_checks", Collapsible).loading = True
|
|
298
|
-
self.query_one("#collapsible_reviews", Collapsible).loading = True
|
|
299
|
-
self.load_checks()
|
|
300
|
-
|
|
301
318
|
@work
|
|
302
319
|
async def add_reviews(self, reviews: list[Review]) -> None:
|
|
303
|
-
reviews_collapsible_container = self.query_one("#collapsible_reviews", Collapsible)
|
|
304
320
|
if not reviews:
|
|
305
|
-
|
|
306
|
-
|
|
321
|
+
self.collapsible_reviews.title = "No reviews"
|
|
322
|
+
self.collapsible_reviews.display = False
|
|
323
|
+
self.collapsible_reviews.loading = False
|
|
307
324
|
return
|
|
308
325
|
|
|
309
326
|
reviews_list = self.query_one("#reviews_list", ListView)
|
|
@@ -323,12 +340,21 @@ class PrOverviewTabPane(TabPane):
|
|
|
323
340
|
reviews_list.extend(ListItem(Label(Content.from_markup(self._review_to_label(r)))) for r in latest_reviews)
|
|
324
341
|
|
|
325
342
|
if all(r.state == ReviewState.APPROVED for r in latest_reviews):
|
|
326
|
-
|
|
343
|
+
self.collapsible_reviews.title = f"PR Reviews: {ReviewState.APPROVED.to_display()}"
|
|
327
344
|
elif any(r.state == ReviewState.CHANGES_REQUESTED for r in latest_reviews):
|
|
328
|
-
|
|
345
|
+
self.collapsible_reviews.title = f"PR Reviews: {ReviewState.CHANGES_REQUESTED.to_display()}"
|
|
329
346
|
else:
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
self.collapsible_reviews.title = "PR Reviews Summary"
|
|
348
|
+
self.collapsible_reviews.loading = False
|
|
349
|
+
self.collapsible_reviews.display = True
|
|
350
|
+
|
|
351
|
+
@work
|
|
352
|
+
async def load_reactions(self) -> None:
|
|
353
|
+
try:
|
|
354
|
+
reactions = await list_reactions_on_issue(self.pr.repo, self.pr)
|
|
355
|
+
await self.pr_reactions.set_reactions(reactions)
|
|
356
|
+
except Exception as e:
|
|
357
|
+
lg.exception(f"Error loading reactions data: {e}")
|
|
332
358
|
|
|
333
359
|
@work
|
|
334
360
|
async def load_checks(self) -> None:
|
|
@@ -336,18 +362,19 @@ class PrOverviewTabPane(TabPane):
|
|
|
336
362
|
# of those
|
|
337
363
|
combined_check_status = await combined_check_status_for_ref(self.pr.repo, self.pr.head.sha)
|
|
338
364
|
status_checks_list = self.query_one("#status_checks_list", ListView)
|
|
339
|
-
collapse_container = self.query_one("#collapsible_status_checks", Collapsible)
|
|
340
365
|
if statuses := combined_check_status.statuses:
|
|
341
366
|
status_labels = sorted(self._status_check_to_label(c) for c in statuses)
|
|
342
367
|
status_checks_list.extend(
|
|
343
368
|
ListItem(Label(Content.from_markup(status_label))) for status_label in status_labels
|
|
344
369
|
)
|
|
345
370
|
|
|
346
|
-
|
|
371
|
+
self.collapsible_status_checks.title = f"Status checks: {combined_check_status.state.to_display()}"
|
|
372
|
+
self.collapsible_status_checks.display = True
|
|
347
373
|
else:
|
|
348
|
-
|
|
374
|
+
self.collapsible_status_checks.title = "No status checks on PR"
|
|
375
|
+
self.collapsible_status_checks.display = False
|
|
349
376
|
|
|
350
|
-
|
|
377
|
+
self.collapsible_status_checks.loading = False
|
|
351
378
|
|
|
352
379
|
|
|
353
380
|
class PrDiffTabPane(TabPane):
|
|
@@ -385,6 +412,7 @@ class PrConversationTabPane(TabPane):
|
|
|
385
412
|
def __init__(self, pr: FullPullRequest) -> None:
|
|
386
413
|
super().__init__("Conversation", id="conversation_pane")
|
|
387
414
|
self.pr = pr
|
|
415
|
+
self.comment_containers: dict[str, IssueCommentContainer] = {}
|
|
388
416
|
|
|
389
417
|
def compose(self) -> ComposeResult:
|
|
390
418
|
yield VerticalScroll(id="pr_comments_and_reviews")
|
|
@@ -393,33 +421,81 @@ class PrConversationTabPane(TabPane):
|
|
|
393
421
|
def comments_and_reviews(self) -> VerticalScroll:
|
|
394
422
|
return self.query_one("#pr_comments_and_reviews", VerticalScroll)
|
|
395
423
|
|
|
396
|
-
@on(
|
|
397
|
-
async def handle_reviews_loaded(self, message:
|
|
424
|
+
@on(ReviewsAndCommentsLoaded)
|
|
425
|
+
async def handle_reviews_loaded(self, message: ReviewsAndCommentsLoaded) -> None:
|
|
398
426
|
review_hierarchy = reconstruct_review_conversation_hierarchy(message.reviews)
|
|
399
|
-
comments = await get_comments(self.pr)
|
|
400
427
|
self.comments_and_reviews.remove_children()
|
|
401
428
|
|
|
402
429
|
handled_comment_node_ids: list[int] = []
|
|
403
430
|
for review in message.reviews:
|
|
431
|
+
if not review.body and review.state == ReviewState.COMMENTED:
|
|
432
|
+
continue
|
|
404
433
|
if review.body:
|
|
405
434
|
handled_comment_node_ids.extend([c.id for c in review.comments])
|
|
406
435
|
review_container = ReviewContainer(self.pr, review, review_hierarchy)
|
|
407
436
|
self.comments_and_reviews.mount(review_container)
|
|
408
437
|
|
|
409
|
-
for comment in comments:
|
|
438
|
+
for comment in message.comments:
|
|
410
439
|
if comment.body and comment.id not in handled_comment_node_ids:
|
|
411
440
|
comment_container = IssueCommentContainer(self.pr, comment)
|
|
412
441
|
self.comments_and_reviews.mount(comment_container)
|
|
413
442
|
|
|
414
443
|
if len(self.comments_and_reviews.children) == 0:
|
|
415
444
|
self.comments_and_reviews.mount(Label("No reviews or comments available"))
|
|
445
|
+
self.comments_and_reviews.display = False
|
|
446
|
+
else:
|
|
447
|
+
self.comments_and_reviews.display = True
|
|
448
|
+
|
|
449
|
+
self.comment_containers: dict[str, IssueCommentContainer] = {}
|
|
450
|
+
for container in self.query(IssueCommentContainer):
|
|
451
|
+
self.comment_containers[str(container.comment.id)] = container
|
|
452
|
+
|
|
453
|
+
self.fetch_reactions(self.pr.repo, message.reviews, message.comments)
|
|
416
454
|
|
|
417
455
|
self.loading = False
|
|
418
456
|
|
|
457
|
+
@on(CommentReactionsLoaded)
|
|
458
|
+
async def handle_comment_reactions_loaded(self, message: CommentReactionsLoaded) -> None:
|
|
459
|
+
tasks: list[Coroutine[Any, Any, None]] = []
|
|
460
|
+
for comment_id, reactions in message.reactions.items():
|
|
461
|
+
if comment := self.comment_containers.get(str(comment_id)):
|
|
462
|
+
tasks.append(comment.add_reaction_display(reactions))
|
|
463
|
+
|
|
464
|
+
await asyncio.gather(*tasks)
|
|
465
|
+
|
|
466
|
+
@work
|
|
467
|
+
async def fetch_reactions(self, repo: Repository, reviews: list[Review], comments: list[IssueComment]) -> None:
|
|
468
|
+
"""
|
|
469
|
+
Loads any reactions on the specified comments or on comments within the specified reviews. Posts a
|
|
470
|
+
`CommentReactionsLoaded` message after completion.
|
|
471
|
+
"""
|
|
472
|
+
comment_reactions: dict[int, ReactionSet] = {}
|
|
473
|
+
|
|
474
|
+
async def _get_comment_reactions(comment: IssueComment) -> None:
|
|
475
|
+
try:
|
|
476
|
+
comment_reactions[comment.id] = await list_reactions_on_comment(repo, comment)
|
|
477
|
+
except GithubApiRequestFailed:
|
|
478
|
+
lg.debug(f"Could not find comment with ID {comment.id}")
|
|
479
|
+
|
|
480
|
+
tasks: list[Coroutine[Any, Any, None]] = []
|
|
481
|
+
tasks.extend(_get_comment_reactions(comment) for comment in comments)
|
|
482
|
+
for review in reviews:
|
|
483
|
+
tasks.extend(_get_comment_reactions(comment) for comment in review.comments)
|
|
484
|
+
|
|
485
|
+
await asyncio.gather(*tasks)
|
|
486
|
+
self.post_message(CommentReactionsLoaded(comment_reactions))
|
|
487
|
+
|
|
419
488
|
@work
|
|
420
489
|
async def fetch_conversation(self) -> None:
|
|
421
|
-
|
|
422
|
-
self.
|
|
490
|
+
reviews_coro = get_reviews(self.pr)
|
|
491
|
+
comments_coro = get_comments(self.pr)
|
|
492
|
+
|
|
493
|
+
try:
|
|
494
|
+
reviews, comments = await reviews_coro, await comments_coro
|
|
495
|
+
except GithubApiRequestFailed:
|
|
496
|
+
lg.error("Error retrieving PR reviews or comments")
|
|
497
|
+
else:
|
|
498
|
+
self.post_message(ReviewsAndCommentsLoaded(reviews, comments))
|
|
423
499
|
|
|
424
500
|
def on_mount(self) -> None:
|
|
425
501
|
self.loading = True
|
|
@@ -59,8 +59,6 @@ class WorkflowRunsContainer(Container):
|
|
|
59
59
|
|
|
60
60
|
self.run_number_column_id = self.table.get_column_index("run_number")
|
|
61
61
|
|
|
62
|
-
self.searchable_table.loading = True
|
|
63
|
-
|
|
64
62
|
def load_cached_workflow_runs(self, repo: Repository) -> None:
|
|
65
63
|
self.searchable_table.loading = True
|
|
66
64
|
self.searchable_table.initialize_from_cache(repo, WorkflowRun)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
VERSION = "0.6.2"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
VERSION = "0.6.1"
|
lazy_github-0.6.1/lint.sh
DELETED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazy_github-0.6.1 → lazy_github-0.6.2}/.quicklinks/queries/python/patterns/textual_imports.scm
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{lazy_github-0.6.1 → lazy_github-0.6.2}/lazy_github/ui/screens/create_or_edit_pull_request.py
RENAMED
|
@@ -18,7 +18,6 @@ from lazy_github.lib.git_cli import (
|
|
|
18
18
|
push_branch_to_remote,
|
|
19
19
|
)
|
|
20
20
|
from lazy_github.lib.github.branches import list_branches
|
|
21
|
-
from lazy_github.lib.pr_drafts import PullRequestDraft, clear_pr_draft, load_pr_draft, save_pr_draft
|
|
22
21
|
from lazy_github.lib.github.pull_requests import (
|
|
23
22
|
create_pull_request,
|
|
24
23
|
list_requested_reviewers,
|
|
@@ -29,6 +28,7 @@ from lazy_github.lib.github.repositories import get_collaborators
|
|
|
29
28
|
from lazy_github.lib.github.users import get_user_by_username
|
|
30
29
|
from lazy_github.lib.logging import lg
|
|
31
30
|
from lazy_github.lib.messages import BranchesLoaded, PullRequestCreatedOrUpdated
|
|
31
|
+
from lazy_github.lib.pr_drafts import PullRequestDraft, clear_pr_draft, load_pr_draft, save_pr_draft
|
|
32
32
|
from lazy_github.models.github import Branch, FullPullRequest, PartialPullRequest
|
|
33
33
|
from lazy_github.ui.screens.confirm import ConfirmDialog
|
|
34
34
|
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|