-
Notifications
You must be signed in to change notification settings - Fork 48
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Factor out reporting Reviewed-by: Laura Barcziová
- Loading branch information
Showing
15 changed files
with
690 additions
and
601 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Copyright Contributors to the Packit project. | ||
# SPDX-License-Identifier: MIT | ||
|
||
from packit_service.worker.reporting.enums import BaseCommitStatus, DuplicateCheckMode | ||
from packit_service.worker.reporting.reporters.base import StatusReporter | ||
from packit_service.worker.reporting.reporters.github import ( | ||
StatusReporterGithubChecks, | ||
StatusReporterGithubStatuses, | ||
) | ||
from packit_service.worker.reporting.reporters.gitlab import StatusReporterGitlab | ||
from packit_service.worker.reporting.utils import ( | ||
report_in_issue_repository, | ||
update_message_with_configured_failure_comment_message, | ||
) | ||
|
||
__all__ = [ | ||
BaseCommitStatus.__name__, | ||
StatusReporter.__name__, | ||
DuplicateCheckMode.__name__, | ||
report_in_issue_repository.__name__, | ||
update_message_with_configured_failure_comment_message.__name__, | ||
StatusReporterGithubChecks.__name__, | ||
StatusReporterGithubStatuses.__name__, | ||
StatusReporterGitlab.__name__, | ||
] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
# Copyright Contributors to the Packit project. | ||
# SPDX-License-Identifier: MIT | ||
|
||
from enum import Enum, auto | ||
from typing import Dict, Union | ||
|
||
from ogr.abstract import CommitStatus | ||
from ogr.services.github.check_run import ( | ||
GithubCheckRunResult, | ||
GithubCheckRunStatus, | ||
) | ||
|
||
|
||
class DuplicateCheckMode(Enum): | ||
"""Enum of possible behaviour for handling duplicates when commenting.""" | ||
|
||
# Do not check for duplicates | ||
do_not_check = auto() | ||
# Check only last comment from us for duplicate | ||
check_last_comment = auto() | ||
# Check the whole comment list for duplicate | ||
check_all_comments = auto() | ||
|
||
|
||
class BaseCommitStatus(Enum): | ||
failure = "failure" | ||
neutral = "neutral" | ||
success = "success" | ||
pending = "pending" | ||
running = "running" | ||
error = "error" | ||
|
||
|
||
MAP_TO_COMMIT_STATUS: Dict[BaseCommitStatus, CommitStatus] = { | ||
BaseCommitStatus.pending: CommitStatus.pending, | ||
BaseCommitStatus.running: CommitStatus.running, | ||
BaseCommitStatus.failure: CommitStatus.failure, | ||
BaseCommitStatus.neutral: CommitStatus.error, | ||
BaseCommitStatus.success: CommitStatus.success, | ||
BaseCommitStatus.error: CommitStatus.error, | ||
} | ||
|
||
MAP_TO_CHECK_RUN: Dict[ | ||
BaseCommitStatus, Union[GithubCheckRunResult, GithubCheckRunStatus] | ||
] = { | ||
BaseCommitStatus.pending: GithubCheckRunStatus.queued, | ||
BaseCommitStatus.running: GithubCheckRunStatus.in_progress, | ||
BaseCommitStatus.failure: GithubCheckRunResult.failure, | ||
BaseCommitStatus.neutral: GithubCheckRunResult.neutral, | ||
BaseCommitStatus.success: GithubCheckRunResult.success, | ||
BaseCommitStatus.error: GithubCheckRunResult.failure, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Copyright Contributors to the Packit project. | ||
# SPDX-License-Identifier: MIT | ||
|
||
from random import choice | ||
|
||
|
||
class News: | ||
__FOOTERS = [ | ||
"Do you maintain a Fedora package and don't have access to the upstream repository? " | ||
"Packit can help. " | ||
"Take a look [here](https://packit.dev/posts/pull-from-upstream/) to know more.", | ||
"Do you maintain a Fedora package and you think it's boring? Packit can help. " | ||
"Take a look [here](https://packit.dev/posts/downstream-automation/) to know more.", | ||
"Want to use a build from a different project when testing? " | ||
"Take a look [here](https://packit.dev/posts/testing-farm-triggering/) to know more.", | ||
"Curious how Packit handles the Release field during propose-downstream? " | ||
"Take a look [here](https://packit.dev/posts/release-field-handling/) to know more.", | ||
"Did you know Packit is on Mastodon? Or, more specifically, on Fosstodon? " | ||
"Follow [@packit@fosstodon.org](https://fosstodon.org/@packit) " | ||
"and be one of the first to know about all the news!", | ||
"Interested in the Packit team plans and priorities? " | ||
"Check [our epic board](https://github.com/orgs/packit/projects/7/views/29).", | ||
"Do you use `propose_downstream`? We would be happy if you could help" | ||
"us with verifying it in staging. " | ||
"See [the details](https://packit.dev/posts/verify-sync-release-volunteers)", | ||
] | ||
|
||
@classmethod | ||
def get_sentence(cls) -> str: | ||
""" | ||
A random sentence to show our users as a footer when adding a status. | ||
(Will be visible at the very bottom of the markdown field. | ||
""" | ||
return choice(cls.__FOOTERS) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,263 @@ | ||
# Copyright Contributors to the Packit project. | ||
# SPDX-License-Identifier: MIT | ||
|
||
import logging | ||
from datetime import datetime, timezone | ||
from typing import Optional, Union, Dict, Callable | ||
|
||
from packit_service.worker.reporting.enums import ( | ||
BaseCommitStatus, | ||
MAP_TO_COMMIT_STATUS, | ||
MAP_TO_CHECK_RUN, | ||
DuplicateCheckMode, | ||
) | ||
|
||
from ogr.abstract import GitProject | ||
from ogr.services.github import GithubProject | ||
from ogr.services.gitlab import GitlabProject | ||
from ogr.services.pagure import PagureProject | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class StatusReporter: | ||
def __init__( | ||
self, | ||
project: GitProject, | ||
commit_sha: str, | ||
packit_user: str, | ||
project_event_id: Optional[int] = None, | ||
pr_id: Optional[int] = None, | ||
): | ||
logger.debug( | ||
f"Status reporter will report for {project}, commit={commit_sha}, pr={pr_id}" | ||
) | ||
self.project: GitProject = project | ||
self._project_with_commit: Optional[GitProject] = None | ||
self._packit_user = packit_user | ||
|
||
self.commit_sha: str = commit_sha | ||
self.project_event_id: int = project_event_id | ||
self.pr_id: Optional[int] = pr_id | ||
|
||
@classmethod | ||
def get_instance( | ||
cls, | ||
project: GitProject, | ||
commit_sha: str, | ||
packit_user: str, | ||
project_event_id: Optional[int] = None, | ||
pr_id: Optional[int] = None, | ||
) -> "StatusReporter": | ||
""" | ||
Get the StatusReporter instance. | ||
""" | ||
from .github import StatusReporterGithubChecks | ||
from .gitlab import StatusReporterGitlab | ||
from .pagure import StatusReporterPagure | ||
|
||
reporter = StatusReporter | ||
if isinstance(project, GithubProject): | ||
reporter = StatusReporterGithubChecks | ||
elif isinstance(project, GitlabProject): | ||
reporter = StatusReporterGitlab | ||
elif isinstance(project, PagureProject): | ||
reporter = StatusReporterPagure | ||
return reporter(project, commit_sha, packit_user, project_event_id, pr_id) | ||
|
||
@property | ||
def project_with_commit(self) -> GitProject: | ||
""" | ||
Returns GitProject from which we can set commit status. | ||
""" | ||
if self._project_with_commit is None: | ||
self._project_with_commit = ( | ||
self.project.get_pr(self.pr_id).source_project | ||
if isinstance(self.project, GitlabProject) and self.pr_id is not None | ||
else self.project | ||
) | ||
|
||
return self._project_with_commit | ||
|
||
@staticmethod | ||
def get_commit_status(state: BaseCommitStatus): | ||
return MAP_TO_COMMIT_STATUS[state] | ||
|
||
@staticmethod | ||
def get_check_run(state: BaseCommitStatus): | ||
return MAP_TO_CHECK_RUN[state] | ||
|
||
def set_status( | ||
self, | ||
state: BaseCommitStatus, | ||
description: str, | ||
check_name: str, | ||
url: str = "", | ||
links_to_external_services: Optional[Dict[str, str]] = None, | ||
markdown_content: str = None, | ||
): | ||
raise NotImplementedError() | ||
|
||
def report( | ||
self, | ||
state: BaseCommitStatus, | ||
description: str, | ||
url: str = "", | ||
links_to_external_services: Optional[Dict[str, str]] = None, | ||
check_names: Union[str, list, None] = None, | ||
markdown_content: str = None, | ||
update_feedback_time: Callable = None, | ||
) -> None: | ||
""" | ||
Set commit check status. | ||
Args: | ||
state: State accepted by github. | ||
description: The long text. | ||
url: Url to point to (logs usually). | ||
Defaults to empty string | ||
links_to_external_services: Direct links to external services. | ||
e.g. `{"Testing Farm": "url-to-testing-farm"}` | ||
Defaults to None | ||
check_names: Those in bold. | ||
Defaults to None | ||
markdown_content: In GitHub checks, we can provide a markdown content. | ||
Defaults to None | ||
update_feedback_time: a callable which tells the caller when a check | ||
status has been updated. | ||
Returns: | ||
None | ||
""" | ||
if not check_names: | ||
logger.warning("No checks to set status for.") | ||
return | ||
|
||
elif isinstance(check_names, str): | ||
check_names = [check_names] | ||
|
||
for check in check_names: | ||
self.set_status( | ||
state=state, | ||
description=description, | ||
check_name=check, | ||
url=url, | ||
links_to_external_services=links_to_external_services, | ||
markdown_content=markdown_content, | ||
) | ||
|
||
if update_feedback_time: | ||
update_feedback_time(datetime.now(timezone.utc)) | ||
|
||
@staticmethod | ||
def is_final_state(state: BaseCommitStatus) -> bool: | ||
return state in { | ||
BaseCommitStatus.success, | ||
BaseCommitStatus.error, | ||
BaseCommitStatus.failure, | ||
} | ||
|
||
def _add_commit_comment_with_status( | ||
self, state: BaseCommitStatus, description: str, check_name: str, url: str = "" | ||
): | ||
"""Add a comment with status to the commit. | ||
A fallback solution when setting commit status fails. | ||
""" | ||
body = ( | ||
"\n".join( | ||
[ | ||
f"- name: {check_name}", | ||
f"- state: {state.name}", | ||
f"- url: {url or 'not provided'}", | ||
] | ||
) | ||
+ f"\n\n{description}" | ||
) | ||
|
||
if self.is_final_state(state): | ||
self.comment(body, DuplicateCheckMode.check_all_comments, to_commit=True) | ||
else: | ||
logger.debug(f"Ain't comment as {state!r} is not a final state") | ||
|
||
def report_status_by_comment( | ||
self, | ||
state: BaseCommitStatus, | ||
url: str, | ||
check_names: Union[str, list, None], | ||
description: str, | ||
): | ||
""" | ||
Reporting build status with MR comment if no permission to the fork project | ||
""" | ||
|
||
if isinstance(check_names, str): | ||
check_names = [check_names] | ||
|
||
comment_table_rows = [ | ||
"| Job | Result |", | ||
"| ------------- | ------------ |", | ||
] + [f"| [{check}]({url}) | {state.name.upper()} |" for check in check_names] | ||
|
||
table = "\n".join(comment_table_rows) | ||
self.comment(table + f"\n### Description\n\n{description}") | ||
|
||
def get_statuses(self): | ||
self.project_with_commit.get_commit_statuses(commit=self.commit_sha) | ||
|
||
def _has_identical_comment( | ||
self, body: str, mode: DuplicateCheckMode, check_commit: bool = False | ||
) -> bool: | ||
"""Checks if the body is the same as the last or any (based on mode) comment. | ||
Check either commit comments or PR comments (if specified). | ||
""" | ||
if mode == DuplicateCheckMode.do_not_check: | ||
return False | ||
|
||
comments = ( | ||
reversed(self.project.get_commit_comments(self.commit_sha)) | ||
if check_commit or not self.pr_id | ||
else self.project.get_pr(pr_id=self.pr_id).get_comments(reverse=True) | ||
) | ||
for comment in comments: | ||
if comment.author.startswith(self._packit_user): | ||
if mode == DuplicateCheckMode.check_last_comment: | ||
return body == comment.body | ||
elif ( | ||
mode == DuplicateCheckMode.check_all_comments | ||
and body == comment.body | ||
): | ||
return True | ||
return False | ||
|
||
def comment( | ||
self, | ||
body: str, | ||
duplicate_check: DuplicateCheckMode = DuplicateCheckMode.do_not_check, | ||
to_commit: bool = False, | ||
): | ||
"""Add a comment. | ||
It's added either to a commit or to a PR (if specified). | ||
Args: | ||
body: The comment text. | ||
duplicate_check: Determines if the comment will be added if | ||
the same comment is already present in the PR | ||
(if the instance is tied to a PR) or in a commit. | ||
to_commit: Add the comment to the commit even if PR is specified. | ||
""" | ||
if self._has_identical_comment(body, duplicate_check, to_commit): | ||
logger.debug("Identical comment already exists") | ||
return | ||
|
||
if to_commit or not self.pr_id: | ||
self.project.commit_comment(commit=self.commit_sha, body=body) | ||
else: | ||
self.project.get_pr(pr_id=self.pr_id).comment(body=body) |
Oops, something went wrong.