Skip to content

Commit

Permalink
Merge the new Trello plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
psss committed Oct 2, 2015
2 parents 4ea8a3c + 434e64d commit a2b6b2e
Show file tree
Hide file tree
Showing 3 changed files with 354 additions and 0 deletions.
2 changes: 2 additions & 0 deletions did/plugins/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@
+----------+-----+
| trac | 400 |
+----------+-----+
| trello | 450 |
+----------+-----+
| rt | 500 |
+----------+-----+
| jira | 600 |
Expand Down
267 changes: 267 additions & 0 deletions did/plugins/trello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# coding: utf-8
"""
Trello actions
Config example::
[tools]
type = trello
apikey = ... [https://trello.com/app-key]
token = ... [http://stackoverflow.com/questions/17178907/how-to-get-a-permanent-user-token-for-writes-using-the-trello-api]
board_links = g9mdhdzg
filters = updateCheckItemStateOnCard,updateCard
user = member
Positional arguments: (apikey and token) OR user, if board is (private OR public)
Optional arguments: boards(default:all), filters(default:all)
Possible API methods to add:
http://developers.trello.com/advanced-reference/member#get-1-members-idmember-or-username-actions
"""

from __future__ import unicode_literals, absolute_import

import json
import urllib
import urllib2

from did.base import Config, ReportError
from did.utils import log, pretty
from did.stats import Stats, StatsGroup


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello Stats
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloStats(Stats):

""" Trello stats """

def __init__(self, trello, filt, option, name=None, parent=None):
super(TrelloStats, self).__init__(
option=option, name=name, parent=parent)
self.options = parent.options
self.filt = filt
self.trello = trello


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello API
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloAPI(object):

def __init__(self, stats, config):
self.stats = stats

self.key = config['apikey']
self.token = config['token']
self.username = config['user'] if "user" in config else "me"
self.board_links = [b.strip()
for b in config['board_links'].split(',')]
self.board_ids = self.board_links_to_ids()

def get_actions(self, filters, since=None, before=None, limit=1000):
"""
Example of data structure:
https://api.trello.com/1/members/ben/actions?limit=2
"""
if limit > 1000:
raise NotImplementedError(
"Fetching more than 1000 items is not implemented")
resp = self.stats.session.open(
"{0}/members/{1}/actions?{2}".format(
self.stats.url, self.username, urllib.urlencode({
"key": self.key,
"token": self.token,
"filter": filters,
"limit": limit,
"since": str(since),
"before": str(before)})))

actions = json.loads(resp.read())
log.data(pretty(actions))
# print[act for act in actions if "shortLink" not in
# act['data']['board'].keys()]
actions = [act for act in actions if act['data']
['board']['id'] in self.board_ids]
return actions

def board_links_to_ids(self):
resp = self.stats.session.open(
"{0}/members/{1}/boards?{2}".format(
self.stats.url, self.username, urllib.urlencode({
"key": self.key,
"token": self.token,
"fields": "shortLink"})))
boards = json.loads(resp.read())

return [board['id'] for board in boards if self.board_links == [""]
or board['shortLink'] in self.board_links]


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello createCard
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloCardsCreated(TrelloStats):

""" Trello cards """

def fetch(self):
log.info(u"Searching for cards created in %s by %s" %
(self.parent.option, self.user))
actions = ["{0} was created".format(act['data']['card']['name'])
for act in self.trello.get_actions(
filters=self.filt,
since=self.options.since.date,
before=self.options.until.date)]
self.stats = sorted(list(set(actions)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello updateCard
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloCards(TrelloStats):

""" Trello cards """

def fetch(self):
log.info(u"Searching for cards updated in %s by %s" %
(self.parent.option, self.user))
actions = [act['data']['card']['name']
for act in self.trello.get_actions(
filters=self.filt,
since=self.options.since.date,
before=self.options.until.date)]
self.stats = sorted(list(set(actions)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello updateCard:closed
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloCardsClosed(TrelloStats):

""" Trello cards closed"""

def fetch(self):
log.info(u"Searching for cards closed in %s by %s" %
(self.parent.option, self.user))
status = {True: 'closed',
False: 'opened'}
actions = ["{0}: {1}".format(act['data']['card']['name'],
status[act['data']['card']['closed']])
for act in self.trello.get_actions(
filters=self.filt,
since=self.options.since.date,
before=self.options.until.date)]

self.stats = sorted(list(set(actions)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello updateCard:idList
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloCardsMoved(TrelloStats):

""" Trello cards moved"""

def fetch(self):
log.info(u"Searching for cards moved in %s by %s" %
(self.parent.option, self.user))
actions = ["{0} moved from {1} to {2}".format(
act['data']['card']['name'],
act['data']['listBefore']['name'],
act['data']['listAfter']['name'])
for act in self.trello.get_actions(
filters=self.filt,
since=self.options.since.date,
before=self.options.until.date)]

self.stats = sorted(list(set(actions)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello updateCheckItemStateOnCard
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloCheckItem(TrelloStats):

""" Trello cards """

def fetch(self):
log.info(u"Searching for CheckItem completed in %s by %s" %
(self.parent.option, self.user))
actions = ["{0}: {1}".format(act['data']['card']['name'],
act['data']['checkItem']['name'])
for act in self.trello.get_actions(
filters=self.filt,
since=self.options.since.date,
before=self.options.until.date)]
self.stats = sorted(list(set(actions)))


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Trello Stats Group
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

class TrelloStatsGroup(StatsGroup):

""" Trello stats group """

# Default order

order = 450

def __init__(self, option, name=None, parent=None):
super(TrelloStatsGroup, self).__init__(
option=option, name=name, parent=parent)

# map appropriate API methods to Classes
filter_map = {'Boards': {},
'Lists': {},
'Cards': {'updateCard': TrelloCards,
'updateCard:closed': TrelloCardsClosed,
'updateCard:idList': TrelloCardsMoved,
'createCard': TrelloCardsCreated},
'Checklists': {'updateCheckItemStateOnCard': TrelloCheckItem}
}
self._session = None
self.url = "https://trello.com/1"
config = dict(Config().section(option))

positional_args = ['apikey', 'token']
if not set(positional_args).issubset(set(config.keys())) and "user" not in config:
raise ReportError(
"No ({0}) or 'user' set in the [{1}] section".format(
' and '.join(["'%s'" % (s) for s in positional_args]), option))

optional_args = ["board_links", "filters", "apikey", "token"]
for arg in optional_args:
if arg not in config:
config[arg] = ""

trello = TrelloAPI(stats=self, config=config)

filters = [filt.strip() for filt in config['filters'].split(',')]
for filt_group in filter_map.keys():
for filt in filter_map[filt_group].keys():
if filters != [""] and filt not in filters:
continue
self.stats.append(filter_map[filt_group][filt](
trello=trello,
filt=filt,
option=option + filt,
name="Actions in %s" % (filt_group),
parent=self))

@property
def session(self):
""" Initialize the session """
if self._session is None:
self._session = urllib2.build_opener(urllib2.HTTPHandler)
return self._session
85 changes: 85 additions & 0 deletions tests/plugins/test_trello.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# -*- coding: utf-8 -*-
# @Author: Eduard Trott
# Test Board: https://trello.com/b/sH1cMiyg/public-test-board

""" Tests for the Trello plugin """

from __future__ import unicode_literals, absolute_import

import pytest
import did.cli

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Constants
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

INTERVAL = "--since 2015-10-01 --until 2015-10-03"

CONFIG = """
[general]
email = "Eduard Trott" <[email protected]>
[trello]
type = trello
user = maybelinot
"""


# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Tests
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

def test_trello_cards_created():
""" Created cards """
did.base.Config(CONFIG)
stats = did.cli.main(INTERVAL)[0][0].stats[0].stats[0].stats
print stats
assert any([
"Card1 was created" in unicode(stat) for stat in stats])


def test_trello_cards_updated():
""" Updated cards """
did.base.Config(CONFIG)
stats = did.cli.main(INTERVAL)[0][0].stats[0].stats[1].stats
print stats
assert any([
"Card3"
in unicode(stat) for stat in stats])


def test_trello_cards_closed():
""" Closed cards """
did.base.Config(CONFIG)
stats = did.cli.main(INTERVAL)[0][0].stats[0].stats[2].stats
print stats
assert any([
"Archived Card: closed"
in unicode(stat) for stat in stats])


def test_trello_cards_moved():
""" Moved cards """
did.base.Config(CONFIG)
stats = did.cli.main(INTERVAL)[0][0].stats[0].stats[3].stats
print stats
assert any([
"Card3 moved from List1 to List3"
in unicode(stat) for stat in stats])


def test_trello_checklists_checkitem():
""" Completed Checkitems in checklists """
did.base.Config(CONFIG)
stats = did.cli.main(INTERVAL)[0][0].stats[0].stats[4].stats
print stats
# print[unicode(stat) for stat in stats]
assert any([
"Card1: CheckItem3"
in unicode(stat) for stat in stats])


def test_trello_missing_apikey():
""" Missing username """
did.base.Config("[trello]\ntype = trello")
with pytest.raises(SystemExit):
did.cli.main(INTERVAL)

0 comments on commit a2b6b2e

Please sign in to comment.