diff options
-rw-r--r-- | models/boards.js | 8 | ||||
-rw-r--r-- | models/import.js | 204 |
2 files changed, 80 insertions, 132 deletions
diff --git a/models/boards.js b/models/boards.js index fd0212c5..e42e06c6 100644 --- a/models/boards.js +++ b/models/boards.js @@ -111,6 +111,14 @@ Boards.helpers({ colorClass() { return `board-color-${this.color}`; }, + + // XXX currently mutations return no value so we have an issue when using addLabel in import + // XXX waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... + pushLabel(name, color) { + const _id = Random.id(6); + Boards.direct.update(this._id, { $push: {labels: { _id, name, color }}}); + return _id; + }, }); Boards.mutations({ diff --git a/models/import.js b/models/import.js index 742fcf2f..be699746 100644 --- a/models/import.js +++ b/models/import.js @@ -11,9 +11,9 @@ class TrelloCreator { cards: {}, lists: {}, }; - // the labels we created, indexed by Trello id (to map when importing cards) + // map of labels Trello ID => Wekan ID this.labels = {}; - // the lists we created, indexed by Trello id (to map when importing cards) + // map of lists Trello ID => Wekan ID this.lists = {}; // the comments, indexed by Trello card id (to map when importing cards) this.comments = {}; @@ -25,41 +25,45 @@ class TrelloCreator { date: DateString, type: String, })]); - // XXX perform deeper checks based on type + // XXX we could perform more thorough checks based on action type } checkBoard(trelloBoard) { check(trelloBoard, Match.ObjectIncluding({ closed: Boolean, - labels: [Match.ObjectIncluding({ - // XXX check versus list - color: String, - name: String, - })], name: String, prefs: Match.ObjectIncluding({ - // XXX check versus list + // XXX refine control by validating 'background' against a list of allowed values (is it worth the maintenance?) background: String, - // XXX check versus list - permissionLevel: String, + permissionLevel: Match.Where((value) => {return ['org', 'private', 'public'].indexOf(value)>= 0;}), }), })); } - checkLists(trelloLists) { - check(trelloLists, [Match.ObjectIncluding({ + checkCards(trelloCards) { + check(trelloCards, [Match.ObjectIncluding({ closed: Boolean, + dateLastActivity: DateString, + desc: String, + idLabels: [String], + idMembers: [String], name: String, + pos: Number, })]); } - checkCards(trelloCards) { - check(trelloCards, [Match.ObjectIncluding({ + checkLabels(trelloLabels) { + check(trelloLabels, [Match.ObjectIncluding({ + // XXX refine control by validating 'color' against a list of allowed values (is it worth the maintenance?) + color: String, + name: String, + })]); + } + + checkLists(trelloLists) { + check(trelloLists, [Match.ObjectIncluding({ closed: Boolean, - desc: String, - // XXX check idLabels name: String, - pos: Number, })]); } @@ -78,8 +82,7 @@ class TrelloCreator { isAdmin: true, isActive: true, }], - // current mapping is easy as trello and wekan use same keys: 'private' and 'public' - permission: trelloBoard.prefs.permissionLevel, + permission: this.getPermission(trelloBoard.prefs.permissionLevel), slug: getSlug(trelloBoard.name) || 'board', stars: 0, title: trelloBoard.name, @@ -91,7 +94,7 @@ class TrelloCreator { name: label.name, }; // we need to remember them by Trello ID, as this is the only ref we have when importing cards - this.labels[label.id] = labelToCreate; + this.labels[label.id] = labelToCreate._id; boardToCreate.labels.push(labelToCreate); }); const now = new Date(); @@ -113,6 +116,23 @@ class TrelloCreator { return boardId; } + /** + * Create labels if they do not exist and load this.labels. + */ + createLabels(trelloLabels, board) { + trelloLabels.forEach((label) => { + const color = label.color; + const name = label.name; + const existingLabel = board.getLabel(name, color); + if (existingLabel) { + this.labels[label.id] = existingLabel._id; + } else { + const idLabelCreated = board.pushLabel(name, color); + this.labels[label.id] = idLabelCreated; + } + }); + } + createLists(trelloLists, boardId) { trelloLists.forEach((list) => { const listToCreate = { @@ -125,8 +145,7 @@ class TrelloCreator { const listId = Lists.direct.insert(listToCreate); const now = new Date(); Lists.direct.update(listId, {$set: {'updatedAt': now}}); - listToCreate._id = listId; - this.lists[list.id] = listToCreate; + this.lists[list.id] = listId; // log activity Activities.direct.insert({ activityType: 'importList', @@ -144,6 +163,7 @@ class TrelloCreator { } createCardsAndComments(trelloCards, boardId) { + const result = []; trelloCards.forEach((card) => { const cardToCreate = { archived: card.closed, @@ -151,7 +171,7 @@ class TrelloCreator { createdAt: this.createdAt.cards[card.id], dateLastActivity: new Date(), description: card.desc, - listId: this.lists[card.idList]._id, + listId: this.lists[card.idList], sort: card.pos, title: card.name, // XXX use the original user? @@ -160,7 +180,7 @@ class TrelloCreator { // add labels if(card.idLabels) { cardToCreate.labelIds = card.idLabels.map((trelloId) => { - return this.labels[trelloId]._id; + return this.labels[trelloId]; }); } // insert card @@ -205,7 +225,9 @@ class TrelloCreator { }); } // XXX add attachments + result.push(cardId); }); + return result; } getColor(trelloColorCode) { @@ -225,6 +247,14 @@ class TrelloCreator { return wekanColor || Boards.simpleSchema()._schema.color.allowedValues[0]; } + getPermission(trelloPermissionCode) { + if(trelloPermissionCode === 'public') { + return 'public'; + } + // Wekan does NOT have organization level, so we default both 'private' and 'org' to private. + return 'private'; + } + parseActions(trelloActions) { trelloActions.forEach((action) => { switch (action.type) { @@ -258,19 +288,23 @@ class TrelloCreator { Meteor.methods({ importTrelloBoard(trelloBoard, data) { const trelloCreator = new TrelloCreator(); + // 1. check all parameters are ok from a syntax point of view try { // we don't use additional data - this should be an empty object check(data, {}); trelloCreator.checkActions(trelloBoard.actions); trelloCreator.checkBoard(trelloBoard); + trelloCreator.checkLabels(trelloBoard.labels); trelloCreator.checkLists(trelloBoard.lists); trelloCreator.checkCards(trelloBoard.cards); } catch(e) { throw new Meteor.Error('error-json-schema'); } + // 2. check parameters are ok from a business point of view (exist & authorized) // nothing to check, everyone can import boards in their account + // 3. create all elements trelloCreator.parseActions(trelloBoard.actions); const boardId = trelloCreator.createBoardAndLabels(trelloBoard); @@ -279,33 +313,19 @@ Meteor.methods({ // XXX add members return boardId; }, + importTrelloCard(trelloCard, data) { + const trelloCreator = new TrelloCreator(); + // 1. check parameters are ok from a syntax point of view - const DateString = Match.Where(function (dateAsString) { - check(dateAsString, String); - return moment(dateAsString, moment.ISO_8601).isValid(); - }); try { - check(trelloCard, Match.ObjectIncluding({ - name: String, - desc: String, - closed: Boolean, - dateLastActivity: DateString, - labels: [Match.ObjectIncluding({ - name: String, - color: String, - })], - actions: [Match.ObjectIncluding({ - type: String, - date: DateString, - data: Object, - })], - members: [Object], - })); check(data, { listId: String, sortIndex: Number, }); + trelloCreator.checkCards([trelloCard]); + trelloCreator.checkLabels(trelloCard.labels); + trelloCreator.checkActions(trelloCard.actions); } catch(e) { throw new Meteor.Error('error-json-schema'); } @@ -321,92 +341,12 @@ Meteor.methods({ } } - // 3. map all fields for the card to create - const dateOfImport = new Date(); - const cardToCreate = { - archived: trelloCard.closed, - boardId: list.boardId, - // this is a default date, we'll fetch the actual one from the actions array - createdAt: dateOfImport, - dateLastActivity: dateOfImport, - description: trelloCard.desc, - listId: list._id, - sort: data.sortIndex, - title: trelloCard.name, - // XXX use the original user? - userId: Meteor.userId(), - }; - - // 4. find actual creation date - const creationAction = trelloCard.actions.find((action) => { - return action.type === 'createCard'; - }); - if(creationAction) { - cardToCreate.createdAt = creationAction.date; - } - - // 5. map labels - create missing ones - trelloCard.labels.forEach((currentLabel) => { - const color = currentLabel.color; - const name = currentLabel.name; - const existingLabel = list.board().getLabel(name, color); - let labelId = undefined; - if (existingLabel) { - labelId = existingLabel._id; - } else { - let labelCreated = list.board().addLabel(name, color); - // XXX currently mutations return no value so we have to fetch the label we just created - // waiting on https://github.com/mquandalle/meteor-collection-mutations/issues/1 to remove... - labelCreated = list.board().getLabel(name, color); - labelId = labelCreated._id; - } - if(labelId) { - if (!cardToCreate.labelIds) { - cardToCreate.labelIds = []; - } - cardToCreate.labelIds.push(labelId); - } - }); - - // 6. insert new card into list - const cardId = Cards.direct.insert(cardToCreate); - Activities.direct.insert({ - activityType: 'importCard', - boardId: cardToCreate.boardId, - cardId, - createdAt: dateOfImport, - listId: cardToCreate.listId, - source: { - id: trelloCard.id, - system: 'Trello', - url: trelloCard.url, - }, - // we attribute the import to current user, not the one from the original card - userId: Meteor.userId(), - }); - - // 7. parse actions and add comments - trelloCard.actions.forEach((currentAction) => { - if(currentAction.type === 'commentCard') { - const commentToCreate = { - boardId: list.boardId, - cardId, - createdAt: currentAction.date, - text: currentAction.data.text, - // XXX use the original comment user instead - userId: Meteor.userId(), - }; - const commentId = CardComments.direct.insert(commentToCreate); - Activities.direct.insert({ - activityType: 'addComment', - boardId: commentToCreate.boardId, - cardId: commentToCreate.cardId, - commentId, - createdAt: commentToCreate.createdAt, - userId: commentToCreate.userId, - }); - } - }); - return cardId; + // 3. create all elements + trelloCreator.lists[trelloCard.idList] = data.listId; + trelloCreator.parseActions(trelloCard.actions); + const board = list.board(); + trelloCreator.createLabels(trelloCard.labels, board); + const cardIds = trelloCreator.createCardsAndComments([trelloCard], board._id); + return cardIds[0]; }, }); |