-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathpullRequestTree.js
155 lines (140 loc) · 5.18 KB
/
pullRequestTree.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
(function() {
var colors = {
stale: { hex: '#f1c232', text: 'STALE, PULL ME THROUGH! (Open more than 5 days)' },
shippable: { hex: '#93c47d', text: "SHIP IT! (Green, no conflicts, QA'ed, LGTM)" },
needsAction: { hex: '#e06666', text: "NEEDS ACTION! (Doesn't have QA label, CI red, or has conflicts)" },
blocked: { hex: '#f6d0d0', text: "Blocked" },
WIP: { hex: '#d1c7f5', text: "WIP" },
normal: { hex: '#cccccc', text: "None of the above" },
}
function PullRequest(branch) {
this.title = branch.title
this.pointingTo = branch.baseRefName
this.branchName = branch.headRefName
this.url = branch.url
this.mergeable = branch.mergeable
this.createdAt = branch.createdAt
this.testStatus = branch.commits ? branch.commits.nodes[0].commit.status.state : ''
this.labels = branch.labels ? branch.labels.nodes.map(function(label) { return label.name }) : []
this.labelString = " (" + this.labels.join(' , ') + ")"
this.setPRState()
this.children = []
}
PullRequest.prototype.setPRState = function() {
var context = this
// save off label states so we only iterate through labels once
this.labels.forEach(function(label) {
switch (label) {
case 'LGTM':
context.LGTM = true; break
case 'QA ✓':
case 'Dev QA ✓':
context.QAed = true; break
case 'Requires QA':
case 'Requires Dev QA':
context.requiresQA = true; break
case 'Blocked':
context.blocked = true; break
case 'WIP':
context.WIP = true; break
default:
break
}
});
this.shippable = this.mergeable && this.QAed && this.LGTM && !this.blocked && this.testStatus === 'SUCCESS'
this.state = this.getState()
}
PullRequest.prototype.getState = function() {
if (this.title === 'master') { return 'normal' }
if (this.shippable) { return 'shippable' }
if (this.needsAction()) { return 'needsAction' }
if (this.blocked) { return 'blocked' }
if (this.WIP) { return 'WIP' }
if (this.stale()) { return 'stale' }
return 'normal'
}
PullRequest.prototype.stale = function() {
var createdAt = new Date(this.createdAt)
var fiveDaysAgo = new Date()
fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 5)
return createdAt < fiveDaysAgo
}
PullRequest.prototype.color = function() { return colors[this.state].hex }
PullRequest.prototype.numLeaves = function() {
var count = 0
if (this.children.length) {
this.children.forEach(function(child) {
count += child.numLeaves()
})
} else {
count ++
}
return count
}
PullRequest.prototype.needsAction = function() {
// if it is in a pull-through-able state
if (!this.WIP && !this.blocked) {
if (!this.requiresQA && !this.QAed) {
// needs QA label of some sort
return true;
}
if (this.testStatus === 'FAILURE' || this.testStatus === 'ERROR') {
// branch is red or messed up
return true
}
if (!this.mergeable) { return true; } // has conflicts
// (to add later): if has reviewable label but no reviewer
// return true
}
}
var dummyObj = {
// nest things here so we can access them and spy on them in specs
fetchBranches: function fetchBranches(options) {
var githubUrl = 'https://api.github.com/graphql'
var query = "query {organization(login: \"" + options.orgName + "\") {repository(name: \"" + options.repoName + "\") {milestone(number: " + options.milestoneNumber + ") {pullRequests(last: 100, states:[OPEN]) {nodes {title url labels (last: 100) {nodes {name}} createdAt baseRefName headRefName mergeable commits (last: 1) {nodes {commit {status {state}}}}}}}}}}"
var requestOptions = {
method: 'POST',
payload: JSON.stringify({
query: query
}),
headers: {
'Content-Type': 'application/json',
'Authorization': 'bearer ' + options.token,
}
};
return JSON.parse(UrlFetchApp.fetch(githubUrl, requestOptions));
},
}
function extractPullRequests(response) {
return response.data.organization.repository.milestone.pullRequests.nodes.map(function(pull) { return new PullRequest(pull); });
}
function convertPRsToTree(PRs, options) {
var master = new PullRequest({
title: 'master',
pointingTo: null,
headRefName: 'master',
url: 'github.com/' + options.orgName.toLowerCase() + '/' + options.repoName
})
var pullsWithMaster = PRs.slice()
pullsWithMaster.push(master)
PRs.forEach(function(pull) {
var parentNode = findParentNode(pull, pullsWithMaster);
parentNode && parentNode.children.push(pull);
})
return master
}
function findParentNode(target, pulls) {
return pulls.filter(function(pull) { return pull.branchName === target.pointingTo; })[0];
}
return {
_private: dummyObj,
pullRequestTree: function(options) {
// options = orgName, repoName, milestoneNumber, token
var response = dummyObj.fetchBranches(options)
var pullRequests = extractPullRequests(response)
var tree = convertPRsToTree(pullRequests, options)
return tree
},
colors: colors,
}
})();