blob: 96c352f7cba324f402b99ae60e50d2634e9ce4e5 [file] [log] [blame]
# 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.
import os
import json
import dateutil.parser
from pylons import tmpl_context as c
from pylons import app_globals as g
from ming.orm import session, ThreadLocalORMSession
from tg import (
expose,
flash,
redirect,
)
from tg.decorators import (
with_trailing_slash,
without_trailing_slash,
)
from allura.lib import helpers as h
from allura.lib.plugin import ImportIdConverter
from allura.lib.decorators import require_post
from allura.lib import validators as v
from allura import model as M
from forgetracker import model as TM
from forgeimporters.base import (
ToolImporter,
ToolImportForm,
ToolImportController,
File,
get_importer_upload_path,
save_importer_upload,
)
class ForgeTrackerImportForm(ToolImportForm):
tickets_json = v.JsonFile(not_empty=True)
class ForgeTrackerImportController(ToolImportController):
import_form = ForgeTrackerImportForm
@with_trailing_slash
@expose('jinja:forgeimporters.forge:templates/tracker/index.html')
def index(self, **kw):
return dict(importer=self.importer,
target_app=self.target_app)
@without_trailing_slash
@expose()
@require_post()
def create(self, tickets_json, mount_point, mount_label, **kw):
if self.importer.enforce_limit(c.project):
save_importer_upload(
c.project, 'tickets.json', json.dumps(tickets_json))
self.importer.post(
mount_point=mount_point,
mount_label=mount_label,
)
flash('Ticket import has begun. Your new tracker will be available '
'when the import is complete.')
redirect(c.project.url() + 'admin/')
else:
flash(
'There are too many imports pending at this time. Please wait and try again.', 'error')
redirect(c.project.url() + 'admin/')
class ForgeTrackerImporter(ToolImporter):
source = 'Allura'
target_app_ep_names = 'tickets'
controller = ForgeTrackerImportController
tool_label = 'Tickets'
def __init__(self, *args, **kwargs):
super(ForgeTrackerImporter, self).__init__(*args, **kwargs)
self.max_ticket_num = 0
def _load_json(self, project):
upload_path = get_importer_upload_path(project)
full_path = os.path.join(upload_path, 'tickets.json')
with open(full_path) as fp:
return json.load(fp)
def import_tool(self, project, user, mount_point=None,
mount_label=None, **kw):
import_id_converter = ImportIdConverter.get()
tracker_json = self._load_json(project)
tracker_json['tracker_config']['options'].pop('ordinal', None)
tracker_json['tracker_config']['options'].pop('mount_point', None)
tracker_json['tracker_config']['options'].pop('mount_label', None)
tracker_json['tracker_config']['options'].pop('import_id', None)
app = project.install_app('tickets', mount_point, mount_label,
import_id={
'source': self.source,
'app_config_id': tracker_json['tracker_config']['_id'],
},
open_status_names=tracker_json[
'open_status_names'],
closed_status_names=tracker_json[
'closed_status_names'],
**tracker_json['tracker_config']['options']
)
ThreadLocalORMSession.flush_all()
try:
M.session.artifact_orm_session._get().skip_mod_date = True
for ticket_json in tracker_json['tickets']:
reporter = self.get_user(ticket_json['reported_by'])
owner = self.get_user(ticket_json['assigned_to'])
with h.push_config(c, user=reporter, app=app):
self.max_ticket_num = max(
ticket_json['ticket_num'], self.max_ticket_num)
ticket = TM.Ticket(
app_config_id=app.config._id,
import_id=import_id_converter.expand(
ticket_json['ticket_num'], app),
description=self.annotate(
self.annotate(
ticket_json['description'],
owner, ticket_json[
'assigned_to'], label=' owned'),
reporter, ticket_json[
'reported_by'], label=' created'),
created_date=dateutil.parser.parse(
ticket_json['created_date']),
mod_date=dateutil.parser.parse(
ticket_json['mod_date']),
ticket_num=ticket_json['ticket_num'],
summary=ticket_json['summary'],
custom_fields=ticket_json['custom_fields'],
status=ticket_json['status'],
labels=ticket_json['labels'],
votes_down=ticket_json['votes_down'],
votes_up=ticket_json['votes_up'],
votes=ticket_json['votes_up'] -
ticket_json['votes_down'],
assigned_to_id=owner._id,
)
# trigger the private property
ticket.private = ticket_json['private']
self.process_comments(
ticket, ticket_json['discussion_thread']['posts'])
session(ticket).flush(ticket)
session(ticket).expunge(ticket)
app.globals.custom_fields = tracker_json['custom_fields']
self.process_bins(app, tracker_json['saved_bins'])
app.globals.last_ticket_num = self.max_ticket_num
M.AuditLog.log(
'import tool %s from exported Allura JSON' % (
app.config.options.mount_point,
),
project=project,
user=user,
url=app.url,
)
g.post_event('project_updated')
app.globals.invalidate_bin_counts()
ThreadLocalORMSession.flush_all()
return app
except Exception:
h.make_app_admin_only(app)
raise
finally:
M.session.artifact_orm_session._get().skip_mod_date = False
def get_user(self, username):
if username is None:
return M.User.anonymous()
user = M.User.by_username(username)
if not user:
user = M.User.anonymous()
return user
def annotate(self, text, user, username, label=''):
if username and user.is_anonymous() and username != 'nobody':
return '*Originally%s by:* %s\n\n%s' % (label, username, text)
return text
def process_comments(self, ticket, comments):
for comment_json in comments:
user = self.get_user(comment_json['author'])
with h.push_config(c, user=user):
p = ticket.discussion_thread.add_post(
text=self.annotate(
comment_json[
'text'], user, comment_json['author']),
ignore_security=True,
timestamp=dateutil.parser.parse(
comment_json['timestamp']),
)
p.add_multiple_attachments([File(a['url'])
for a in comment_json['attachments']])
def process_bins(self, app, bins):
TM.Bin.query.remove({'app_config_id': app.config._id})
for bin_json in bins:
bin_json.pop('_id', None)
TM.Bin(app_config_id=app.config._id, **bin_json)