Tests are passing again
authorJude N <juden@pwan.org>
Sun, 14 Apr 2019 13:09:28 +0000 (09:09 -0400)
committerJude N <juden@pwan.org>
Sun, 14 Apr 2019 13:09:28 +0000 (09:09 -0400)
.pylintrc
plugins/marge.py
tests/test_marge.py

index 20e66a0..694a07c 100644 (file)
--- a/.pylintrc
+++ b/.pylintrc
@@ -297,7 +297,7 @@ callbacks=cb_,_cb
 [MISCELLANEOUS]
 
 # List of note tags to take in consideration, separated by a comma.
 [MISCELLANEOUS]
 
 # List of note tags to take in consideration, separated by a comma.
-notes=FIXME,XXX,TODO
+notes=FIXME,XXX
 
 
 [DESIGN]
 
 
 [DESIGN]
index 2764209..e81e00a 100755 (executable)
@@ -3,13 +3,55 @@ Margebot: A Errbot Plugin for Gitlab MR reminders
 """
 import re
 from datetime import datetime, timezone
 """
 import re
 from datetime import datetime, timezone
+from time import sleep
 from dateutil import parser
 from dateutil.tz import tzutc
 from dateutil.relativedelta import relativedelta
 from dateutil import parser
 from dateutil.tz import tzutc
 from dateutil.relativedelta import relativedelta
-from errbot import BotPlugin, arg_botcmd, botcmd, re_botcmd, webhook
+from errbot import BotPlugin, botcmd, arg_botcmd, re_botcmd, webhook
 from errbot.templating import tenv
 from errcron.bot import CrontabMixin
 import gitlab
 from errbot.templating import tenv
 from errcron.bot import CrontabMixin
 import gitlab
+import requests
+
+
+def addprojecthook_extra(self, project_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
+    """
+    A copy parent addprojecthook with an extra_data field
+    """
+    data = {"id": project_id, "url": url}
+    if extra_data:
+        for ed_key, ed_value in extra_data.items():
+            data[ed_key] = ed_value
+    data['push_events'] = int(bool(push))
+    data['issues_events'] = int(bool(issues))
+    data['merge_requests_events'] = int(bool(merge_requests))
+    data['tag_push_events'] = int(bool(tag_push))
+    request = requests.post("{0}/{1}/hooks".format(self.projects_url, project_id),
+                            headers=self.headers, data=data, verify=self.verify_ssl)
+    if request.status_code == 201:
+        return request.json()
+    return False
+
+gitlab.Gitlab.addprojecthook_extra = addprojecthook_extra
+
+
+def editprojecthook_extra(self, project_id, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
+    """
+    A copy of the parent editprojecthook with an extra_data field
+    """
+    data = {"id": project_id, "hook_id": hook_id, "url": url}
+    if extra_data:
+        for ed_key, ed_value in extra_data.items():
+            data[ed_key] = ed_value
+    data['push_events'] = int(bool(push))
+    data['issues_events'] = int(bool(issues))
+    data['merge_requests_events'] = int(bool(merge_requests))
+    data['tag_push_events'] = int(bool(tag_push))
+    request = requests.put("{0}/{1}/hooks/{2}".format(self.projects_url, project_id, hook_id),
+                           headers=self.headers, data=data, verify=self.verify_ssl)
+    return request.status_code == 200
+
+gitlab.Gitlab.editprojecthook_extra = editprojecthook_extra
 
 
 def deltastr(any_delta):
 
 
 def deltastr(any_delta):
@@ -144,7 +186,13 @@ class Marge(BotPlugin, CrontabMixin):
 
             target_project_id = request['object_attributes']['target_project_id']
             iid = request['object_attributes']['iid']
 
             target_project_id = request['object_attributes']['target_project_id']
             iid = request['object_attributes']['iid']
-            mr_id = request['object_attributes']['id']
+
+            # If the MR is tagged 'never-close' ignore it
+            if 'labels' in request:
+                for a_label in request['labels']:
+                    if a_label['title'] == 'never-close':
+                        self.log.info("Skipping never-close notice for {} MR".format(url))
+                        return "OK"
 
             msg_template = "Hi there ! {} has opened a new {}MR: \"{}\"\n{}/merge_requests/{}"
             msg = msg_template.format(author_name, wip, title, url, iid)
 
             msg_template = "Hi there ! {} has opened a new {}MR: \"{}\"\n{}/merge_requests/{}"
             msg = msg_template.format(author_name, wip, title, url, iid)
@@ -155,14 +203,21 @@ class Marge(BotPlugin, CrontabMixin):
 
             open_mrs = self['OPEN_MRS']
 
 
             open_mrs = self['OPEN_MRS']
 
-            if (target_project_id, mr_id, rooms) not in open_mrs:
+            if (target_project_id, iid, rooms) not in open_mrs:
                 for a_room in rooms.split(','):
                     if self.config:
                         self.send(self.build_identifier(a_room + '@' + self.chatroom_host), msg)
 
                 for a_room in rooms.split(','):
                     if self.config:
                         self.send(self.build_identifier(a_room + '@' + self.chatroom_host), msg)
 
-                self.log.info("webhook: Saving ({}, {}, {})".format(target_project_id, mr_id, rooms))
-                open_mrs[(target_project_id, mr_id, rooms)] = True
+                self.log.info("webhook: Saving ({}, {}, {})".format(target_project_id, iid, rooms))
+                open_mrs[(target_project_id, iid, rooms)] = True
                 self['OPEN_MRS'] = open_mrs
                 self['OPEN_MRS'] = open_mrs
+
+        # TODO:  Add check if an MR has toggled the WIP indicator
+        # (trigger on updates (what's that look like in request['object_attributes']['state'])
+        # Then check in request['changes']['title']['previous'] starts with 'WIP:'
+        # but not request['changes']['title']['current'], and vice versa
+        # See https://gitlab.com/gitlab-org/gitlab-ce/issues/53529
+
         return "OK"
 
     def mr_status_msg(self, a_mr, author=None):
         return "OK"
 
     def mr_status_msg(self, a_mr, author=None):
@@ -201,9 +256,15 @@ class Marge(BotPlugin, CrontabMixin):
         # getapprovals is only available in GitLab 8.9 EE or greater
         # (not the open source CE version)
         # approvals = self.gitlab.getapprovals(a_mr['id'])
         # getapprovals is only available in GitLab 8.9 EE or greater
         # (not the open source CE version)
         # approvals = self.gitlab.getapprovals(a_mr['id'])
-        # also_approved = ""
+        # approved = ""
         # for approved in approvals['approved_by']:
         # for approved in approvals['approved_by']:
-        #    also_approved += "," + approved['user']['name']
+        #    approved += "," + approvals['user']['name']
+
+        # See https://gitlab.com/gitlab-org/gitlab-ce/issues/35498
+        # awards = GET /projects/:id/merge_requests/:merge_request_iid/award_emoji
+        #  for an award in awards:
+        #    if name==??? and award_type==???:
+        #      approved += "," + award["user"]["username"]
 
         upvotes = a_mr['upvotes']
         msg = "{} (opened {})".format(a_mr['web_url'], str_open_since)
 
         upvotes = a_mr['upvotes']
         msg = "{} (opened {})".format(a_mr['web_url'], str_open_since)
@@ -249,6 +310,7 @@ class Marge(BotPlugin, CrontabMixin):
         # initialize the reminders
         rooms = self.rooms()
         for a_room in rooms:
         # initialize the reminders
         rooms = self.rooms()
         for a_room in rooms:
+            self.log.info("poller: a_room.node: {}".format(a_room.node))
             reminder_msg[a_room.node] = []
 
         still_open_mrs = {}
             reminder_msg[a_room.node] = []
 
         still_open_mrs = {}
@@ -282,7 +344,12 @@ class Marge(BotPlugin, CrontabMixin):
                 continue
 
             for a_room in notify_rooms.split(','):
                 continue
 
             for a_room in notify_rooms.split(','):
-                reminder_msg[a_room].append(msg_dict)
+                if a_room in reminder_msg:
+                    reminder_msg[a_room].append(msg_dict)
+                else:
+                    self.log.error("{} not in reminder_msg (project_id={}, mr_id={})".format(a_room, project_id, mr_id))
+
+        self['OPEN_MRS'] = open_mrs
 
         # Remind each of the rooms about open MRs
         for a_room, room_msg_list in reminder_msg.items():
 
         # Remind each of the rooms about open MRs
         for a_room, room_msg_list in reminder_msg.items():
@@ -333,6 +400,7 @@ class Marge(BotPlugin, CrontabMixin):
         msg = ""
         still_open_mrs = {}
         open_mrs = self['OPEN_MRS']
         msg = ""
         still_open_mrs = {}
         open_mrs = self['OPEN_MRS']
+        self.log.info('open_mrs: {}'.format(open_mrs))
         for (project, mr_id, notify_rooms) in open_mrs:
 
             # Lookup the MR from the project/id
         for (project, mr_id, notify_rooms) in open_mrs:
 
             # Lookup the MR from the project/id
@@ -350,6 +418,7 @@ class Marge(BotPlugin, CrontabMixin):
             # If the MR is no longer open, skip to the next MR,
             # and don't include this MR in the next check
             if 'opened' not in a_mr['state']:
             # If the MR is no longer open, skip to the next MR,
             # and don't include this MR in the next check
             if 'opened' not in a_mr['state']:
+                self.log.info('state not opened: {}'.format(a_mr['state']))
                 continue
             else:
                 still_open_mrs[(project, mr_id, notify_rooms)] = True
                 continue
             else:
                 still_open_mrs[(project, mr_id, notify_rooms)] = True
@@ -396,7 +465,7 @@ class Marge(BotPlugin, CrontabMixin):
             return msg
         else:
             for a_hook in hooks:
             return msg
         else:
             for a_hook in hooks:
-                self.log.info('a_hook: {} {}'.format(a_hook, self.webhook_url))
+                self.log.info('a_hook: {}'.format(a_hook))
                 if a_hook['merge_requests_events'] and a_hook['url'].startswith(self.webhook_url):
                     marge_hook = a_hook
                     break
                 if a_hook['merge_requests_events'] and a_hook['url'].startswith(self.webhook_url):
                     marge_hook = a_hook
                     break
@@ -411,11 +480,11 @@ class Marge(BotPlugin, CrontabMixin):
                 self.log.info('watchrepo: {}'.format(msg))
                 return msg
             else:
                 self.log.info('watchrepo: {}'.format(msg))
                 return msg
             else:
-                hook_updated = self.gitlab.editprojecthook(target_project_id, marge_hook['id'], url, merge_requests=True)
+                hook_updated = self.gitlab.editprojecthook_extra(target_project_id, marge_hook['id'], url, merge_requests=True, extra_data={'enable_ssl_verification': True})
                 s_watch_msg = "Updating room list for {} MRs from {} to {}".format(repo, old_rooms, rooms)
                 s_action = "update"
         else:
                 s_watch_msg = "Updating room list for {} MRs from {} to {}".format(repo, old_rooms, rooms)
                 s_action = "update"
         else:
-            hook_updated = self.gitlab.addprojecthook(target_project_id, url, merge_requests=True)
+            hook_updated = self.gitlab.addprojecthook_extra(target_project_id, url, merge_requests=True, extra_data={'enable_ssl_verification': True})
             s_watch_msg = "Now watching for new MRs in the {} repo to the {} room(s)".format(repo, rooms)
             s_action = "add"
 
             s_watch_msg = "Now watching for new MRs in the {} repo to the {} room(s)".format(repo, rooms)
             s_action = "add"
 
@@ -442,14 +511,17 @@ class Marge(BotPlugin, CrontabMixin):
 
         # If adding a new repo, check for existing opened/reopened MRs in the repo.
         else:
 
         # If adding a new repo, check for existing opened/reopened MRs in the repo.
         else:
+            # For debugging the 'watchrepo didn't find my MR' issue.
+            # mr_list = self.gitlab.getmergerequests(target_project_id, page=1, per_page=100)
+            # self.log.error('juden: mr_list state: {}'.format(mr_list[0]['state']))
             for state in ['opened', 'reopened']:
                 page = 1
                 mr_list = self.gitlab.getmergerequests(target_project_id, page=page, per_page=100, state=state)
                 while (mr_list is not False) and (mr_list != []):
                     for an_mr in mr_list:
                         mr_count += 1
             for state in ['opened', 'reopened']:
                 page = 1
                 mr_list = self.gitlab.getmergerequests(target_project_id, page=page, per_page=100, state=state)
                 while (mr_list is not False) and (mr_list != []):
                     for an_mr in mr_list:
                         mr_count += 1
-                        self.log.info('watchrepo: an_mr WATS THE ID\n{}'.format(an_mr))
-                        mr_id = an_mr['id']
+                        self.log.info('watchrepo: an_mr WATS THE IID\n{}'.format(an_mr))
+                        mr_id = an_mr['iid']
                         open_mrs[(target_project_id, mr_id, rooms)] = True
                     # Get the next page of MRs
                     page += 1
                         open_mrs[(target_project_id, mr_id, rooms)] = True
                     # Get the next page of MRs
                     page += 1
@@ -463,11 +535,19 @@ class Marge(BotPlugin, CrontabMixin):
             mr_msg = "1 open MR was found in the repo.  Run !reviews to see the updated MR list."
         else:
             mr_msg = "{} open MRs were found in the repo.  Run !reviews to see the updated MR list."
             mr_msg = "1 open MR was found in the repo.  Run !reviews to see the updated MR list."
         else:
             mr_msg = "{} open MRs were found in the repo.  Run !reviews to see the updated MR list."
-
         return "{}\n{}".format(s_watch_msg, mr_msg)
 
     # pragma pylint: disable=unused-argument
 
         return "{}\n{}".format(s_watch_msg, mr_msg)
 
     # pragma pylint: disable=unused-argument
 
+    @botcmd()
+    def xyzzy(self, msg, args):
+        """
+        Don't call this command...
+        """
+        yield "/me whispers \"All open MRs have been merged into master.\""
+        sleep(5)
+        yield "(just kidding)"
+
     @re_botcmd(pattern=r"I blame marge(bot)?", prefixed=False, flags=re.IGNORECASE)
     def dont_blame_margebot(self, msg, match):
         """
     @re_botcmd(pattern=r"I blame marge(bot)?", prefixed=False, flags=re.IGNORECASE)
     def dont_blame_margebot(self, msg, match):
         """
@@ -475,6 +555,16 @@ class Marge(BotPlugin, CrontabMixin):
         """
         yield u"(\u300D\uFF9F\uFF9B\uFF9F)\uFF63NOOOooooo say it ain't so."
 
         """
         yield u"(\u300D\uFF9F\uFF9B\uFF9F)\uFF63NOOOooooo say it ain't so."
 
+    @re_botcmd(pattern=r"\u0028\u256F\u00B0\u25A1\u00B0\uFF09\u256F\uFE35\u0020\u253B(\u2501+)\u253B", prefixed=False)
+    def deflipped(self, msg, match):
+        """
+        Unflip a properly sized table
+        """
+        table_len = len(match.group(1))
+        deflip_table = u"\u252c" + (u"\u2500" * table_len) + u"\u252c \u30ce( \u309c-\u309c\u30ce)"
+        # yield u"\u252c\u2500\u2500\u252c \u30ce( \u309c-\u309c\u30ce)"
+        yield deflip_table
+
     @re_botcmd(pattern=r"good bot", prefixed=False, flags=re.IGNORECASE)
     def best_bot(self, msg, match):
         """
     @re_botcmd(pattern=r"good bot", prefixed=False, flags=re.IGNORECASE)
     def best_bot(self, msg, match):
         """
@@ -489,13 +579,42 @@ class Marge(BotPlugin, CrontabMixin):
         """
         return "More like MargeFest, amirite ?"
 
         """
         return "More like MargeFest, amirite ?"
 
+# ha the dev-infra room sez koji sooooooooooooooooooooooooooooooooooooooooooo much
+#    @re_botcmd(pattern=r"k+o+j+i+", prefixed=False, flags=re.IGNORECASE)
+#    def koji(self, msg, args):
+#        """
+#        More like daikaiju, amirite ?
+#        """
+#        return "More like kaiju, amirite ?"
+
+    @re_botcmd(pattern=r"peruvian chicken", prefixed=False, flags=re.IGNORECASE)
+    def booruvian(self, msg, args):
+        """
+        They put hard boiled eggs in their tamales too.
+        """
+        return "More like Booruvian chicken, amirite ?"
+
+    @re_botcmd(pattern=r"booruvian chicken", prefixed=False, flags=re.IGNORECASE)
+    def booihoohooruvian(self, msg, args):
+        """
+        That chicken I do not like.
+        """
+        return "More like Boohoohooruvian chicken, amirite ?"
+
     @re_botcmd(pattern=r"margebot sucks", prefixed=False, flags=re.IGNORECASE)
     @re_botcmd(pattern=r"margebot sucks", prefixed=False, flags=re.IGNORECASE)
-    def margebot_sucks(self, msg, args):
+    def new_agenda_item(self, msg, args):
         """
         Bring it up with the committee
         """
         return "Bring it up with the Margebot steering committee."
 
         """
         Bring it up with the committee
         """
         return "Bring it up with the Margebot steering committee."
 
+    @re_botcmd(pattern=r"jackie", prefixed=False, flags=re.IGNORECASE)
+    def jackie(self, msg, args):
+        """
+        Who dat ?
+        """
+        return "I don't know any Jackies. I'm calling security."
+
     @re_botcmd(pattern=r".*", prefixed=True)
     def catchall(self, msg, args):
         """
     @re_botcmd(pattern=r".*", prefixed=True)
     def catchall(self, msg, args):
         """
index 2fc7429..d8b85b3 100644 (file)
@@ -166,13 +166,14 @@ class TestMarge(object):
             return []
         monkeypatch.setattr(gitlab.Gitlab, 'getmergerequests', mock_getmergerequests)
 
             return []
         monkeypatch.setattr(gitlab.Gitlab, 'getmergerequests', mock_getmergerequests)
 
-        def mock_addprojecthook(self, project, url, **kwargs):
+        def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
             return True
             return True
-        monkeypatch.setattr(gitlab.Gitlab, 'addprojecthook', mock_addprojecthook)
+        monkeypatch.setattr(gitlab.Gitlab, 'addprojecthook_extra', mock_addprojecthook_extra, raising=False)
 
 
-        def mock_editprojecthook(self, project, hook_id, url, **kwargs):
+        def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
             return True
             return True
-        monkeypatch.setattr(gitlab.Gitlab, 'editprojecthook', mock_editprojecthook)
+        monkeypatch.setattr(gitlab.Gitlab, 'editprojecthook_extra', mock_editprojecthook_extra, raising=False)
+
         return gitlab.Gitlab
 
 
         return gitlab.Gitlab
 
 
@@ -180,12 +181,12 @@ class TestMarge(object):
 #    def MargeGitlab(self, monkeypatch):
 #        def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
 #            return True
 #    def MargeGitlab(self, monkeypatch):
 #        def mock_addprojecthook_extra(self, project, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
 #            return True
-#        monkeypatch.setattr('plugins.marge.MargeGitlab.addprojecthook_extra', mock_addprojecthook_extra)
+#        monkeypatch.setattr(plugins.marge.MargeGitlab, 'addprojecthook_extra')
 #
 #        def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
 #            return True
 #
 #        def mock_editprojecthook_extra(self, project, hook_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
 #            return True
-#        monkeypatch.setattr('plugins.marge.MargeGitlab.editprojecthook_extra', mock_editprojecthook_extra)
-#        return marge.MargeGitlab
+#        monkeypatch.setattr(plugins.marge.MargeGitlab, 'editprojecthook_extra')
+#        return plugins.marge.MargeGitlab
 
     @pytest.fixture
     def gitlab_no_reviews(self, gitlab, monkeypatch):
 
     @pytest.fixture
     def gitlab_no_reviews(self, gitlab, monkeypatch):
@@ -196,7 +197,7 @@ class TestMarge(object):
 
     @pytest.fixture
     def gitlab_one_review(self, gitlab, monkeypatch):
 
     @pytest.fixture
     def gitlab_one_review(self, gitlab, monkeypatch):
-        ret_mr = {'id': 'mr_id',
+        ret_mr = {'iid': 'mr_id',
                   'author': {'id': 2001},
                   'created_at': 'Oct 29, 2017 2:37am',
                   'merge_status': 'can_be_merged',
                   'author': {'id': 2001},
                   'created_at': 'Oct 29, 2017 2:37am',
                   'merge_status': 'can_be_merged',
@@ -278,7 +279,6 @@ class TestMarge(object):
         pm = margebot.pop_message()
         assert 'Updating room list for group/new_repo MRs from room1,room2,room3 to room4,room5,room6' in pm
 
         pm = margebot.pop_message()
         assert 'Updating room list for group/new_repo MRs from room1,room2,room3 to room4,room5,room6' in pm
 
-#    @pytest.mark.skip(reason='until I figure out how to mock MargeGitlab')
     def test_watchrepo_existing_mr(self, margebot, gitlab_one_review):
         margebot.push_message('!watchrepo sample/mr room1,room2,room3')
         pm = margebot.pop_message()
     def test_watchrepo_existing_mr(self, margebot, gitlab_one_review):
         margebot.push_message('!watchrepo sample/mr room1,room2,room3')
         pm = margebot.pop_message()