util.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487
  1. /*
  2. op-mattermost provides an integration for Mattermost and Open Project.
  3. Copyright (C) 2020 to present , Girish M
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <https://www.gnu.org/licenses/>
  14. */
  15. const moment = require("moment");
  16. class Util {
  17. constructor() {
  18. this.timeLogSuccessMsg = "\n**Time logged! You are awesome :sunglasses: **\n To view time logged try `/op tl`";
  19. this.timeLogForbiddenMsg = "**It seems that you don't have permission to log time for this project :confused: **"
  20. this.timeLogFailMsg = "**That didn't work :pensive: An internal error occurred!**";
  21. this.timeLogDelMsg = "**Time log deleted!**";
  22. this.timeLogDelErrMsg = "**That didn't work :pensive: Couldn't delete time log\n Please try again...`/op dtl`**";
  23. this.cnfDelTimeLogMsg = "**Confirm time log deletion?**";
  24. this.subscribeMsg = "**Subscribed to OpenProject notifications :sunglasses: **";
  25. this.wpDtlEmptyMsg = "**Work package details not entered :( Let's try again...**\n `/op`";
  26. this.saveWPSuccessMsg = "\n**Work package created! You are awesome :sunglasses: **\n To log time for a work package try `/op lt`";
  27. this.wpFetchErrMsg = "**That didn't work :pensive: Couldn't fetch work packages from OP**";
  28. this.cnfDelWPMsg = "**Confirm work package deletion?** This will delete all associated time entries and child work packages";
  29. this.wpDelErrMsg = "**That didn't work :pensive: Couldn't delete work package\n Please try again... `/op dwp`**";
  30. this.wpForbiddenMsg = "**Couldn't delete work package :confused: You are not authorized to do so.**";
  31. this.wpDelMsg = "**Work package deleted successfully!**";
  32. this.wpTypeErrMsg = "**Work package type is not set to one of the allowed values. Couldn't create work package :pensive: **";
  33. this.wpCreateForbiddenMsg = "**It seems that you don't have permission to create work package for this project :confused: **"
  34. this.dateErrMsg = "**It seems that date was incorrect :thinking: Please enter a date within last one year and in YYYY-MM-DD format. **";
  35. this.billableHoursErrMsg = "**It seems that billable hours was incorrect :thinking: Please note billable hours should be less than or equal to logged hours. **";
  36. this.dlgCreateErrMsg = "**It's an internal problem. Dialog creation failed :pensive: Can you please try `/op lt` again?**";
  37. this.activityFetchErrMsg = "**Couldn't fetch activities from OP :confused: You are not authorized to log time for this work package.**";
  38. this.typeFetchErrMsg = "**That didn't work :pensive: Couldn't to fetch types from OP**";
  39. this.dlgCancelMsg = "** If you would like to try again then, `/op cwp` **";
  40. this.genericErrMsg = "** Unknown error occurred :pensive: Can you please try again? **";
  41. }
  42. checkHours(hoursLog, hours) {
  43. if (isNaN(hours) || hours < 0.0 || hours > 99.9) {
  44. return false;
  45. }
  46. else {
  47. /*Check for billable hours to be less than hours log*/
  48. return hours <= hoursLog;
  49. }
  50. }
  51. checkDate(moment, dateTxt) {
  52. /*Valid dates within last one year*/
  53. let dateDiff = moment().diff(moment(dateTxt, 'YYYY-MM-DD', true), 'days');
  54. let daysUpperBound = 365;
  55. if (moment().isLeapYear()) {
  56. daysUpperBound = 366;
  57. }
  58. return dateDiff >= 0 && dateDiff <= daysUpperBound;
  59. }
  60. getLogTimeDlgObj(triggerId, url, activityOptArray) {
  61. return {
  62. "trigger_id": triggerId,
  63. "url": url + 'logTime',
  64. "dialog": {
  65. "callback_id": "log_time_dlg",
  66. "title": "Log time for work package",
  67. "icon_url": url + 'getLogo',
  68. "elements": [
  69. {
  70. "display_name": "Date",
  71. "name": "spent_on",
  72. "type": "text",
  73. "placeholder": "YYYY-MM-DD",
  74. "help_text": "Please enter date within last one year and in YYYY-MM-DD format",
  75. "default": moment().format('YYYY-MM-DD')
  76. },
  77. {
  78. "display_name": "Comment",
  79. "name": "comments",
  80. "type": "textarea",
  81. "placeholder": "Please mention comments if any",
  82. "optional": true
  83. },
  84. {
  85. "display_name": "Select Activity",
  86. "name": "activity",
  87. "type": "select",
  88. "placeholder": "Type to search for activity",
  89. "options": activityOptArray,
  90. "default": activityOptArray[0].value
  91. },
  92. {
  93. "display_name": "Spent hours",
  94. "name": "spent_hours",
  95. "type": "text",
  96. "placeholder": "hours like 0.5, 1, 3 ...",
  97. "default": "0.5",
  98. "help_text": "Please enter spent hours to be logged"
  99. },
  100. {
  101. "display_name": "Billable hours",
  102. "name": "billable_hours",
  103. "type": "text",
  104. "placeholder": "hours like 0.5, 1, 3 ...",
  105. "default": "0.0",
  106. "help_text": "Please ensure billable hours is less than or equal to spent hours"
  107. }],
  108. "submit_label": "Log time",
  109. "notify_on_cancel": true
  110. }
  111. };
  112. }
  113. getProjectOptJSON(url, optArray, action, mode = '') {
  114. let projectOptObj = {
  115. "response_type": "in_channel",
  116. "message": "*Please select a project*",
  117. "props": {
  118. "attachments": [
  119. {
  120. "actions": [
  121. {
  122. "name": "Type to search for a project...",
  123. "integration": {
  124. "url": url + "projSel",
  125. "context": {
  126. "action": action
  127. }
  128. },
  129. "type": "select",
  130. "options": optArray
  131. },
  132. {
  133. "name": "Cancel Project search",
  134. "integration": {
  135. "url": url + "bye"
  136. }
  137. }
  138. ]
  139. }
  140. ]
  141. }
  142. };
  143. if (mode === 'update') {
  144. return {
  145. "update": projectOptObj
  146. };
  147. }
  148. else {
  149. return projectOptObj;
  150. }
  151. }
  152. getWpOptJSON(url, optArray, action, mode = '') {
  153. let wpOptJSON = {
  154. "response_type": "in_channel",
  155. "message": "*Please select a work package*",
  156. "props": {
  157. "attachments": [
  158. {
  159. "actions": [
  160. {
  161. "name": "Type to search for a work package...",
  162. "integration": {
  163. "url": url + "wpSel",
  164. "context": {
  165. "action": action
  166. }
  167. },
  168. "type": "select",
  169. "options": optArray
  170. },
  171. {
  172. "name": "Cancel WP search",
  173. "integration": {
  174. "url": url + "createTimeLog"
  175. }
  176. }]
  177. }
  178. ]
  179. }
  180. };
  181. if(mode === 'update') {
  182. wpOptJSON = {
  183. "update": wpOptJSON
  184. }
  185. }
  186. return wpOptJSON;
  187. }
  188. getTimeLogOptJSON(url, optArray, action, mode = '') {
  189. let timeLogOptJSON = {
  190. "response_type": "in_channel",
  191. "props": {}
  192. }
  193. if(optArray.length !== 0) {
  194. timeLogOptJSON.props = {
  195. "attachments": [
  196. {
  197. "actions": [
  198. {
  199. "name": "Type to search for a time log...",
  200. "integration": {
  201. "url": url + "delTimeLog",
  202. "context": {
  203. "action": action
  204. }
  205. },
  206. "type": "select",
  207. "options": optArray
  208. },
  209. {
  210. "name": "Cancel search",
  211. "integration": {
  212. "url": url + "bye"
  213. }
  214. }]
  215. }
  216. ]
  217. }
  218. }
  219. else {
  220. timeLogOptJSON.text = "Couldn't find time entries to delete :confused: Try logging time using `/op`";
  221. }
  222. if(mode === 'update') {
  223. return {
  224. "update": timeLogOptJSON
  225. };
  226. }
  227. else {
  228. return timeLogOptJSON;
  229. }
  230. }
  231. getTimeLogDelMsgJSON(msg, url) {
  232. return {
  233. "update": {
  234. "response_type": "in_channel",
  235. "props": {
  236. "attachments": [
  237. {
  238. "text": msg,
  239. "actions": [
  240. {
  241. "name": "View time logs",
  242. "integration": {
  243. "url": url + "getTimeLog",
  244. "context": {
  245. "action": "getTimeLog"
  246. }
  247. }
  248. }
  249. ]
  250. }
  251. ]
  252. }
  253. }
  254. };
  255. }
  256. getTimeLogJSON(timeLogArray, mode = '') {
  257. let tableTxt = '';
  258. let timeLogObj = {
  259. "response_type": "in_channel",
  260. "props": {}
  261. };
  262. if (timeLogArray.length !== 0) {
  263. tableTxt = "#### Time entries logged by you\n";
  264. tableTxt += "| Spent On | Project | Work Package | Activity | Logged Time | Billed Time | Comment |\n";
  265. tableTxt += "|:---------|:--------|:-------------|:---------|:------------|:------------|:--------|\n";
  266. timeLogArray.forEach(element => {
  267. if (element.comment === null) {
  268. element.comment = "";
  269. }
  270. tableTxt += "| " + element.spentOn + " | " + element.project + " | " + element.workPackage + " | " + element.activity + " | " + element.loggedHours + " | " + element.billableHours + " | " + element.comment.replace(/\n/g, " ") + " |\n";
  271. });
  272. }
  273. else {
  274. tableTxt = "Couldn't find time entries logged by you :confused: Try logging time using `/op`";
  275. }
  276. if(mode === 'update') {
  277. timeLogObj = {
  278. "update": timeLogObj
  279. };
  280. timeLogObj.update.message = tableTxt;
  281. }
  282. else {
  283. timeLogObj.text = tableTxt;
  284. }
  285. return timeLogObj;
  286. }
  287. getWPDelMsgJSON(msg) {
  288. return {
  289. "update": {
  290. "response_type": "in_channel",
  291. "props": {
  292. "attachments": [
  293. {
  294. "text": msg,
  295. "actions": []
  296. }
  297. ]
  298. }
  299. }
  300. };
  301. }
  302. getWpCreateJSON(triggerId, url, typeArray, assigneeArray) {
  303. return {
  304. "trigger_id": triggerId,
  305. "url": url + 'saveWP',
  306. "dialog": {
  307. "callback_id": "create_wp_dlg",
  308. "title": "Create a work package",
  309. "introduction_text": "Create a work package by providing following details",
  310. "icon_url": url + 'getLogo',
  311. "elements": [{
  312. "display_name": "Subject",
  313. "name": "subject",
  314. "type": "text",
  315. "placeholder": "Name of work package"
  316. },
  317. {
  318. "display_name": "Select Type",
  319. "name": "type",
  320. "type": "select",
  321. "placeholder": "Type to search for type",
  322. "options": typeArray,
  323. "default": "opt1"
  324. },
  325. {
  326. "display_name": "Assignee",
  327. "name": "assignee",
  328. "type": "select",
  329. "placeholder": "Type to search for users",
  330. "options": assigneeArray,
  331. "optional": true
  332. },
  333. {
  334. "display_name": "Notify interested users?",
  335. "placeholder": "Send email.",
  336. "name": "notify",
  337. "type": "bool",
  338. "optional": true,
  339. "help_text": "Note that this controls notifications for all users interested in changes to the work package (e.g. current user, watchers, author and assignee)"
  340. }],
  341. "submit_label": "Create Work Package",
  342. "notify_on_cancel": true
  343. }
  344. };
  345. }
  346. getMenuBtnJSON(url, firstName) {
  347. return {
  348. "response_type": "in_channel",
  349. "props": {
  350. "attachments": [
  351. {
  352. "pretext": "Hello " + firstName + " :)",
  353. "text": "What would you like me to do?",
  354. "actions": [
  355. {
  356. "name": "Log time",
  357. "integration": {
  358. "url": url + "createTimeLog",
  359. "context": {
  360. "action": "showSelWP"
  361. }
  362. }
  363. },
  364. {
  365. "name": "Create Work Package",
  366. "integration": {
  367. "url": url + "createWP",
  368. "context": {
  369. "action": "createWP"
  370. }
  371. }
  372. },
  373. {
  374. "name": "View time logs",
  375. "integration": {
  376. "url": url + "getTimeLog",
  377. "context": {
  378. "action": "getTimeLog"
  379. }
  380. }
  381. },
  382. {
  383. "name": "Delete time log",
  384. "integration": {
  385. "url": url + "delTimeLog",
  386. "context": {
  387. "action": "delTimeLog"
  388. }
  389. }
  390. },
  391. {
  392. "name": "Delete Work Package",
  393. "integration": {
  394. "url": url + "delWP",
  395. "context": {
  396. "action": ""
  397. }
  398. }
  399. },
  400. {
  401. "name": "Subscribe to notifications",
  402. "integration": {
  403. "url": url + "subscribe",
  404. "context": {
  405. "action": ""
  406. }
  407. }
  408. },
  409. {
  410. "name": "Bye :wave:",
  411. "integration": {
  412. "url": url + "bye",
  413. "context": {
  414. "action": "bye"
  415. }
  416. }
  417. }
  418. ]
  419. }
  420. ]
  421. }
  422. };
  423. }
  424. getCnfDelBtnJSON(url, msg, action) {
  425. return {
  426. "update": {
  427. "response_type": "in_channel",
  428. "props": {
  429. "attachments": [
  430. {
  431. "text": msg,
  432. "actions": [
  433. {
  434. "name": "Yes, Delete!",
  435. "integration": {
  436. "url": url,
  437. "context": {
  438. "action": action
  439. }
  440. }
  441. },
  442. {
  443. "name": "No, go back.",
  444. "integration": {
  445. "url": url,
  446. "context": {
  447. "action": ""
  448. }
  449. }
  450. }
  451. ]
  452. }
  453. ]
  454. }
  455. }
  456. };
  457. }
  458. }
  459. module.exports = Util;