Преглед изворни кода

Provide a default date for lists and cards creation date

See https://github.com/wekan/wekan/pull/362#issuecomment-149645497
for motivation.

This commit also contains cosmetic changes to the import Popup and
on the code style to be more consistent with the code base.
Maxime Quandalle пре 9 година
родитељ
комит
118b434a5a

+ 3 - 2
client/components/boards/boardHeader.jade

@@ -107,8 +107,9 @@ template(name="createBoardPopup")
           | {{{_ 'board-private-info'}}}
         a.js-change-visibility {{_ 'change'}}.
     input.primary.wide(type="submit" value="{{_ 'create'}}")
-    | {{_ 'or'}}
-    a.js-import {{_ 'import-board'}}
+    span.quiet
+      | {{_ 'or'}}
+      a.js-import {{_ 'import-board'}}
 
 
 template(name="boardChangeTitlePopup")

+ 2 - 3
client/components/import/import.jade

@@ -2,7 +2,6 @@ template(name="importPopup")
   if error.get
     .warning {{_ error.get}}
   form
-    label
-      | {{_ getLabel}}
-      textarea.js-card-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
+    p: label(for='import-textarea') {{_ getLabel}}
+    textarea#import-textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus)
     input.primary.wide(type="submit" value="{{_ 'import'}}")

+ 15 - 13
client/components/import/import.js

@@ -1,20 +1,21 @@
-/**
- * Abstract root for all import popup screens.
- * Descendants must define:
- * - getMethodName(): return the Meteor method to call for import, passing json data decoded as object
- * and additional data (see below)
- * - getAdditionalData(): return object containing additional data passed to Meteor method
- * (like list ID and position for a card import)
- * - getLabel(): i18n key for the text displayed in the popup, usually to explain how to get the data out of the
- * source system.
- */
+/// Abstract root for all import popup screens.
+/// Descendants must define:
+/// - getMethodName(): return the Meteor method to call for import, passing json
+/// data decoded as object and additional data (see below);
+/// - getAdditionalData(): return object containing additional data passed to
+/// Meteor method (like list ID and position for a card import);
+/// - getLabel(): i18n key for the text displayed in the popup, usually to
+/// explain how to get the data out of the source system.
 const ImportPopup = BlazeComponent.extendComponent({
-  template() {return 'importPopup';},
+  template() {
+    return 'importPopup';
+  },
+
   events() {
     return [{
       'submit': (evt) => {
         evt.preventDefault();
-        const dataJson = $(evt.currentTarget).find('textarea').val();
+        const dataJson = $(evt.currentTarget).find('.js-import-json').val();
         let dataObject;
         try {
           dataObject = JSON.parse(dataJson);
@@ -52,7 +53,8 @@ const ImportPopup = BlazeComponent.extendComponent({
 ImportPopup.extendComponent({
   getAdditionalData() {
     const listId = this.data()._id;
-    const firstCardDom = $(`#js-list-${this.currentData()._id} .js-minicard:first`).get(0);
+    const selector = `#js-list-${this.currentData()._id} .js-minicard:first`;
+    const firstCardDom = $(selector).get(0);
     const sortIndex = Utils.calculateIndex(null, firstCardDom).base;
     const result = {listId, sortIndex};
     return result;

+ 3 - 3
client/components/main/popup.styl

@@ -17,9 +17,11 @@ $popupWidth = 300px
     margin: 4px -10px
     width: $popupWidth
 
+  p,
+  textarea,
   input[type="text"],
   input[type="email"],
-  input[type="password"]
+  input[type="password"],
   input[type="file"]
     margin: 4px 0 12px
     width: 100%
@@ -30,8 +32,6 @@ $popupWidth = 300px
 
   textarea
     height: 72px
-    margin: 4px 0 12px
-    width: 100%
 
   .header
     height: 36px

+ 36 - 24
models/import.js

@@ -5,17 +5,18 @@ const DateString = Match.Where(function (dateAsString) {
 
 class TrelloCreator {
   constructor() {
-    // the object creation dates, indexed by Trello id (so we only parse actions once!)
+    // The object creation dates, indexed by Trello id (so we only parse actions
+    // once!)
     this.createdAt = {
       board: null,
       cards: {},
       lists: {},
     };
-    // map of labels Trello ID => Wekan ID
+    // Map of labels Trello ID => Wekan ID
     this.labels = {};
-    // map of lists Trello ID => Wekan ID
+    // Map of lists Trello ID => Wekan ID
     this.lists = {};
-    // the comments, indexed by Trello card id (to map when importing cards)
+    // The comments, indexed by Trello card id (to map when importing cards)
     this.comments = {};
   }
 
@@ -33,9 +34,12 @@ class TrelloCreator {
       closed: Boolean,
       name: String,
       prefs: Match.ObjectIncluding({
-        // XXX refine control by validating 'background' against a list of allowed values (is it worth the maintenance?)
+        // XXX refine control by validating 'background' against a list of
+        // allowed values (is it worth the maintenance?)
         background: String,
-        permissionLevel: Match.Where((value) => {return ['org', 'private', 'public'].indexOf(value)>= 0;}),
+        permissionLevel: Match.Where((value) => {
+          return ['org', 'private', 'public'].indexOf(value)>= 0;
+        }),
       }),
     }));
   }
@@ -54,7 +58,8 @@ class TrelloCreator {
 
   checkLabels(trelloLabels) {
     check(trelloLabels, [Match.ObjectIncluding({
-      // XXX refine control by validating 'color' against a list of allowed values (is it worth the maintenance?)
+      // XXX refine control by validating 'color' against a list of allowed
+      // values (is it worth the maintenance?)
       color: String,
       name: String,
     })]);
@@ -67,9 +72,7 @@ class TrelloCreator {
     })]);
   }
 
-  /**
-   * must call parseActions before calling this one
-   */
+  // You must call parseActions before calling this one.
   createBoardAndLabels(trelloBoard) {
     const createdAt = this.createdAt.board;
     const boardToCreate = {
@@ -93,7 +96,8 @@ class TrelloCreator {
         color: label.color,
         name: label.name,
       };
-      // we need to remember them by Trello ID, as this is the only ref we have when importing cards
+      // We need to remember them by Trello ID, as this is the only ref we have
+      // when importing cards.
       this.labels[label.id] = labelToCreate._id;
       boardToCreate.labels.push(labelToCreate);
     });
@@ -110,15 +114,14 @@ class TrelloCreator {
         system: 'Trello',
         url: trelloBoard.url,
       },
-      // we attribute the import to current user, not the one from the original object
+      // We attribute the import to current user, not the one from the original
+      // object.
       userId: Meteor.userId(),
     });
     return boardId;
   }
 
-  /**
-   * Create labels if they do not exist and load this.labels.
-   */
+  // Create labels if they do not exist and load this.labels.
   createLabels(trelloLabels, board) {
     trelloLabels.forEach((label) => {
       const color = label.color;
@@ -138,7 +141,11 @@ class TrelloCreator {
       const listToCreate = {
         archived: list.closed,
         boardId,
-        createdAt: this.createdAt.lists[list.id],
+        // We are being defensing here by providing a default date (now) if the
+        // creation date wasn't found on the action log. This happen on old
+        // Trello boards (eg from 2013) that didn't log the 'createList' action
+        // we require.
+        createdAt: new Date(this.createdAt.lists[list.id] || Date.now()),
         title: list.name,
         userId: Meteor.userId(),
       };
@@ -156,7 +163,8 @@ class TrelloCreator {
           id: list.id,
           system: 'Trello',
         },
-        // we attribute the import to current user, not the one from the original object
+        // We attribute the import to current user, not the one from the
+        // original object
         userId: Meteor.userId(),
       });
     });
@@ -168,7 +176,7 @@ class TrelloCreator {
       const cardToCreate = {
         archived: card.closed,
         boardId,
-        createdAt: this.createdAt.cards[card.id],
+        createdAt: new Date(this.createdAt.cards[card.id]  || Date.now()),
         dateLastActivity: new Date(),
         description: card.desc,
         listId: this.lists[card.idList],
@@ -197,7 +205,8 @@ class TrelloCreator {
           system: 'Trello',
           url: card.url,
         },
-        // we attribute the import to current user, not the one from the original card
+        // we attribute the import to current user, not the one from the
+        // original card
         userId: Meteor.userId(),
       });
       // add comments
@@ -212,7 +221,8 @@ class TrelloCreator {
             // XXX use the original comment user instead
             userId: Meteor.userId(),
           };
-          // dateLastActivity will be set from activity insert, no need to update it ourselves
+          // dateLastActivity will be set from activity insert, no need to
+          // update it ourselves
           const commentId = CardComments.direct.insert(commentToCreate);
           Activities.direct.insert({
             activityType: 'addComment',
@@ -251,7 +261,8 @@ class TrelloCreator {
     if(trelloPermissionCode === 'public') {
       return 'public';
     }
-    // Wekan does NOT have organization level, so we default both 'private' and 'org' to private.
+    // Wekan does NOT have organization level, so we default both 'private' and
+    // 'org' to private.
     return 'private';
   }
 
@@ -302,8 +313,8 @@ Meteor.methods({
       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
+    // 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);
@@ -330,7 +341,8 @@ Meteor.methods({
       throw new Meteor.Error('error-json-schema');
     }
 
-    // 2. check parameters are ok from a business point of view (exist & authorized)
+    // 2. check parameters are ok from a business point of view (exist &
+    // authorized)
     const list = Lists.findOne(data.listId);
     if(!list) {
       throw new Meteor.Error('error-list-doesNotExist');