rulesHelper.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401
  1. import { ReactiveCache } from '/imports/reactiveCache';
  2. RulesHelper = {
  3. executeRules(activity) {
  4. const matchingRules = this.findMatchingRules(activity);
  5. for (let i = 0; i < matchingRules.length; i++) {
  6. const action = matchingRules[i].getAction();
  7. if (action !== undefined) {
  8. this.performAction(activity, action);
  9. }
  10. }
  11. },
  12. findMatchingRules(activity) {
  13. const activityType = activity.activityType;
  14. if (TriggersDef[activityType] === undefined) {
  15. return [];
  16. }
  17. const matchingFields = TriggersDef[activityType].matchingFields;
  18. const matchingMap = this.buildMatchingFieldsMap(activity, matchingFields);
  19. const matchingTriggers = ReactiveCache.getTriggers(matchingMap);
  20. const matchingRules = [];
  21. matchingTriggers.forEach(function(trigger) {
  22. const rule = trigger.getRule();
  23. // Check that for some unknown reason there are some leftover triggers
  24. // not connected to any rules
  25. if (rule !== undefined) {
  26. matchingRules.push(trigger.getRule());
  27. }
  28. });
  29. return matchingRules;
  30. },
  31. buildMatchingFieldsMap(activity, matchingFields) {
  32. const matchingMap = { activityType: activity.activityType };
  33. matchingFields.forEach(field => {
  34. // Creating a matching map with the actual field of the activity
  35. // and with the wildcard (for example: trigger when a card is added
  36. // in any [*] board
  37. let value = activity[field];
  38. if (field === 'oldListName') {
  39. const oldList = ReactiveCache.getList(activity.oldListId);
  40. if (oldList) {
  41. value = oldList.title;
  42. }
  43. } else if (field === 'oldSwimlaneName') {
  44. const oldSwimlane = ReactiveCache.getSwimlane(activity.oldSwimlaneId);
  45. if (oldSwimlane) {
  46. value = oldSwimlane.title;
  47. }
  48. }
  49. let matchesList = [value, '*'];
  50. if ((field === 'cardTitle') && (value !== undefined)) {
  51. matchesList = value.split(/\W/).concat(matchesList);
  52. }
  53. matchingMap[field] = {
  54. $in: matchesList,
  55. };
  56. });
  57. return matchingMap;
  58. },
  59. performAction(activity, action) {
  60. const card = ReactiveCache.getCard(activity.cardId);
  61. const boardId = activity.boardId;
  62. if (
  63. action.actionType === 'moveCardToTop' ||
  64. action.actionType === 'moveCardToBottom'
  65. ) {
  66. let list;
  67. let listId;
  68. if (action.listName === '*') {
  69. list = card.list();
  70. if (boardId !== action.boardId) {
  71. list = ReactiveCache.getList({ title: list.title, boardId: action.boardId });
  72. }
  73. } else {
  74. list = ReactiveCache.getList({
  75. title: action.listName,
  76. boardId: action.boardId,
  77. });
  78. }
  79. if (list === undefined) {
  80. listId = '';
  81. } else {
  82. listId = list._id;
  83. }
  84. let swimlane;
  85. let swimlaneId;
  86. if (action.swimlaneName === '*') {
  87. swimlane = ReactiveCache.getSwimlane(card.swimlaneId);
  88. if (boardId !== action.boardId) {
  89. swimlane = ReactiveCache.getSwimlane({
  90. title: swimlane.title,
  91. boardId: action.boardId,
  92. });
  93. }
  94. } else {
  95. swimlane = ReactiveCache.getSwimlane({
  96. title: action.swimlaneName,
  97. boardId: action.boardId,
  98. });
  99. }
  100. if (swimlane === undefined) {
  101. swimlaneId = ReactiveCache.getSwimlane({
  102. title: 'Default',
  103. boardId: action.boardId,
  104. })._id;
  105. } else {
  106. swimlaneId = swimlane._id;
  107. }
  108. if (action.actionType === 'moveCardToTop') {
  109. const minOrder = _.min(
  110. list.cardsUnfiltered(swimlaneId).map(c => c.sort),
  111. );
  112. card.move(action.boardId, swimlaneId, listId, minOrder - 1);
  113. } else {
  114. const maxOrder = _.max(
  115. list.cardsUnfiltered(swimlaneId).map(c => c.sort),
  116. );
  117. card.move(action.boardId, swimlaneId, listId, maxOrder + 1);
  118. }
  119. }
  120. if (action.actionType === 'sendEmail') {
  121. const to = action.emailTo;
  122. const text = action.emailMsg || '';
  123. const subject = action.emailSubject || '';
  124. try {
  125. // Try to detect the recipient's language preference if it's a Wekan user
  126. // Otherwise, use the default language for the rule-triggered emails
  127. let recipientUser = null;
  128. let recipientLang = TAPi18n.getLanguage() || 'en';
  129. // Check if recipient is a Wekan user to get their language
  130. if (to && to.includes('@')) {
  131. recipientUser = ReactiveCache.getUser({ 'emails.address': to.toLowerCase() });
  132. if (recipientUser && typeof recipientUser.getLanguage === 'function') {
  133. recipientLang = recipientUser.getLanguage();
  134. }
  135. }
  136. // Use EmailLocalization if available
  137. if (typeof EmailLocalization !== 'undefined') {
  138. EmailLocalization.sendEmail({
  139. to,
  140. from: Accounts.emailTemplates.from,
  141. subject,
  142. text,
  143. language: recipientLang,
  144. userId: recipientUser ? recipientUser._id : null
  145. });
  146. } else {
  147. // Fallback to standard Email.send
  148. Email.send({
  149. to,
  150. from: Accounts.emailTemplates.from,
  151. subject,
  152. text,
  153. });
  154. }
  155. } catch (e) {
  156. // eslint-disable-next-line no-console
  157. console.error(e);
  158. return;
  159. }
  160. }
  161. if (action.actionType === 'setDate') {
  162. try {
  163. const currentDateTime = new Date();
  164. switch (action.dateField) {
  165. case 'startAt': {
  166. const resStart = card.getStart();
  167. if (typeof resStart === 'undefined') {
  168. card.setStart(currentDateTime);
  169. }
  170. break;
  171. }
  172. case 'endAt': {
  173. const resEnd = card.getEnd();
  174. if (typeof resEnd === 'undefined') {
  175. card.setEnd(currentDateTime);
  176. }
  177. break;
  178. }
  179. case 'dueAt': {
  180. const resDue = card.getDue();
  181. if (typeof resDue === 'undefined') {
  182. card.setDue(currentDateTime);
  183. }
  184. break;
  185. }
  186. case 'receivedAt': {
  187. const resReceived = card.getReceived();
  188. if (typeof resReceived === 'undefined') {
  189. card.setReceived(currentDateTime);
  190. }
  191. break;
  192. }
  193. }
  194. } catch (e) {
  195. // eslint-disable-next-line no-console
  196. console.error(e);
  197. return;
  198. }
  199. }
  200. if (action.actionType === 'updateDate') {
  201. const currentDateTimeUpdate = new Date();
  202. switch (action.dateField) {
  203. case 'startAt': {
  204. card.setStart(currentDateTimeUpdate);
  205. break;
  206. }
  207. case 'endAt': {
  208. card.setEnd(currentDateTimeUpdate);
  209. break;
  210. }
  211. case 'dueAt': {
  212. card.setDue(currentDateTimeUpdate);
  213. break;
  214. }
  215. case 'receivedAt': {
  216. card.setReceived(currentDateTimeUpdate);
  217. break;
  218. }
  219. }
  220. }
  221. if (action.actionType === 'removeDate') {
  222. switch (action.dateField) {
  223. case 'startAt': {
  224. card.unsetStart();
  225. break;
  226. }
  227. case 'endAt': {
  228. card.unsetEnd();
  229. break;
  230. }
  231. case 'dueAt': {
  232. card.unsetDue();
  233. break;
  234. }
  235. case 'receivedAt': {
  236. card.unsetReceived();
  237. break;
  238. }
  239. }
  240. }
  241. if (action.actionType === 'archive') {
  242. card.archive();
  243. }
  244. if (action.actionType === 'unarchive') {
  245. card.restore();
  246. }
  247. if (action.actionType === 'setColor') {
  248. card.setColor(action.selectedColor);
  249. }
  250. if (action.actionType === 'addLabel') {
  251. card.addLabel(action.labelId);
  252. }
  253. if (action.actionType === 'removeLabel') {
  254. card.removeLabel(action.labelId);
  255. }
  256. if (action.actionType === 'addMember') {
  257. const memberId = ReactiveCache.getUser({ username: action.username })._id;
  258. card.assignMember(memberId);
  259. }
  260. if (action.actionType === 'removeMember') {
  261. if (action.username === '*') {
  262. const members = card.members;
  263. for (let i = 0; i < members.length; i++) {
  264. card.unassignMember(members[i]);
  265. }
  266. } else {
  267. const memberId = ReactiveCache.getUser({ username: action.username })._id;
  268. card.unassignMember(memberId);
  269. }
  270. }
  271. if (action.actionType === 'checkAll') {
  272. const checkList = ReactiveCache.getChecklist({
  273. title: action.checklistName,
  274. cardId: card._id,
  275. });
  276. checkList.checkAllItems();
  277. }
  278. if (action.actionType === 'uncheckAll') {
  279. const checkList = ReactiveCache.getChecklist({
  280. title: action.checklistName,
  281. cardId: card._id,
  282. });
  283. checkList.uncheckAllItems();
  284. }
  285. if (action.actionType === 'checkItem') {
  286. const checkList = ReactiveCache.getChecklist({
  287. title: action.checklistName,
  288. cardId: card._id,
  289. });
  290. const checkItem = ReactiveCache.getChecklistItem({
  291. title: action.checkItemName,
  292. checkListId: checkList._id,
  293. });
  294. checkItem.check();
  295. }
  296. if (action.actionType === 'uncheckItem') {
  297. const checkList = ReactiveCache.getChecklist({
  298. title: action.checklistName,
  299. cardId: card._id,
  300. });
  301. const checkItem = ReactiveCache.getChecklistItem({
  302. title: action.checkItemName,
  303. checkListId: checkList._id,
  304. });
  305. checkItem.uncheck();
  306. }
  307. if (action.actionType === 'addChecklist') {
  308. Checklists.insert({
  309. title: action.checklistName,
  310. cardId: card._id,
  311. sort: 0,
  312. });
  313. }
  314. if (action.actionType === 'removeChecklist') {
  315. Checklists.remove({
  316. title: action.checklistName,
  317. cardId: card._id,
  318. sort: 0,
  319. });
  320. }
  321. if (action.actionType === 'addSwimlane') {
  322. Swimlanes.insert({
  323. title: action.swimlaneName,
  324. boardId,
  325. sort: 0,
  326. });
  327. }
  328. if (action.actionType === 'addChecklistWithItems') {
  329. const checkListId = Checklists.insert({
  330. title: action.checklistName,
  331. cardId: card._id,
  332. sort: 0,
  333. });
  334. const itemsArray = action.checklistItems.split(',');
  335. const checkList = ReactiveCache.getChecklist(checkListId);
  336. for (let i = 0; i < itemsArray.length; i++) {
  337. ChecklistItems.insert({
  338. title: itemsArray[i],
  339. checklistId: checkListId,
  340. cardId: card._id,
  341. sort: checkList.itemCount(),
  342. });
  343. }
  344. }
  345. if (action.actionType === 'createCard') {
  346. const list = ReactiveCache.getList({ title: action.listName, boardId });
  347. let listId = '';
  348. let swimlaneId = '';
  349. const swimlane = ReactiveCache.getSwimlane({
  350. title: action.swimlaneName,
  351. boardId,
  352. });
  353. if (list === undefined) {
  354. listId = '';
  355. } else {
  356. listId = list._id;
  357. }
  358. if (swimlane === undefined) {
  359. swimlaneId = ReactiveCache.getSwimlane({ title: 'Default', boardId })._id;
  360. } else {
  361. swimlaneId = swimlane._id;
  362. }
  363. Cards.insert({
  364. title: action.cardName,
  365. listId,
  366. swimlaneId,
  367. sort: 0,
  368. boardId
  369. });
  370. }
  371. if (action.actionType === 'linkCard') {
  372. const list = ReactiveCache.getList({ title: action.listName, boardId: action.boardId });
  373. const card = ReactiveCache.getCard(activity.cardId);
  374. let listId = '';
  375. let swimlaneId = '';
  376. const swimlane = ReactiveCache.getSwimlane({
  377. title: action.swimlaneName,
  378. boardId: action.boardId,
  379. });
  380. if (list === undefined) {
  381. listId = '';
  382. } else {
  383. listId = list._id;
  384. }
  385. if (swimlane === undefined) {
  386. swimlaneId = ReactiveCache.getSwimlane({ title: 'Default', boardId: action.boardId })._id;
  387. } else {
  388. swimlaneId = swimlane._id;
  389. }
  390. card.link(action.boardId, swimlaneId, listId);
  391. }
  392. },
  393. };