@@ 1,248 1,661 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
-import trello2redmine_config as cfg
-
-import sys
import json
+import os
+import re
+import sys
+
import requests
+
+import trello2redmine_config as cfg
+
try:
from urlparse import urlparse
except ImportError: # python 3
from urllib.parse import urlparse
-if len(sys.argv) > 3 or (len(sys.argv) == 2 and sys.argv[1] == '-h'):
- print('Usage: {0} [<options>])'.format(sys.argv[0]))
- print('See %s for configuration.' % cfg.__file__)
- print('Options:')
- print(' -h Displays this information.')
- print(' -c Commits the import. Otherwise does a dry run (prints resulting JSON to screen instead of sending it to Redmine).')
- sys.exit(0)
+if len(sys.argv) > 3 or (len(sys.argv) == 2 and sys.argv[1] == "-h"):
+ print("Usage: {0} [<options>])".format(sys.argv[0]))
+ print("See %s for configuration." % cfg.__file__)
+ print("Options:")
+ print(" -h Displays this information.")
+ print(
+ " -c Commits the import. Otherwise does a dry run (prints resulting JSON to screen instead of sending it to Redmine)."
+ )
+ sys.exit(0)
# If a dry run, JSON is printed instead of submitted to Redmine.
-#dry_run = len(sys.argv) < 2 or sys.argv[2] != '-c'
-dry_run = False
+dry_run = len(sys.argv) == 2 and sys.argv[1] == "-c"
+dry_run = True
if dry_run:
- print('Making a dry run! Re-run with -c to commit the import into Redmine.')
+ print("Making a dry run! Re-run with -c to commit the import into Redmine.")
else:
- print('Making a commit!')
+ print("Making a commit!")
url = urlparse(cfg.trello_board_link)
-if not url.netloc.endswith('trello.com'):
- print('URL does not seem to be a Trello board link. Are you sure this is correct?')
- sys.exit(1)
+if not url.netloc.endswith("trello.com"):
+ print("URL does not seem to be a Trello board link. Are you sure this is correct?")
+ sys.exit(1)
# ============================================================================
# Trello JSON processing starts here.
# ============================================================================
-print('Downloading board JSON from Trello...')
+print("Downloading board JSON from Trello...")
sys.stdout.flush()
board = json.loads(open(sys.argv[1]).read())
-#board = requests.get(cfg.trello_board_link + '.json').json()
+# board = requests.get(cfg.trello_board_link + '.json').json()
-print('Processing board JSON...')
+print("Processing board JSON...")
sys.stdout.flush()
+no_attachment_list = []
+list_status_dict = {}
orig_lists_dict = {}
lists_dict = {}
for l in board["lists"]:
- name = l["name"]
- if name in cfg.list_map:
- name = cfg.list_map[name]
- lists_dict[l["id"]] = name
- orig_lists_dict[l["id"]] = l["name"]
-
+ name = l["name"]
+ if name in cfg.list_map:
+ name = cfg.list_map[name]
+ lists_dict[l["id"]] = name
+ orig_lists_dict[l["id"]] = l["name"]
+ list_status_dict[l["id"]] = l["closed"]
+
+ if l["name"] in cfg.no_file_import:
+ no_attachment_list.append(l["id"])
+
checklists_dict = {}
for c in board["checklists"]:
- checklists_dict[c["id"]] = c["checkItems"]
+ checklists_dict[c["id"]] = c["checkItems"]
members_dict = {}
for m in board["members"]:
- name = m["fullName"]
- if name in cfg.username_map:
- name = cfg.username_map[name]
- members_dict[m["id"]] = name
+ name = m["fullName"]
+ if name in cfg.username_map:
+ name = cfg.username_map[name]
+ members_dict[m["id"]] = name
labels_dict = {}
for l in board["labels"]:
- label = l["name"]
- if label in cfg.label_map:
- label = cfg.label_map[label]
- labels_dict[l["id"]] = label
+ label = l["name"]
+ if label in cfg.label_map:
+ label = cfg.label_map[label]
+ labels_dict[l["id"]] = label
comments_dict = {}
for a in board["actions"]:
- if a["type"] == "commentCard":
- card_id = a["data"]["card"]["id"]
- author_id = a["idMemberCreator"]
- comment_list = comments_dict[card_id] if card_id in comments_dict else []
- comment_list.append({
- "author": members_dict[author_id] if author_id in members_dict else '',
- "text": a["data"]["text"],
- })
- comments_dict[card_id] = comment_list
-
-#print('Members:\n' + str(members_dict))
-#print('Lists:\n' + str(lists_dict))
-#print('Checklists:\n' + str(checklists_dict))
-#print('Comments:\n' + str(comments_dict))
+ if a["type"] == "commentCard":
+ card_id = a["data"]["card"]["id"]
+ author_id = a["idMemberCreator"]
+ comment_list = comments_dict[card_id] if card_id in comments_dict else []
+ comment_list.append(
+ {
+ "author": members_dict[author_id] if author_id in members_dict else "",
+ "text": a["data"]["text"],
+ }
+ )
+ comments_dict[card_id] = comment_list
+
+# print('Members:\n' + str(members_dict))
+# print('Lists:\n' + str(lists_dict))
+# print('Checklists:\n' + str(checklists_dict))
+# print('Comments:\n' + str(comments_dict))
# ============================================================================
# Redmine configuration processing starts here.
# ============================================================================
-print('Querying Redmine configuration...')
+print("Querying Redmine configuration...")
sys.stdout.flush()
-redmine_projects = requests.get(cfg.redmine_root_url + '/projects.json?limit=200', verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key}).json()
+fname = f"json/{cfg.redmine_project_identifier}-projects.json"
+if not os.path.exists(fname):
+ redmine_projects = requests.get(
+ cfg.redmine_root_url + "/projects.json?limit=200",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_projects, fp)
+else:
+ redmine_projects = json.load(open(fname))
+
redmine_project_id = -1
for rp in redmine_projects["projects"]:
- if rp["identifier"] == cfg.redmine_project_identifier:
- redmine_project_id = rp["id"]
- break
+ if rp["identifier"] == cfg.redmine_project_identifier:
+ redmine_project_id = rp["id"]
+ break
if redmine_project_id < 0:
- print('Project with identifier {0} does not exist in Redmine!\n\n{1}'.format(cfg.redmine_project_identifier, str(redmine_users_dict)))
- sys.exit(1)
-#print('Redmine project ID: {0}'.format(redmine_project_id))
+ print(
+ "Project with identifier {0} does not exist in Redmine!\n\n".format(
+ cfg.redmine_project_identifier
+ )
+ )
+ sys.exit(1)
+# print('Redmine project ID: {0}'.format(redmine_project_id))
+
+fname = f"json/{cfg.redmine_project_identifier}-users.json"
+if not os.path.exists(fname):
+ redmine_users = requests.get(
+ cfg.redmine_root_url + "/users.json?limit=200",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_users, fp)
+else:
+ redmine_users = json.load(open(fname))
-# offset 2676 ensures 'Oliver Kurz' shows up
-redmine_users = requests.get(cfg.redmine_root_url + '/users.json?offset=2676', verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key}).json()
redmine_users_dict = {}
for ru in redmine_users["users"]:
- fullname = u'{0} {1}'.format(ru["firstname"], ru["lastname"])
- redmine_users_dict[fullname] = ru["id"]
-
-print('Redmine users:\n' + str(redmine_users_dict))
-
-if not cfg.redmine_default_user in redmine_users_dict:
- print('Default user does not exist in Redmine!\n\n{0}'.format(str(redmine_users_dict)))
- sys.exit(1)
+ fullname = "{0} {1}".format(ru["firstname"], ru["lastname"])
+ redmine_users_dict[fullname] = ru["id"]
+
+print("Redmine users:\n" + str(redmine_users_dict))
+
+if cfg.redmine_default_user not in redmine_users_dict:
+ print(
+ "Default user does not exist in Redmine!\n\n{0}".format(str(redmine_users_dict))
+ )
+ sys.exit(1)
+
+fname = f"json/{cfg.redmine_project_identifier}-priorities.json"
+if not os.path.exists(fname):
+ redmine_priorities = requests.get(
+ cfg.redmine_root_url + "/enumerations/issue_priorities.json",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_priorities, fp)
+else:
+ redmine_priorities = json.load(open(fname))
-redmine_priorities = requests.get(cfg.redmine_root_url + '/enumerations/issue_priorities.json', verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key}).json()
redmine_priorities_dict = {}
redmine_default_priority = "Normal"
for rp in redmine_priorities["issue_priorities"]:
- redmine_priorities_dict[rp["name"]] = rp["id"]
- if "is_default" in rp and rp["is_default"]:
- redmine_default_priority = rp["name"]
-
-#print(u'Redmine priorities:\n' + str(redmine_priorities_dict))
+ redmine_priorities_dict[rp["name"]] = rp["id"]
+ if "is_default" in rp and rp["is_default"]:
+ redmine_default_priority = rp["name"]
+
+# print(u'Redmine priorities:\n' + str(redmine_priorities_dict))
+
+fname = f"json/{cfg.redmine_project_identifier}-trackers.json"
+if not os.path.exists(fname):
+ redmine_trackers = requests.get(
+ cfg.redmine_root_url + "/trackers.json",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_trackers, fp)
+else:
+ redmine_trackers = json.load(open(fname))
+
+redmine_tracker_dict = {}
+redmine_default_tracker = "Task"
+for ts in redmine_trackers["trackers"]:
+ redmine_tracker_dict[ts["name"]] = ts["id"]
+
+fname = f"json/{cfg.redmine_project_identifier}-statuses.json"
+if not os.path.exists(fname):
+ redmine_statuses = requests.get(
+ cfg.redmine_root_url + "/issue_statuses.json",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_statuses, fp)
+else:
+ redmine_statuses = json.load(open(fname))
-redmine_statuses = requests.get(cfg.redmine_root_url + '/issue_statuses.json', verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key}).json()
redmine_statuses_dict = {}
redmine_default_status = "New"
for rs in redmine_statuses["issue_statuses"]:
- redmine_statuses_dict[rs["name"]] = rs["id"]
- if "is_default" in rs and rs["is_default"]:
- redmine_default_status = rs["name"]
-
-#print(u'Redmine statuses:\n' + str(redmine_priorities_dict))
+ redmine_statuses_dict[rs["name"]] = rs["id"]
+ if "is_default" in rs and rs["is_default"]:
+ redmine_default_status = rs["name"]
+
+
+# GET VERSIONS
+def get_redmine_versions(clean=False):
+ fname = f"json/{cfg.redmine_project_identifier}-versions.json"
+ if clean:
+ try:
+ os.unlink(fname)
+ except FileNotFoundError:
+ pass
+
+ if not os.path.exists(fname):
+ redmine_versions = requests.get(
+ cfg.redmine_root_url + f"/projects/{redmine_project_id}/versions.json",
+ verify=cfg.redmine_verify_certificates,
+ headers={"X-Redmine-API-Key": cfg.redmine_api_key},
+ ).json()
+ with open(fname, "w") as fp:
+ json.dump(redmine_versions, fp)
+ else:
+ redmine_versions = json.load(open(fname))
+
+ return redmine_versions
+
+
+redmine_versions = get_redmine_versions()
+
+i_fname = f"json/{cfg.redmine_project_identifier}-import-history.json"
+if not os.path.exists(i_fname):
+ import_history = []
+else:
+ import_history = json.load(open(i_fname))
+
+
+def write_import_history():
+ if dry_run:
+ return
+
+ with open(i_fname, "w") as fp:
+ json.dump(import_history, fp)
+
+
+# print(u'Redmine statuses:\n' + str(redmine_priorities_dict))
# ============================================================================
# Direct Trello-to-Redmine mappings are made here.
# ============================================================================
-print('Generating configuration mappings...')
+print("Generating configuration mappings...")
sys.stdout.flush()
user_map = {}
-for id, fullname in members_dict.iteritems():
- if not fullname in redmine_users_dict:
- print(u'Warning: user {0} not found in Redmine, defaulting to {1}'.format(fullname, cfg.redmine_default_user))
- fullname = cfg.redmine_default_user
- redmine_id = redmine_users_dict[fullname]
- user_map[id] = redmine_id
- print(u'Matched user {0}, Trello ID {1}, Redmine ID {2}'.format(fullname, id, redmine_id).encode('utf-8'))
-
-print(u'User ID map:\n{0}\nDefault user ID: {1}'.format(str(user_map), redmine_users_dict[cfg.redmine_default_user]).encode('utf-8'))
+for id, fullname in members_dict.items():
+ if fullname not in redmine_users_dict:
+ print(
+ "Warning: user {0} not found in Redmine, defaulting to {1}".format(
+ fullname, cfg.redmine_default_user
+ )
+ )
+ fullname = cfg.redmine_default_user
+ redmine_id = redmine_users_dict[fullname]
+ user_map[id] = redmine_id
+ print(
+ "Matched user {0}, Trello ID {1}, Redmine ID {2}".format(
+ fullname, id, redmine_id
+ ).encode("utf-8")
+ )
+
+print(
+ "User ID map:\n{0}\nDefault user ID: {1}".format(
+ str(user_map), redmine_users_dict[cfg.redmine_default_user]
+ ).encode("utf-8")
+)
priority_map = {}
-for id, name in labels_dict.iteritems():
- if not name in redmine_priorities_dict:
- print(u'Warning: Trello label {0} is not mapped to a Redmine priority, defaulting to {1}'.format(name, redmine_default_priority).encode('utf-8'))
- name = redmine_default_priority
- redmine_id = redmine_priorities_dict[name]
- priority_map[id] = redmine_id
- #print(u'Matched label {0}, Trello ID {1}, Redmine ID {2}'.format(name, id, redmine_id).encode('utf-8'))
-
-#print('Redmine priorities:\n' + str(redmine_priorities_dict))
+for id, name in labels_dict.items():
+ if name not in redmine_priorities_dict:
+ name = redmine_default_priority
+ redmine_id = redmine_priorities_dict[name]
+ priority_map[id] = redmine_id
+ # print(u'Matched label {0}, Trello ID {1}, Redmine ID {2}'.format(name, id, redmine_id).encode('utf-8'))
+
+# print('Redmine priorities:\n' + str(redmine_priorities_dict))
+
+tracker_map = {}
+for id, name in labels_dict.items():
+ if name not in redmine_tracker_dict:
+ name = redmine_default_tracker
+ redmine_id = redmine_tracker_dict[name]
+ tracker_map[id] = redmine_id
status_map = {}
-for id, name in lists_dict.iteritems():
- if not name in redmine_statuses_dict:
- print(u'Warning: Trello list {0} is not mapped to a Redmine status, defaulting to {1}'.format(name, redmine_default_status).encode('utf-8'))
- name = redmine_default_status
- redmine_id = redmine_statuses_dict[name]
- status_map[id] = redmine_id
- #print(u'Matched list {0}, Trello ID {1}, Redmine ID {2}'.format(name, id, redmine_id).encode('utf-8'))
-
-#print('Redmine statuses:\n' + str(redmine_statuses_dict))
+for id, name in lists_dict.items():
+ if name not in redmine_statuses_dict:
+ if not list_status_dict[id]:
+ print(
+ "Warning: Trello list {0} is not mapped to a Redmine status, defaulting to {1}".format(
+ name, redmine_default_status
+ ).encode(
+ "utf-8"
+ )
+ )
+ name = redmine_default_status
+ redmine_id = redmine_statuses_dict[name]
+ status_map[id] = redmine_id
+ # print(u'Matched list {0}, Trello ID {1}, Redmine ID {2}'.format(name, id, redmine_id).encode('utf-8'))
+
+if cfg.closed_list_status not in redmine_statuses_dict:
+ print(
+ f"Warning: 'closed_list_status' value not found in Redmine statuses. Defaulting to {redmine_default_status}"
+ )
+ id = redmine_statuses_dict[redmine_default_status]
+ redmine_statuses_dict[cfg.closed_list_status] = id
+
+# print('Redmine statuses:\n' + str(redmine_statuses_dict))
+
+refetch_versions = False
+version_map = {}
+list_version_map = {}
+_versions = [v for _, v in cfg.version_map.items()]
+
+for l_id, l_name in orig_lists_dict.items():
+ v_name = l_name.split()[-1].replace("v.1", "v1")
+ if re.match(r"v\d\.\d\.\d$", v_name) or re.match(r"v\d\.\d$", v_name):
+ v_name = v_name[1:] # remove leading "v"
+ _versions.append(v_name)
+ list_version_map[l_id] = v_name
+
+_versions = list(set(_versions))
+
+for v in redmine_versions["versions"]:
+ version_map[v["name"]] = v["id"]
+
+for v in _versions:
+ if v not in version_map:
+ refetch_versions = True
+ version = {
+ "version": {"name": v},
+ }
+
+ if dry_run:
+ print(f"Creating FAKE VERSION {v}")
+ version_map[v] = -1
+ else:
+ result = requests.post(
+ cfg.redmine_root_url + f"/projects/{redmine_project_id}/versions.json",
+ data=json.dumps(version),
+ verify=cfg.redmine_verify_certificates,
+ headers={
+ "X-Redmine-API-Key": cfg.redmine_api_key,
+ "Content-Type": "application/json",
+ },
+ )
+ if result.status_code >= 400:
+ print(
+ "Error on post {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}".format(
+ str(result),
+ result.request.headers,
+ result.headers,
+ result.content,
+ )
+ )
+ print(
+ json.dumps(
+ version, sort_keys=False, indent=4, separators=(",", ": ")
+ )
+ )
+ sys.exit(1)
+ else:
+ vjson = result.json()
+ version_map[v] = vjson["version"]["id"]
+ print(f"Created version '{v}'")
+
+if refetch_versions:
+ redmine_versions = get_redmine_versions(clean=True)
# ============================================================================
# Finally, cards processing.
# ============================================================================
-print ('Processing cards...')
+print("Processing cards...")
sys.stdout.flush()
+
+def _set_tracker_or_status(c):
+ result = {}
+ if c["idLabels"]:
+ label_id = c["idLabels"][0]
+ result["tracker_id"] = tracker_map[label_id]
+ label_name = labels_dict[label_id]
+ if label_name.startswith("Status:"):
+ lbname = label_name[7:]
+ if lbname in redmine_statuses_dict:
+ result["status_id"] = redmine_statuses_dict[lbname]
+
+ # List to status and version mapping
+ if list_status_dict[c["idList"]]:
+ # List is archived
+ result["status_id"] = redmine_statuses_dict[cfg.closed_list_status]
+
+ lname = orig_lists_dict[c["idList"]]
+ if lname in cfg.version_map:
+ result["fixed_version_id"] = version_map[cfg.version_map[lname]]
+ elif c["idList"] in list_version_map:
+ v_name = list_version_map[c["idList"]]
+ result["fixed_version_id"] = version_map[v_name]
+
+ # User assignee and watchers
+ a_id = None
+ w_ids = []
+ for mid in c["idMembers"]:
+ if a_id is None:
+ a_id = mid
+ else:
+ w_ids.append(mid)
+
+ if a_id is not None:
+ result["assigned_to_id"] = a_id
+ if len(w_ids):
+ result["watcher_user_ids"] = w_ids
+
+ return result
+
+
+def handle_attachments(c):
+ uploads = []
+ if not c["attachments"]:
+ return uploads
+
+ # Don't upload files in specific lists
+ if c["idList"] in no_attachment_list:
+ print("Skipping upload for card in prohibited upload list.")
+ return uploads
+
+ for a in c["attachments"]:
+ if not a["isUpload"]:
+ continue
+
+ filename = a["fileName"].strip().replace(" ", "_")
+ filename = re.sub(r"(?u)[^-\w.]", "", filename) # from django
+ file_path = f"attachments/{filename}"
+ upload = {
+ "filename": filename,
+ "content_type": a["mimeType"],
+ }
+ try:
+ r = requests.get(
+ a["url"],
+ stream=True,
+ headers={
+ "Authorization": f'OAuth oauth_consumer_key="{cfg.trello_apikey}", oauth_token="{cfg.trello_token}"',
+ },
+ )
+ except Exception as e:
+ print(f"Failuring downloading file {a['url']}, error: {e}")
+ continue
+
+ with open(file_path, "wb") as fp:
+ for chunk in r.iter_content(chunk_size=8192):
+ fp.write(chunk)
+
+ print(f"Downloaded {a['url']}...")
+
+ # Upload file to Redmine
+ try:
+ with open(file_path, "rb") as fp:
+ r = requests.post(
+ cfg.redmine_root_url + f"/uploads.json?filename={filename}",
+ data=fp,
+ verify=cfg.redmine_verify_certificates,
+ headers={
+ "X-Redmine-API-Key": cfg.redmine_api_key,
+ "Content-Type": "application/octet-stream",
+ },
+ )
+ if r.status_code >= 400:
+ print(
+ "Error on post {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}".format(
+ str(r), r.request.headers, r.headers, r.content
+ )
+ )
+ raise Exception("Redmine upload failed")
+ else:
+ rmfile = r.json()
+ upload["token"] = rmfile["upload"]["token"]
+
+ except Exception as e:
+ print(f"Failuring uploading file to Redmine {file_path}, error: {e}")
+ continue
+
+ # Delete file locally
+ os.unlink(file_path)
+
+ # Wrap up this attachment
+ uploads.append(upload)
+
+ return uploads
+
+
dry_run_counter = 0
+import_counter = 0
for c in board["cards"]:
- desc = c["desc"]
- if c["idChecklists"]:
- desc += u'\n'
- for id in c["idChecklists"]:
- for item in checklists_dict[id]:
- desc += u'\n[{0}] {1}'.format(u'x' if item["state"] == u'complete' else u' ', item["name"])
- card = {
- "issue": {
- "subject": u'[{0}][{1}] {2}'.format(board["name"], orig_lists_dict[c["idList"]], c["name"]),
- "description": desc,
- "project_id": redmine_project_id,
- "assigned_to_id": user_map[c["idMembers"][0]] if c["idMembers"] else redmine_users_dict[cfg.redmine_default_user],
- "status_id": status_map[c["idList"]] if c["idList"] else redmine_statuses_dict[redmine_default_status],
- "priority_id": priority_map[c["idLabels"][0]] if c["idLabels"] else redmine_priorities_dict[redmine_default_priority],
- "is_private": False
- }
- }
- print(u'Importing {0}...'.format(card["issue"]["subject"]).encode('utf-8'))
- issue_id = -1
- if dry_run:
- dry_run_counter += 1
- issue_id = dry_run_counter
- print(json.dumps(card, sort_keys=False, indent=4, separators=(',', ': ')))
- else:
- result = requests.post(cfg.redmine_root_url + '/issues.json', data=json.dumps(card), verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key, "Content-Type": "application/json"})
- if result.status_code >= 400:
- print('Error on post {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}'.format(str(result), result.request.headers, result.headers, result.content))
- print(json.dumps(card, sort_keys=False, indent=4, separators=(',', ': ')))
- sys.exit(1)
- else:
- print(str(result))
- issue = result.json()
- issue_id = issue["issue"]["id"]
- #print(u'Response JSON:\n{0}\nIssue ID: {1}'.format(str(issue), issue_id).encode('utf-8'))
- if issue_id >= 0 and c["id"] in comments_dict:
- for index, comment in enumerate(reversed(comments_dict[c["id"]])):
- update = {
- "issue": {
- "notes": u'{0}:\n{1}'.format(comment["author"], comment["text"]).format('utf-8') if len(comment["author"]) > 0 else comment["text"]
- }
- }
- print(u'Importing comment {0} of {1}...'.format(index + 1, len(comments_dict[c["id"]])).encode('utf-8'))
- if dry_run:
- print(json.dumps(update, sort_keys=False, indent=4, separators=(',', ': ')))
- else:
- result = requests.put(cfg.redmine_root_url + '/issues/{0}.json'.format(issue_id), data=json.dumps(update), verify=cfg.redmine_verify_certificates, headers={'X-Redmine-API-Key': cfg.redmine_api_key, "Content-Type": "application/json"})
- if result.status_code >= 400:
- print('Error for comments {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}'.format(str(result), result.request.headers, result.headers, result.content))
- sys.exit(1)
- else:
- print(str(result))
- #print("Aborting after one for testing")
- #sys.exit(0)
-
-print('Done!')
+ if dry_run and dry_run_counter > 0:
+ print("ENDING DRY RUN")
+ sys.exit(0)
+
+ if c["id"] in import_history:
+ # Already imported this card, continue
+ continue
+
+ desc = c["desc"]
+ desc += "\n\n"
+ desc += "Imported from Trello: [{0}][{1}]".format(
+ board["name"], orig_lists_dict[c["idList"]]
+ )
+ all_checklists = []
+ if c["idChecklists"]:
+ for id in c["idChecklists"]:
+ for item in checklists_dict[id]:
+ all_checklists.append(
+ {
+ "subject": item["name"],
+ "is_done": 1 if item["state"] == "complete" else 0,
+ }
+ )
+ card = {
+ "issue": {
+ "subject": "{0}".format(c["name"]),
+ "description": desc,
+ "project_id": redmine_project_id,
+ "assigned_to_id": user_map[c["idMembers"][0]]
+ if c["idMembers"]
+ else redmine_users_dict[cfg.redmine_default_user],
+ "status_id": status_map[c["idList"]]
+ if c["idList"]
+ else redmine_statuses_dict[redmine_default_status],
+ "priority_id": priority_map[c["idLabels"][0]]
+ if c["idLabels"]
+ else redmine_priorities_dict[redmine_default_priority],
+ "is_private": False,
+ }
+ }
+
+ trk = _set_tracker_or_status(c)
+ if trk:
+ card["issue"].update(trk)
+
+ if len(all_checklists):
+ card["issue"]["checklists_attributes"] = all_checklists
+
+ uploads = handle_attachments(c)
+ if len(uploads):
+ card["issue"]["uploads"] = uploads
+
+ print("Importing {0}...".format(card["issue"]["subject"]).encode("utf-8"))
+ issue_id = -1
+ if dry_run:
+ dry_run_counter += 1
+ issue_id = dry_run_counter
+ print(json.dumps(card, sort_keys=False, indent=4, separators=(",", ": ")))
+ else:
+ result = requests.post(
+ cfg.redmine_root_url + "/issues.json",
+ data=json.dumps(card),
+ verify=cfg.redmine_verify_certificates,
+ headers={
+ "X-Redmine-API-Key": cfg.redmine_api_key,
+ "Content-Type": "application/json",
+ },
+ )
+ if result.status_code >= 400:
+ print(
+ "Error on post {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}".format(
+ str(result), result.request.headers, result.headers, result.content
+ )
+ )
+ print(json.dumps(card, sort_keys=False, indent=4, separators=(",", ": ")))
+ sys.exit(1)
+ else:
+ print(str(result))
+ issue = result.json()
+ issue_id = issue["issue"]["id"]
+ # print(u'Response JSON:\n{0}\nIssue ID: {1}'.format(str(issue), issue_id).encode('utf-8'))
+ if issue_id >= 0 and c["id"] in comments_dict:
+ for index, comment in enumerate(reversed(comments_dict[c["id"]])):
+ try:
+ update = {
+ "issue": {
+ "notes": "{0}:\n{1}".format(
+ comment["author"], comment["text"]
+ ).format("utf-8")
+ if len(comment["author"]) > 0
+ else comment["text"]
+ }
+ }
+ except KeyError as err:
+ print(f"ERR: {err}, data: {comment}")
+
+ print(
+ "Importing comment {0} of {1}...".format(
+ index + 1, len(comments_dict[c["id"]])
+ ).encode("utf-8")
+ )
+ if dry_run:
+ print(
+ json.dumps(
+ update, sort_keys=False, indent=4, separators=(",", ": ")
+ )
+ )
+ else:
+ result = requests.put(
+ cfg.redmine_root_url + "/issues/{0}.json".format(issue_id),
+ data=json.dumps(update),
+ verify=cfg.redmine_verify_certificates,
+ headers={
+ "X-Redmine-API-Key": cfg.redmine_api_key,
+ "Content-Type": "application/json",
+ },
+ )
+ if result.status_code >= 400:
+ print(
+ "Error for comments {0}: Request headers:\n{1}\nResponse headers:\n{2}\nContent: {3}".format(
+ str(result),
+ result.request.headers,
+ result.headers,
+ result.content,
+ )
+ )
+ sys.exit(1)
+ else:
+ print(str(result))
+
+ import_history.append(c["id"])
+ import_counter += 1
+ if import_counter >= 5:
+ write_import_history()
+ # print("Aborting after one for testing")
+ # sys.exit(0)
+
+print("Done!")