Skip to content

Commit

Permalink
Implement GitHub Issues service (closes #3)
Browse files Browse the repository at this point in the history
  • Loading branch information
textbook committed Jul 3, 2016
1 parent 34ae16c commit 47947b6
Show file tree
Hide file tree
Showing 5 changed files with 140 additions and 3 deletions.
5 changes: 3 additions & 2 deletions flash_services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@

from .codeship import Codeship
from .coveralls import Coveralls
from .github import GitHub
from .github import GitHub, GitHubIssues
from .tracker import Tracker
from .travis import TravisOS, TravisPro

__author__ = 'Jonathan Sharpe'
__version__ = '0.3.2'
__version__ = '0.3.3'

blueprint = Blueprint(
'services',
Expand All @@ -28,6 +28,7 @@
codeship=Codeship,
coveralls=Coveralls,
github=GitHub,
gh_issues=GitHubIssues,
tracker=Tracker,
travis=TravisOS,
travis_pro=TravisPro,
Expand Down
38 changes: 37 additions & 1 deletion flash_services/github.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"""Defines the GitHub service integration."""

import logging
from collections import OrderedDict
from collections import defaultdict, OrderedDict

import requests

Expand Down Expand Up @@ -112,3 +112,39 @@ def format_commit(cls, commit):
committed=occurred(commit.get('committer', {}).get('date')),
message=commit.get('message', ''),
))


class GitHubIssues(GitHub):
"""Show the current status of GitHub issues and pull requests."""

FRIENDLY_NAME = 'GitHub Issues'
TEMPLATE = 'gh-issues-section'

def update(self):
logger.debug('fetching GitHub issue data')
url_params = OrderedDict(state='all')
response = requests.get(
self.url_builder(
'/repos/{repo}/issues',
params={'repo': self.repo_name},
url_params=url_params,
),
headers=self.headers,
)
if response.status_code == 200:
return self.format_data(self.name, response.json())
logger.error('failed to update GitHub issue data')
return {}

@classmethod
def format_data(cls, name, data):
counts = defaultdict(int)
for issue in data:
if issue.get('pull_request') is not None:
counts['{}-pull-requests'.format(issue['state'])] += 1
else:
counts['{}-issues'.format(issue['state'])] += 1
return dict(
issues=counts,
name=name,
)
9 changes: 9 additions & 0 deletions flash_services/static/scripts/services.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,15 @@ var SERVICES = {
});
}
},
gh_issues: function (pane, data) {
if (data.issues) {
var states = ['open-issues', 'closed-issues', 'open-pull-requests',
'closed-pull-requests'];
states.forEach(function (state) {
pane.find('.' + state).text(data.issues[state] || 0);
});
}
},
github: function (pane, data) {
if (data.commits) {
updateItems(pane, data.commits, '.commit', updateCommit);
Expand Down
18 changes: 18 additions & 0 deletions flash_services/templates/partials/gh-issues-section.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<section class="pane short-pane gh_issues-pane">
<div class="pane-item">
<span class="item-title">Open issues: </span>
<span class="open-issues"></span>
</div>
<div class="pane-item">
<span class="item-title">Closed issues: </span>
<span class="closed-issues"></span>
</div>
<div class="pane-item">
<span class="item-title">Open PRs: </span>
<span class="open-pull-requests"></span>
</div>
<div class="pane-item">
<span class="item-title">Closed PRs: </span>
<span class="closed-pull-requests"></span>
</div>
</section>
73 changes: 73 additions & 0 deletions tests/test_github_issues_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from unittest import mock

import pytest

from flash_services.core import Service
from flash_services.github import GitHubIssues


@pytest.fixture
def service():
return GitHubIssues(api_token='foobar', account='foo', repo='bar')


def test_tracker_service_type():
assert issubclass(GitHubIssues, Service)


def test_correct_config():
assert GitHubIssues.AUTH_PARAM == 'access_token'
assert GitHubIssues.FRIENDLY_NAME == 'GitHub Issues'
assert GitHubIssues.REQUIRED == {'api_token', 'account', 'repo'}
assert GitHubIssues.ROOT == 'https://api.github.com'
assert GitHubIssues.TEMPLATE == 'gh-issues-section'


@mock.patch('flash_services.github.logger.debug')
@mock.patch('flash_services.github.requests.get', **{
'return_value.status_code': 200,
'return_value.json.return_value': [],
})
def test_update_success(get, debug, service):
result = service.update()

get.assert_called_once_with(
'https://api.github.com/repos/foo/bar/issues?state=all&access_token=foobar',
headers={'User-Agent': 'bar'}
)
debug.assert_called_once_with('fetching GitHub issue data')
assert result == {'issues': {}, 'name': 'foo/bar'}


@mock.patch('flash_services.github.logger.error')
@mock.patch('flash_services.github.requests.get', **{
'return_value.status_code': 401,
})
def test_update_failure(get, error, service):
result = service.update()

get.assert_called_once_with(
'https://api.github.com/repos/foo/bar/issues?state=all&access_token=foobar',
headers={'User-Agent': 'bar'}
)
error.assert_called_once_with('failed to update GitHub issue data')
assert result == {}


@pytest.mark.parametrize('input_, expected', [
(('hello', []), dict(name='hello', issues={})),
(
('hello', [{'state': 'open'}, {'state': 'open'}]),
dict(name='hello', issues={'open-issues': 2}),
),
(
('hello', [{'state': 'open'}, {'state': 'closed'}]),
dict(name='hello', issues={'open-issues': 1, 'closed-issues': 1}),
),
(
('hello', [{'state': 'open'}, {'state': 'open', 'pull_request': {}}]),
dict(name='hello', issues={'open-issues': 1, 'open-pull-requests': 1}),
),
])
def test_format_data(input_, expected):
assert GitHubIssues.format_data(*input_) == expected

0 comments on commit 47947b6

Please sign in to comment.