import.js 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. import { trelloGetMembersToMap } from './trelloMembersMapper';
  3. import { wekanGetMembersToMap } from './wekanMembersMapper';
  4. import { csvGetMembersToMap } from './csvMembersMapper';
  5. import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
  6. const Papa = require('papaparse');
  7. BlazeComponent.extendComponent({
  8. title() {
  9. return `import-board-title-${Session.get('importSource')}`;
  10. },
  11. }).register('importHeaderBar');
  12. BlazeComponent.extendComponent({
  13. onCreated() {
  14. this.error = new ReactiveVar('');
  15. this.steps = ['importTextarea', 'importMapMembers'];
  16. this._currentStepIndex = new ReactiveVar(0);
  17. this.importedData = new ReactiveVar();
  18. this.membersToMap = new ReactiveVar([]);
  19. this.importSource = Session.get('importSource');
  20. },
  21. currentTemplate() {
  22. return this.steps[this._currentStepIndex.get()];
  23. },
  24. nextStep() {
  25. const nextStepIndex = this._currentStepIndex.get() + 1;
  26. if (nextStepIndex >= this.steps.length) {
  27. this.finishImport();
  28. } else {
  29. this._currentStepIndex.set(nextStepIndex);
  30. }
  31. },
  32. importData(evt, dataSource) {
  33. evt.preventDefault();
  34. const input = this.find('.js-import-json').value;
  35. if (dataSource === 'csv') {
  36. const csv = input.indexOf('\t') > 0 ? input.replace(/(\t)/g, ',') : input;
  37. const ret = Papa.parse(csv);
  38. if (ret && ret.data && ret.data.length) this.importedData.set(ret.data);
  39. else throw new Meteor.Error('error-csv-schema');
  40. const membersToMap = this._prepareAdditionalData(ret.data);
  41. this.membersToMap.set(membersToMap);
  42. this.nextStep();
  43. } else {
  44. try {
  45. const dataObject = JSON.parse(input);
  46. this.setError('');
  47. this.importedData.set(dataObject);
  48. const membersToMap = this._prepareAdditionalData(dataObject);
  49. // store members data and mapping in Session
  50. // (we go deep and 2-way, so storing in data context is not a viable option)
  51. this.membersToMap.set(membersToMap);
  52. this.nextStep();
  53. } catch (e) {
  54. this.setError('error-json-malformed');
  55. }
  56. }
  57. },
  58. setError(error) {
  59. this.error.set(error);
  60. },
  61. finishImport() {
  62. const additionalData = {};
  63. const membersMapping = this.membersToMap.get();
  64. if (membersMapping) {
  65. const mappingById = {};
  66. membersMapping.forEach(member => {
  67. if (member.wekanId) {
  68. mappingById[member.id] = member.wekanId;
  69. }
  70. });
  71. additionalData.membersMapping = mappingById;
  72. }
  73. this.membersToMap.set([]);
  74. Meteor.call(
  75. 'importBoard',
  76. this.importedData.get(),
  77. additionalData,
  78. this.importSource,
  79. Session.get('fromBoard'),
  80. (err, res) => {
  81. if (err) {
  82. this.setError(err.error);
  83. } else {
  84. let title = getSlug(this.importedData.get().title) || 'imported-board';
  85. Session.set('fromBoard', null);
  86. FlowRouter.go('board', {
  87. id: res,
  88. slug: title,
  89. })
  90. //Utils.goBoardId(res);
  91. }
  92. },
  93. );
  94. },
  95. _prepareAdditionalData(dataObject) {
  96. const importSource = Session.get('importSource');
  97. let membersToMap;
  98. switch (importSource) {
  99. case 'trello':
  100. membersToMap = trelloGetMembersToMap(dataObject);
  101. break;
  102. case 'wekan':
  103. membersToMap = wekanGetMembersToMap(dataObject);
  104. break;
  105. case 'csv':
  106. membersToMap = csvGetMembersToMap(dataObject);
  107. break;
  108. }
  109. return membersToMap;
  110. },
  111. _screenAdditionalData() {
  112. return 'mapMembers';
  113. },
  114. }).register('import');
  115. BlazeComponent.extendComponent({
  116. template() {
  117. return 'importTextarea';
  118. },
  119. instruction() {
  120. return `import-board-instruction-${Session.get('importSource')}`;
  121. },
  122. importPlaceHolder() {
  123. const importSource = Session.get('importSource');
  124. if (importSource === 'csv') {
  125. return 'import-csv-placeholder';
  126. } else {
  127. return 'import-json-placeholder';
  128. }
  129. },
  130. events() {
  131. return [
  132. {
  133. submit(evt) {
  134. return this.parentComponent().importData(
  135. evt,
  136. Session.get('importSource'),
  137. );
  138. },
  139. },
  140. ];
  141. },
  142. }).register('importTextarea');
  143. BlazeComponent.extendComponent({
  144. onCreated() {
  145. this.usersLoaded = new ReactiveVar(false);
  146. this.autorun(() => {
  147. const handle = this.subscribe(
  148. 'user-miniprofile',
  149. this.members().map(member => {
  150. return member.username;
  151. }),
  152. );
  153. Tracker.nonreactive(() => {
  154. Tracker.autorun(() => {
  155. if (
  156. handle.ready() &&
  157. !this.usersLoaded.get() &&
  158. this.members().length
  159. ) {
  160. this._refreshMembers(
  161. this.members().map(member => {
  162. if (!member.wekanId) {
  163. let user = ReactiveCache.getUser({ username: member.username });
  164. if (!user) {
  165. user = ReactiveCache.getUser({ importUsernames: member.username });
  166. }
  167. if (user) {
  168. // eslint-disable-next-line no-console
  169. // console.log('found username:', user.username);
  170. member.wekanId = user._id;
  171. }
  172. }
  173. return member;
  174. }),
  175. );
  176. }
  177. this.usersLoaded.set(handle.ready());
  178. });
  179. });
  180. });
  181. },
  182. members() {
  183. return this.parentComponent().membersToMap.get();
  184. },
  185. _refreshMembers(listOfMembers) {
  186. return this.parentComponent().membersToMap.set(listOfMembers);
  187. },
  188. /**
  189. * Will look into the list of members to import for the specified memberId,
  190. * then set its property to the supplied value.
  191. * If unset is true, it will remove the property from the rest of the list as well.
  192. *
  193. * use:
  194. * - memberId = null to use selected member
  195. * - value = null to unset a property
  196. * - unset = true to ensure property is only set on 1 member at a time
  197. */
  198. _setPropertyForMember(property, value, memberId, unset = false) {
  199. const listOfMembers = this.members();
  200. let finder = null;
  201. if (memberId) {
  202. finder = member => member.id === memberId;
  203. } else {
  204. finder = member => member.selected;
  205. }
  206. listOfMembers.forEach(member => {
  207. if (finder(member)) {
  208. if (value !== null) {
  209. member[property] = value;
  210. } else {
  211. delete member[property];
  212. }
  213. if (!unset) {
  214. // we shortcut if we don't care about unsetting the others
  215. return false;
  216. }
  217. } else if (unset) {
  218. delete member[property];
  219. }
  220. return true;
  221. });
  222. // Session.get gives us a copy, we have to set it back so it sticks
  223. this._refreshMembers(listOfMembers);
  224. },
  225. setSelectedMember(memberId) {
  226. return this._setPropertyForMember('selected', true, memberId, true);
  227. },
  228. /**
  229. * returns the member with specified id,
  230. * or the selected member if memberId is not specified
  231. */
  232. getMember(memberId = null) {
  233. const allMembers = this.members();
  234. let finder = null;
  235. if (memberId) {
  236. finder = user => user.id === memberId;
  237. } else {
  238. finder = user => user.selected;
  239. }
  240. return allMembers.find(finder);
  241. },
  242. mapSelectedMember(wekanId) {
  243. return this._setPropertyForMember('wekanId', wekanId, null);
  244. },
  245. unmapMember(memberId) {
  246. return this._setPropertyForMember('wekanId', null, memberId);
  247. },
  248. onSubmit(evt) {
  249. evt.preventDefault();
  250. this.parentComponent().nextStep();
  251. },
  252. events() {
  253. return [
  254. {
  255. submit: this.onSubmit,
  256. 'click .js-select-member'(evt) {
  257. const memberToMap = this.currentData();
  258. if (memberToMap.wekan) {
  259. // todo xxx ask for confirmation?
  260. this.unmapMember(memberToMap.id);
  261. } else {
  262. this.setSelectedMember(memberToMap.id);
  263. Popup.open('importMapMembersAdd')(evt);
  264. }
  265. },
  266. },
  267. ];
  268. },
  269. }).register('importMapMembers');
  270. BlazeComponent.extendComponent({
  271. onRendered() {
  272. this.find('.js-map-member input').focus();
  273. },
  274. onSelectUser() {
  275. Popup.getOpenerComponent(5).mapSelectedMember(this.currentData().__originalId);
  276. Popup.back();
  277. },
  278. events() {
  279. return [
  280. {
  281. 'click .js-select-import': this.onSelectUser,
  282. },
  283. ];
  284. },
  285. }).register('importMapMembersAddPopup');
  286. Template.importMapMembersAddPopup.helpers({
  287. searchIndex: () => UserSearchIndex,
  288. })