aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorS Page <spage@wikimedia.org>2014-12-10 18:47:38 -0800
committerS Page <spage@wikimedia.org>2015-02-20 12:00:15 -0800
commitd652499cf2f64d88c711d75bb7010dd5280b53d4 (patch)
tree57fbf280eba77e2733be63743aab7f2ca7d7ad70
parentcb46ee27b4646ad267ad1d57e91c705aa030c962 (diff)
Import Trello card JSON into Phabricator
Processes Trello board exported as JSON to import its cards as Phabricator tasks. It imports name, description, members and commenters (as task subscribers), labels (as text in description), and checklists (as markup in description). Doesn't handle attachments, comments, correct timestamps and authors, etc. Phabricator user "Trellimport" makes all the changes in the present. Similar to bugzilla_import.py, but does much less, runs in a single shot, and only uses the phabricator conduit API. Uses TrelloCard, TrelloDAO, and TrelloScrubber objects from a "cburroughs"'s export_trello.py to figure_stuff_out(). Modifies cburroughs export_trello.py to * handle changes in the Trello JSON format of idLabels, column list, and shortUrl * write out idLabels, generate checklists in Description * fake out its setup_logging() * etc. It uneasily combines two logging systems and options. You have to first generate Trello name -> Phabricator PHID mappings, see change Ia574d589. Detailed instructions to run this are in trello_create.py, more info is in https://www.mediawiki.org/wiki/Phabricator/Trello_to_Phabricator and T821. spage is happy to help! Bug: T821 Change-Id: Ia57b5c034bf58f8f3c502a2fa98ed779c135f1cd
-rw-r--r--export_trello.py127
-rw-r--r--trello_create.py346
2 files changed, 454 insertions, 19 deletions
diff --git a/export_trello.py b/export_trello.py
index fde6305..75714fa 100644
--- a/export_trello.py
+++ b/export_trello.py
@@ -46,7 +46,7 @@
# ._-~//( )/ )) ` ~~-'_/_/ /~~~~~~~__--~
# ;'( ')/ ,)( ~~~~~~~~~~
# ' ') '( (/
-# ' ' `
+# ' ' `
# This script uses a trello enterprise export which even if you have
# an enterprise account is different from what you get by hitting the
@@ -85,7 +85,7 @@ def mkdir_p(path):
# handling such a mess?
def parse_trello_ts_str(s):
d = dateutil.parser.parse(s)
- return calendar.timegm(d.utctimetuple())
+ return calendar.timegm(d.utctimetuple())
def s_to_us(ts):
@@ -99,15 +99,39 @@ class TrelloDAO(object):
def __init__(self, fname):
with open(fname) as f:
self.blob = json.load(f)
+ self.uid2label = None
self.uid2username = None
self.column_id2name = None
+ def get_board_id(self):
+ return self.blob['id']
+
def get_board_name(self):
return self.blob['name']
def get_board_url(self):
return self.blob['url']
+ # Label definitions. Like members, an array of dicts
+ def get_labelnames(self):
+ labelnames = []
+ for label in self.blob['labels']:
+ # TODO? the label['idBoard'] might not be the current board, but if it's unique maybe write it anyway.
+ labelnames.append('%s (%s)' % (label['name'],label['color']))
+ return sorted(labelnames)
+
+ # Returns a string for the Trello label.
+ def get_label(self, uid):
+ if self.uid2label is None:
+ self.uid2label = {}
+ for label in self.blob['labels']:
+ self.uid2label[label['id']] = ('%s (%s)' % (label['name'],label['color']))
+ if uid in self.uid2label:
+ return self.uid2label[uid]
+ else:
+ return 'UNKNOWN_LABEL_' + uid
+
+
def get_usernames(self):
usernames = []
for user in self.blob['members']:
@@ -129,7 +153,17 @@ class TrelloDAO(object):
self.column_id2name = {}
for t_list in self.blob['lists']:
self.column_id2name[t_list['id']] = t_list['name']
- return self.column_id2name[column_id]
+ # This failed in card 108 of a simple Trello export, referencing a
+ # "Done" column that is no longer part of this board, or maybe the card
+ # was on a different board.
+ try:
+ column_name = self.column_id2name[column_id]
+ except KeyError as e:
+ log.warn('get_column_name found no name for column id' + column_id)
+ column_name = 'NOTFOUND-'+column_id
+ self.column_id2name[column_id] = column_name
+ return column_name
+
# due to cards moving and whatnot constraints like 'there should
# be a created record for each card' can not be satisfied
@@ -170,6 +204,7 @@ class TrelloDAO(object):
if action['type'] == 'createCard':
if action['data']['card']['id'] == card.card_id:
reporter = scrubber.get_phab_uid(self.get_username(action['idMemberCreator']))
+ # TODO: spagewmf: found a reporter, break out of the loop
if reporter is None and card.idMembers:
reporter = scrubber.get_phab_uid(self.get_username(card.idMembers[0]))
return reporter if reporter else 'import-john-doe'
@@ -190,8 +225,9 @@ class TrelloDAO(object):
return sorted(subscribers)
+ # Note: this method is unused.
def get_checklist_items(self, card_id):
- for c_list in self.blob['checklists']:
+ for c_list in self.blob['checklists']: # TODO Only in Enterprise export, not available in board export
if c_list['idCard'] == card_id:
check_items = c_list['checkItems']
return sorted(check_items, key=lambda e: e['pos'])
@@ -217,28 +253,35 @@ class TrelloScrubber(object):
# as members or anywhere within in the export
if trello_username.startswith('UNKNOWN_'):
junk = trello_username.split('UNKNOWN_')[1]
- return self.conf['uid-cheatsheet'][junk]
+ if junk in self.conf['uid-cheatsheet']:
+ return self.conf['uid-cheatsheet'][junk]
+ else:
+ return 'FAILED-'+trello_username # TODO log error
else:
- return self.conf['uid-map'][trello_username]
+ if trello_username in self.conf['uid-map']:
+ return self.conf['uid-map'][trello_username]
+ else:
+ return 'FAILED-'+trello_username # TODO log error
class TrelloCard(object):
- # [u'attachments', Never used on board
- # u'labels', Rarely used color stuff
+ # [u'attachments', list of attachments # TODO handle attachment images!
+ # u'labels', Old way of representing label colors.
+ # u'idLabels', array of ids of labels for card (usually only one)
# u'pos', Physical position, ridiculous LOE to port so ignoring
# u'manualCoverAttachment', Duno but it's always false
# u'id', unique id
# u'badges', something about fogbugz integration?
# u'idBoard', parent board id
# u'idShort', "short" and thus not unique uid
- # u'due', rarely used durdate
+ # u'due', rarely used duedate
# u'shortUrl', pre-shorted url
# u'closed', boolean for if it's archived
# u'subscribed', boolean, no idea what it means
# u'email', no idea, always none
# u'dateLastActivity', 2014-04-22T14:09:49.917Z
- # u'idList', it's an id, not sure exactly how it works
+ # u'idList', it's the ID of the current column of the card
# u'idMembersVoted', never used
# u'idMembers', # Whose face shows up next to it
# u'checkItemStates', Something to do with checklists?
@@ -253,7 +296,11 @@ class TrelloCard(object):
def __init__(self, blob, scrubber):
self.scrubber = scrubber
self.card_id = blob['id']
- self.labels = blob['labels']
+ if "labels" in blob:
+ self.labels = blob['labels']
+ else:
+ self.labels = []
+ self.idLabels = blob['idLabels']
self.idBoard = blob['idBoard']
self.due = blob['due']
self.closed = blob['closed']
@@ -263,9 +310,26 @@ class TrelloCard(object):
self.desc = blob['desc']
self.name = blob['name']
self.url = blob['url']
+ # Board export has shortUrl and shortLink, but Enterprise export doesn't - crazy.
+ if 'shortUrl' in blob:
+ self.shortUrl = blob['shortUrl']
+ else:
+ # Trim the end off e.g. https://trello.com/c/mpFNXCXp/464-long-title-here
+ self.shortUrl = self.url[0:self.url.rfind('/')]
+ if 'shortLink' in blob:
+ self.shortLink = blob['shortLink']
+ else:
+ # Again, the last piece.
+ self.shortLink = self.shortUrl[self.shortUrl.rfind('/')+1:]
+
self.idChecklists = blob['idChecklists']
- self.checklists = blob['checklists']
-
+ if 'checklists' in blob:
+ self.checklists = blob['checklists']
+ else:
+ # TODO Only in Enterprise export, not available in board export
+ self.checklists = None
+
+ self.checklist_strs = []
self.change_history = []
self.column = None
self.final_comment_fields = {}
@@ -283,8 +347,17 @@ class TrelloCard(object):
for action in dao.get_relevant_actions(self.card_id):
self.handle_change(action, dao)
+ self.column = dao.get_column_name(self.idList)
+ # labels is text, idLabels is UIDs, append them to the end.
+ label_comment = ''
if self.labels:
- self.final_comment_fields['labes'] = sorted(map(lambda k: k['color'], self.labels))
+ label_comment = sorted(map(lambda k: k['color'], self.labels))
+ if self.idLabels:
+ for label_id in self.idLabels:
+ label_comment = ' ' + dao.get_label(label_id)
+ if len(label_comment) > 0:
+ self.final_comment_fields['labels'] = label_comment
+
if self.due:
self.final_comment_fields['due'] = self.due
@@ -293,17 +366,21 @@ class TrelloCard(object):
return None
s = ''
if self.checklists is None:
- log.warning('Failed to find checklist %s for card %s' % self.card_id)
+ log.warning('Failed to find checklists for card %s' % self.card_id)
return
for checklist in self.checklists:
- s += 'Checklist: \n'
+ headerText = checklist['name'] if ('name' in checklist) else 'Checklist'
+ s += '==== %s ====\n' % (headerText)
for item in checklist['checkItems']:
s+= ' * [%s] %s \n' % ('x' if item['state'] == 'complete' else '', item['name'])
s += '\n'
change = {'type': 'comment', 'author': self.owner,
'comment': s,
'change_time_us': s_to_us(parse_trello_ts_str(self.dateLastActivity))}
- self.change_history.append(change)
+ # SPage: cburroughs turns the checklist into a comment:
+ # self.change_history.append(change)
+ # SPage: instead, add to checklists string
+ self.checklist_strs.append(s)
def make_final_comment(self):
s = 'Trello Board: %s `%s` \n' % (self.board_name, self.idBoard)
@@ -322,6 +399,8 @@ class TrelloCard(object):
'val': dao.get_column_name(j_change['data']['listAfter']['id']),
'change_time_us': s_to_us(parse_trello_ts_str(j_change['date']))}
self.change_history.append(change)
+ # XXX This doesn't result in the card having the right column,
+ # elsewhere it's set to the column from the card's idList.
self.column = dao.get_column_name(j_change['data']['listBefore']['id'])
elif j_change['type'] == 'commentCard':
change = {'type': 'comment',
@@ -352,10 +431,11 @@ class TrelloCard(object):
self.change_history.append(change)
elif j_change['type'] == 'updateCard' and 'old' in j_change['data'] and 'due' in j_change['data']['old']:
pass # Will just use the final due date
+ elif j_change['type'] == 'updateCard' and 'old' in j_change['data'] and 'idAttachmentCover' in j_change['data']['old']:
+ pass # changing the cover image. TODO? could link to this image in self.desc
elif j_change['type'] == 'updateCard' and 'old' in j_change['data'] and 'pos' in j_change['data']['old']:
pass # just moving cards around in a list
else:
- print j_change
log.warn('Unknown change condition type:%s id:%s for card %s' % (j_change['type'], j_change['id'], self.card_id))
def to_transform_dict(self, import_project, task_id):
@@ -387,10 +467,16 @@ def cmd_foo(args):
pass
+def cmd_print_labelnames(args):
+ board = TrelloDAO(args.trello_file)
+ pprint.pprint(board.get_labelnames())
+
+ pass
+
def cmd_print_users(args):
board = TrelloDAO(args.trello_file)
pprint.pprint(board.get_usernames())
-
+
pass
def cmd_print_user_map_test(args):
@@ -442,6 +528,9 @@ def parse_args(argv):
foo_p = db_cmd(sub_p, 'foo', '')
foo_p.set_defaults(func=cmd_foo)
+ print_labelnames_p = db_cmd(sub_p, 'print-labelnames', '')
+ print_labelnames_p.set_defaults(func=cmd_print_labelnames)
+
print_users_p = db_cmd(sub_p, 'print-users', '')
print_users_p.set_defaults(func=cmd_print_users)
diff --git a/trello_create.py b/trello_create.py
new file mode 100644
index 0000000..c25663e
--- /dev/null
+++ b/trello_create.py
@@ -0,0 +1,346 @@
+#!/usr/bin/python
+# vim: set fileencoding=UTF-8
+#
+# Create Phabricator tasks out of cards in a Trello board exported as JSON file.
+# This uses WMF's phabricator tooling and independently cburrough's export_trello.py,
+# one excuse for messy code.
+#
+# Steps to run:
+# 1. Export all Trello boards from https://trello.com/wikimediafoundation, unzip.
+#
+# (You'll do the following config steps twice, first to create tasks on the
+# test Phab host phab-01.wmflabs.org and then for reals on the production Phab
+# host phabricator.wikimedia.org.)
+#
+# 2. Modify /etc/phabtools.conf for either test host phab-01.wmflabs.org or
+# production host phabricator.wikimedia.org.
+# 3. Check that data/trello_names_<HOST>.yaml has up-to-date
+# Trello_username: Phab_username, mapping the members of the Trello board
+# you're importing to their Phab username on either the test phab-01 or
+# production phabricator host. (Commit any changes you make)
+# 4. Run trello_makePHIDs.py, it creates a new conf/trello-scrub_<HOST>.yaml
+# Check this for missing Phabricator user PHIDs.
+# 5. Copy or symlink conf/trello-scrub_<HOST>.yaml to conf/trello-scrub.yaml.
+#
+# X Now you're ready to run trello_create.py
+#
+# 6. Run trello_create.py specifying the JSON export file of the board you want.
+# 6a. Do a verbose dry-run of trello_create.py to a test project on the test host
+# (this doesn't actually create cards), e.g.:
+# python trello_create.py -v -vv --test-run --dry-run \
+# -j trabulous_dir/wikimediafoundation_20150218_083222/boards/flow_backlog/flow_backlog.json \
+# --phab_project 'Flow (test)' \
+# --column 'Send to Phabricator - Collaboration-Team board'
+# Check the command output.
+# 6b. Do a test-run of trello_create.py to a test project on the test host (this
+# creates cards named 'TEST RUN: xxx'). Same command-line as above without "--test-run".
+# Check the created tasks in the test project.
+#
+# 7. If it looks good, repeat steps 2-5 to configure for the production Phab
+# host phabricator.wikimedia.org.
+#
+# 8. Run trello_create.py again
+# 8a. Do another verbose dry-run of trello_create.py to the actual production phab project
+# (this doesn't actually create cards), e.g.:
+# python -m pdb trello_create.py -v -vv --test-run --dry-run \
+# -j trabulous_dir/wikimediafoundation_20150218_083222/boards/flow_backlog/flow_backlog.json \
+# --phab_project '§Collaboration-Team' \
+# --column 'Send to Phabricator - Collaboration-Team board'
+# Check the command output.
+# 8b. Do you feel lucky?
+# Alert the pros in #wikimedia-devtools on IRC...
+# Do the actual run of trello_create.py to the actual production phab project. Same
+# command line as above without "--test-run --dry-run"
+# Capture the standard output and stderr.
+# 9. Check the created tasks on phabricator.wikimedia.org
+# 10.Save the standard output and stderr of the run somewhere. In particular
+# save the mapping lines
+# Created task: TNNN (PHID-TASK-xxxxxxx) from Trello card xXxXxXxX ('Card NameXxxx')
+# so that an improved version of the tool could improve the migrated
+# cards (set the correct users and timestamps, add comments, and attachments, etc.)
+# 11. Party like it's 1999.
+#
+# Documentation at https://www.mediawiki.org/wiki/Phabricator/Trello_to_Phabricator
+# TODO 2015-01-25: log and write out the mapping card_id -> trello Tnnnn; for now use 'Created task:' log lines.
+# TODO 2015-02-18: owner and one CC seems to work (see "Table of Contents: No-JS version"), but unknown users are dropped.
+# TODO 2015-02-19: do something with attachments!
+
+
+import argparse
+import json
+import sys
+import yaml
+
+# cburroughs' work
+from export_trello import TrelloCard, TrelloDAO, TrelloScrubber, setup_logging
+
+from phabricator import Phabricator
+from wmfphablib import Phab as phabmacros # Cleaner (?), see wmfphablib/phabapi.py
+from wmfphablib import config
+from wmfphablib import log
+from wmfphablib import vlog
+from wmfphablib import errorlog as elog
+
+class TrelloImporter:
+ # Map from Trello username to phabricator user.
+ userMapPhab01 = {
+ 'gtisza': 'Tgr',
+ "legoktm": "legoktm",
+ "matthewflaschen": "mattflaschen",
+ "pauginer": None,
+ "spage1": "spage",
+ }
+ userMapPhabWMF = {
+ # from mingleterminator.py
+ 'fflorin': 'Fabrice_Florin',
+ 'gdubuc': 'Gilles',
+ 'mholmquist': 'MarkTraceur',
+ 'gtisza': 'Tgr',
+ 'pginer': 'Pginer-WMF',
+ # From ack username trabulous_dir/flow-current-iteration_lOh4XCy7_fm.json \
+ # | perl -pe 's/^\s+//' | sort | uniq
+ # Collaboration-Team members: Eloquence, DannyH, Pginer-WMF, Spage, Quiddity, Mattflaschen, matthiasmullie, EBernhardson
+ "alexandermonk": None,
+ "antoinemusso": None,
+ "benmq": None,
+ "bsitu": None,
+ "dannyhorn1": "DannyH",
+ "erikbernhardson": "EBernhardson",
+ "jaredmzimmerman": None,
+ "jonrobson1": None,
+ "kaityhammerstein": None,
+ "legoktm": None,
+ "matthewflaschen": "mattflaschen",
+ "matthiasmullie": "matthiasmullie",
+ "maygalloway": None,
+ "moizsyed_": None,
+ "oliverkeyes": None,
+ "pauginer": "Pginer-WMF",
+ "quiddity1": "Quiddity",
+ "shahyarghobadpour": None,
+ "spage1": "Spage",
+ "wctaiwan": None,
+ "werdnum": None,
+ }
+
+ def __init__(self, jsonName, args):
+ self.jsonName = jsonName
+ self.args = args # FIXME unpack all the args into member variables?
+ self.verbose = args.verbose
+ if config.phab_host.find('phab-01') != -1:
+ self.host = 'phab-01'
+ elif config.phab_host.find('phabricator.wikimedia.org') != -1:
+ self.host = 'phabricator'
+ else:
+ self.json_error('Unrecognized host %s in config' % (config.phab_host))
+ sys.exit(3)
+ self.board = TrelloDAO(self.jsonName)
+ trelloBoardName = self.board.get_board_name();
+ vlog('Trello board = %s' % (trelloBoardName))
+
+
+ def connect_to_phab(self):
+ self.phab = Phabricator(config.phab_user,
+ config.phab_cert,
+ config.phab_host)
+
+ self.phab.update_interfaces()
+ self.phabm = phabmacros('', '', '')
+ self.phabm.con = self.phab
+ # DEBUG to verify the API connection worked: print phab.user.whoami()
+ vlog('Looks like we connected to the phab API \o/')
+
+
+ def sanity_check(self):
+ if not 'cards' in self.trelloJson:
+ self.json_error('No cards in input file')
+ sys.exit(1)
+
+ def testify(self, str):
+ if self.args.test_run:
+ str = "TEST Trello_create RUN: " + str
+
+ return str
+
+ def json_error(self, str):
+ elog('ERROR: %s in input file %s' % (str, self.jsonName))
+
+ # Determine projectPHID for the project name in which this will create tasks.
+ def get_projectPHID(self, phabProjectName):
+ # Similar conduit code in trello_makePHIDs.py get_trelloUserPHIDs
+ response = self.phab.project.query(names = [phabProjectName])
+ for projInfo in response.data.values():
+ if projInfo["name"] == phabProjectName:
+ vlog('Phabricator project %s has PHID %s' % (phabProjectName, projInfo["phid"] ) )
+ return projInfo["phid"]
+
+ elog('Phabricator project %s not found' % (phabProjectName))
+ sys.exit(4)
+ return # not reached
+
+ # This is the workhorse
+ def createTask(self, card):
+ # Default some keys we always pass to createtask.
+ taskinfo = {
+ 'ownerPHID' : None,
+ 'ccPHIDs' : [],
+ 'projectPHIDs' : [self.projectPHID],
+ }
+
+ taskinfo["title"] = self.testify(card.name)
+
+ # TODO: if Trello board is using scrum for Trello browser extension,
+ # could extract story points /\s+\((\d+)\)' from card title to feed into Sprint extension.
+
+ # TODO: process attachments
+ # TODO: taskinfo["assignee"]
+ desc = self.testify(card.desc)
+
+ if card.checklist_strs:
+ desc += '\n' + '\n\n'.join(card.checklist_strs)
+ desc_tail = '\n--------------------------'
+ desc_tail += '\n**Trello card**: [[ %s | %s ]]\n' % (card.url, card.shortLink)
+ # Mention column the same way as the card.final_comment_fields below from export_trello.py.
+ desc_tail += '\n * column: %s\n' % (unicode(card.column))
+ if len(card.final_comment_fields) > 0:
+ s = ''
+ s += '\n'
+ for key in sorted(card.final_comment_fields):
+ s += ' * %s: %s\n' % (str(key), unicode(card.final_comment_fields[key]))
+ desc_tail += s
+
+ # TODO: could add additional info (main attachment, etc.) to desc_tail.
+ taskinfo["description"] = desc + '\n' + desc_tail
+ # TODO: chasemp: what priority?
+ taskinfo['priority'] = 50
+ # TODO: chasemp: can I put "Trello lOh4XCy7" in "Reference" field?
+
+ # Take the set of members
+ idMembers = card.idMembers
+ # Get the Trello username for the idMember
+ # memberNames = [ TrelloDAO.get_username(id) for id in idMembers if TrelloDAO.get_username(id)]
+
+ # export_trello.py sets names it can't match to 'import-john-doe'
+ if not 'FAILED' in card.owner and not card.owner == 'import-john-doe':
+ taskinfo['ownerPHID'] = card.owner
+ taskinfo['ccPHIDs'] = [u for u in card.subscribers if not 'FAILED' in u and not u == 'import-john-doe']
+
+ # TODO: Add any other members with a PHID to the ccPHIDs
+ # TODO: Note remaining Trello members in desc_tail
+
+ # TODO: bugzilla_create.py and wmfphablib/phabapi.py use axuiliary for
+ # BZ ref, but it doesn't work for Trello ref?
+ taskinfo["auxiliary"] = {"std:maniphest:external_reference":"Trello %s" % (card.shortLink)}
+
+ if self.args.conduit:
+ # This prints fields for maniphest.createtask
+ print '"%s"\n"%s"\n\n' % (taskinfo["title"].encode('unicode-escape'),
+ taskinfo["description"].encode('unicode-escape'))
+ else:
+ if self.args.dry_run:
+ log("dry-run to create a task for Trello card %s ('%s')" %
+ (card.shortLink, taskinfo["title"]))
+ else:
+ ticket = self.phab.maniphest.createtask(
+ title = taskinfo['title'],
+ description = taskinfo['description'],
+ projectPHIDs = taskinfo['projectPHIDs'],
+ ownerPHID = taskinfo['ownerPHID'],
+ ccPHIDs = taskinfo['ccPHIDs'],
+ auxiliary = taskinfo['auxiliary']
+ )
+
+ log("Created task: T%s (%s) from Trello card %s ('%s')" %
+ (ticket['id'], ticket['phid'], card.shortLink, taskinfo["title"]))
+
+
+ # Here bugzilla_create goes on to log actual creating user and view/edit policy,
+ # then set_task_ctime to creation_time.
+
+ # Should I add comments to the card here,
+ # or a separate step that goes through action in self.board.blob["actions"]
+ # handling type="commentCard"?
+
+
+ # Here are the types of objects in the "actions" array.
+ # 20 "type": "addAttachmentToCard",
+ # 9 "type": "addChecklistToCard",
+ # 2 "type": "addMemberToBoard",
+ # 38 "type": "addMemberToCard",
+ # 69 "type": "commentCard",
+ # 1 "type": "copyCard",
+ # 25 "type": "createCard",
+ # 3 "type": "createList",
+ # 6 "type": "deleteAttachmentFromCard",
+ # 29 "type": "moveCardFromBoard",
+ # 18 "type": "moveCardToBoard",
+ # 4 "type": "moveListFromBoard",
+ # 2 "type": "moveListToBoard",
+ # 3 "type": "removeChecklistFromCard",
+ # 14 "type": "removeMemberFromCard",
+ # 3 "type": "updateBoard",
+ # 698 "type": "updateCard",
+ # 48 "type": "updateCheckItemStateOnCard",
+ # 8 "type": "updateList",
+ # def getCardCreationMeta(self, cardId):
+ # Look around in JSON for ["actions"] array for member with type:"createCard"
+ # with ["card"]["id"] = cardId
+ # and use the siblings ["date"] and ["memberCreator"]["id"]
+
+ # def getCardComments(self, cardId):
+ # Look around in JSON ["actions"] for member with type:""commentCard"
+ # with ["card"]["id"] = cardId
+ # and use the siblings ["date"] and ["memberCreator"]["id"]
+
+ def process_cards(self):
+ self.connect_to_phab()
+
+ self.projectPHID = self.get_projectPHID(self.args.phab_project);
+
+ # This file has Trello_username: user_PHID mapping created by trello_makePHIDs.py.
+ scrubber = TrelloScrubber('conf/trello-scrub.yaml')
+ for j_card in self.board.blob["cards"]:
+ card = TrelloCard(j_card, scrubber)
+ card.figure_stuff_out(self.board)
+ if self.args.column and not card.column == self.args.column:
+ continue
+ # Skip archived cards ("closed" seems to correspond?)
+ # But I think archive all cards in column doesn't set this.
+ if card.closed:
+ continue
+
+ # TODO: skip cards that are bugs
+ # TODO: skip cards that already exist in Phab.
+ self.createTask(card)
+
+
+def main():
+ parser = argparse.ArgumentParser()
+
+ parser.add_argument("-v", "--verbose", action="store_true",
+ help="increase output verbosity")
+ parser.add_argument("-vv", "--verbose-logging", action="store_true",
+ help="wmfphablib verbose logging")
+ parser.add_argument("-j", "--json", required=True,
+ help="Trello board JSON export file" )
+ parser.add_argument("-c", "--conduit", action="store_true",
+ help="print out lines suitable for conduit maniphest.createtask")
+ parser.add_argument("-d", "--dry-run", action="store_true",
+ help="don't actually add anything to Phabricator")
+ parser.add_argument("-l", "--column",
+ help="Name the one column to import")
+ parser.add_argument("-t", "--test-run", action="store_true",
+ help="prefix titles and description with 'TEST trabuloust TEST' disclaimers")
+ # type to handle `--phab_project '§Collaboration-Team'`, from http://stackoverflow.com/questions/24552854
+ parser.add_argument("-p", "--phab_project", type=lambda s : unicode(s, sys.stdin.encoding),
+ required=True,
+ help="name of Phabricator project for imported tasks")
+ args = parser.parse_args()
+
+ # As used in export_trello.py functions.
+ setup_logging('stdout', 'user', 'WARNING')
+ trell = TrelloImporter(args.json, args)
+ trell.process_cards()
+
+if __name__ == '__main__':
+ main()