1 |
|
2 |
|
3 |
|
4 |
|
5 |
|
6 |
|
7 |
|
8 |
|
9 |
|
10 |
|
11 |
|
12 | moment = require 'moment'
|
13 | _ = require 'lodash'
|
14 | GitHubApi = require 'github'
|
15 |
|
16 | module.exports = (robot) ->
|
17 |
|
18 | robot.brain.data.teams ||= {}
|
19 | robot.brain.data.prsForReview ||= []
|
20 |
|
21 |
|
22 | robot.on 'review-needed', (pullRequest) ->
|
23 | pr = new PullRequest(pullRequest)
|
24 | robot.brain.data.prsForReview["#{pr.repo}/#{pr.number}"] = pr
|
25 | message = "Rapid Response needs a review of #{pr.url}"
|
26 | messageAOs(message)
|
27 |
|
28 | robot.on 'review-no-longer-needed', (pr) ->
|
29 | delete robot.brain.data.prsForReview["#{pr.repo}/#{pr.number}"]
|
30 | message = "Review no longer needed for #{pr.url}. The PR either was closed or review label was removed."
|
31 | messageAOs(message)
|
32 |
|
33 |
|
34 | robot.router.post 'hubot/gh-issues', (req, res) ->
|
35 | robot.logger.info "issue detected via GitHub webook: #{res}"
|
36 | if res.action == 'labeled' && res.label.name == process.env.HUBOT_REVIEW_NEEDED_LABEL
|
37 | robot.logger.info "emitting review-needed event: url: #{res.issue.url}"
|
38 | robot.emit 'review-needed',
|
39 | url: res.issue.url
|
40 | repo: res.repository.full_name
|
41 | number: res.issue.number
|
42 | if reviewNoLongerNeeded res
|
43 | robot.logger.info "emitting review-no-longer-needed event: url: #{res.issue.url}"
|
44 | robot.emit 'review-no-longer-needed',
|
45 | url: res.issue.url
|
46 | repo: res.repository.full_name
|
47 | number: res.issue.number
|
48 |
|
49 | reviewNoLongerNeeded = (gitHubIssuesResponse) ->
|
50 | labelRemoved = res.action == 'unlabeled' && res.label.name == process.env.HUBOT_REVIEW_NEEDED_LABEL
|
51 | issueClosed = res.action == 'closed' && _.some(res.labels, (label) -> label.name == process.env.HUBOT_REVIEW_NEEDED_LABEL)
|
52 | labelRemoved || issueClosed
|
53 |
|
54 | robot.respond /(list|show) (active owners|AO's|AOs)/i, (msg) ->
|
55 | teams = robot.brain.data['teams']
|
56 | if Object.keys(teams).length == 0
|
57 | response = "Sorry, I'm not keeping track of any teams or their AOs.\n" +
|
58 | "Get started with 'Add <team name> to teams'."
|
59 | return msg.send response
|
60 | aoStatus = (team) ->
|
61 | if team.aoUserId?
|
62 | aoName = robot.brain.userForId(team.aoUserId).name
|
63 | return "#{aoName} has been active owner on #{team.name} for #{moment(team.aoUserAssignedDt).fromNow(true)}"
|
64 | else
|
65 | "* #{team.name} has no active owner! Use: 'Assign <user> as AO for <team>'."
|
66 | aoStatusList = (aoStatus(team) for teamName, team of teams)
|
67 | msg.send "AOs:\n" + aoStatusList.join("\n")
|
68 |
|
69 | robot.respond /(list|show) needed reviews/i, (msg) ->
|
70 | prs = robot.brain.data['prsForReview']
|
71 | if Object.keys(prs).length == 0
|
72 | response = "Nothing needs review as far as I know."
|
73 | return msg.send response
|
74 | prDescription = (pr) ->
|
75 | return "Added #{moment(pr.aoUserAssignedDt).fromNow()}: #{pr.url}"
|
76 | prDescriptionList = (prDescription(pr) for prKey, pr of prs)
|
77 | msg.send "PRs in need of review:\n" + prDescriptionList.join("\n")
|
78 |
|
79 | robot.respond /add ([a-z0-9 ]+) to teams/i, (msg) ->
|
80 | teamName = msg.match[1]
|
81 | if getTeam(teamName)
|
82 | return msg.send "#{teamName} already being tracked."
|
83 | addTeam(teamName)
|
84 | msg.send "#{teamName} added."
|
85 |
|
86 | robot.respond /(delete|remove) ([a-z0-9 ]+) from teams/i, (msg) ->
|
87 | teamName = msg.match[2]
|
88 | if getTeam(teamName)
|
89 | removeTeam(teamName)
|
90 | return msg.send "Removed #{teamName} from tracked teams."
|
91 | msg.send "I wasn't tracking #{teamName}."
|
92 |
|
93 | robot.respond /assign ([a-z0-9 -]+) as AO for ([a-z0-9 ]+)/i, (msg) ->
|
94 | userId = robot.brain.userForName(msg.match[1])?.id
|
95 | teamName = msg.match[2]
|
96 | assignTeam userId, teamName, msg
|
97 |
|
98 | robot.respond /I'm AO for ([a-z0-9 ]+)/i, (msg) ->
|
99 | teamName = msg.match[1]
|
100 | assignTeam msg.message.user.id, teamName, msg
|
101 |
|
102 | assignTeam = (userId, teamName, msg) ->
|
103 | team = getTeam(teamName)
|
104 | return msg.send "Never heard of that team. You can add a team with 'Add <team name> to teams'." unless team
|
105 | return msg.send "I have no idea who you're talking about." unless userId
|
106 | team.assignAo(userId)
|
107 | msg.send 'Got it.'
|
108 |
|
109 | getTeam = (name) ->
|
110 | return robot.brain.data.teams[name.toLowerCase()]
|
111 |
|
112 | removeTeam = (name) ->
|
113 | delete robot.brain.data.teams[name.toLowerCase()]
|
114 |
|
115 | addTeam = (name) ->
|
116 | robot.brain.data.teams[name.toLowerCase()] = new Team(name)
|
117 |
|
118 | messageAOs = (message) ->
|
119 | messageAO(team, message) for teamName, team of robot.brain.data.teams
|
120 |
|
121 | messageAO = (team, message) ->
|
122 | if team.aoUserId
|
123 | aoUser = robot.brain.userForId(team.aoUserId)
|
124 | robot.send(aoUser, message)
|
125 |
|
126 | class PullRequest
|
127 | constructor: (pr) ->
|
128 | @repo = pr.repo
|
129 | @url = pr.url
|
130 | @number = pr.number
|
131 | @reviewNeededDt = new Date()
|
132 |
|
133 | class Team
|
134 | constructor: (name) ->
|
135 | @name = name
|
136 | @members = []
|
137 | @aoUserId = undefined
|
138 | @aoUserAssignedDt = undefined
|
139 |
|
140 | assignAo: (userId) ->
|
141 | @aoUserId = userId
|
142 | @aoUserAssignedDt = new Date()
|