Lattice Goals - Add Ideal

Determines ideal goal value based on Created on and Due dates

  1. // ==UserScript==
  2. // @name Lattice Goals - Add Ideal
  3. // @namespace Violentmonkey Scripts
  4. // @match http://*.latticehq.com/goals/*
  5. // @match https://*.latticehq.com/goals/*
  6. // @grant none
  7. // @version 1.3
  8. // @author pedro-mass
  9. // @description Determines ideal goal value based on Created on and Due dates
  10. // @run-at document-idle
  11. // @require http://code.jquery.com/jquery-3.5.0.min.js
  12. // ==/UserScript==
  13.  
  14. waitUntilTrue(shouldRun, run);
  15.  
  16. function isPercentageBasedGoal() {
  17. // todo: make this actually check if there is a percentage based check?
  18. return true;
  19. }
  20.  
  21. function run() {
  22. console.log("Starting Lattice Goal Percentage calculations...");
  23. if (!isPercentageBasedGoal()) return;
  24.  
  25. const Dates = getDates();
  26. const GoalStats = getGoalStats();
  27. const percentageByDate = getRelativePercentage(
  28. Dates.start,
  29. Dates.end,
  30. Dates.current
  31. );
  32.  
  33. const idealValue = Math.round(
  34. getRelativeValue(GoalStats.start, GoalStats.goal, percentageByDate)
  35. );
  36.  
  37. const goalDirection = GoalStats.start <= GoalStats.end ? 1 : -1;
  38.  
  39. return insertIdeal(
  40. idealValue,
  41. GoalStats.unit,
  42. GoalStats.current,
  43. goalDirection
  44. );
  45. }
  46.  
  47. function getRelativeValue(start, end, percentage) {
  48. const offset = start;
  49. return (end - offset) * percentage + offset;
  50. }
  51.  
  52. function getDates() {
  53. const start = getDate(/^created\n\n/i);
  54. const end = getDate(/^due\n\n/i);
  55. const current = Date.now();
  56.  
  57. return {
  58. start,
  59. end,
  60. current,
  61. };
  62. }
  63.  
  64. function getGoalStats() {
  65. const unit = getValue(/^start: /i, "span").replace(/\d+/, "");
  66. const getNumber = (regex) =>
  67. Number(getValue(regex, "span").replace(unit, ""));
  68. const start = getNumber(/^start: /i);
  69. const current = getNumber(/^current: /i);
  70. const goal = getNumber(/^goal: /i);
  71.  
  72. return {
  73. start,
  74. goal,
  75. current,
  76. unit,
  77. };
  78. }
  79.  
  80. function getProgressIndicator(ideal, current) {
  81. if (current == null) return "";
  82.  
  83. if (ideal <= current) {
  84. return "🎉";
  85. }
  86.  
  87. return "😢";
  88. }
  89.  
  90. function insertIdeal(ideal, unit, current, isAscendingGoal) {
  91. if (contains("span", /^ideally: /i).length > 0) {
  92. return;
  93. }
  94.  
  95. const progressIndicator = isAscendingGoal
  96. ? getProgressIndicator(ideal, current)
  97. : getProgressIndicator(current, ideal);
  98.  
  99. const idealElem = `<span class="css-1mddpa2">Ideally: <span>${ideal}${unit}</span> <span>${progressIndicator}</span></span>`;
  100. const goalsContainer = contains("div", /^start:/i);
  101.  
  102. return $(goalsContainer).find("span").first().after(idealElem);
  103. }
  104.  
  105. function shouldRun() {
  106. const pageCheck = contains("span", /^start: /i);
  107. return pageCheck != null && pageCheck.length > 0;
  108. }
  109.  
  110. function getRelativePercentage(startDate, endDate, currentDate) {
  111. const offset = startDate;
  112. endDate = endDate - offset;
  113. currentDate = currentDate - offset;
  114. return currentDate / endDate;
  115. }
  116.  
  117. function getDate(regex, selector = "div") {
  118. return new Date(getValue(regex, selector)).getTime();
  119. }
  120.  
  121. function getValue(regex, selector) {
  122. return replaceString(getText(first(contains(selector, regex))), regex, "");
  123. }
  124.  
  125. function replaceString(string, searchString, newString) {
  126. if (!string) {
  127. console.warn("Received bad params", { string, searchString, newString });
  128. return string;
  129. }
  130.  
  131. return string.replace(searchString, newString);
  132. }
  133.  
  134. function contains(selector, text) {
  135. var elements = document.querySelectorAll(selector);
  136. return Array.prototype.filter.call(elements, function (element) {
  137. return RegExp(text).test(getText(element));
  138. });
  139. }
  140.  
  141. function getText(element) {
  142. return element.innerText;
  143. }
  144.  
  145. function first(arr) {
  146. return arr[0];
  147. }
  148.  
  149. function waitUntilTrue(checkFn, cb = (x) => x, timeout = 250) {
  150. const intervalId = setInterval(checkInterval, timeout);
  151. function checkInterval() {
  152. if (checkFn()) {
  153. clearInterval(intervalId);
  154. cb();
  155. }
  156. }
  157. }