From 1742bcd9b15737c5853e9bcd0a6301139498307d Mon Sep 17 00:00:00 2001 From: Bryan Mutai Date: Thu, 7 May 2020 01:29:22 +0300 Subject: add: import board/cards/lists using CSV/TSV --- client/components/import/csvMembersMapper.js | 37 ++++++++++++++++++++ client/components/import/import.jade | 2 +- client/components/import/import.js | 52 +++++++++++++++++++++------- client/components/sidebar/sidebar.jade | 2 ++ 4 files changed, 80 insertions(+), 13 deletions(-) create mode 100644 client/components/import/csvMembersMapper.js (limited to 'client/components') diff --git a/client/components/import/csvMembersMapper.js b/client/components/import/csvMembersMapper.js new file mode 100644 index 00000000..cf8d5837 --- /dev/null +++ b/client/components/import/csvMembersMapper.js @@ -0,0 +1,37 @@ +export function getMembersToMap(data) { + // we will work on the list itself (an ordered array of objects) when a + // mapping is done, we add a 'wekan' field to the object representing the + // imported member + + const membersToMap = []; + const importedMembers = []; + let membersIndex; + + for (let i = 0; i < data[0].length; i++) { + if (data[0][i].toLowerCase() === 'members') { + membersIndex = i; + } + } + + for (let i = 1; i < data.length; i++) { + if (data[i][membersIndex]) { + for (const importedMember of data[i][membersIndex].split(' ')) { + if (importedMember && importedMembers.indexOf(importedMember) === -1) { + importedMembers.push(importedMember); + } + } + } + } + + for (let importedMember of importedMembers) { + importedMember = { + username: importedMember, + id: importedMember, + }; + const wekanUser = Users.findOne({ username: importedMember.username }); + if (wekanUser) importedMember.wekanId = wekanUser._id; + membersToMap.push(importedMember); + } + + return membersToMap; +} diff --git a/client/components/import/import.jade b/client/components/import/import.jade index 1551a7dd..2bea24ae 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -13,7 +13,7 @@ template(name="import") template(name="importTextarea") form p: label(for='import-textarea') {{_ instruction}} {{_ 'import-board-instruction-about-errors'}} - textarea.js-import-json(placeholder="{{_ 'import-json-placeholder'}}" autofocus) + textarea.js-import-json(placeholder="{{_ importPlaceHolder}}" autofocus) | {{jsonText}} input.primary.wide(type="submit" value="{{_ 'import'}}") diff --git a/client/components/import/import.js b/client/components/import/import.js index 6368885b..673900fd 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,5 +1,8 @@ import trelloMembersMapper from './trelloMembersMapper'; import wekanMembersMapper from './wekanMembersMapper'; +import csvMembersMapper from './csvMembersMapper'; + +const Papa = require('papaparse'); BlazeComponent.extendComponent({ title() { @@ -30,20 +33,30 @@ BlazeComponent.extendComponent({ } }, - importData(evt) { + importData(evt, dataSource) { evt.preventDefault(); - const dataJson = this.find('.js-import-json').value; - try { - const dataObject = JSON.parse(dataJson); - this.setError(''); - this.importedData.set(dataObject); - const membersToMap = this._prepareAdditionalData(dataObject); - // store members data and mapping in Session - // (we go deep and 2-way, so storing in data context is not a viable option) + const input = this.find('.js-import-json').value; + if (dataSource === 'csv') { + const csv = input.indexOf('\t') > 0 ? input.replace(/(\t)/g, ',') : input; + const ret = Papa.parse(csv); + if (ret && ret.data && ret.data.length) this.importedData.set(ret.data); + else throw new Meteor.Error('error-csv-schema'); + const membersToMap = this._prepareAdditionalData(ret.data); this.membersToMap.set(membersToMap); this.nextStep(); - } catch (e) { - this.setError('error-json-malformed'); + } else { + try { + const dataObject = JSON.parse(input); + this.setError(''); + this.importedData.set(dataObject); + const membersToMap = this._prepareAdditionalData(dataObject); + // store members data and mapping in Session + // (we go deep and 2-way, so storing in data context is not a viable option) + this.membersToMap.set(membersToMap); + this.nextStep(); + } catch (e) { + this.setError('error-json-malformed'); + } } }, @@ -91,6 +104,9 @@ BlazeComponent.extendComponent({ case 'wekan': membersToMap = wekanMembersMapper.getMembersToMap(dataObject); break; + case 'csv': + membersToMap = csvMembersMapper.getMembersToMap(dataObject); + break; } return membersToMap; }, @@ -109,11 +125,23 @@ BlazeComponent.extendComponent({ return `import-board-instruction-${Session.get('importSource')}`; }, + importPlaceHolder() { + const importSource = Session.get('importSource'); + if (importSource === 'csv') { + return 'import-csv-placeholder'; + } else { + return 'import-json-placeholder'; + } + }, + events() { return [ { submit(evt) { - return this.parentComponent().importData(evt); + return this.parentComponent().importData( + evt, + Session.get('importSource'), + ); }, }, ]; diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 6bfedc9c..89622ac1 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -230,6 +230,8 @@ template(name="chooseBoardSource") a(href="{{pathFor '/import/trello'}}") {{_ 'from-trello'}} li a(href="{{pathFor '/import/wekan'}}") {{_ 'from-wekan'}} + li + a(href="{{pathFor '/import/csv'}}") {{_ 'from-csv'}} template(name="archiveBoardPopup") p {{_ 'close-board-pop'}} -- cgit v1.2.3-1-g7c22 From a570c4a86157ce4b60e056a4f0583ebc0fe009cf Mon Sep 17 00:00:00 2001 From: Bryan Mutai Date: Sun, 10 May 2020 23:58:15 +0300 Subject: add: export board/cards/lists to CSV/TSV --- client/components/sidebar/sidebar.jade | 17 +++++++++- client/components/sidebar/sidebar.js | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) (limited to 'client/components') diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade index 89622ac1..280eaeaf 100644 --- a/client/components/sidebar/sidebar.jade +++ b/client/components/sidebar/sidebar.jade @@ -302,7 +302,7 @@ template(name="boardMenuPopup") ul.pop-over-list if withApi li - a(href="{{exportUrl}}", download="{{exportFilename}}") + a.js-export-board i.fa.fa-share-alt | {{_ 'export-board'}} li @@ -362,6 +362,21 @@ template(name="boardMenuPopup") i.fa.fa-sitemap | {{_ 'subtask-settings'}} +template(name="exportBoard") + ul.pop-over-list + li + a(href="{{exportUrl}}", download="{{exportJsonFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-json'}} + li + a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-csv'}} + li + a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}") + i.fa.fa-share-alt + | {{_ 'export-board-tsv'}} + template(name="labelsWidget") .board-widget.board-widget-labels h3 diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index cbe00797..0541df5e 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,3 +1,4 @@ +sidebar.js; import { Cookies } from 'meteor/ostrio:cookies'; const cookies = new Cookies(); Sidebar = null; @@ -213,6 +214,7 @@ Template.boardMenuPopup.events({ 'click .js-import-board': Popup.open('chooseBoardSource'), 'click .js-subtask-settings': Popup.open('boardSubtaskSettings'), 'click .js-card-settings': Popup.open('boardCardSettings'), + 'click .js-export-board': Popup.open('exportBoard'), }); Template.boardMenuPopup.onCreated(function() { @@ -405,6 +407,63 @@ BlazeComponent.extendComponent({ }, }).register('chooseBoardSourcePopup'); +BlazeComponent.extendComponent({ + template() { + return 'exportBoard'; + }, + withApi() { + return Template.instance().apiEnabled.get(); + }, + exportUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path('/api/boards/:boardId/export', params, queryParams); + }, + exportCsvUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + }; + return FlowRouter.path( + '/api/boards/:boardId/export/csv', + params, + queryParams, + ); + }, + exportTsvUrl() { + const params = { + boardId: Session.get('currentBoard'), + }; + const queryParams = { + authToken: Accounts._storedLoginToken(), + delimiter: '\t', + }; + return FlowRouter.path( + '/api/boards/:boardId/export/csv', + params, + queryParams, + ); + }, + exportJsonFilename() { + const boardId = Session.get('currentBoard'); + return `wekan-export-board-${boardId}.json`; + }, + exportCsvFilename() { + const boardId = Session.get('currentBoard'); + return `wekan-export-board-${boardId}.csv`; + }, + exportTsvFilename() { + const boardId = Session.get('currentBoard'); + return `wekan-export-board-${boardId}.tsv`; + }, +}).register('exportBoardPopup'); + Template.labelsWidget.events({ 'click .js-label': Popup.open('editLabel'), 'click .js-add-label': Popup.open('createLabel'), -- cgit v1.2.3-1-g7c22 From 8a2509007c21a1056f79d183c71cb9f1b3e0857d Mon Sep 17 00:00:00 2001 From: Lauri Ojansivu Date: Wed, 13 May 2020 05:25:04 +0300 Subject: Fix syntax. Maybe sometime later think about translations. Thanks to xet7 ! --- client/components/sidebar/sidebar.js | 1 - 1 file changed, 1 deletion(-) (limited to 'client/components') diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js index 0541df5e..2c1cfd75 100644 --- a/client/components/sidebar/sidebar.js +++ b/client/components/sidebar/sidebar.js @@ -1,4 +1,3 @@ -sidebar.js; import { Cookies } from 'meteor/ostrio:cookies'; const cookies = new Cookies(); Sidebar = null; -- cgit v1.2.3-1-g7c22