diff --git a/CHANGELOG b/CHANGELOG index 6f6615e1..91b3d0a2 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,13 @@ # Change Log All notable changes to this project will be documented in this file. +## [4.0.0] - 2024-03-06 +# Removed +- Support of tasks as it unused to due its incompatibility with GitHub. + +# Added +- Bert-E's status notifications through a build status check. + ## [3.12.0] - 2024-02-26 # Added - Add toggable list of options and commands to init message. diff --git a/bert_e/bert_e.py b/bert_e/bert_e.py index f2a03b76..8af8ea53 100644 --- a/bert_e/bert_e.py +++ b/bert_e/bert_e.py @@ -49,10 +49,6 @@ def __init__(self, settings): ) if settings.repository_host == 'bitbucket': self.settings.robot.account_id = self.client.get_user_id() - if settings.repository_host == 'github': - if settings['tasks']: - LOG.warning("Disabling tasks on GitHub repo") - settings['tasks'] = [] self.project_repo = self.client.get_repository( owner=settings.repository_owner, slug=settings.repository_slug diff --git a/bert_e/exceptions.py b/bert_e/exceptions.py index dc46cb9d..66231997 100644 --- a/bert_e/exceptions.py +++ b/bert_e/exceptions.py @@ -12,6 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. +from typing import Literal from bert_e.lib.template_loader import render # When dont_repeat_if_in_history is None, Bert-E will look for the message @@ -27,6 +28,7 @@ class BertE_Exception(Exception): class TemplateException(BertE_Exception): code = -2 template = None + status: Literal[None, "in_progress", "success", "failure"] = None # whether to re-publish if the message is already in the history dont_repeat_if_in_history = -1 @@ -40,6 +42,11 @@ def __init__(self, **kwargs): self.msg = render(self.template, code=self.code, **kwargs) super(TemplateException, self).__init__(self.msg) + @property + def title(self) -> str: + # Return the exception class name as the title + return self.__class__.__name__ + class InternalException(BertE_Exception): code = 1 @@ -69,6 +76,7 @@ class HelpMessage(TemplateException): class SuccessMessage(TemplateException): code = 102 template = 'successful_merge.md' + status = "success" class CommandNotImplemented(TemplateException): @@ -86,61 +94,73 @@ class StatusReport(TemplateException): class IncompatibleSourceBranchPrefix(TemplateException): code = 106 template = 'incompatible_source_branch_prefix.md' + status = "failure" class MissingJiraId(TemplateException): code = 107 template = 'missing_jira_id.md' + status = "failure" class JiraIssueNotFound(TemplateException): code = 108 template = 'jira_issue_not_found.md' + status = "failure" class IssueTypeNotSupported(TemplateException): code = 109 template = 'issue_type_not_supported.md' + status = "failure" class IncorrectJiraProject(TemplateException): code = 110 template = 'incorrect_jira_project.md' + status = "failure" class MismatchPrefixIssueType(TemplateException): code = 111 template = 'mismatch_prefix_issue_type.md' + status = "failure" class IncorrectFixVersion(TemplateException): code = 112 template = 'incorrect_fix_version.md' + status = "failure" class BranchHistoryMismatch(TemplateException): code = 113 template = 'history_mismatch.md' + status = "failure" class Conflict(TemplateException): code = 114 template = 'conflict.md' + status = "failure" class ApprovalRequired(TemplateException): code = 115 template = 'need_approval.md' + status = "in_progress" class BuildFailed(TemplateException): code = 118 template = 'build_failed.md' + status = "failure" class AfterPullRequest(TemplateException): code = 120 template = 'after_pull_request.md' + status = "in_progress" class IntegrationDataCreated(InformationException): @@ -151,21 +171,25 @@ class IntegrationDataCreated(InformationException): class UnknownCommand(TemplateException): code = 122 template = 'unknown_command.md' + status = "failure" class NotEnoughCredentials(TemplateException): code = 123 template = "not_enough_credentials.md" + status = "failure" class QueueConflict(TemplateException): code = 124 template = "queue_conflict.md" + status = "failure" class Queued(TemplateException): code = 125 template = 'queued.md' + status = "in_progress" def __init__(self, branches, ignored, issue, author, active_options): """Save args for later use by tests.""" @@ -187,11 +211,13 @@ class PartialMerge(TemplateException): code = 126 template = 'partial_merge.md' dont_repeat_if_in_history = 0 # allow repeating as many times as it occurs + status = "success" class QueueOutOfOrder(TemplateException): code = 127 template = "queue_out_of_order.md" + status = "failure" class ResetComplete(TemplateException): @@ -202,36 +228,44 @@ class ResetComplete(TemplateException): class LossyResetWarning(TemplateException): code = 129 template = "lossy_reset.md" + status = "failure" class IncorrectCommandSyntax(TemplateException): code = 130 template = "incorrect_command_syntax.md" + status = "failure" class IncorrectPullRequestNumber(TemplateException): code = 131 template = "incorrect_pull_request_number.md" + status = "failure" class SourceBranchTooOld(TemplateException): code = 132 template = "source_branch_too_old.md" + status = "failure" class FlakyGitHost(TemplateException): code = 133 template = "flaky_git_host.md" + status = "failure" class NotAuthor(TemplateException): code = 134 template = "not_author.md" + status = "failure" class RequestIntegrationBranches(TemplateException): code = 135 template = "request_integration_branches.md" + # TODO: review if it should be failure. + status = "in_progress" # internal exceptions diff --git a/bert_e/git_host/base.py b/bert_e/git_host/base.py index 5d36a0a4..a38ab926 100644 --- a/bert_e/git_host/base.py +++ b/bert_e/git_host/base.py @@ -250,23 +250,8 @@ def key(self) -> str: """The build status key.""" -class AbstractTask(metaclass=ABCMeta): - """Abstract class defining a task's interface.""" - # Empty, but used as a return value below - - class AbstractComment(metaclass=ABCMeta): """Abstract class defining the interface of a pull requests's comment.""" - @abstractmethod - def add_task(self, msg: str) -> AbstractTask: - """Attach a new task attached to this comment. - - Args: - - msg: the message of the task to attach. - - Returns: the newly created task. - - """ @abstractmethod def delete(self) -> None: @@ -326,14 +311,6 @@ def get_approvals(self) -> Iterable[str]: def get_participants(self) -> Iterable[str]: """Get the usernames of the participants to this pull request.""" - @abstractmethod - def get_tasks(self) -> Iterable[AbstractTask]: - """Get this pull request's tasks. - - Returns: an iterable over the Task objects. - - """ - @abstractmethod def comment_review(self): """Request changes on this pull request.""" @@ -350,6 +327,17 @@ def approve(self): def decline(self): """Decline this pull request.""" + @abstractmethod + def set_bot_status(self, status: str | None, title: str, + summary: str) -> None: + """Set a status check reporting its advancement regarding Bert-E's checks + + Args: + - status: the status of the check. + - title: the title of the check. + - summary: the summary of the check. + """ + @property @abstractmethod def id(self) -> str: diff --git a/bert_e/git_host/bitbucket/__init__.py b/bert_e/git_host/bitbucket/__init__.py index 082f93f4..6e24460c 100644 --- a/bert_e/git_host/bitbucket/__init__.py +++ b/bert_e/git_host/bitbucket/__init__.py @@ -23,7 +23,6 @@ from . import schema from .. import base, cache, factory -from bert_e.exceptions import TaskAPIError MAX_PR_TITLE_LEN = 255 @@ -356,6 +355,10 @@ def add_comment(self, msg): pull_request_id=self['id'] ) + def set_bot_status(self, status: str | None, title: str, summary: str): + raise NotImplementedError('"set_bot_status" feature ' + 'is not available in bitbucket') + def get_comments(self, deleted=False): return sorted( (comment @@ -367,10 +370,6 @@ def get_comments(self, deleted=False): key=lambda c: c.created_on ) - def get_tasks(self): - return Task.get_list(self.client, full_name=self.full_name(), - pull_request_id=self['id']) - def get_change_requests(self): # Not supported by bitbucket, default to an empty tuple of usernames return tuple() @@ -485,11 +484,6 @@ def full_name(self): p = Path(urlparse(self.data['links']['self']['href']).path).resolve() return '%s/%s' % p.parts[3:5] - def add_task(self, msg): - return Task(self.client, content=msg, full_name=self.full_name(), - pull_request_id=self.data['pullrequest']['id'], - comment_id=self.id).create() - def delete(self): return super().delete(self.client, full_name=self.full_name(), pull_request_id=self.data['pullrequest']['id'], @@ -520,46 +514,6 @@ def deleted(self): return self.data['deleted'] -class Task(BitBucketObject, base.AbstractTask): - get_url = 'https://bitbucket.org/!api/internal/repositories/$full_name/' \ - 'pullrequests/$pull_request_id/tasks/$task_id' - add_url = 'https://bitbucket.org/!api/internal/repositories/$full_name/' \ - 'pullrequests/$pull_request_id/tasks' - list_url = add_url + '?page=$page' - - def __init__(self, client, **kwargs): - super().__init__(client, **kwargs) - if 'comment_id' in self._json_data: - self._json_data['comment'] = {'id': self._json_data['comment_id']} - if 'content' in self._json_data: - self._json_data['content'] = {'raw': self._json_data['content']} - - def create(self, *args, **kwargs): - try: - return super().create(*args, **kwargs) - except Exception as err: - raise TaskAPIError('create', err) - - def delete(self, *args, **kwargs): - try: - return super().delete(*args, **kwargs) - except Exception as err: - raise TaskAPIError('delete', err) - - def get(self, *args, **kwargs): - try: - return super().get(*args, **kwargs) - except Exception as err: - raise TaskAPIError('get', err) - - @classmethod - def get_list(self, *args, **kwargs): - try: - return list(super().get_list(*args, **kwargs)) - except Exception as err: - raise TaskAPIError('get_list', err) - - class BuildStatus(BitBucketObject, base.AbstractBuildStatus): get_url = 'https://api.bitbucket.org/2.0/repositories/$owner/$repo_slug/' \ 'commit/$revision/statuses/build/$key' diff --git a/bert_e/git_host/github/__init__.py b/bert_e/git_host/github/__init__.py index e4948c4e..e8f52d4b 100644 --- a/bert_e/git_host/github/__init__.py +++ b/bert_e/git_host/github/__init__.py @@ -22,7 +22,6 @@ from requests import HTTPError from urllib.parse import quote_plus as quote -from bert_e.exceptions import TaskAPIError from bert_e.lib.lru_cache import LRUCache from . import schema from .. import base, cache, factory @@ -825,8 +824,27 @@ def add_comment(self, msg: str): url = self.data['comments_url'] return Comment.create(self.client, {'body': msg}, url=url) - def add_checkrun( - self, name: str, status: str, conclusion: str, + def set_bot_status(self, status: str | None, title: str, summary: str): + if self.client and self.client.is_app is False: + LOG.error("Cannot set bot status without a GitHub App") + return + conclusion: str | None = None + if status == "success": + conclusion = "success" + status = "completed" + elif status == "in_progress": + conclusion = None + elif status == "failure": + conclusion = "failure" + status = "completed" + + self._add_checkrun( + name='bert-e', status=status, conclusion=conclusion, + title=title, summary=summary + ) + + def _add_checkrun( + self, name: str, status: str, conclusion: str | None, title: str, summary: str): return CheckRun.create( client=self.client, @@ -854,9 +872,6 @@ def comments(self): def repo(self): return Repository(**self.data['base']['repo'], client=self.client) - def get_tasks(self): - raise TaskAPIError("PullRequest.get_tasks", NotImplemented) - def get_reviews(self): repo = self.repo self._reviews = list(Review.list( @@ -968,9 +983,6 @@ class Comment(base.AbstractGitHostObject, base.AbstractComment): SCHEMA = schema.Comment CREATE_SCHEMA = schema.CreateComment - def add_task(self, *args): - raise TaskAPIError("Comment.add_task", NotImplemented) - @property def author(self) -> str: return self.data['user']['login'].lower() diff --git a/bert_e/git_host/mock.py b/bert_e/git_host/mock.py index d1c229da..02138b94 100644 --- a/bert_e/git_host/mock.py +++ b/bert_e/git_host/mock.py @@ -19,7 +19,6 @@ import requests from . import base -from ..exceptions import TaskAPIError from ..lib.git import Branch as GitBranch from ..lib.git import Repository as GitRepository from .factory import api_client @@ -168,19 +167,24 @@ def __init__(self, client, owner, repo_slug, scm='git', is_private=True): self.size = 76182262 self.is_private = is_private self.uuid = "{9970a9b6-2d86-413f-8555-da8e1ac0e542}" - - # ############### + # revisions will be used to store the build status + # It is a dict that will return the build status for a given + # revision and key. + # The value of revisions will be consistent across + # every instance of Repository even if the class is + # instantiated multiple times, therefore it needs to be + # cached at the class level. + if not hasattr(Repository, 'revisions'): + Repository.revisions = {} def delete(self): super().delete() PullRequest.items = [] - Task.items = [] Comment.items = [] def create(self): self.gitrepo = GitRepository(None) self.gitrepo.cmd('git init --bare') - self.gitrepo.revisions = {} # UGLY Repository.repos[(self.repo_owner, self.repo_slug)] = self.gitrepo return super().create() @@ -235,11 +239,11 @@ def get_commit_url(self, revision): def get_build_url(self, revision, key): key = '{}-build'.format(revision) - return self.gitrepo.revisions.get((revision, key), None) + return self.revisions.get((revision, key), None) def get_build_status(self, revision, key): try: - return self.gitrepo.revisions[(revision, key)] + return self.revisions[(revision, key)] except KeyError: return 'NOTSTARTED' @@ -248,7 +252,7 @@ def invalidate_build_status_cache(self): def set_build_status(self, revision, key, state, **kwargs): self.get_git_url() - self.gitrepo.revisions[(revision, key)] = state + self.revisions[(revision, key)] = state @property def owner(self): @@ -274,11 +278,6 @@ def get_comments(self): self.client, full_name=self.controlled.full_name(), pull_request_id=self.controlled.id)) - def get_tasks(self): - return [Controller(self.client, t) for t in Task.get_list( - self.client, full_name=self.controlled.full_name(), - pull_request_id=self.controlled.id)] - def merge(self): raise NotImplementedError('Merge') @@ -351,6 +350,15 @@ def decline(self): except Exception: pass + def set_bot_status(self, status: str, title: str, summary: str): + + self['repo'].set_build_status( + revision=self.src_commit, + key="bert-e", + state=status, + description=f"# {title}\n{summary}" + ) + @property def id(self): return self['id'] @@ -397,14 +405,6 @@ def comments(self): class CommentController(Controller, base.AbstractComment): - def add_task(self, msg): - task = Task(self.client, content=msg, - full_name=self.controlled.full_name, - pr_id=self.controlled.pull_request_id, - comment_id=self.controlled.id).create() - - PullRequest.items[self.controlled.pull_request_id - 1].task_count += 1 - return task def delete(self): self.controlled.delete() @@ -470,7 +470,6 @@ def __init__(self, repo, title, name, source, destination, "commit": Branch(self.repo.gitrepo, source['branch']['name']), "repository": fake_repository_dict("") } - self.task_count = 0 self.title = title self.type = "pullrequest" self.updated_on = "2016-01-12T19:31:23.673329+00:00" @@ -518,35 +517,6 @@ def get_list(client, full_name, pull_request_id): c.pull_request_id == pull_request_id] -class Task(BitBucketObject, base.AbstractTask): - add_url = 'legit_add_url' - list_url = 'legit_list_url' - items = [] - - def __init__(self, client, content, pr_id, full_name, comment_id): - # URL params # - self.pull_request_id = pr_id - self.full_name = full_name - self.comment_id = comment_id - - # JSON params # - self.content = {"raw": content} - self.id = len(Task.items) - - def create(self): - if self.add_url != 'legit_add_url': - raise TaskAPIError('create', 'url does not work') - self.__class__.items.append(self) - return self - - @staticmethod - def get_list(client, full_name, pull_request_id): - if Task.list_url != 'legit_list_url': - raise TaskAPIError('get_list', 'url does not work') - return [t for t in Task.items if t.full_name == full_name and - t.pull_request_id == pull_request_id] - - class BuildStatus(BitBucketObject, base.AbstractBuildStatus): pass diff --git a/bert_e/settings.py b/bert_e/settings.py index 3628dacd..d00cf318 100644 --- a/bert_e/settings.py +++ b/bert_e/settings.py @@ -166,7 +166,6 @@ class Meta: UserSettingSchema, many=True, load_default=[]) project_leaders = fields.Nested( UserSettingSchema, many=True, load_default=[]) - tasks = fields.List(fields.Str(), load_default=[]) max_commit_diff = fields.Int(required=False, load_default=0) @@ -194,6 +193,8 @@ class Meta: github_private_key = fields.Str(required=False, load_default='') github_installation_id = fields.Int(required=False, load_default='') + send_bot_status = fields.Bool(required=False, load_default=False) + @pre_load(pass_many=True) def load_env(self, data, **kwargs): """Load environment variables""" diff --git a/bert_e/templates/init.md b/bert_e/templates/init.md index 85ced5fc..a9533c83 100644 --- a/bert_e/templates/init.md +++ b/bert_e/templates/init.md @@ -11,12 +11,6 @@ pull request. {% if frontend_url on this process, or consult the [user documentation]( {{ frontend_url }}/doc/user).{% endif %} -{% if tasks %} -I have created below the minimum set of tasks expected to be performed during -this review. -{% endif%} - - {% if options %}
Available options diff --git a/bert_e/tests/assets/settings-env.yml b/bert_e/tests/assets/settings-env.yml index 560e1064..0e310c4c 100644 --- a/bert_e/tests/assets/settings-env.yml +++ b/bert_e/tests/assets/settings-env.yml @@ -29,10 +29,6 @@ admins: - username_admin_1 - username_admin_2@557042:08898ca4-5f12-4042-9942-87e167728afd -tasks: - - do this - - do that - max_commit_diff: 100 always_create_integration_pull_requests: true diff --git a/bert_e/tests/test_bert_e.py b/bert_e/tests/test_bert_e.py index af2a8dbc..ad2646b7 100644 --- a/bert_e/tests/test_bert_e.py +++ b/bert_e/tests/test_bert_e.py @@ -87,9 +87,6 @@ - {admin} project_leaders: - {admin} -tasks: - - do foo - - do bar """ # noqa @@ -4212,154 +4209,6 @@ def test_settings(self): pr.id, options=['bypass_author_approval'], backtrace=True, settings=settings) - def test_task_list_creation(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - pr = self.create_pr('feature/death-ray', 'development/10.0') - try: - self.handle(pr.id) - except requests.HTTPError as err: - self.fail("Error from bitbucket: %s" % err.response.text) - # retrieving tasks from private bitbucket API only works for admin - pr_admin = self.admin_bb.get_pull_request(pull_request_id=pr.id) - self.assertEqual(len(list(pr_admin.get_tasks())), 2) - init_comment = pr.comments[0].text - self.assertIn('task', init_comment) - - def test_task_list_missing(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - pr = self.create_pr('feature/death-ray', 'development/10.0') - settings = """ -repository_owner: {owner} -repository_slug: {slug} -repository_host: {host} -robot: {robot} -robot_email: nobody@nowhere.com -pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} -commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} -build_key: pre-merge -required_leader_approvals: 0 -required_peer_approvals: 0 -admins: - - {admin} -""" # noqa - try: - self.handle(pr.id, settings=settings) - except requests.HTTPError as err: - self.fail("Error from bitbucket: %s" % err.response.text) - pr_admin = self.admin_bb.get_pull_request(pull_request_id=pr.id) - self.assertEqual(len(list(pr_admin.get_tasks())), 0) - init_comment = pr.comments[0].text - self.assertNotIn('task', init_comment) - - def test_task_list_funky(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - pr = self.create_pr('feature/death-ray', 'development/10.0') - settings = """ -repository_owner: {owner} -repository_slug: {slug} -repository_host: {host} -robot: {robot} -robot_email: nobody@nowhere.com -pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} -commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} -build_key: pre-merge -required_leader_approvals: 0 -required_peer_approvals: 0 -admins: - - {admin} -tasks: - - '' - - zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz - - éâàë€ - - 1 - - 2 - - 3 - - 3 - - 3 - - 3 - - 3 - - 3 - - 3 - - 3 - - 3 - - 3 -""" # noqa - try: - self.handle(pr.id, settings=settings) - except requests.HTTPError as err: - self.fail("Error from bitbucket: %s" % err.response.text) - pr_admin = self.admin_bb.get_pull_request(pull_request_id=pr.id) - self.assertEqual(len(list(pr_admin.get_tasks())), 15) - - def test_task_list_illegal(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - pr = self.create_pr('feature/death-ray', 'development/10.0') - settings = """ -repository_owner: {owner} -repository_slug: {slug} -repository_host: {host} -robot: {robot} -robot_email: nobody@nowhere.com -pull_request_base_url: https://bitbucket.org/{owner}/{slug}/bar/pull-requests/{{pr_id}} -commit_base_url: https://bitbucket.org/{owner}/{slug}/commits/{{commit_id}} -build_key: pre-merge -required_leader_approvals: 0 -required_peer_approvals: 0 -admins: - - {admin} -tasks: - - ['a task in a list'] -""" # noqa - with self.assertRaises(exns.MalformedSettings): - self.handle(pr.id, backtrace=True, settings=settings) - - def test_task_list_incompatible_api_update_create(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - try: - real = bitbucket_api.Task.add_url - bitbucket_api.Task.add_url = 'https://bitbucket.org/plouf' - - pr = self.create_pr('feature/death-ray', 'development/10.0') - try: - self.handle(pr.id) - except requests.HTTPError as err: - self.fail("Error from bitbucket: %s" % err.response.text) - pr_admin = self.admin_bb.get_pull_request(pull_request_id=pr.id) - self.assertEqual(len(list(pr_admin.get_tasks())), 0) - - finally: - bitbucket_api.Task.add_url = real - - def test_task_list_incompatible_api_update_list(self): - if self.args.git_host == 'github': - self.skipTest("Tasks are not supported on GitHub") - - try: - real = bitbucket_api.Task.list_url - bitbucket_api.Task.list_url = 'https://bitbucket.org/plouf' - - pr = self.create_pr('feature/death-ray', 'development/10.0') - try: - self.handle(pr.id) - except requests.HTTPError as err: - self.fail("Error from bitbucket: %s" % err.response.text) - pr_admin = self.admin_bb.get_pull_request(pull_request_id=pr.id) - with self.assertRaises(exns.TaskAPIError): - len(list(pr_admin.get_tasks())) - - finally: - bitbucket_api.Task.list_url = real - def test_branches_have_diverged(self): settings = DEFAULT_SETTINGS + 'max_commit_diff: 5' pr = self.create_pr('feature/time-warp', 'development/10.0') @@ -4936,6 +4785,21 @@ def test_init_message(self): for command in ['help', 'reset']: assert command in init_message + def test_set_bot_status(self): + """Test Bert-E's capability to its own status on PRs""" + settings = DEFAULT_SETTINGS + "send_bot_status: true" + pr = self.create_pr('bugfix/TEST-01', 'development/4.3') + self.handle(pr.id) + assert self.get_build_status( + pr.src_commit, key="bert-e") == "NOTSTARTED" + self.handle(pr.id, settings=settings) + assert self.get_build_status( + pr.src_commit, key="bert-e") == "failure" + self.handle(pr.id, settings=settings, options=["bypass_jira_check"]) + assert self.get_build_status( + pr.src_commit, key="bert-e") == "in_progress" + self.handle(pr.id, settings=settings, options=self.bypass_all) + class TestQueueing(RepositoryTests): """Tests which validate all things related to the merge queue. @@ -7808,7 +7672,6 @@ def main(): if RepositoryTests.args.git_host == 'mock': bitbucket_api.Client = bitbucket_api_mock.Client bitbucket_api.Repository = bitbucket_api_mock.Repository - bitbucket_api.Task = bitbucket_api_mock.Task jira_api.JiraIssue = jira_api_mock.JiraIssue if RepositoryTests.args.verbose: diff --git a/bert_e/tests/test_git_host.py b/bert_e/tests/test_git_host.py index 76f0660a..b82084d4 100644 --- a/bert_e/tests/test_git_host.py +++ b/bert_e/tests/test_git_host.py @@ -244,20 +244,6 @@ def test_pull_request_comments(self, workspace): assert cmt1.text == 'First comment' assert cmt2.text == 'Last comment' - def test_tasks(self, workspace): - if workspace.host == 'github': - pytest.skip('Tasks are not supported by this host.') - - pull_request = make_pull_request(workspace, 'test_tasks', 'master') - pull_request.get_tasks() - - comment = pull_request.add_comment('Some comment') - comment2 = pull_request.add_comment('Some other comment') - comment.add_task('do spam') - comment.add_task('do egg') - comment2.add_task('do bacon') - assert len(list(pull_request.get_tasks())) == 3 - def test_build_status(self, workspace): pull_request = make_pull_request(workspace, 'test_build_status', 'master') diff --git a/bert_e/tests/test_server_data.py b/bert_e/tests/test_server_data.py index d1d5326c..05adce32 100644 --- a/bert_e/tests/test_server_data.py +++ b/bert_e/tests/test_server_data.py @@ -225,7 +225,6 @@ 'type': 'repository', 'uuid': '{49a4c5bf-6684-4102-9e82-f9016f691392}'}}, 'state': 'OPEN', - 'task_count': 0, 'title': 'Bugfix/releng 1966 allow bitbucket pipeline to deploy', 'type': 'pullrequest', 'updated_on': '2016-07-30T14:17:11.959821+00:00'} diff --git a/bert_e/tests/unit/test_github_app_auth.py b/bert_e/tests/unit/test_github_app_auth.py index 1ff0bc18..f4ad7110 100644 --- a/bert_e/tests/unit/test_github_app_auth.py +++ b/bert_e/tests/unit/test_github_app_auth.py @@ -38,6 +38,12 @@ def test_github_auth_app(client_app): def test_github_check_run(client_app): repository = client_app.get_repository('octo-org', 'Hello-World') pr = repository.get_pull_request(1) - check_run = pr.add_checkrun( + check_run = pr._add_checkrun( 'bert-e', 'completed', 'success', 'title', 'summary') assert check_run.name == check_run.data['name'] + + +def test_github_set_status(client_app): + repository = client_app.get_repository('octo-org', 'Hello-World') + pr = repository.get_pull_request(1) + pr.set_bot_status('success', 'title', 'summary') diff --git a/bert_e/workflow/gitwaterflow/__init__.py b/bert_e/workflow/gitwaterflow/__init__.py index d4ec7fed..9ea21833 100644 --- a/bert_e/workflow/gitwaterflow/__init__.py +++ b/bert_e/workflow/gitwaterflow/__init__.py @@ -24,7 +24,7 @@ from bert_e.lib.cli import confirm from bert_e.reactor import Reactor, NotFound, NotPrivileged, NotAuthored from ..git_utils import push, clone_git_repo -from ..pr_utils import find_comment, send_comment, create_task +from ..pr_utils import find_comment, notify_user from .branches import ( branch_factory, build_branch_cascade, is_cascade_consumer, is_cascade_producer, BranchCascade, QueueBranch, IntegrationBranch @@ -55,7 +55,7 @@ def handle_pull_request(job: PullRequestJob): try: _handle_pull_request(job) except messages.TemplateException as err: - send_comment(job.settings, job.pull_request, err) + notify_user(job.settings, job.pull_request, err) raise @@ -264,26 +264,23 @@ def early_checks(job): def send_greetings(job): - """Send welcome message to the pull request's author and set default tasks. + """Send welcome message to the pull request's author. """ username = job.settings.robot if find_comment(job.pull_request, username=username): return - tasks = list(reversed(job.settings.tasks)) - - comment = send_comment( - job.settings, job.pull_request, messages.InitMessage( - bert_e=username, author=job.pull_request.author_display_name, - status={}, active_options=job.active_options, tasks=tasks, - frontend_url=job.bert_e.settings.frontend_url, - options=Reactor.get_options(), commands=Reactor.get_commands() - ) + init_message = messages.InitMessage( + bert_e=username, author=job.pull_request.author_display_name, + status={}, active_options=job.active_options, + frontend_url=job.bert_e.settings.frontend_url, + options=Reactor.get_options(), commands=Reactor.get_commands() ) - for task in tasks: - create_task(job.settings, task, comment) + notify_user( + job.settings, job.pull_request, init_message + ) def handle_comments(job): diff --git a/bert_e/workflow/gitwaterflow/integration.py b/bert_e/workflow/gitwaterflow/integration.py index 7817bcba..da96d2c7 100644 --- a/bert_e/workflow/gitwaterflow/integration.py +++ b/bert_e/workflow/gitwaterflow/integration.py @@ -22,7 +22,7 @@ from bert_e.lib import git from ..git_utils import consecutive_merge, robust_merge, push -from ..pr_utils import send_comment +from ..pr_utils import notify_user from .branches import (branch_factory, build_branch_cascade, GhostIntegrationBranch) @@ -207,7 +207,7 @@ def create_integration_pull_requests(job, wbranches): def notify_integration_data(job, wbranches, child_prs): if len(wbranches) > 1: - send_comment( + notify_user( job.settings, job.pull_request, exceptions.IntegrationDataCreated( bert_e=job.settings.robot, diff --git a/bert_e/workflow/gitwaterflow/queueing.py b/bert_e/workflow/gitwaterflow/queueing.py index 08c08c0d..69664021 100644 --- a/bert_e/workflow/gitwaterflow/queueing.py +++ b/bert_e/workflow/gitwaterflow/queueing.py @@ -22,7 +22,7 @@ from bert_e.lib import git from ..git_utils import clone_git_repo, consecutive_merge, robust_merge, push -from ..pr_utils import send_comment +from ..pr_utils import notify_user from .branches import (BranchCascade, DevelopmentBranch, GWFBranch, IntegrationBranch, QueueBranch, QueueCollection, QueueIntegrationBranch, branch_factory, @@ -174,7 +174,7 @@ def close_queued_pull_request(job, pr_id, cascade): if dst.includes_commit(src.get_latest_commit()): # Everything went fine, send a success message - send_comment( + notify_user( job.settings, pull_request, exceptions.SuccessMessage( branches=target_branches, ignored=job.git.cascade.ignored_branches, @@ -189,7 +189,7 @@ def close_queued_pull_request(job, pr_id, cascade): # have disappeared, so the normal pre-queuing workflow will restart # naturally. commits = list(src.get_commit_diff(dst)) - send_comment( + notify_user( job.settings, pull_request, exceptions.PartialMerge( commits=commits, branches=job.git.cascade.dst_branches, active_options=[]) diff --git a/bert_e/workflow/pr_utils.py b/bert_e/workflow/pr_utils.py index 0c21f8b5..e8b69fb7 100644 --- a/bert_e/workflow/pr_utils.py +++ b/bert_e/workflow/pr_utils.py @@ -81,33 +81,29 @@ def _send_comment(settings, pull_request: AbstractPullRequest, msg: str, return LOG.debug('SENDING MESSAGE %s', msg) - return pull_request.add_comment(msg) + pull_request.add_comment(msg) -def send_comment(settings, pull_request: AbstractPullRequest, - comment: exceptions.TemplateException): - """Post a comment in a pull request.""" - try: - return _send_comment(settings, pull_request, str(comment), - comment.dont_repeat_if_in_history) - except exceptions.CommentAlreadyExists: - LOG.info("Comment '%s' already posted", comment.__class__.__name__) - - -def create_task(settings, task: str, comment: AbstractComment): - """Add a task to a comment.""" - if settings.no_comment: - LOG.debug("Not setting task (no_comment==True)") +def _send_bot_status(settings, pull_request: AbstractPullRequest, + comment: exceptions.TemplateException): + """Post the bot status in a pull request.""" + if settings.send_bot_status is False: + LOG.debug("Not sending bot status (send_bot_status==False)") return + LOG.info(f"Setting bot status to {comment.status} as {comment.title}") + pull_request.set_bot_status( + comment.status, + title=comment.title, + summary=str(comment), + ) - if settings.interactive: - print(task, '\n') - if not confirm('Do you want to create this task?'): - return - - LOG.debug('CREATING TASK %s', task) +def notify_user(settings, pull_request: AbstractPullRequest, + comment: exceptions.TemplateException): + """Notify user by sending a comment or a build status in a pull request.""" try: - comment.add_task(task) - except exceptions.TaskAPIError as err: - LOG.error('Could not create task %s (%s)', task, err) + _send_bot_status(settings, pull_request, comment) + _send_comment(settings, pull_request, str(comment), + comment.dont_repeat_if_in_history) + except exceptions.CommentAlreadyExists: + LOG.info("Comment '%s' already posted", comment.__class__.__name__) diff --git a/charts/bert-e/templates/settings.yaml b/charts/bert-e/templates/settings.yaml index b1dfed27..703bfb4b 100644 --- a/charts/bert-e/templates/settings.yaml +++ b/charts/bert-e/templates/settings.yaml @@ -47,12 +47,6 @@ stringData: - {{ . | quote }} {{- end }} {{- end }} - {{- if .Values.bertE.gating.tasks }} - tasks: - {{- range .Values.bertE.gating.tasks }} - - {{ . | quote }} - {{- end }} - {{- end }} {{- if .Values.bertE.jira.enabled }} jira_account_url: {{ .Values.bertE.jira.accountUrl }} jira_email: {{ .Values.bertE.jira.email }} diff --git a/charts/bert-e/values.yaml b/charts/bert-e/values.yaml index be04b289..7a9ad533 100644 --- a/charts/bert-e/values.yaml +++ b/charts/bert-e/values.yaml @@ -196,13 +196,6 @@ bertE: ## projectLeaders: [] - ## tasks is a list of tasks to create automatically on new pull requests. - ## - ## Not supported on GitHub. - ## - ## default value: empty - ## - tasks: [] ## maxCommitDiff specifies the maximum authorized divergence from target branches. ## diff --git a/settings.sample.yml b/settings.sample.yml index 0d3f56cc..55ec0476 100644 --- a/settings.sample.yml +++ b/settings.sample.yml @@ -162,17 +162,6 @@ admins: - username_admin_2@557042:08898ca4-5f12-4042-9942-87e167728afd -# tasks [OPTIONAL]: -# The list of tasks to create by default on the githost pull request -# NOT SUPPORTED ON GITHUB. -# -# default value: empty -# empty: no task is created by default -tasks: - - do this - - do that - - # max_commit_diff [OPTIONAL]: # If > 0, the robot refuses to work on pull requests that have diverged with # their destination branch by more that `max_commit_diff` commits.