1from builtins import object 2import re 3 4import pypandoc 5from pyac.library import activeCollab 6from bugwarrior.services import IssueService, Issue 7from bugwarrior.config import die 8 9import logging 10log = logging.getLogger(__name__) 11 12 13class ActiveCollabClient(object): 14 def __init__(self, url, key, user_id): 15 self.url = url 16 self.key = key 17 self.user_id = int(user_id) 18 self.activecollabtivecollab = activeCollab( 19 key=key, 20 url=url, 21 user_id=user_id 22 ) 23 24 25class ActiveCollabIssue(Issue): 26 BODY = 'acbody' 27 NAME = 'acname' 28 PERMALINK = 'acpermalink' 29 TASK_ID = 'actaskid' 30 FOREIGN_ID = 'acid' 31 PROJECT_ID = 'acprojectid' 32 PROJECT_NAME = 'acprojectname' 33 TYPE = 'actype' 34 CREATED_ON = 'accreatedon' 35 CREATED_BY_NAME = 'accreatedbyname' 36 ESTIMATED_TIME = 'acestimatedtime' 37 TRACKED_TIME = 'actrackedtime' 38 MILESTONE = 'acmilestone' 39 LABEL = 'aclabel' 40 41 UDAS = { 42 BODY: { 43 'type': 'string', 44 'label': 'ActiveCollab Body' 45 }, 46 NAME: { 47 'type': 'string', 48 'label': 'ActiveCollab Name' 49 }, 50 PERMALINK: { 51 'type': 'string', 52 'label': 'ActiveCollab Permalink' 53 }, 54 TASK_ID: { 55 'type': 'numeric', 56 'label': 'ActiveCollab Task ID' 57 }, 58 FOREIGN_ID: { 59 'type': 'numeric', 60 'label': 'ActiveCollab ID', 61 }, 62 PROJECT_ID: { 63 'type': 'numeric', 64 'label': 'ActiveCollab Project ID' 65 }, 66 PROJECT_NAME: { 67 'type': 'string', 68 'label': 'ActiveCollab Project Name' 69 }, 70 TYPE: { 71 'type': 'string', 72 'label': 'ActiveCollab Task Type' 73 }, 74 CREATED_ON: { 75 'type': 'date', 76 'label': 'ActiveCollab Created On' 77 }, 78 CREATED_BY_NAME: { 79 'type': 'string', 80 'label': 'ActiveCollab Created By' 81 }, 82 ESTIMATED_TIME: { 83 'type': 'numeric', 84 'label': 'ActiveCollab Estimated Time' 85 }, 86 TRACKED_TIME: { 87 'type': 'numeric', 88 'label': 'ActiveCollab Tracked Time' 89 }, 90 MILESTONE: { 91 'type': 'string', 92 'label': 'ActiveCollab Milestone' 93 }, 94 LABEL: { 95 'type': 'string', 96 'label': 'ActiveCollab Label' 97 } 98 } 99 UNIQUE_KEY = (FOREIGN_ID, ) 100 101 def to_taskwarrior(self): 102 record = { 103 'project': re.sub(r'\W+', '-', self.record['project']).lower(), 104 'priority': self.get_priority(), 105 'annotations': self.extra.get('annotations', []), 106 self.NAME: self.record.get('name', ''), 107 self.BODY: pypandoc.convert(self.record.get('body'), 108 'md', format='html').rstrip(), 109 self.PERMALINK: self.record['permalink'], 110 self.TASK_ID: int(self.record.get('task_id')), 111 self.PROJECT_NAME: self.record['project'], 112 self.PROJECT_ID: int(self.record['project_id']), 113 self.FOREIGN_ID: int(self.record['id']), 114 self.TYPE: self.record.get('type', 'subtask').lower(), 115 self.CREATED_BY_NAME: self.record['created_by_name'], 116 self.MILESTONE: self.record['milestone'], 117 self.ESTIMATED_TIME: self.record.get('estimated_time', 0), 118 self.TRACKED_TIME: self.record.get('tracked_time', 0), 119 self.LABEL: self.record.get('label'), 120 } 121 122 if self.TYPE == 'subtask': 123 # Store the parent task ID for subtasks 124 record['actaskid'] = int(self.record['task_id']) 125 126 if isinstance(self.record.get('due_on'), dict): 127 record['due'] = self.parse_date( 128 self.record.get('due_on')['formatted_date'] 129 ) 130 131 if isinstance(self.record.get('created_on'), dict): 132 record[self.CREATED_ON] = self.parse_date( 133 self.record.get('created_on')['formatted_date'] 134 ) 135 return record 136 137 def get_annotations(self): 138 return self.extra.get('annotations', []) 139 140 def get_priority(self): 141 value = self.record.get('priority') 142 if value > 0: 143 return 'H' 144 elif value < 0: 145 return 'L' 146 else: 147 return 'M' 148 149 def get_default_description(self): 150 return self.build_default_description( 151 title=( 152 self.record.get('name') 153 if self.record.get('name') 154 else self.record.get('body') 155 ), 156 url=self.get_processed_url(self.record['permalink']), 157 number=self.record['id'], 158 cls=self.record.get('type', 'subtask').lower(), 159 ) 160 161 162class ActiveCollabService(IssueService): 163 ISSUE_CLASS = ActiveCollabIssue 164 CONFIG_PREFIX = 'activecollab' 165 166 def __init__(self, *args, **kw): 167 super(ActiveCollabService, self).__init__(*args, **kw) 168 169 self.url = self.config.get('url').rstrip('/') 170 self.key = self.config.get('key') 171 self.user_id = int(self.config.get('user_id')) 172 self.client = ActiveCollabClient( 173 self.url, self.key, self.user_id 174 ) 175 self.activecollab = activeCollab(url=self.url, key=self.key, 176 user_id=self.user_id) 177 178 @classmethod 179 def validate_config(cls, service_config, target): 180 for k in ('url', 'key', 'user_id'): 181 if k not in service_config: 182 die("[%s] has no 'activecollab.%s'" % (target, k)) 183 184 IssueService.validate_config(service_config, target) 185 186 def _comments(self, issue): 187 comments = self.activecollab.get_comments( 188 issue['project_id'], 189 issue['task_id'] 190 ) 191 comments_formatted = [] 192 if comments is not None: 193 for comment in comments: 194 comments_formatted.append( 195 dict(user=comment['created_by']['display_name'], 196 body=comment['body'])) 197 return comments_formatted 198 199 def get_owner(self, issue): 200 if issue['assignee_id']: 201 return issue['assignee_id'] 202 203 def annotations(self, issue, issue_obj): 204 if 'type' not in issue: 205 # Subtask 206 return [] 207 comments = self._comments(issue) 208 if comments is None: 209 return [] 210 211 return self.build_annotations( 212 (( 213 c['user'], 214 pypandoc.convert(c['body'], 'md', format='html').rstrip() 215 ) for c in comments), 216 issue_obj.get_processed_url(issue_obj.record['permalink']), 217 ) 218 219 def issues(self): 220 data = self.activecollab.get_my_tasks() 221 label_data = self.activecollab.get_assignment_labels() 222 labels = dict() 223 for item in label_data: 224 labels[item['id']] = re.sub(r'\W+', '_', item['name']) 225 task_count = 0 226 issues = [] 227 for key, record in data.items(): 228 for task_id, task in record['assignments'].items(): 229 task_count = task_count + 1 230 # Add tasks 231 if task['assignee_id'] == self.user_id: 232 task['label'] = labels.get(task['label_id']) 233 issues.append(task) 234 if 'subtasks' in task: 235 for subtask_id, subtask in task['subtasks'].items(): 236 # Add subtasks 237 task_count = task_count + 1 238 if subtask['assignee_id'] is self.user_id: 239 # Add some data from the parent task 240 subtask['label'] = labels.get(subtask['label_id']) 241 subtask['project_id'] = task['project_id'] 242 subtask['project'] = task['project'] 243 subtask['task_id'] = task['task_id'] 244 subtask['milestone'] = task['milestone'] 245 issues.append(subtask) 246 log.debug(" Found %i total", task_count) 247 log.debug(" Pruned down to %i", len(issues)) 248 for issue in issues: 249 issue_obj = self.get_issue_for_record(issue) 250 extra = { 251 'annotations': self.annotations(issue, issue_obj) 252 } 253 issue_obj.update_extra(extra) 254 yield issue_obj 255