diff --git a/cron/issue.py b/cron/issue.py index 57c22e7..899caea 100644 --- a/cron/issue.py +++ b/cron/issue.py @@ -1,5 +1,7 @@ import datetime import re +import github3 +import requests from config import CONFIG_VARS as cvar @@ -168,3 +170,54 @@ def needs_reply(self): return not any([c for c in comments if c.created_at.replace(tzinfo=None) > label_time and c.user.login != e.actor.login]) return False + + +def close_old_issues(): + + gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) + repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) + issues = repo.iter_issues() + + try: # Read message templates from remote URL + msg = requests.get(cvar['CLOSING_TEMPLATE']).text + except: # Read from local file + msg = open(cvar['CLOSING_TEMPLATE']).read() + + closed = [Issue(i).close(msg=msg, reason='old') for i in issues] + + print 'Old Issues Closed: ' + print filter(lambda x: x is not None, closed) + + +def close_noreply_issues(): + + gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) + repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) + issues = repo.iter_issues() + + try: # Read message templates from remote URL + msg = requests.get(cvar['CLOSING_NOREPLY_TEMPLATE']).text + except: # Read from local file + msg = open(cvar['CLOSING_NOREPLY_TEMPLATE']).read() + + closed = [Issue(i).close(msg=msg, reason='noreply') for i in issues] + + print "No-Reply Issues Closed: " + print filter(lambda x: x is not None, closed) + + +def warn_old_issues(): + + gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) + repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) + issues = repo.iter_issues() + + try: # Read from remote URL + warning_msg = requests.get(cvar['WARNING_TEMPLATE']).text + except: # Read from local file + warning_msg = open(cvar['WARNING_TEMPLATE']).read() + + warned = [Issue(i).warn(msg=warning_msg) for i in issues] + + print "Old Issues Warned: " + print filter(lambda x: x is not None, warned) diff --git a/decorators.py b/decorators.py new file mode 100644 index 0000000..e8aa3c1 --- /dev/null +++ b/decorators.py @@ -0,0 +1,43 @@ +from datetime import timedelta +from flask import make_response, request, current_app +from functools import update_wrapper + + +def crossdomain(origin=None, methods=None, headers=None, max_age=21600, attach_to_all=True, automatic_options=True): + if methods is not None: + methods = ', '.join(sorted(x.upper() for x in methods)) + if headers is not None and not isinstance(headers, basestring): + headers = ', '.join(x.upper() for x in headers) + if not isinstance(origin, basestring): + origin = ', '.join(origin) + if isinstance(max_age, timedelta): + max_age = max_age.total_seconds() + + def get_methods(): + if methods is not None: + return methods + + options_resp = current_app.make_default_options_response() + return options_resp.headers['allow'] + + def decorator(f): + def wrapped_function(*args, **kwargs): + if automatic_options and request.method == 'OPTIONS': + resp = current_app.make_default_options_response() + else: + resp = make_response(f(*args, **kwargs)) + if not attach_to_all and request.method != 'OPTIONS': + return resp + + h = resp.headers + + h['Access-Control-Allow-Origin'] = origin + h['Access-Control-Allow-Methods'] = get_methods() + h['Access-Control-Max-Age'] = str(max_age) + if headers is not None: + h['Access-Control-Allow-Headers'] = headers + return resp + + f.provide_automatic_options = False + return update_wrapper(wrapped_function, f) + return decorator diff --git a/main.py b/main.py index 46c157c..d2ebd88 100644 --- a/main.py +++ b/main.py @@ -1,13 +1,14 @@ import json import os -import requests -import github3 +import threading from flask import Response, request, Flask, render_template -from config import CONFIG_VARS as cvar -from cron.issue import Issue +from decorators import crossdomain +from cron.issue import close_old_issues, warn_old_issues +from cron.issue import close_noreply_issues from webhooks.pull_request import validate_commit_messages from webhooks.issue import flag_if_submitted_through_github -from webhooks.issue_comment import remove_flag_if_valid +from webhooks.issue_updated import remove_notice_if_valid + app = Flask(__name__) @@ -25,75 +26,51 @@ def cron_close_old_issues(): """ An endpoint for a cronjob to call. Closes issues older than REMOVAL_DAYS. - @return: a JSON array containing the ids of closed issues """ - gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) - repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) - issues = repo.iter_issues() - - try: # Read message templates from remote URL - msg = requests.get(cvar['CLOSING_TEMPLATE']).text - except: # Read from local file - msg = open(cvar['CLOSING_TEMPLATE']).read() + t = threading.Thread(target=close_old_issues) + t.start() + msg = 'close_old_issues task forked to background' - closed = [Issue(i).close(msg=msg, reason='old') for i in issues] - closed = filter(lambda x: x is not None, closed) - - return Response(json.dumps({'closed': closed}), mimetype='application/json') + return Response(json.dumps({'message': msg}), mimetype='application/json') @app.route("/api/close-noreply-issues", methods=['GET', 'POST']) -def cron_noreply_issues(): +def cron_close_noreply_issues(): """ An endpoint for a cronjob to call. Closes issues that never received a requested reply. - @return: a JSON array containing the ids of closed issues """ - gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) - repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) - issues = repo.iter_issues() - - try: # Read message templates from remote URL - msg = requests.get(cvar['CLOSING_NOREPLY_TEMPLATE']).text - except: # Read from local file - msg = open(cvar['CLOSING_NOREPLY_TEMPLATE']).read() + t = threading.Thread(target=close_noreply_issues) + t.start() + msg = 'close_noreply_issues task forked to background' - closed = [Issue(i).close(msg=msg, reason='noreply') for i in issues] - closed = filter(lambda x: x is not None, closed) - - return Response(json.dumps({'closed': closed}), mimetype='application/json') + return Response(json.dumps({'message': msg}), mimetype='application/json') @app.route("/api/warn-old-issues", methods=['GET', 'POST']) def cron_warn_old_issues(): """ An endpoint for a cronjob to call. - Adds a warning message to issues older than REMOVAL_WARNING_DAYS.. - @return: a JSON array containing the ids of warned issues + Adds a warning message to issues older than REMOVAL_WARNING_DAYS. """ - gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) - repo = gh.repository(cvar['REPO_USERNAME'], cvar['REPO_ID']) - issues = repo.iter_issues() - - try: # Read from remote URL - warning_msg = requests.get(cvar['WARNING_TEMPLATE']).text - except: # Read from local file - warning_msg = open(cvar['WARNING_TEMPLATE']).read() - warned = [Issue(i).warn(msg=warning_msg) for i in issues] - warned = filter(lambda x: x is not None, warned) + t = threading.Thread(target=warn_old_issues) + t.start() + msg = 'warn_old_issues task forked to background' - return Response(json.dumps({'warned': warned}), mimetype='application/json') + return Response(json.dumps({'message': msg}), mimetype='application/json') -@app.route("/api/webhook", methods=['GET', 'POST']) +@app.route("/api/webhook", methods=['GET', 'POST', 'OPTIONS']) +@crossdomain(origin='*', headers=['Content-Type', 'X-Github-Event']) def webhook_router(): - event_type = request.headers['X-Github-Event'] - payload = json.loads(request.data) response = [] + event_type = request.headers['X-Github-Event'] + if request.data: + payload = json.loads(request.data) if event_type == 'pull_request': response.append(validate_commit_messages(payload)) @@ -101,8 +78,8 @@ def webhook_router(): if event_type == 'issues': response.append(flag_if_submitted_through_github(payload)) - if event_type == 'issue_comment': - response.append(remove_flag_if_valid(payload)) + if event_type == 'issue_updated': + response.append(remove_notice_if_valid(request.args['issueNum'])) return Response(json.dumps(response), mimetype='application/json') diff --git a/webhooks/issue_comment.py b/webhooks/issue_updated.py similarity index 64% rename from webhooks/issue_comment.py rename to webhooks/issue_updated.py index 0ce4511..4673d06 100644 --- a/webhooks/issue_comment.py +++ b/webhooks/issue_updated.py @@ -2,15 +2,17 @@ from config import CONFIG_VARS as cvar -def remove_flag_if_valid(payload): +def notice_flag_if_valid(issueNum): """ - Removes the flag (automated comments and label) if the issue has been resubmitted - through the custom form on the Ionic site. + Removes the notice flag (automated comments and label) if the issue has been + resubmitted through the custom form on the Ionic site. + @param issueNum: the issue number that should be refreshed (string) @return: whether or not the flag was removed (bool) """ + print issueNum gh = github3.login(cvar['GITHUB_USERNAME'], cvar['GITHUB_PASSWORD']) - i = gh.issue(cvar['REPO_USERNAME'], cvar['REPO_ID'], payload['issue']['number']) + i = gh.issue(cvar['REPO_USERNAME'], cvar['REPO_ID'], str(issueNum)) if i.labels: labels = [l.name for l in i.labels]