diff --git a/models/import.js b/models/import.js index ba68bd66c..7c6a30e28 100644 --- a/models/import.js +++ b/models/import.js @@ -42,9 +42,23 @@ Meteor.methods({ check(currentBoardId, Match.Maybe(String)); const exporter = new Exporter(sourceBoardId); const data = exporter.build(); - const addData = {}; - addData.membersMapping = getMembersToMap(data); - const creator = new WekanCreator(addData); + const additionalData = {}; + + //get the members to map + const membersMapping = getMembersToMap(data); + + //now mirror the mapping done in finishImport in client/components/import/import.js: + if (membersMapping) { + const mappingById = {}; + membersMapping.forEach(member => { + if (member.wekanId) { + mappingById[member.id] = member.wekanId; + } + }); + additionalData.membersMapping = mappingById; + } + + const creator = new WekanCreator(additionalData); //data.title = `${data.title } - ${ TAPi18n.__('copy-tag')}`; data.title = `${data.title}`; return creator.create(data, currentBoardId); diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 9914f8171..77e5fa6d8 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -15,6 +15,7 @@ export class WekanCreator { cards: {}, lists: {}, swimlanes: {}, + customFields: {}, }; // The object creator Wekan Id, indexed by the object Wekan id // (so we only parse actions once!) @@ -30,6 +31,8 @@ export class WekanCreator { this.lists = {}; // Map of cards Wekan ID => Wekan ID this.cards = {}; + // Map of custom fields Wekan ID => Wekan ID + this.customFields = {}; // Map of comments Wekan ID => Wekan ID this.commentIds = {}; // Map of attachments Wekan ID => Wekan ID @@ -244,6 +247,7 @@ export class WekanCreator { swimlaneId: false, }, ], + presentParentTask: boardToImport.presentParentTask, // Standalone Export has modifiedAt missing, adding modifiedAt to fix it modifiedAt: this._now(boardToImport.modifiedAt), permission: boardToImport.permission, @@ -352,10 +356,40 @@ export class WekanCreator { cardToCreate.members = wekanMembers; } } + // add assignees + if (card.assignees) { + const wekanAssignees = []; + // we can't just map, as some members may not have been mapped + card.assignees.forEach(sourceMemberId => { + if (this.members[sourceMemberId]) { + const wekanId = this.members[sourceMemberId]; + // we may map multiple Wekan members to the same wekan user + // in which case we risk adding the same user multiple times + if (!wekanAssignees.find(wId => wId === wekanId)) { + wekanAssignees.push(wekanId); + } + } + return true; + }); + if (wekanAssignees.length > 0) { + cardToCreate.assignees = wekanAssignees; + } + } // set color if (card.color) { cardToCreate.color = card.color; } + + // add custom fields + if (card.customFields) { + cardToCreate.customFields = card.customFields.map(field => { + return { + _id: this.customFields[field._id], + value: field.value, + }; + }); + } + // insert card const cardId = Cards.direct.insert(cardToCreate); // keep track of Wekan id => Wekan id @@ -481,6 +515,39 @@ export class WekanCreator { return result; } + /** + * Create the Wekan custom fields corresponding to the supplied Wekan + * custom fields. + * @param wekanCustomFields + * @param boardId + */ + createCustomFields(wekanCustomFields, boardId) { + wekanCustomFields.forEach((field, fieldIndex) => { + const fieldToCreate = { + boardIds: [boardId], + name: field.name, + type: field.type, + settings: field.settings, + showOnCard: field.showOnCard, + showLabelOnMiniCard: field.showLabelOnMiniCard, + automaticallyOnCard: field.automaticallyOnCard, + //use date "now" if now created at date is provided (e.g. for very old boards) + createdAt: this._now(this.createdAt.customFields[field._id]), + modifiedAt: field.modifiedAt, + }; + //insert copy of custom field + const fieldId = CustomFields.direct.insert(fieldToCreate); + //set modified date to now + CustomFields.direct.update(fieldId, { + $set: { + modifiedAt: this._now(), + }, + }); + //store mapping of old id to new id + this.customFields[field._id] = fieldId; + }); + } + // Create labels if they do not exist and load this.labels. createLabels(wekanLabels, board) { wekanLabels.forEach(label => { @@ -560,6 +627,35 @@ export class WekanCreator { }); } + createSubtasks(wekanCards) { + wekanCards.forEach(card => { + // get new id of card (in created / new board) + const cardIdInNewBoard = this.cards[card._id]; + + //If there is a mapped parent card, use the mapped card + // this means, the card and parent were in the same source board + //If there is no mapped parent card, use the original parent id, + // this should handle cases where source and parent are in different boards + // Note: This can only handle board cloning (within the same wekan instance). + // When importing boards between instances the IDs are definitely + // lost if source and parent are two different boards + // This is not the place to fix it, the entire subtask system needs to be rethought there. + const parentIdInNewBoard = this.cards[card.parentId] + ? this.cards[card.parentId] + : card.parentId; + + //if the parent card exists, proceed + if (Cards.findOne(parentIdInNewBoard)) { + //set parent id of the card in the new board to the new id of the parent + Cards.direct.update(cardIdInNewBoard, { + $set: { + parentId: parentIdInNewBoard, + }, + }); + } + }); + } + createChecklists(wekanChecklists) { const result = []; wekanChecklists.forEach((checklist, checklistIndex) => { @@ -690,6 +786,11 @@ export class WekanCreator { this.createdAt.swimlanes[swimlaneId] = activity.createdAt; break; } + case 'createCustomField': { + const customFieldId = activity.customFieldId; + this.createdAt.customFields[customFieldId] = activity.createdAt; + break; + } } }); } @@ -840,7 +941,9 @@ export class WekanCreator { const boardId = this.createBoardAndLabels(board); this.createLists(board.lists, boardId); this.createSwimlanes(board.swimlanes, boardId); + this.createCustomFields(board.customFields, boardId); this.createCards(board.cards, boardId); + this.createSubtasks(board.cards); this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId);