375 lines
17 KiB
Python
375 lines
17 KiB
Python
# -*- coding: utf-8 -*-
|
|
# Copyright (C) 2014-2017 Andrey Antukh <niwi@niwi.nz>
|
|
# Copyright (C) 2014-2017 Jesús Espino <jespinog@gmail.com>
|
|
# Copyright (C) 2014-2017 David Barragán <bameda@dbarragan.com>
|
|
# Copyright (C) 2014-2017 Alejandro Alonso <alejandro.alonso@kaleidos.net>
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as
|
|
# published by the Free Software Foundation, either version 3 of the
|
|
# License, or (at your option) any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
import datetime
|
|
from collections import OrderedDict
|
|
|
|
from django.template.defaultfilters import slugify
|
|
from taiga.projects.references.models import recalc_reference_counter
|
|
from taiga.projects.models import Project, ProjectTemplate, Points
|
|
from taiga.projects.userstories.models import UserStory, RolePoints
|
|
from taiga.projects.tasks.models import Task
|
|
from taiga.projects.milestones.models import Milestone
|
|
from taiga.projects.epics.models import Epic, RelatedUserStory
|
|
from taiga.projects.history.services import take_snapshot
|
|
from taiga.timeline.rebuilder import rebuild_timeline
|
|
from taiga.timeline.models import Timeline
|
|
from .common import JiraImporterCommon
|
|
from taiga.importers import services as import_service
|
|
|
|
|
|
class JiraAgileImporter(JiraImporterCommon):
|
|
def list_projects(self):
|
|
return [{"id": board['id'],
|
|
"name": board['name'],
|
|
"description": "",
|
|
"is_private": True,
|
|
"importer_type": "agile"} for board in self._client.get_agile('/board')['values']]
|
|
|
|
def list_issue_types(self, project_id):
|
|
board_project = self._client.get_agile("/board/{}/project".format(project_id))['values'][0]
|
|
statuses = self._client.get("/project/{}/statuses".format(board_project['id']))
|
|
return statuses
|
|
|
|
def import_project(self, project_id, options=None):
|
|
self.resolve_user_bindings(options)
|
|
project = self._import_project_data(project_id, options)
|
|
self._import_epics_data(project_id, project, options)
|
|
self._import_user_stories_data(project_id, project, options)
|
|
self._cleanup(project, options)
|
|
Timeline.objects.filter(project=project).delete()
|
|
rebuild_timeline(None, None, project.id)
|
|
recalc_reference_counter(project)
|
|
return project
|
|
|
|
def _import_project_data(self, project_id, options):
|
|
project = self._client.get_agile("/board/{}".format(project_id))
|
|
project_config = self._client.get_agile("/board/{}/configuration".format(project_id))
|
|
if project['type'] == "scrum":
|
|
project_template = ProjectTemplate.objects.get(slug="scrum")
|
|
options['type'] = "scrum"
|
|
elif project['type'] == "kanban":
|
|
project_template = ProjectTemplate.objects.get(slug="kanban")
|
|
options['type'] = "kanban"
|
|
|
|
project_template.is_epics_activated = True
|
|
project_template.epic_statuses = OrderedDict()
|
|
project_template.us_statuses = OrderedDict()
|
|
project_template.task_statuses = OrderedDict()
|
|
project_template.issue_statuses = OrderedDict()
|
|
|
|
counter = 0
|
|
for column in project_config['columnConfig']['columns']:
|
|
column_slug = slugify(column['name'])
|
|
project_template.epic_statuses[column_slug] = {
|
|
"name": column['name'],
|
|
"slug": column_slug,
|
|
"is_closed": False,
|
|
"is_archived": False,
|
|
"color": "#999999",
|
|
"wip_limit": None,
|
|
"order": counter,
|
|
}
|
|
project_template.us_statuses[column_slug] = {
|
|
"name": column['name'],
|
|
"slug": column_slug,
|
|
"is_closed": False,
|
|
"is_archived": False,
|
|
"color": "#999999",
|
|
"wip_limit": None,
|
|
"order": counter,
|
|
}
|
|
project_template.task_statuses[column_slug] = {
|
|
"name": column['name'],
|
|
"slug": column_slug,
|
|
"is_closed": False,
|
|
"is_archived": False,
|
|
"color": "#999999",
|
|
"wip_limit": None,
|
|
"order": counter,
|
|
}
|
|
project_template.issue_statuses[column_slug] = {
|
|
"name": column['name'],
|
|
"slug": column_slug,
|
|
"is_closed": False,
|
|
"is_archived": False,
|
|
"color": "#999999",
|
|
"wip_limit": None,
|
|
"order": counter,
|
|
}
|
|
counter += 1
|
|
|
|
project_template.epic_statuses = list(project_template.epic_statuses.values())
|
|
project_template.us_statuses = list(project_template.us_statuses.values())
|
|
project_template.task_statuses = list(project_template.task_statuses.values())
|
|
project_template.issue_statuses = list(project_template.issue_statuses.values())
|
|
project_template.default_options["epic_status"] = project_template.epic_statuses[0]['name']
|
|
project_template.default_options["us_status"] = project_template.us_statuses[0]['name']
|
|
project_template.default_options["task_status"] = project_template.task_statuses[0]['name']
|
|
project_template.default_options["issue_status"] = project_template.issue_statuses[0]['name']
|
|
|
|
project_template.points = [{
|
|
"value": None,
|
|
"name": "?",
|
|
"order": 0,
|
|
}]
|
|
|
|
main_permissions = project_template.roles[0]['permissions']
|
|
project_template.roles = [{
|
|
"name": "Main",
|
|
"slug": "main",
|
|
"computable": True,
|
|
"permissions": main_permissions,
|
|
"order": 70,
|
|
}]
|
|
|
|
project = Project.objects.create(
|
|
name=options.get('name', None) or project['name'],
|
|
description=options.get('description', None) or project.get('description', ''),
|
|
owner=self._user,
|
|
creation_template=project_template,
|
|
is_private=options.get('is_private', False),
|
|
)
|
|
|
|
self._create_custom_fields(project)
|
|
import_service.create_memberships(options.get('users_bindings', {}), project, self._user, "main")
|
|
|
|
if project_template.slug == "scrum":
|
|
for sprint in self._client.get_agile("/board/{}/sprint".format(project_id))['values']:
|
|
start_datetime = sprint.get('startDate', None)
|
|
end_datetime = sprint.get('startDate', None)
|
|
start_date = datetime.date.today()
|
|
if start_datetime:
|
|
start_date = start_datetime[:10]
|
|
end_date = datetime.date.today()
|
|
if end_datetime:
|
|
end_date = end_datetime[:10]
|
|
|
|
milestone = Milestone.objects.create(
|
|
name=sprint['name'],
|
|
slug=slugify(sprint['name']),
|
|
owner=self._user,
|
|
project=project,
|
|
estimated_start=start_date,
|
|
estimated_finish=end_date,
|
|
)
|
|
Milestone.objects.filter(id=milestone.id).update(
|
|
created_date=start_datetime or datetime.datetime.now(),
|
|
modified_date=start_datetime or datetime.datetime.now(),
|
|
)
|
|
return project
|
|
|
|
def _import_user_stories_data(self, project_id, project, options):
|
|
users_bindings = options.get('users_bindings', {})
|
|
project_conf = self._client.get_agile("/board/{}/configuration".format(project_id))
|
|
if options['type'] == "scrum":
|
|
estimation_field = project_conf['estimation']['field']['fieldId']
|
|
|
|
counter = 0
|
|
offset = 0
|
|
while True:
|
|
issues = self._client.get_agile("/board/{}/issue".format(project_id), {
|
|
"startAt": offset,
|
|
"expand": "changelog",
|
|
})
|
|
offset += issues['maxResults']
|
|
|
|
for issue in issues['issues']:
|
|
issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key']))
|
|
assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None)
|
|
owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user)
|
|
|
|
external_reference = None
|
|
if options.get('keep_external_reference', False):
|
|
external_reference = ["jira", self._client.get_issue_url(issue['key'])]
|
|
|
|
try:
|
|
milestone = project.milestones.get(name=(issue['fields'].get('sprint', {}) or {}).get('name', ''))
|
|
except Milestone.DoesNotExist:
|
|
milestone = None
|
|
|
|
us = UserStory.objects.create(
|
|
project=project,
|
|
owner=owner,
|
|
assigned_to=assigned_to,
|
|
status=project.us_statuses.get(slug=slugify(issue['fields']['status']['name'])),
|
|
kanban_order=counter,
|
|
sprint_order=counter,
|
|
backlog_order=counter,
|
|
subject=issue['fields']['summary'],
|
|
description=issue['fields']['description'] or '',
|
|
tags=issue['fields']['labels'],
|
|
external_reference=external_reference,
|
|
milestone=milestone,
|
|
)
|
|
|
|
try:
|
|
epic = project.epics.get(ref=int(issue['fields'].get("epic", {}).get("key", "FAKE-0").split("-")[1]))
|
|
RelatedUserStory.objects.create(
|
|
user_story=us,
|
|
epic=epic,
|
|
order=1
|
|
)
|
|
except Epic.DoesNotExist:
|
|
pass
|
|
|
|
if options['type'] == "scrum":
|
|
estimation = None
|
|
if issue['fields'].get(estimation_field, None):
|
|
estimation = float(issue['fields'].get(estimation_field))
|
|
|
|
(points, _) = Points.objects.get_or_create(
|
|
project=project,
|
|
value=estimation,
|
|
defaults={
|
|
"name": str(estimation),
|
|
"order": estimation,
|
|
}
|
|
)
|
|
RolePoints.objects.filter(user_story=us, role__slug="main").update(points_id=points.id)
|
|
|
|
self._import_to_custom_fields(us, issue, options)
|
|
|
|
us.ref = issue['key'].split("-")[1]
|
|
UserStory.objects.filter(id=us.id).update(
|
|
ref=us.ref,
|
|
modified_date=issue['fields']['updated'],
|
|
created_date=issue['fields']['created']
|
|
)
|
|
take_snapshot(us, comment="", user=None, delete=False)
|
|
self._import_subtasks(project_id, project, us, issue, options)
|
|
self._import_comments(us, issue, options)
|
|
self._import_attachments(us, issue, options)
|
|
self._import_changelog(project, us, issue, options)
|
|
counter += 1
|
|
|
|
if len(issues['issues']) < issues['maxResults']:
|
|
break
|
|
|
|
def _import_subtasks(self, project_id, project, us, issue, options):
|
|
users_bindings = options.get('users_bindings', {})
|
|
|
|
if len(issue['fields']['subtasks']) == 0:
|
|
return
|
|
|
|
counter = 0
|
|
offset = 0
|
|
while True:
|
|
issues = self._client.get_agile("/board/{}/issue".format(project_id), {
|
|
"jql": "parent={}".format(issue['key']),
|
|
"startAt": offset,
|
|
"expand": "changelog",
|
|
})
|
|
offset += issues['maxResults']
|
|
|
|
for issue in issues['issues']:
|
|
issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key']))
|
|
assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None)
|
|
owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user)
|
|
|
|
external_reference = None
|
|
if options.get('keep_external_reference', False):
|
|
external_reference = ["jira", self._client.get_issue_url(issue['key'])]
|
|
|
|
task = Task.objects.create(
|
|
user_story=us,
|
|
project=project,
|
|
owner=owner,
|
|
assigned_to=assigned_to,
|
|
status=project.task_statuses.get(slug=slugify(issue['fields']['status']['name'])),
|
|
subject=issue['fields']['summary'],
|
|
description=issue['fields']['description'] or '',
|
|
tags=issue['fields']['labels'],
|
|
external_reference=external_reference,
|
|
milestone=us.milestone,
|
|
)
|
|
|
|
self._import_to_custom_fields(task, issue, options)
|
|
|
|
task.ref = issue['key'].split("-")[1]
|
|
Task.objects.filter(id=task.id).update(
|
|
ref=task.ref,
|
|
modified_date=issue['fields']['updated'],
|
|
created_date=issue['fields']['created']
|
|
)
|
|
take_snapshot(task, comment="", user=None, delete=False)
|
|
for subtask in issue['fields']['subtasks']:
|
|
print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key']))
|
|
self._import_comments(task, issue, options)
|
|
self._import_attachments(task, issue, options)
|
|
self._import_changelog(project, task, issue, options)
|
|
counter += 1
|
|
if len(issues['issues']) < issues['maxResults']:
|
|
break
|
|
|
|
def _import_epics_data(self, project_id, project, options):
|
|
users_bindings = options.get('users_bindings', {})
|
|
|
|
counter = 0
|
|
offset = 0
|
|
while True:
|
|
issues = self._client.get_agile("/board/{}/epic".format(project_id), {
|
|
"startAt": offset,
|
|
})
|
|
offset += issues['maxResults']
|
|
|
|
for epic in issues['values']:
|
|
issue = self._client.get_agile("/issue/{}".format(epic['key']))
|
|
issue['fields']['issuelinks'] += self._client.get("/issue/{}/remotelink".format(issue['key']))
|
|
assigned_to = users_bindings.get(issue['fields']['assignee']['key'] if issue['fields']['assignee'] else None, None)
|
|
owner = users_bindings.get(issue['fields']['creator']['key'] if issue['fields']['creator'] else None, self._user)
|
|
|
|
external_reference = None
|
|
if options.get('keep_external_reference', False):
|
|
external_reference = ["jira", self._client.get_issue_url(issue['key'])]
|
|
|
|
epic = Epic.objects.create(
|
|
project=project,
|
|
owner=owner,
|
|
assigned_to=assigned_to,
|
|
status=project.epic_statuses.get(slug=slugify(issue['fields']['status']['name'])),
|
|
subject=issue['fields']['summary'],
|
|
description=issue['fields']['description'] or '',
|
|
epics_order=counter,
|
|
tags=issue['fields']['labels'],
|
|
external_reference=external_reference,
|
|
)
|
|
|
|
self._import_to_custom_fields(epic, issue, options)
|
|
|
|
epic.ref = issue['key'].split("-")[1]
|
|
Epic.objects.filter(id=epic.id).update(
|
|
ref=epic.ref,
|
|
modified_date=issue['fields']['updated'],
|
|
created_date=issue['fields']['created']
|
|
)
|
|
|
|
take_snapshot(epic, comment="", user=None, delete=False)
|
|
for subtask in issue['fields']['subtasks']:
|
|
print("WARNING: Ignoring subtask {} because parent isn't a User Story".format(subtask['key']))
|
|
self._import_comments(epic, issue, options)
|
|
self._import_attachments(epic, issue, options)
|
|
issue_with_changelog = self._client.get("/issue/{}".format(issue['key']), {
|
|
"expand": "changelog"
|
|
})
|
|
self._import_changelog(project, epic, issue_with_changelog, options)
|
|
counter += 1
|
|
|
|
if len(issues['values']) < issues['maxResults']:
|
|
break
|