2
0
Эх сурвалжийг харах

Import board: map team permission, and refactor code to share with card import

Xavier Priour 9 жил өмнө
parent
commit
8e0ad91191
2 өөрчлөгдсөн 80 нэмэгдсэн , 132 устгасан
  1. 8 0
      models/boards.js
  2. 72 132
      models/import.js

+ 8 - 0
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({

+ 72 - 132
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];
   },
 });