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