Skip to content

Commit

Permalink
Merge pull request #34 from umonkey/develop
Browse files Browse the repository at this point in the history
Support for TeamLab and RedMine
  • Loading branch information
ralphbean committed Dec 12, 2012
2 parents 524f6a0 + 39aeb8b commit fe1915c
Show file tree
Hide file tree
Showing 4 changed files with 217 additions and 1 deletion.
22 changes: 21 additions & 1 deletion bugwarrior/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ It currently supports the following remote resources:
- `trac <http://trac.edgewall.org/>`_
- `bugzilla <http://www.bugzilla.org/>`_
- `megaplan <http://www.megaplan.ru/>`_
- `teamlab <http://www.teamlab.com/>`_
- `redmine <http://www.redmine.org/>`_

Configuring
-----------
Expand Down Expand Up @@ -102,7 +104,7 @@ Create a ``~/.bugwarriorrc`` file with the following contents.
bugzilla.username = rbean@redhat.com
bugzilla.password = OMG_LULZ

# Here/s an example of a megaplan target.
# Here's an example of a megaplan target.
[my_megaplan]
service = megaplan

Expand All @@ -113,6 +115,24 @@ Create a ``~/.bugwarriorrc`` file with the following contents.
default_priority = H
project_name = example

# Here's an example of a teamlab target.
[my_teamlab]
service = teamlab

hostname = teamlab.example.com
login = alice
password = secret

project_name = example_teamlab

# Here's an example of a redmine target.
[my_redmine]
service = redmine
url = http://redmine.example.org/
key = c0c4c014cafebabe
user_id = 7
project_name = redmine


.. example
Expand Down
4 changes: 4 additions & 0 deletions bugwarrior/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,8 @@ def get_owner(self, issue):
from bitbucket import BitbucketService
from trac import TracService
from bz import BugzillaService
from teamlab import TeamLabService
from redmine import RedMineService


# Constant dict to be used all around town.
Expand All @@ -96,6 +98,8 @@ def get_owner(self, issue):
'bitbucket': BitbucketService,
'trac': TracService,
'bugzilla': BugzillaService,
'teamlab': TeamLabService,
'redmine': RedMineService,
}


Expand Down
79 changes: 79 additions & 0 deletions bugwarrior/services/redmine.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
from twiggy import log

from bugwarrior.services import IssueService
from bugwarrior.config import die

import datetime
import urllib
import urllib2
import json


class Client(object):
def __init__(self, url, key):
self.url = url
self.key = key

def find_issues(self, user_id=None):
args = {}
if user_id is not None:
args["assigned_to_id"] = user_id
return self.call_api("/issues.json", args)["issues"]

def call_api(self, uri, get=None):
url = self.url.rstrip("/") + uri

if get:
url += "?" + urllib.urlencode(get)

req = urllib2.Request(url)
req.add_header("X-Redmine-API-Key", self.key)

res = urllib2.urlopen(req)

return json.loads(res.read())


class RedMineService(IssueService):
def __init__(self, *args, **kw):
super(RedMineService, self).__init__(*args, **kw)

self.url = self.config.get(self.target, 'url').rstrip("/")
self.key = self.config.get(self.target, 'key')
self.user_id = self.config.get(self.target, 'user_id')

self.client = Client(self.url, self.key)

self.project_name = None
if self.config.has_option(self.target, "project_name"):
self.project_name = self.config.get(self.target, "project_name")

@classmethod
def validate_config(cls, config, target):
for k in ('url', 'key', 'user_id'):
if not config.has_option(target, k):
die("[%s] has no '%s'" % (target, k))

IssueService.validate_config(config, target)

def get_issue_url(self, issue):
return self.url + "/issues/" + str(issue["id"])

def get_project_name(self, issue):
if self.project_name:
return self.project_name
return issue["project"]["name"]

def issues(self):
issues = self.client.find_issues(self.user_id)
log.debug(" Found {0} total.", len(issues))

return [dict(
description=self.description(
issue["subject"],
self.get_issue_url(issue),
issue["id"], cls="issue",
),
project=self.get_project_name(issue),
priority=self.default_priority,
) for issue in issues]
113 changes: 113 additions & 0 deletions bugwarrior/services/teamlab.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
from twiggy import log

from bugwarrior.services import IssueService
from bugwarrior.config import die

import datetime
import json
import urllib
import urllib2


class Client(object):
def __init__(self, hostname, verbose=False):
self.hostname = hostname
self.verbose = verbose
self.token = None

def authenticate(self, login, password):
resp = self.call_api("/api/1.0/authentication.json", post={
"userName": str(login),
"password": str(password),
})

self.token = str(resp["token"])

def get_task_list(self):
resp = self.call_api("/api/1.0/project/task/@self.json")
return resp

def call_api(self, uri, post=None, get=None):
uri = "http://" + self.hostname + uri

if post is None:
data = None
else:
data = urllib.urlencode(post)

if get is not None:
uri += "?" + urllib.urlencode(get)

self.log("Fetching %s" % uri)

req = urllib2.Request(uri, data)
if self.token is not None:
req.add_header("Authorization", self.token)
req.add_header("Accept", "application/json")

res = urllib2.urlopen(req)
if res.getcode() >= 400:
raise Exception("Error accessing the API: %s" % res.read())

response = res.read()

return json.loads(response)["response"]

def log(self, message):
if self.verbose:
print message


class TeamLabService(IssueService):
def __init__(self, *args, **kw):
super(TeamLabService, self).__init__(*args, **kw)

self.hostname = self.config.get(self.target, 'hostname')
_login = self.config.get(self.target, 'login')
_password = self.config.get(self.target, 'password')

self.client = Client(self.hostname)
self.client.authenticate(_login, _password)

self.project_name = self.hostname
if self.config.has_option(self.target, "project_name"):
self.project_name = self.config.get(self.target, "project_name")

@classmethod
def validate_config(cls, config, target):
for k in ('login', 'password', 'hostname'):
if not config.has_option(target, k):
die("[%s] has no '%s'" % (target, k))

IssueService.validate_config(config, target)

def get_issue_url(self, issue):
return "http://%s/products/projects/tasks.aspx?prjID=%d&id=%d" \
% (self.hostname, issue["projectOwner"]["id"], issue["id"])

def get_priority(self, issue):
if issue["priority"] == 1:
return "H"
else:
return "M"

def issues(self):
issues = self.client.get_task_list()
log.debug(" Remote has {0} total issues.", len(issues))
if not issues:
return []

# Filter out closed tasks.
issues = filter(lambda i: i["status"] == 1, issues)
log.debug(" Remote has {0} active issues.", len(issues))

return [dict(
description=self.description(
issue["title"],
self.get_issue_url(issue),
issue["id"], cls="issue",
),
project=self.project_name,
priority=self.get_priority(issue),
) for issue in issues]

0 comments on commit fe1915c

Please sign in to comment.