Skip to content

Commit

Permalink
Merge pull request #96 from kostajh/refactor_bugwarrior_ac3
Browse files Browse the repository at this point in the history
ActiveCollab 3.x/4.x support
  • Loading branch information
ralphbean committed Mar 10, 2014
2 parents 466cfa2 + 8590e4a commit 833f7c5
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 79 deletions.
39 changes: 19 additions & 20 deletions bugwarrior/README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -306,8 +306,8 @@ Create a ``~/.bugwarriorrc`` file with the following contents.
# - redmineid
#description_template = #{{ redmineid }}: {{ redminesubject }}

# Here's an example of an activecollab3 target. This is only valid for
# activeCollab 3.x, see below for activeCollab 2.x.
# Here's an example of an activecollab target. This is only valid for
# activeCollab 3.x and greater, see below for activeCollab 2.x.
#
# Obtain your user ID and API url by logging in, clicking on your avatar on
# the lower left-hand of the page. When on that page, look at the URL. The
Expand All @@ -320,31 +320,30 @@ Create a ``~/.bugwarriorrc`` file with the following contents.
# list. Note that if you have 10 projects in your favorites list, bugwarrior
# will make 21 API calls on each run: 1 call to get a list of favorites, then
# 2 API calls per projects, one for tasks and one for subtasks.

[activecollab3]
service = activecollab3
activecollab3.url = https://ac.example.org/api.php
activecollab3.key = your-api-key
activecollab3.user_id = 15
[activecollab]
service = activecollab
activecollab.url = https://ac.example.org/api.php
activecollab.key = your-api-key
activecollab.user_id = 15
add_tags = php

# You can override how an issue's description is created by entering
# a one-line Jinja template like the below; in addition to the default
# taskwarrior issue properties (project, priority, due, etc), the
# following properties are available for ActiveCollab3 issues:
# - ac3body
# - ac3name
# - ac3permalink
# - ac3taskid
# - ac3id
# - ac3projectid
# - ac3type
# - ac3createdon
# - ac3createdbyid
#description_template = #{{ac3id}} - {% if ac3name %}{{ ac3name }}{% else %}{{ ac3body }}{% endif %}
# following properties are available for ActiveCollab issues:
# - acbody
# - acname
# - acpermalink
# - actaskid
# - acid
# - acprojectid
# - actype
# - accreatedon
# - accreatedbyid
#description_template = #{{acid}} - {% if acname %}{{ acname }}{% else %}{{ acbody }}{% endif %}

# Here's an example of an activecollab2 target. Note that this will only work
# with ActiveCollab 2.x - see above for 3.x.
# with ActiveCollab 2.x - see above for 3.x and greater.
#
# You can obtain your user ID and API url by logging into ActiveCollab and
# clicking on "Profile" and then "API Settings". When on that page, look
Expand Down
4 changes: 2 additions & 2 deletions bugwarrior/services/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -441,7 +441,7 @@ def aggregate_issues(conf):


from .activecollab2 import ActiveCollab2Service
from .activecollab3 import ActiveCollab3Service
from .activecollab import ActiveCollabService
from .bitbucket import BitbucketService
from .bz import BugzillaService
from .github import GithubService
Expand All @@ -459,7 +459,7 @@ def aggregate_issues(conf):
'teamlab': TeamLabService,
'redmine': RedMineService,
'activecollab2': ActiveCollab2Service,
'activecollab3': ActiveCollab3Service,
'activecollab': ActiveCollabService,
}

try:
Expand Down
Original file line number Diff line number Diff line change
@@ -1,26 +1,49 @@
import itertools
import json
import re
import urllib2
import time

import six
from twiggy import log

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

from .activecollab2 import (
ActiveCollab2Client,
ActiveCollab2Issue,
ActiveCollab2Service,
)

class ActiveCollabClient(object):
def __init__(self, url, key, user_id, projects):
self.url = url
self.key = key
self.user_id = user_id
self.projects = projects

class ActiveCollab3Client(ActiveCollab2Client):
def get_project_slug(self, project_name):
# Take a project name like "Client: Example Project" and return a
# string in project slug format: "client-example-project"
project_name = project_name.lower()
project_name = re.sub('[\s+]', '-', project_name)
project_name = re.sub('[:,"/"]', '', project_name)
def get_task_dict(self, project, key, task):
assigned_task = {
'project': project
}
if task[u'type'] == 'Task':
# Load Task data
ticket_data = self.call_api(
"/projects/" + six.text_type(task[u'project_id']) +
"/tickets/" + six.text_type(task[u'ticket_id']))
assignees = ticket_data[u'assignees']

for k, v in enumerate(assignees):
if (
(v[u'is_owner'] is True)
and (v[u'user_id'] == int(self.user_id))
):
assigned_task.update(ticket_data)
return assigned_task
elif task[u'type'] == 'Subtask':
# Load SubTask data
assigned_task.update(task)
return assigned_task


def get_project_slug(self, permalink):
project_name = permalink.split('/')[4]
return project_name

def call_api(self, uri, key, url):
Expand All @@ -42,7 +65,7 @@ def get_issue_generator(self, user_id, project_id, project_name):
(task[u'assignee_id'] == int(self.user_id))
and (task[u'completed_on'] is None)
):
task['project'] = self.get_project_slug(project_name)
task['project'] = self.get_project_slug(task['permalink'])
task['type'] = 'task'
yield task

Expand All @@ -58,77 +81,87 @@ def get_issue_generator(self, user_id, project_id, project_name):
(subtask[u'assignee_id'] == int(self.user_id))
and (subtask[u'completed_on'] is None)
):
subtask['project'] = self.get_project_slug(project_name)
subtask['project'] = self.get_project_slug(subtask['permalink'])
subtask['type'] = 'subtask'
yield subtask


class ActiveCollab3Issue(ActiveCollab2Issue):
BODY = 'ac3body'
NAME = 'ac3name'
PERMALINK = 'ac3permalink'
TASK_ID = 'ac3taskid'
FOREIGN_ID = 'ac3id'
PROJECT_ID = 'ac3projectid'
TYPE = 'ac3type'
CREATED_ON = 'ac3createdon'
CREATED_BY_ID = 'ac3createdbyid'
class ActiveCollabIssue(Issue):
BODY = 'acbody'
NAME = 'acname'
PERMALINK = 'acpermalink'
TASK_ID = 'actaskid'
FOREIGN_ID = 'acid'
PROJECT_ID = 'acprojectid'
TYPE = 'actype'
CREATED_ON = 'accreatedon'
CREATED_BY_ID = 'accreatedbyid'

UDAS = {
BODY: {
'type': 'string',
'label': 'ActiveCollab3 Body'
'label': 'ActiveCollab Body'
},
NAME: {
'type': 'string',
'label': 'ActiveCollab3 Name'
'label': 'ActiveCollab Name'
},
PERMALINK: {
'type': 'string',
'label': 'ActiveCollab3 Permalink'
'label': 'ActiveCollab Permalink'
},
TASK_ID: {
'type': 'string',
'label': 'ActiveCollab3 Task ID'
'label': 'ActiveCollab Task ID'
},
FOREIGN_ID: {
'type': 'string',
'label': 'ActiveCollab3 ID',
'label': 'ActiveCollab ID',
},
PROJECT_ID: {
'type': 'string',
'label': 'ActiveCollab3 Project ID'
'label': 'ActiveCollab Project ID'
},
TYPE: {
'type': 'string',
'label': 'ActiveCollab3 Task Type'
'label': 'ActiveCollab Task Type'
},
CREATED_ON: {
'type': 'date',
'label': 'ActiveCollab3 Created On'
'label': 'ActiveCollab Created On'
},
CREATED_BY_ID: {
'type': 'string',
'label': 'ActiveCollab3 Created By'
'label': 'ActiveCollab Created By'
},
}
UNIQUE_KEY = (PERMALINK, )

def to_taskwarrior(self):

record = {
'project': self.record['project'],
'priority': self.get_priority(),
'due': self.parse_date(self.record.get('due_on')),

self.NAME: self.record.get('name'),
self.BODY: self.record.get('body'),
self.PERMALINK: self.record['permalink'],
self.TASK_ID: self.record.get('task_id'),
self.PROJECT_ID: self.record['project_id'],
self.PROJECT_ID: self.record['project'],
self.FOREIGN_ID: self.record['id'],
self.TYPE: self.record['type'],
self.CREATED_BY_ID: self.record['created_by_id'],
}

if self.record['type'] == 'subtask':
" Store the parent task ID for subtasks "
record['actaskid'] = self.record['permalink'].split('/')[6]

due_on = self.record.get('due_on')
if isinstance(due_on, dict):
record['due'] = self.parse_date(due_on['mysql'])
elif due_on is not None:
record['due'] = due_on

if isinstance(self.record.get('created_on'), basestring):
record[self.CREATED_ON] = self.parse_date(
self.record['created_on']
Expand All @@ -151,27 +184,31 @@ def get_project(self):
def get_default_description(self):
return self.build_default_description(
title=(
self.record['name']
if self.record['name']
else self.record['body']
self.record.get('name')
if self.record.get('name')
else self.record.get('body')
),
url=self.get_processed_url(self.record['permalink']),
number=self.record['id'],
cls=self.record['type'],
)


class ActiveCollab3Service(ActiveCollab2Service):
ISSUE_CLASS = ActiveCollab3Issue
CONFIG_PREFIX = 'activecollab3'
class ActiveCollabService(IssueService):
ISSUE_CLASS = ActiveCollabIssue
CONFIG_PREFIX = 'activecollab'

def __init__(self, *args, **kw):
super(ActiveCollab3Service, self).__init__(*args, **kw)
self.client = ActiveCollab3Client(
super(ActiveCollabService, self).__init__(*args, **kw)

self.url = self.config_get('url').rstrip('/')
self.key = self.config_get('key')
self.user_id = self.config_get('user_id')
self.projects = []
self.client = ActiveCollabClient(
self.url, self.key, self.user_id, self.projects
)

self.projects = []
data = self.client.call_api("/projects", self.key, self.url)
for item in data:
if item[u'is_favorite'] == 1:
Expand All @@ -180,9 +217,30 @@ def __init__(self, *args, **kw):
@classmethod
def validate_config(cls, config, target):
for k in (
'activecollab3.url', 'activecollab3.key', 'activecollab3.user_id'
'activecollab.url', 'activecollab.key', 'activecollab.user_id'
):
if not config.has_option(target, k):
die("[%s] has no '%s'" % (target, k))

IssueService.validate_config(config, target)


def issues(self):
# Loop through each project
start = time.time()
issue_generators = []
projects = self.projects
for project in projects:
for project_id, project_name in project.iteritems():
log.name(self.target).debug(" Getting tasks for #%d %s" % (project_id, project_name))
issue_generators.append(
self.client.get_issue_generator(
self.user_id, project_id, project_name
)
)

log.name(self.target).debug(
" Elapsed Time: %s" % (time.time() - start))

for record in itertools.chain(*issue_generators):
yield self.get_issue_for_record(record)
20 changes: 10 additions & 10 deletions tests/test_activecollab3.py → tests/test_activecollab.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,27 @@

import mock

from bugwarrior.services.activecollab3 import (
ActiveCollab3Client,
ActiveCollab3Service
from bugwarrior.services.activecollab import (
ActiveCollabClient,
ActiveCollabService
)

from .base import ServiceTest


class TestActiveCollab3Issue(ServiceTest):
class TestActiveCollabIssue(ServiceTest):
SERVICE_CONFIG = {
'activecollab3.url': 'hello',
'activecollab3.key': 'howdy',
'activecollab3.user_id': 'hola',
'activecollab3.projects': '1:one, 2:two'
'activecollab.url': 'hello',
'activecollab.key': 'howdy',
'activecollab.user_id': 'hola',
'activecollab.projects': '1:one, 2:two'
}

def setUp(self):
with mock.patch(
'bugwarrior.services.activecollab3.ActiveCollab3Client.call_api'
'bugwarrior.services.activecollab.ActiveCollabClient.call_api'
):
self.service = self.get_mock_service(ActiveCollab3Service)
self.service = self.get_mock_service(ActiveCollabService)

def test_to_taskwarrior(self):
arbitrary_due_on = (
Expand Down

0 comments on commit 833f7c5

Please sign in to comment.