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

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 10 жил өмнө
parent
commit
118b434a5a

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

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

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

@@ -2,7 +2,6 @@ template(name="importPopup")
   if error.get
   if error.get
     .warning {{_ error.get}}
     .warning {{_ error.get}}
   form
   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'}}")
     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({
 const ImportPopup = BlazeComponent.extendComponent({
-  template() {return 'importPopup';},
+  template() {
+    return 'importPopup';
+  },
+
   events() {
   events() {
     return [{
     return [{
       'submit': (evt) => {
       'submit': (evt) => {
         evt.preventDefault();
         evt.preventDefault();
-        const dataJson = $(evt.currentTarget).find('textarea').val();
+        const dataJson = $(evt.currentTarget).find('.js-import-json').val();
         let dataObject;
         let dataObject;
         try {
         try {
           dataObject = JSON.parse(dataJson);
           dataObject = JSON.parse(dataJson);
@@ -52,7 +53,8 @@ const ImportPopup = BlazeComponent.extendComponent({
 ImportPopup.extendComponent({
 ImportPopup.extendComponent({
   getAdditionalData() {
   getAdditionalData() {
     const listId = this.data()._id;
     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 sortIndex = Utils.calculateIndex(null, firstCardDom).base;
     const result = {listId, sortIndex};
     const result = {listId, sortIndex};
     return result;
     return result;

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

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

+ 36 - 24
models/import.js

@@ -5,17 +5,18 @@ const DateString = Match.Where(function (dateAsString) {
 
 
 class TrelloCreator {
 class TrelloCreator {
   constructor() {
   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 = {
     this.createdAt = {
       board: null,
       board: null,
       cards: {},
       cards: {},
       lists: {},
       lists: {},
     };
     };
-    // map of labels Trello ID => Wekan ID
+    // Map of labels Trello ID => Wekan ID
     this.labels = {};
     this.labels = {};
-    // map of lists Trello ID => Wekan ID
+    // Map of lists Trello ID => Wekan ID
     this.lists = {};
     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 = {};
     this.comments = {};
   }
   }
 
 
@@ -33,9 +34,12 @@ class TrelloCreator {
       closed: Boolean,
       closed: Boolean,
       name: String,
       name: String,
       prefs: Match.ObjectIncluding({
       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,
         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) {
   checkLabels(trelloLabels) {
     check(trelloLabels, [Match.ObjectIncluding({
     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,
       color: String,
       name: 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) {
   createBoardAndLabels(trelloBoard) {
     const createdAt = this.createdAt.board;
     const createdAt = this.createdAt.board;
     const boardToCreate = {
     const boardToCreate = {
@@ -93,7 +96,8 @@ class TrelloCreator {
         color: label.color,
         color: label.color,
         name: label.name,
         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;
       this.labels[label.id] = labelToCreate._id;
       boardToCreate.labels.push(labelToCreate);
       boardToCreate.labels.push(labelToCreate);
     });
     });
@@ -110,15 +114,14 @@ class TrelloCreator {
         system: 'Trello',
         system: 'Trello',
         url: trelloBoard.url,
         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(),
       userId: Meteor.userId(),
     });
     });
     return boardId;
     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) {
   createLabels(trelloLabels, board) {
     trelloLabels.forEach((label) => {
     trelloLabels.forEach((label) => {
       const color = label.color;
       const color = label.color;
@@ -138,7 +141,11 @@ class TrelloCreator {
       const listToCreate = {
       const listToCreate = {
         archived: list.closed,
         archived: list.closed,
         boardId,
         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,
         title: list.name,
         userId: Meteor.userId(),
         userId: Meteor.userId(),
       };
       };
@@ -156,7 +163,8 @@ class TrelloCreator {
           id: list.id,
           id: list.id,
           system: 'Trello',
           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(),
         userId: Meteor.userId(),
       });
       });
     });
     });
@@ -168,7 +176,7 @@ class TrelloCreator {
       const cardToCreate = {
       const cardToCreate = {
         archived: card.closed,
         archived: card.closed,
         boardId,
         boardId,
-        createdAt: this.createdAt.cards[card.id],
+        createdAt: new Date(this.createdAt.cards[card.id]  || Date.now()),
         dateLastActivity: new Date(),
         dateLastActivity: new Date(),
         description: card.desc,
         description: card.desc,
         listId: this.lists[card.idList],
         listId: this.lists[card.idList],
@@ -197,7 +205,8 @@ class TrelloCreator {
           system: 'Trello',
           system: 'Trello',
           url: card.url,
           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(),
         userId: Meteor.userId(),
       });
       });
       // add comments
       // add comments
@@ -212,7 +221,8 @@ class TrelloCreator {
             // XXX use the original comment user instead
             // XXX use the original comment user instead
             userId: Meteor.userId(),
             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);
           const commentId = CardComments.direct.insert(commentToCreate);
           Activities.direct.insert({
           Activities.direct.insert({
             activityType: 'addComment',
             activityType: 'addComment',
@@ -251,7 +261,8 @@ class TrelloCreator {
     if(trelloPermissionCode === 'public') {
     if(trelloPermissionCode === 'public') {
       return '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';
     return 'private';
   }
   }
 
 
@@ -302,8 +313,8 @@ Meteor.methods({
       throw new Meteor.Error('error-json-schema');
       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
     // 3. create all elements
     trelloCreator.parseActions(trelloBoard.actions);
     trelloCreator.parseActions(trelloBoard.actions);
@@ -330,7 +341,8 @@ Meteor.methods({
       throw new Meteor.Error('error-json-schema');
       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);
     const list = Lists.findOne(data.listId);
     if(!list) {
     if(!list) {
       throw new Meteor.Error('error-list-doesNotExist');
       throw new Meteor.Error('error-list-doesNotExist');