1 diff --git a/bugwarrior/services/mantisbt.py b/bugwarrior/services/mantisbt.py
5 +++ b/bugwarrior/services/mantisbt.py
7 +from builtins import filter
12 +from jinja2 import Template
14 +from bugwarrior.config import asbool, aslist, die
15 +from bugwarrior.services import IssueService, Issue, ServiceClient
18 +log = logging.getLogger(__name__)
21 +class MantisbtClient(ServiceClient):
22 + def __init__(self, host, token):
24 + self.session = requests.Session()
25 + self.session.headers['Authorization'] = token
27 + def _api_url(self, path, **context):
28 + """ Build the full url to the API endpoint """
29 + baseurl = "https://{}/api/rest".format(self.host)
30 + return baseurl + path.format(**context)
33 + return self.json_response(self.session.get(self._api_url("/users/me")))
35 + def get_projects(self):
36 + return self._getter(self._api_url("/projects"), subkey="projects")
38 + def get_issues(self):
39 + url = self._api_url("/issues?page_size=50")
40 + return self._getter(url, page_size=50, subkey="issues")
42 + def get_assigned_issues(self):
43 + """ Returns all issues assigned to authenticated user.
45 + url = self._api_url("/issues?page_size=50&filter_id=assigned")
46 + return self._getter(url, page_size=50, subkey="issues")
48 + def get_monitored_issues(self):
49 + """ Returns all issues monitored by authenticated user.
51 + url = self._api_url("/issues?page_size=50&filter_id=monitored")
52 + return self._getter(url, page_size=50, subkey="issues")
54 + def get_reported_issues(self):
55 + """ Returns all issues reported by authenticated user.
57 + url = self._api_url("/issues?page_size=50&filter_id=reported")
58 + return self._getter(url, page_size=50, subkey="issues")
60 + def _getter(self, url, page_size=None, subkey=None):
61 + """ Pagination utility. Obnoxious. """
64 + link = dict(next=url)
67 + while 'next' in link:
68 + if page_size is not None:
69 + response = self.session.get(link['next'] + "&page=" + str(page_number))
71 + response = self.session.get(link['next'])
73 + json_res = self.json_response(response)
75 + if subkey is not None:
76 + json_res = json_res[subkey]
80 + if page_size is not None and len(json_res) == page_size:
87 +class MantisbtIssue(Issue):
88 + TITLE = 'mantisbttitle'
89 + BODY = 'mantisbtbody'
90 + CREATED_AT = 'mantisbtcreatedon'
91 + UPDATED_AT = 'mantisbtupdatedat'
92 + CLOSED_AT = 'mantisbtclosedon'
94 + PROJECT = 'mantisbtproject'
95 + NUMBER = 'mantisbtnumber'
96 + USER = 'mantisbtuser'
97 + CATEGORY = 'mantisbtcategory'
98 + STATE = 'mantisbtstate'
103 + 'label': 'Mantisbt Title',
107 + 'label': 'Mantisbt Body',
111 + 'label': 'Mantisbt Created',
115 + 'label': 'Mantisbt Updated',
119 + 'label': 'Mantisbt Closed',
123 + 'label': 'Mantisbt Project',
127 + 'label': 'Mantisbt URL',
131 + 'label': 'Mantisbt Issue #',
135 + 'label': 'Mantisbt User',
139 + 'label': 'Mantisbt Category',
143 + 'label': 'Mantisbt State',
146 + UNIQUE_KEY = (URL, NUMBER, )
148 + def _normalize_tag(self, label):
149 + return re.sub(r'[^a-zA-Z0-9]', '_', label)
151 + def to_taskwarrior(self):
152 + body = self.record.get('description')
154 + body = body.replace('\r\n', '\n')
156 + created = self.parse_date(self.record.get('created_at'))
157 + updated = self.parse_date(self.record.get('updated_at'))
159 + if self.record["status"]["name"] in ["closed", "resolved"]:
160 + for history in self.record.get("history", []):
161 + if history.get("field", {}).get("name", "") == "status"\
162 + and history.get("new_value", {}).get("name", "") in ["closed", "resolved"]:
163 + closed_date = history["created_at"]
164 + closed = self.parse_date(closed_date)
167 + 'project': self.record['project']['name'],
168 + 'priority': self.origin['default_priority'],
169 + 'annotations': self.get_annotations(),
170 + 'tags': self.get_tags(),
174 + self.TITLE: self.record.get('summary'),
176 + self.CREATED_AT: created,
177 + self.UPDATED_AT: updated,
178 + self.CLOSED_AT: closed,
179 + self.URL: self.get_url(),
180 + self.PROJECT: self.record['project'].get('name'),
181 + self.NUMBER: self.record['id'],
182 + self.USER: self.record['reporter'].get('name'),
183 + self.CATEGORY: self.record['category'].get('name'),
184 + self.STATE: self.record['status'].get('label'),
188 + return "https://{}view.php?id={}".format(self.extra['host'], self.record["id"])
190 + def get_annotations(self):
193 + context = self.record.copy()
194 + annotation_template = Template(self.origin['annotation_template'])
196 + for annotation_dict in self.record.get('notes', []):
198 + 'text': annotation_dict['text'],
199 + 'date': annotation_dict['created_at'],
200 + 'author': annotation_dict['reporter'].get('name', 'unknown'),
201 + 'view': annotation_dict['view_state']['label'],
203 + annotations.append(
204 + annotation_template.render(context)
208 + def get_tags(self):
211 + context = self.record.copy()
212 + tag_template = Template(self.origin['tag_template'])
214 + for tag_dict in self.record.get('tags', []):
216 + 'tag': self._normalize_tag(tag_dict['name'])
219 + tag_template.render(context)
224 + def get_default_description(self):
225 + return self.build_default_description(
226 + title=self.record['summary'],
227 + url=self.get_processed_url(self.get_url()),
228 + number=self.record['id'],
232 +class MantisbtService(IssueService):
233 + ISSUE_CLASS = MantisbtIssue
234 + CONFIG_PREFIX = 'mantisbt'
236 + def __init__(self, *args, **kw):
237 + super(MantisbtService, self).__init__(*args, **kw)
239 + self.host = self.config.get('host', 'www.mantisbt.org/bugs/')
241 + token = self.get_password('token')
243 + self.client = MantisbtClient(self.host, token)
246 + self.exclude_projects = self.config.get('exclude_projects', [], aslist)
247 + self.include_projects = self.config.get('include_projects', [], aslist)
249 + self.involved_issues = self.config.get(
250 + 'involved_issues', default=True, to_type=asbool
252 + self.assigned_issues = self.config.get(
253 + 'assigned_issues', default=False, to_type=asbool
255 + self.monitored_issues = self.config.get(
256 + 'monitored_issues', default=False, to_type=asbool
258 + self.reported_issues = self.config.get(
259 + 'reported_issues', default=False, to_type=asbool
261 + self.tag_template = self.config.get(
262 + 'tag_template', default='{{tag}}', to_type=six.text_type
264 + self.annotation_template = self.config.get(
265 + 'annotation_template', default='{{date}} {{author}} ({{view}}): {{text}}', to_type=six.text_type
268 + def get_service_metadata(self):
270 + 'tag_template': self.tag_template,
271 + 'annotation_template': self.annotation_template,
274 + def filter_involved_issues(self, issue):
276 + user = self.client.get_user()
278 + if issue["reporter"]["id"] != uid and \
279 + issue.get("handler", {}).get("id") != uid and \
280 + all([ x.get("user", {}).get("id") != uid for x in issue.get("history", [])]) and \
281 + all([ x.get("user", {}).get("id") != uid for x in issue.get("monitors", [])]):
283 + return self.filter_project_name(issue["project"]["name"])
285 + def filter_issues(self, issue):
287 + return self.filter_project_name(issue["project"]["name"])
289 + def filter_project_name(self, name):
290 + if self.exclude_projects:
291 + if name in self.exclude_projects:
294 + if self.include_projects:
295 + if name in self.include_projects:
303 + def get_keyring_service(service_config):
304 + host = service_config.get('host', 'www.mantisbt.org/bugs/')
305 + username = service_config.get('username', default='nousername')
306 + return "mantisbt://{username}@{host}".format(username=username,
310 + def to_issue_dict(issues):
311 + return { i['id']: i for i in issues }
313 + def get_owner(self, issue):
314 + return issue.get("handler", {}).get("name")
316 + def get_author(self, issue):
317 + return issue.get("reporter", {}).get("name")
321 + is_limited = self.assigned_issues or self.monitored_issues or self.reported_issues
323 + if self.assigned_issues:
325 + filter(self.filter_issues, self.to_issue_dict(self.client.get_assigned_issues()).items())
327 + if self.monitored_issues:
329 + filter(self.filter_issues, self.to_issue_dict(self.client.get_monitored_issues()).items())
331 + if self.reported_issues:
333 + filter(self.filter_issues, self.to_issue_dict(self.client.get_reported_issues()).items())
337 + all_issues = self.to_issue_dict(self.client.get_issues())
338 + if self.involved_issues:
340 + filter(self.filter_involved_issues, all_issues.items())
344 + filter(self.filter_issues, all_issues.items())
347 + log.debug(" Found %i issues.", len(issues))
349 + issues = list(filter(self.include, issues.values()))
351 + issues = list(issues.values())
352 + log.debug(" Pruned down to %i issues.", len(issues))
354 + for issue in issues:
355 + issue_obj = self.get_issue_for_record(issue)
359 + issue_obj.update_extra(extra)
363 + def validate_config(cls, service_config, target):
364 + if 'token' not in service_config:
365 + die("[%s] has no 'mantisbt.token'" % target)
367 + super(MantisbtService, cls).validate_config(service_config, target)
368 diff --git a/setup.py b/setup.py
369 index d6d957a..665e36e 100644
372 @@ -80,6 +80,7 @@ setup(name='bugwarrior',
373 activecollab2=bugwarrior.services.activecollab2:ActiveCollab2Service
374 activecollab=bugwarrior.services.activecollab:ActiveCollabService
375 jira=bugwarrior.services.jira:JiraService
376 + mantisbt=bugwarrior.services.mantisbt:MantisbtService
377 megaplan=bugwarrior.services.megaplan:MegaplanService
378 phabricator=bugwarrior.services.phab:PhabricatorService
379 versionone=bugwarrior.services.versionone:VersionOneService