Skip to content

Commit 4673f28

Browse files
committed
Merge branch 'dev'
2 parents 3b91d28 + c5bf2df commit 4673f28

File tree

10 files changed

+353
-72
lines changed

10 files changed

+353
-72
lines changed

Procfile

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
web: python3 labelbot/run.py -c -d hook_test web

README.md

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
# Github Labelbot
22
Configurable Github bot used for automatic issues labeling
33

4+
Sample webhook is running at https://labelbot-api.herokuapp.com/
5+
46
## Configuration
57
TBA
68

@@ -16,8 +18,16 @@ $ pip install -r requirements.txt
1618

1719
Now you can run the command:
1820

19-
`$ ./main.py --help`
21+
`$ labelbot/run.py --help`
22+
23+
24+
## Heroku deployment
25+
While deploying the app on Heroku, following environment variables have to be set:
2026

27+
```
28+
WEBHOOK_TOKEN=webhook_token - GitHub webhook secret/token
29+
GITHUB_TOKEN=github_token - GitHub API token
30+
```
2131

2232
## About
23-
Github Labelbot is being developed as part of MI-PYT lecture at Faculty of Information technology, Czech Technical University in Prague.
33+
Github Labelbot is being developed as part of MI-PYT course at Faculty of Information technology, Czech Technical University in Prague.

labelbot/console.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
#!/usr/bin/env python3
2+
3+
4+
def run(labelbot, interval, repo_urls):
5+
labelbot.interval = interval
6+
labelbot.add_repos(repo_urls)
7+
labelbot.run_scheduled()

labelbot/labelbot.py

Lines changed: 89 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -22,27 +22,28 @@ class LabelBot(object):
2222
issue_comments_endpoint = urljoin(issue_endpoint,
2323
'comments')
2424

25-
def __init__(self, token_file, rules_file, default_label, interval,
25+
def __init__(self, token_file, github_token, rules_file, default_label,
2626
check_comments, skip_labeled):
2727
self.last_issue_checked = 0
28+
self.iterval = 0
2829
self.scheduler = sched.scheduler(time.time, time.sleep)
2930
self.default_label = default_label
30-
self.interval = interval
3131
self.check_comments = check_comments
3232
self.skip_labeled = skip_labeled
3333

3434
# get GitHub token
35-
self.token = self._get_token(token_file)
35+
if github_token:
36+
self.token = github_token
37+
else:
38+
self.token = self._get_token(token_file)
3639

3740
# get request session and validate token
3841
self.session = self._get_requests_session(self.token)
3942

4043
# load and validate rules
4144
self.rules = self._get_rules(rules_file)
4245

43-
# TODO check status_code
44-
self.available_repos_json = self.session.get(
45-
self.repos_endpoint).json()
46+
self._update_accessible_repos()
4647

4748
def add_repos(self, repos):
4849
"""Check provided URLs are valid GitHub repositories.
@@ -65,16 +66,37 @@ def add_repos(self, repos):
6566

6667
# start labeling issues in given repos
6768
for repo in repo_names:
68-
self.scheduler.enter(0, 1, self._label_issues,
69+
self.scheduler.enter(0, 1, self._label_repo,
6970
argument=(repo,))
7071

71-
def run(self):
72+
def check_repo_accessible(self, repo):
73+
"""Updates list of available repos and checks whether given repo is
74+
accessible by labelbot.
75+
76+
Args:
77+
repo: full_name of github repository
78+
"""
79+
# update available repos
80+
self._update_accessible_repos()
81+
82+
if repo in [available_repo['full_name'] for available_repo
83+
in self.available_repos_json]:
84+
return True
85+
else:
86+
return False
87+
88+
def _update_accessible_repos(self):
89+
# TODO check status_code
90+
self.available_repos_json = self.session.get(
91+
self.repos_endpoint).json()
92+
93+
def run_scheduled(self):
7294
"""Initiate labeling by running a scheduler"""
7395
self.scheduler.run()
7496

75-
def _label_issues(self, repo):
76-
"""Iterates through all issues in given repo, tries to match all rules
77-
and sets a new labels if needed.
97+
def _label_repo(self, repo, reschedule=True):
98+
"""Iterates through all issues in given repo and runs _label_issue() on
99+
each of them.
78100
79101
Args:
80102
repo: Full name of repository in form 'user/repo_name' as returned
@@ -98,53 +120,64 @@ def _label_issues(self, repo):
98120

99121
# iterate through all isues
100122
for issue in issues:
101-
labels_to_add = []
102-
matched = False
103-
104-
# get existing label strings
105-
existing_labels = [label['name'] for label in issue['labels']]
106-
107-
# skip this issue if it is already labeled and skip_labeled
108-
# flag was used
109-
if self.skip_labeled and len(existing_labels) > 0:
110-
continue
111-
112-
# match rules in issue body and title
113-
for rule in self.rules:
114-
if rule.pattern.findall(issue['body'])\
115-
or rule.pattern.findall(issue['title']):
116-
labels_to_add.append(rule.label)
117-
matched = True
118-
119-
# match rules in issue comments if needed
120-
if self.check_comments:
121-
response = self.session.get(self.issue_comments_endpoint
122-
.format(
123-
issue=str(issue['number']),
124-
repo=repo))
125-
# TODO check status_code
126-
comments = response.json()
127-
for comment in comments:
128-
for rule in self.rules:
129-
if rule.pattern.findall(comment['body']):
130-
labels_to_add.append(rule.label)
131-
matched = True
132-
133-
# set default label if needed
134-
if self.default_label and matched == 0:
135-
labels_to_add.append(self.default_label)
136-
137-
# set new labels
138-
labels_to_add = list(set(labels_to_add)) # make values unique
139-
new_labels = existing_labels + labels_to_add
140-
if not new_labels == existing_labels:
141-
response = self.session.patch(self.issue_endpoint.format(
142-
issue=str(issue['number']), repo=repo),
143-
data=json.dumps({'labels': new_labels}))
123+
self._label_issue(repo, issue)
144124

145125
# run this again after given interval
146-
self.scheduler.enter(self.interval, 1, self._label_issues,
147-
argument=(repo,))
126+
if reschedule:
127+
self.scheduler.enter(self.interval, 1, self._label_repo,
128+
argument=(repo,))
129+
130+
def label_issue(self, repo, issue):
131+
"""Iterates through an issue and labels it.
132+
133+
Args:
134+
repo: Full name of repository in form 'user/repo_name' as returned
135+
by GitHub API
136+
issue: json interpretation of issue as returned by GitHub API
137+
"""
138+
labels_to_add = []
139+
matched = False
140+
141+
# get existing label strings
142+
existing_labels = [label['name'] for label in issue['labels']]
143+
144+
# skip this issue if it is already labeled and skip_labeled
145+
# flag was used
146+
if self.skip_labeled and len(existing_labels) > 0:
147+
return
148+
149+
# match rules in issue body and title
150+
for rule in self.rules:
151+
if rule.pattern.findall(issue['body'])\
152+
or rule.pattern.findall(issue['title']):
153+
labels_to_add.append(rule.label)
154+
matched = True
155+
156+
# match rules in issue comments if needed
157+
if self.check_comments:
158+
response = self.session.get(self.issue_comments_endpoint
159+
.format(
160+
issue=str(issue['number']),
161+
repo=repo))
162+
# TODO check status_code
163+
comments = response.json()
164+
for comment in comments:
165+
for rule in self.rules:
166+
if rule.pattern.findall(comment['body']):
167+
labels_to_add.append(rule.label)
168+
matched = True
169+
170+
# set default label if needed
171+
if self.default_label and matched == 0:
172+
labels_to_add.append(self.default_label)
173+
174+
# set new labels
175+
labels_to_add = list(set(labels_to_add)) # make values unique
176+
new_labels = existing_labels + labels_to_add
177+
if not new_labels == existing_labels:
178+
response = self.session.patch(self.issue_endpoint.format(
179+
issue=str(issue['number']), repo=repo),
180+
data=json.dumps({'labels': new_labels}))
148181

149182
def _get_rules(self, rules_file):
150183
"""Parse labeling rules from the provided file.
Lines changed: 33 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
#!/usr/bin/env python3
22

33
import click
4+
import console as labelbot_console
5+
import os
46

57
from labelbot import LabelBot, UrlParam
8+
from web import app
69

710

8-
@click.command()
11+
@click.group()
912
@click.option('--token-file',
1013
'-t',
1114
type=click.Path(exists=True,
1215
file_okay=True,
1316
readable=True),
14-
default='token.cfg',
17+
default='token.cfg.sample',
1518
help='file containing GitHub token information')
1619
@click.option('--rules-file',
1720
'-u',
@@ -20,11 +23,10 @@
2023
readable=True),
2124
default='rules.cfg.sample',
2225
help='file containing issues labeling rules')
23-
@click.option('--interval',
24-
'-i',
25-
type=int,
26-
default=10,
27-
help='time interval in seconds in which to check issues')
26+
@click.option('--github-token',
27+
'-g',
28+
help='github token used for authentication',
29+
default=lambda: os.environ.get('GITHUB_TOKEN'))
2830
@click.option('--default-label',
2931
'-d',
3032
help='default label to use after no rule is matched')
@@ -36,16 +38,34 @@
3638
'-s',
3739
is_flag=True,
3840
help='skip labeling issues that already have any label')
41+
@click.pass_context
42+
def cli(ctx, token_file, github_token, rules_file, default_label,
43+
check_comments, skip_labeled):
44+
ctx.obj = LabelBot(token_file, github_token, rules_file, default_label,
45+
check_comments, skip_labeled)
46+
47+
48+
@cli.command(short_help='Run console daemon periodically checking issues')
49+
@click.option('--interval',
50+
'-i',
51+
type=int,
52+
default=10,
53+
help='time interval in seconds in which to check issues')
3954
@click.argument('repo_urls',
4055
nargs=-1,
4156
required=True,
4257
type=UrlParam())
43-
def cli(repo_urls, token_file, rules_file, interval, default_label,
44-
check_comments, skip_labeled):
45-
labelbot = LabelBot(token_file, rules_file, default_label, interval,
46-
check_comments, skip_labeled)
47-
labelbot.add_repos(repo_urls)
48-
labelbot.run()
58+
@click.pass_obj
59+
def console(labelbot, interval, repo_urls):
60+
labelbot_console.run(labelbot, interval, repo_urls)
61+
62+
63+
@cli.command(short_help='Run web API listening for issue updates')
64+
@click.pass_obj
65+
def web(labelbot):
66+
port = int(os.environ.get('PORT', 80))
67+
app.config['labelbot'] = labelbot
68+
app.run(host='0.0.0.0', port=port, debug=True)
4969

5070
if __name__ == '__main__':
5171
cli()

labelbot/static/custom.css

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/* Space out content a bit */
2+
body {
3+
padding-top: 1.5rem;
4+
padding-bottom: 1.5rem;
5+
}
6+
7+
/* Everything but the jumbotron gets side spacing for mobile first views */
8+
.header,
9+
.marketing,
10+
.footer {
11+
padding-right: 1rem;
12+
padding-left: 1rem;
13+
}
14+
15+
/* Custom page header */
16+
.header {
17+
padding-bottom: 1rem;
18+
border-bottom: .05rem solid #e5e5e5;
19+
}
20+
/* Make the masthead heading the same height as the navigation */
21+
.header h3 {
22+
margin-top: 0;
23+
margin-bottom: 0;
24+
line-height: 3rem;
25+
}
26+
27+
/* Custom page footer */
28+
.footer {
29+
padding-top: 1.5rem;
30+
color: #777;
31+
border-top: .05rem solid #e5e5e5;
32+
}
33+
34+
/* Customize container */
35+
@media (min-width: 48em) {
36+
.container {
37+
max-width: 46rem;
38+
}
39+
}
40+
.container-narrow > hr {
41+
margin: 2rem 0;
42+
}
43+
44+
45+
/* Supporting marketing content */
46+
.marketing {
47+
margin: 3rem 0;
48+
}
49+
.marketing p + h4 {
50+
margin-top: 1.5rem;
51+
}
52+
53+
/* Responsive: Portrait tablets and up */
54+
@media screen and (min-width: 48em) {
55+
/* Remove the padding we set earlier */
56+
.header,
57+
.marketing,
58+
.footer {
59+
padding-right: 0;
60+
padding-left: 0;
61+
}
62+
/* Space out the masthead */
63+
.header {
64+
margin-bottom: 2rem;
65+
}
66+
}

0 commit comments

Comments
 (0)