Tests are passing again
[margebot.git] / plugins / marge.py
index 0cbe525..e81e00a 100755 (executable)
@@ -7,54 +7,51 @@ 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
 import requests
 
 from errbot.templating import tenv
 from errcron.bot import CrontabMixin
 import gitlab
 import requests
 
-class MargeGitlab(gitlab.Gitlab):
+
+def addprojecthook_extra(self, project_id, url, push=False, issues=False, merge_requests=False, tag_push=False, extra_data=None):
     """
     """
-    Subclass gitlab.Gitlab so extra_data args can be added
-    to the addprojecthook() and editprojecthook() methods
+    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
 
 
-    def __init__(self, host, token="", oauth_token="", verify_ssl=True):
-        super().__init__(host, token, oauth_token, verify_ssl)
-
-    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
-
-    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):
@@ -143,7 +140,7 @@ class Marge(BotPlugin, CrontabMixin):
         Marge.CRONTAB = ['{} .crontab_hook'.format(self.config['CRONTAB'])]
         gitlab_auth_token = self.config['GITLAB_ADMIN_TOKEN']
         verify_ssl = self.config['VERIFY_SSL']
         Marge.CRONTAB = ['{} .crontab_hook'.format(self.config['CRONTAB'])]
         gitlab_auth_token = self.config['GITLAB_ADMIN_TOKEN']
         verify_ssl = self.config['VERIFY_SSL']
-        self.gitlab = MargeGitlab(self.git_host, gitlab_auth_token, verify_ssl=verify_ssl)
+        self.gitlab = gitlab.Gitlab(self.git_host, gitlab_auth_token, verify_ssl=verify_ssl)
         self.activate_crontab()
 
         self.soak_delta = relativedelta(hours=self.config['CRONTAB_SOAK_HOURS'])
         self.activate_crontab()
 
         self.soak_delta = relativedelta(hours=self.config['CRONTAB_SOAK_HOURS'])
@@ -189,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)
@@ -200,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):
@@ -246,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)
@@ -294,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 = {}
@@ -327,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():
@@ -378,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
@@ -395,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
@@ -456,12 +480,12 @@ 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_extra(target_project_id, marge_hook['id'], url, merge_requests=True, extra_data={'enable_ssl_verification': False})
+                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_extra(target_project_id, url, merge_requests=True, extra_data={'enable_ssl_verification': False})
-            s_watch_msg = "Now watching for new MRs in the {} repo to the {} roomi(s)".format(repo, rooms)
+            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"
 
         if not hook_updated:
             s_action = "add"
 
         if not hook_updated:
@@ -487,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
@@ -508,7 +535,6 @@ 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
@@ -529,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):
         """
@@ -543,19 +579,47 @@ 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 bring_it_up_with_the_steering_committee(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".*", prefixed=True)
-    def catchall(self, msg,args):
-       """
-       Don't have the bot complain about unknown commands if the first word in a msg is its name
-       """
-       return
+    @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):
+        """
+        Don't have the bot complain about unknown commands if the first word in a msg is its name
+        """
+        return
 
     # pragma pylint: enable=unused-argument
 
     # pragma pylint: enable=unused-argument