2 from errbot
import BotPlugin
, botcmd
, webhook
3 from errbot
.backends
import xmpp
4 from errcron
.bot
import CrontabMixin
7 from Secret
import admin_token
8 git_host
= "gitlab.services.zz" #TODO: move this to some sort of plugin config
11 class Marge(BotPlugin
, CrontabMixin
):
13 I remind you about merge requests
17 Add a merge request webook of the form 'https://webookserver/margeboot/<rooms>' to the projects you want tracked.
18 <rooms> should be a comma-separated list of rooms you want notified.
20 Add <roomname>@domain to the CHATROOM_PRESENCE list in config.py for rooms margebot should join
24 '0 11,17 * * * .crontab_hook' # 7:00AM and 1:00PM EST warnings
27 # def __init__(self, bot):
30 def configure(self
, configuration
):
31 ## TODO: set up a connection to the gitlab API
33 self
.gitlab
= gitlab
.Gitlab(git_host
,admin_token
,verify_ssl
=False)
35 # notify_re = re.compile('notify_(.*)')
37 # def get_mr_rooms(self, project_id):
39 # Return a list of errbot room '<roomname>@domain' names which have a 'notify_<roomname>' label on the project
40 # Log an error if you found a 'notify_<roomname>' but margebot isn't in a <roomname> room...
43 # labels = this.gitlab.getlabels(project_id)
44 # for a_label in labels:
45 # notify_match = self.notify_re.search(a_label['name'])
47 # roomname = notify_match.group(1)
49 # b_room_found = False
50 # marge_rooms = xmpp.rooms()
51 # for a_room in marge_rooms:
52 # if a_room.node() == roomname:
53 # retval.append(a_room.person()) # yeah rooms are people: person = node@domain
55 # if not b_room_found:
56 # self.log.error("Label of {} found, but margebot isn't tracking that room".format(roomname))
58 # retval.append(roomname)
61 @webhook('/margebot/<rooms>/'
62 def gitlab_hook(self
, request
):
64 Webhook that listens on http://<server>:<port>/gitlab
67 # TODO: Will errbot return a json struct or not ?
69 # verify it's a merge request
70 if requests
['object_kind'] != 'merge_request':
71 self
.log
.error('expecting object_kind of merge_request but got {}'.format(requests
['object_kind']))
72 elif request
['object_attributes']['state'] == 'opened':
73 if request
['object_attributes']['work_in_progress']:
77 url
= request
['object_attributes']['url']
78 state
= request
['object_attributes']['state']
79 title
= request
['object_attributes']['title']
81 author_id
= request
['object_attributes']['author_id'] # map this to user name ...
82 author
= self
.gitlab
.getuser(author_id
)
83 author_name
= author
['username']
85 target_project_id
= request
['object_attributes']['target_project_id']
86 iid
= request
['object_attributes']['iid']
88 user_name
= request
['user']['username'] # will this always be Administrator ?
90 message
= "Reviews: {} has opened a new {} MR: {}\n{}".format(author_name
, wip
, title
, url
)
92 # TODO: Maybe also check the notify_<room> labels assigned to the MR as well ?
93 #mr_rooms = self.get_mr_rooms(target_project_id)
94 for a_room
in rooms
.split(','):
95 self
.send( self
.build_identifier(a_room
), message
)
97 with self
.mutable('OPEN_MRS') as open_mrs
:
98 open_mrs
[(target_project_id
,iid
,rooms
)] = True
102 # def get_open_mrs(self, roomname=None, log_warnings=False):
104 # for a_project in self.gitlab.getprojects():
105 # for a_mr in self.gitlab.getmergerequests(a_project['id'], state='opened'):
106 # rooms = self.get_mr_rooms(a_mr['target_project_id'])
107 # if len(rooms) == 0 and log_warnings:
108 # self.log.warning('No notify room with MRs in project: {}'.format(a_mr['target_project_id']))
112 # for a_room in rooms:
113 # if a_room.startswith(roomname):
117 def crontab_hook(self
):
119 Send a scheduled message to the rooms margebot is watching about open MRs the room cares about.
122 reminder_msg
= {} # Map of reminder_msg['roomname@domain'] = msg
124 # initialize the reminders
127 reminder_msg
[a_room
.node()] = ''
131 # Let's walk through the MRs we've seen already:
132 for (project
,iid
,notify_rooms
) in self
['OPEM_MRS']:
134 # Lookup the MR from the project/iid
135 a_mr
= self
.gitlab
.getmergerequest(project
, iid
)
137 # If the MR is no longer open, skip to the next MR,
138 # and don't include this MR in the next check
139 if a_mr
['state'] != 'open':
142 still_open_mrs
[(project
, iid
, notify_rooms
) = True
144 # TODO: Warn if an open MR has has conflicts (merge_status == ??)
145 # TODO: Include the count of opened MR notes (does the API show resolved state ??)
147 approvals
= self
.gitlab
.getapprovals(a_mr
['id'])
149 for approved
in approvals
['approved_by']:
150 also_approved
+= "," + approved
['user']['name']
152 upvotes
= a_mr
['upvotes']
154 msg
= "\n{}: Has 2+ upvotes and could be merged in now.".format(a_mr
['web_url'])
156 msg
= "\n{}: {} already approved and is waiting for another upvote.".format(a_mr
['web_url'], also_approved
[1:])
158 msg
= '\n{}: Has no upvotes.'.format(a_mr
['web_url'])
160 for a_room
in notify_rooms
.split(','):
161 reminder_msg
[a_room
] += msg
163 # Remind each of the rooms about open MRs
164 for a_room
, room_msg
in reminder_msg
.iteritems():
166 self
.send(self
.build_identifier(a_room
), "Heads up these MRs need some luv:{}\n You can get a list of open reviews I know about by sending me a 'Marge, reviews' command.".format(room_msg
))
168 self
['OPEN_MRS'] = still_open_mrs
170 # def callback_message(self, mess):
172 # Look for messages that include an URL that looks like 'https://<gitlab_server>/<group_name>/<project_name>/merge_request/<iid>'
173 # Check if gitlab mergerequest webhook already exists for this group/project, and this it reports to this room ?
174 # Add the project to the OPEN_MRS list
176 # TODO: compiled re check against mess.body searching for
177 # https://<gitlabe_server/(group_name)/(project_name)/merge_request/(iid)
178 # project = self.gitlab.getprojects(group_name+'%2F'+project_name)
179 # orig_mrs = self['OPEN_MRS']
180 # orig_mrs[(project['id'],iid,mess.to.node()] = True
181 # self.send(mess.to, "Another MR ! YUM !")
184 @botcmd # flags a command
185 def reviews(self
, msg
, args
): # a command callable with !mrs
187 Returns a list of MRs that are waiting for some luv.
188 Also returns a list of MRs that have had enough luv but aren't merged in yet.
192 send_gitlab_id
= None
193 for user
in self
.gitlab
.getusers():
194 if user
['username'] == sender
.node
:
195 sender_gitlab_id
= user
['id']
198 if not send_gitlab_id
:
199 self
.log
.error('problem mapping {} to a gitlab user'.format(sender
.node
))
200 self
.send(self
.build_identifier(msg
.frm
), "Sorry I couldn't find your gitlab ID")
203 # TODO: how to get the room the message was sent from ? I'm assuming this will either be in msg.frm or msg.to
204 # TODO: weed out MRs the sender opened or otherwise indicate they've opened or have already +1'd
206 roomname
= msg
.to
.domain
#???
208 # Let's walk through the MRs we've seen already:
211 for (project
,iid
,notify_rooms
) in self
['OPEM_MRS']:
213 # Lookup the MR from the project/iid
214 a_mr
= self
.gitlab
.getmergerequest(project
, iid
)
216 # If the MR is no longer open, skip to the next MR,
217 # and don't include this MR in the next check
218 if a_mr
['state'] != 'open':
221 still_open_mrs
[(project
, iid
, notify_rooms
) = True
223 authored
= (a_mr
['author_id'] == sender_gitlab_id
)
224 already_approved
= False
226 approvals
= self
.gitlab
.getapprovals(a_mr
['id'])
228 for approved
in approvals
['approved_by']:
229 if approved
['user']['id'] == sender_gitlab_id
:
230 already_approved
= True
232 also_approved
+= "," + approved
['user']['name']
234 upvotes
= a_mr
['upvotes']
236 msg
= "\n{}: has 2+ upvotes from {} and could be merged in now.".format(a_mr
['web_url'], also_approved
[1:])
240 msg
= "\n{}: has already been approved by you and is waiting for another upvote.".format(a_mr
['web_url'])
242 msg
= "\n{}: has been approved by {} and is waiting for your upvote.".format(a_mr
['web_url'], also_approved
[1:])
244 msg
= "\n{}: Your MR has approved by {} and is waiting for another upvote.".format(a_mr
['web_url'], also_approved
[1:])
248 msg
= '\n{}: Has no upvotes and needs your attention.'.format(a_mr
['web_url'])
250 msg
= '\n{}: Your MR has no upvotes.'.format(a_mr
['web_url'])
254 response
= 'I found no open merge requests for you.'
258 self
.send(self
.build_identifier(msg
.frm
), response
)
260 self
['OPEN_MRS'] = still_open_mrs
265 def hello(self
,msg
, args
):