| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| from collections import defaultdict |
| from datetime import datetime |
| |
| from pylons import tmpl_context as c |
| from pylons import app_globals as g |
| from ming.orm import session, ThreadLocalORMSession |
| |
| from allura import model as M |
| from allura.lib import helpers as h |
| |
| from forgetracker.tracker_main import ForgeTrackerApp |
| from forgetracker import model as TM |
| from ..base import ToolImporter |
| from . import GoogleCodeProjectExtractor |
| |
| |
| class GoogleCodeTrackerImporter(ToolImporter): |
| source = 'Google Code' |
| target_app = ForgeTrackerApp |
| controller = None |
| tool_label = 'Issues' |
| |
| field_types = defaultdict(lambda: 'string', |
| milestone='milestone', |
| priority='select', |
| type='select', |
| ) |
| |
| def __init__(self, *args, **kwargs): |
| super(GoogleCodeTrackerImporter, self).__init__(*args, **kwargs) |
| self.open_milestones = set() |
| self.custom_fields = {} |
| self.max_ticket_num = 0 |
| |
| def import_tool(self, project, user, project_name, mount_point=None, |
| mount_label=None, **kw): |
| app = project.install_app('tickets', mount_point, mount_label, |
| EnableVoting=True, |
| open_status_names='New Accepted Started', |
| closed_status_names='Fixed Verified Invalid Duplicate WontFix Done', |
| ) |
| ThreadLocalORMSession.flush_all() |
| try: |
| M.session.artifact_orm_session._get().skip_mod_date = True |
| with h.push_config(c, user=M.User.anonymous(), app=app): |
| for ticket_num, issue in GoogleCodeProjectExtractor.iter_issues(project_name): |
| self.max_ticket_num = max(ticket_num, self.max_ticket_num) |
| ticket = TM.Ticket( |
| app_config_id=app.config._id, |
| custom_fields=dict(), |
| ticket_num=ticket_num) |
| self.process_fields(ticket, issue) |
| self.process_labels(ticket, issue) |
| self.process_comments(ticket, issue) |
| session(ticket).flush(ticket) |
| session(ticket).expunge(ticket) |
| app.globals.custom_fields = self.postprocess_custom_fields() |
| app.globals.last_ticket_num = self.max_ticket_num |
| ThreadLocalORMSession.flush_all() |
| g.post_event('project_updated') |
| app.globals.invalidate_bin_counts() |
| return app |
| finally: |
| M.session.artifact_orm_session._get().skip_mod_date = False |
| |
| def custom_field(self, name): |
| if name not in self.custom_fields: |
| self.custom_fields[name] = { |
| 'type': self.field_types[name.lower()], |
| 'label': name, |
| 'name': u'_%s' % name.lower(), |
| 'options': set(), |
| } |
| return self.custom_fields[name] |
| |
| def process_fields(self, ticket, issue): |
| ticket.summary = issue.get_issue_summary() |
| ticket.status = issue.get_issue_status() |
| ticket.created_date = datetime.strptime(issue.get_issue_created_date(), '%c') |
| ticket.mod_date = datetime.strptime(issue.get_issue_mod_date(), '%c') |
| ticket.votes_up = issue.get_issue_stars() |
| owner = issue.get_issue_owner() |
| if owner: |
| owner_line = '*Originally owned by:* {owner}\n'.format(owner=owner) |
| else: |
| owner_line = '' |
| ticket.description = ( |
| u'*Originally created by:* {creator}\n' |
| u'{owner}' |
| u'\n' |
| u'{body}').format( |
| creator=issue.get_issue_creator(), |
| owner=owner_line, |
| body=h.plain2markdown(issue.get_issue_description(), preserve_multiple_spaces=True, has_html_entities=True), |
| ) |
| ticket.add_multiple_attachments(issue.get_issue_attachments()) |
| |
| def process_labels(self, ticket, issue): |
| labels = set() |
| custom_fields = defaultdict(set) |
| for label in issue.get_issue_labels(): |
| if u'-' in label: |
| name, value = label.split(u'-', 1) |
| cf = self.custom_field(name) |
| cf['options'].add(value) |
| custom_fields[cf['name']].add(value) |
| if cf['name'] == '_milestone' and ticket.status in c.app.globals.open_status_names: |
| self.open_milestones.add(value) |
| else: |
| labels.add(label) |
| ticket.labels = list(labels) |
| ticket.custom_fields = {n: u', '.join(sorted(v)) for n,v in custom_fields.iteritems()} |
| |
| def process_comments(self, ticket, issue): |
| for comment in issue.iter_comments(): |
| p = ticket.discussion_thread.add_post( |
| text = comment.annotated_text, |
| ignore_security = True, |
| timestamp = datetime.strptime(comment.created_date, '%c'), |
| ) |
| p.add_multiple_attachments(comment.attachments) |
| |
| def postprocess_custom_fields(self): |
| custom_fields = [] |
| for name, field in self.custom_fields.iteritems(): |
| if field['name'] == '_milestone': |
| field['milestones'] = [{ |
| 'name': milestone, |
| 'due_date': None, |
| 'complete': milestone not in self.open_milestones, |
| } for milestone in sorted(field['options'])] |
| field['options'] = '' |
| elif field['type'] == 'select': |
| field['options'] = ' '.join(field['options']) |
| else: |
| field['options'] = '' |
| custom_fields.append(field) |
| return custom_fields |