Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add build status #11

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
19 changes: 19 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,25 @@ Optional

- ``SLACK_CHANNEL``: The Slack channel you want the reminders to be posted in, defaults to #general.

- ``SHOW_BUILD_STATUS``: If True, will show pull request status icons in
the slack message. Defaults to True.

- ``SUCCESS_EMOJI``: Slack emoji name to show for pull requests with the
'success' status. Defaults to '✓'. Can be a slack emoji string like
':white_check_mark:'

- ``PENDING_EMOJI``: Slack emoji name to show for pull requests with the
'pending' status. Defaults to ':large_orange_diamond:'. Can be a slack
emoji string like ':large_orange_diamond:'

- ``FAILURE_EMOJI``: Slack emoji name to show for pull requests with the
'failure' status. Defaults to ':x:'.Can be a slack emoji string like
':x:'

Its recommended to set up custom slack emoji such as the ones `here
<https://github.com/markddavidoff/icons/tree/master/build_status/>`_ as
the ones that come with slack and unicode ones are not very pretty.

Example
~~~~~~~

Expand Down
4 changes: 2 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

setup(
name='slack-pull-reminder',
version='0.1.4',
version='0.1.5',
url='http://github.com/ekmartin/slack-pull-reminder',
author='Martin Ek',
author_email='[email protected]',
Expand All @@ -15,7 +15,7 @@
license='MIT',
install_requires=[
'requests==2.21.0',
'github3.py==1.0.0a4'
'github3.py==1.2.0'
],
entry_points='''
[console_scripts]
Expand Down
85 changes: 83 additions & 2 deletions slack_pull_reminder.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import os
import sys
from collections import namedtuple

import requests
from github3 import login
from github3.repos import status

CombinedBuildStatus = namedtuple('CombinedBuildStatus', ('success', 'pending', 'failure'))

POST_URL = 'https://slack.com/api/chat.postMessage'

Expand All @@ -17,6 +21,18 @@

SLACK_CHANNEL = os.environ.get('SLACK_CHANNEL', '#general')

SHOW_BUILD_STATUS = os.environ.get('SHOW_BUILD_STATUS', True)
SUCCESS_EMOJI = os.environ.get('SUCCESS_EMOJI', u'✓')
PENDING_EMOJI = os.environ.get('PENDING_EMOJI', u'⟳')
FAILURE_EMOJI = os.environ.get('FAILURE_EMOJI', u'⨉')

state_to_emoji = {
CombinedBuildStatus.success: SUCCESS_EMOJI,
CombinedBuildStatus.pending: PENDING_EMOJI,
CombinedBuildStatus.failure: FAILURE_EMOJI,
None: ' ' # for when there is no status for one PR but you still want the rest of the text to line up
}

try:
SLACK_API_TOKEN = os.environ['SLACK_API_TOKEN']
GITHUB_API_TOKEN = os.environ['GITHUB_API_TOKEN']
Expand Down Expand Up @@ -49,14 +65,78 @@ def is_valid_title(title):
return True


def fetch_combined_build_status(pull_request):
"""
Can't use the github api pull request combined statuses endpoint as for some reason it combines review statuses
and build statuses and we only want build statuses
:param pull_request: github3.py PullRequest obj
:return: a CombinedBuildStatus enum value or None if no statuses
"""
build_statuses = fetch_pull_request_build_statuses(pull_request)

# { status.context: most recently updated Status with that context}
most_recent_status_by_context = {}

for build_status in build_statuses:
if build_status.updated_at is None:
# gihub3.py seems to imply this can be None, so ignore those statuses
continue

if build_status.context not in most_recent_status_by_context:
most_recent_status_by_context[build_status.context] = build_status
elif build_status.updated_at > most_recent_status_by_context[build_status.context].updated_at:
most_recent_status_by_context[build_status.context] = build_status

most_recent_statuses = most_recent_status_by_context.values()

if len(most_recent_statuses) == 0:
# no statuses, return None
return None

any_pending = False
for status in most_recent_statuses:
if status.state == CombinedBuildStatus.failure:
# if any failed, return overall failure
return CombinedBuildStatus.failure
if status.state == CombinedBuildStatus.pending:
# can't return here in case there's a failed one after it
any_pending = True
return CombinedBuildStatus.pending if any_pending else CombinedBuildStatus.success


def fetch_pull_request_build_statuses(pull_request):
"""
# TODO Remove after the PR adding this is merged into github3.py
# PR: https://github.com/sigmavirus24/github3.py/pull/896
Return iterator of all Statuses associated with head of this pull request.

:param pull_request: PullRequest object
:returns:
generator of statuses for this pull request
:rtype:
:class:`~github3.repos.Status`
"""
if pull_request.repository is None:
return []
url = pull_request._build_url(
'statuses', pull_request.head.sha, base_url=pull_request.repository._api
)
return pull_request._iter(-1, url, status.Status)


def format_pull_requests(pull_requests, owner, repository):
lines = []

for pull in pull_requests:
if is_valid_title(pull.title):
creator = pull.user.login
line = '*[{0}/{1}]* <{2}|{3} - by {4}>'.format(
owner, repository, pull.html_url, pull.title, creator)
combined_status = fetch_combined_build_status(pull)
if SHOW_BUILD_STATUS and combined_status:
build_status = state_to_emoji.get(combined_status)
else:
build_status = ""
line = '*{}[{}/{}]* <{}|{} - by {}>'.format(
build_status, owner, repository, pull.html_url, pull.title, creator)
lines.append(line)

return lines
Expand Down Expand Up @@ -101,5 +181,6 @@ def cli():
text = INITIAL_MESSAGE + '\n'.join(lines)
send_to_slack(text)


if __name__ == '__main__':
cli()