FEATURE: Remove user topic timers and migrate to bookmarks with reminders (#10474)

FEATURE: Remove user topic timers and migrate to bookmarks with reminders (#10474)

This PR removes the user reminder topic timers, because that system has been supplanted and improved by bookmark reminders. The option is removed from the UI and all existing user reminder topic timers are migrated to bookmark reminders.

Migration does this:

  • Get all topic_timers with status_type 5 (reminders)
  • Gets all bookmarks where the user ID and topic ID match
  • Loops through the found topic timers
    • If there is no bookmark for the OP of the topic, then we just create a bookmark with a reminder
    • If there is a bookmark for the OP of the topic and it does not have a reminder set, then just update it with the topic timer reminder
    • If there is a bookmark for the OP of the topic with a reminder then just discard the topic timer
  • Cancels all outstanding user reminder topic timers
  • Trashes (not deletes) all user reminder topic timers

Notes:

  • For now I have left the user reminder topic timer job class in place; this is so the jobs can be cancelled in the migration. It and the specs will be deleted in the next PR.
  • At a later date I will write a migration to delete all trashed user topic timers. They are not deleted here in case there are data issues and they need to be recovered.
  • A future PR will change the UI of the topic timer modal to make it look more like the bookmark modal.
diff --git a/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js b/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js
index 9513308..ecacbdf 100644
--- a/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js
+++ b/app/assets/javascripts/discourse/app/components/edit-topic-timer-form.js
@@ -6,7 +6,6 @@ import {
   PUBLISH_TO_CATEGORY_STATUS_TYPE,
   OPEN_STATUS_TYPE,
   DELETE_STATUS_TYPE,
-  REMINDER_TYPE,
   CLOSE_STATUS_TYPE,
   BUMP_TYPE,
   DELETE_REPLIES_TYPE,
@@ -19,9 +18,8 @@ export default Component.extend({
   autoDelete: equal("selection", DELETE_STATUS_TYPE),
   autoBump: equal("selection", BUMP_TYPE),
   publishToCategory: equal("selection", PUBLISH_TO_CATEGORY_STATUS_TYPE),
-  reminder: equal("selection", REMINDER_TYPE),
   autoDeleteReplies: equal("selection", DELETE_REPLIES_TYPE),
-  showTimeOnly: or("autoOpen", "autoDelete", "reminder", "autoBump"),
+  showTimeOnly: or("autoOpen", "autoDelete", "autoBump"),
   showFutureDateInput: or(
     "showTimeOnly",
     "publishToCategory",
diff --git a/app/assets/javascripts/discourse/app/components/topic-timer-info.js b/app/assets/javascripts/discourse/app/components/topic-timer-info.js
index c5e5da4..177b5d9 100644
--- a/app/assets/javascripts/discourse/app/components/topic-timer-info.js
+++ b/app/assets/javascripts/discourse/app/components/topic-timer-info.js
@@ -4,10 +4,7 @@ import { cancel, later } from "@ember/runloop";
 import Component from "@ember/component";
 import { iconHTML } from "discourse-common/lib/icon-library";
 import Category from "discourse/models/category";
-import {
-  REMINDER_TYPE,
-  DELETE_REPLIES_TYPE,
-} from "discourse/controllers/edit-topic-timer";
+import { DELETE_REPLIES_TYPE } from "discourse/controllers/edit-topic-timer";
 import { isTesting } from "discourse-common/config/environment";
 
 export default Component.extend({
@@ -20,9 +17,8 @@ export default Component.extend({
   notice: null,
   showTopicTimer: null,
 
-  @discourseComputed("statusType")
-  canRemoveTimer(type) {
-    if (type === REMINDER_TYPE) return true;
+  @discourseComputed
+  canRemoveTimer() {
     return this.currentUser && this.currentUser.get("canManageTopic");
   },
 
diff --git a/app/assets/javascripts/discourse/app/controllers/edit-topic-timer.js b/app/assets/javascripts/discourse/app/controllers/edit-topic-timer.js
index f3d2b97..784a6d0 100644
--- a/app/assets/javascripts/discourse/app/controllers/edit-topic-timer.js
+++ b/app/assets/javascripts/discourse/app/controllers/edit-topic-timer.js
@@ -1,4 +1,5 @@
 import I18n from "I18n";
+import { alias } from "@ember/object/computed";
 import EmberObject, { setProperties } from "@ember/object";
 import Controller from "@ember/controller";
 import discourseComputed from "discourse-common/utils/decorators";
@@ -11,7 +12,6 @@ export const CLOSE_STATUS_TYPE = "close";
 export const OPEN_STATUS_TYPE = "open";
 export const PUBLISH_TO_CATEGORY_STATUS_TYPE = "publish_to_category";
 export const DELETE_STATUS_TYPE = "delete";
-export const REMINDER_TYPE = "reminder";
 export const BUMP_TYPE = "bump";
 export const DELETE_REPLIES_TYPE = "delete_replies";
 
@@ -58,24 +58,7 @@ export default Controller.extend(ModalFunctionality, {
     return types;
   },
 
-  @discourseComputed()
-  privateTimerTypes() {
-    return [{ id: REMINDER_TYPE, name: I18n.t("topic.reminder.title") }];
-  },
-
-  @discourseComputed("isPublic", "publicTimerTypes", "privateTimerTypes")
-  selections(isPublic, publicTimerTypes, privateTimerTypes) {
-    return "true" === isPublic ? publicTimerTypes : privateTimerTypes;
-  },
-
-  @discourseComputed(
-    "isPublic",
-    "model.topic_timer",
-    "model.private_topic_timer"
-  )
-  topicTimer(isPublic, publicTopicTimer, privateTopicTimer) {
-    return "true" === isPublic ? publicTopicTimer : privateTopicTimer;
-  },
+  topicTimer: alias("model.topic_timer"),
 
   _setTimer(time, duration, statusType, basedOnLastPost, categoryId) {
     this.set("loading", true);
@@ -100,9 +83,7 @@ export default Controller.extend(ModalFunctionality, {
 
           this.set("model.closed", result.closed);
         } else {
-          const topicTimer =
-            this.isPublic === "true" ? "topic_timer" : "private_topic_timer";
-          this.set(`model.${topicTimer}`, EmberObject.create({}));
+          this.set("model.topic_timer", EmberObject.create({}));
 
           this.setProperties({
             selection: null,
diff --git a/app/assets/javascripts/discourse/app/routes/topic.js b/app/assets/javascripts/discourse/app/routes/topic.js
index fa3e316..28d6161 100644
--- a/app/assets/javascripts/discourse/app/routes/topic.js
+++ b/app/assets/javascripts/discourse/app/routes/topic.js
@@ -114,11 +114,6 @@ const TopicRoute = DiscourseRoute.extend({
         model.set("topic_timer", {});
       }
 
-      const privateTopicTimer = model.get("private_topic_timer");
-      if (!privateTopicTimer) {
-        model.set("private_topic_timer", {});
-      }
-
       showModal("edit-topic-timer", { model });
       this.controllerFor("modal").set("modalClass", "edit-topic-timer-modal");
     },
diff --git a/app/assets/javascripts/discourse/app/templates/modal/edit-topic-timer.hbs b/app/assets/javascripts/discourse/app/templates/modal/edit-topic-timer.hbs
index 7f66342..cc3fffe 100644
--- a/app/assets/javascripts/discourse/app/templates/modal/edit-topic-timer.hbs
+++ b/app/assets/javascripts/discourse/app/templates/modal/edit-topic-timer.hbs
@@ -1,20 +1,8 @@
 {{#d-modal-body title="topic.topic_status_update.title" autoFocus="false"}}
-  <div class="radios">
-    <label for="public-topic-timer">
-      {{radio-button id="public-topic-timer" name="topic-timer" value="true" selection=isPublic}}
-      <b>{{i18n "topic.topic_status_update.public_timer_types"}}</b>
-    </label>
-
-    <label for="private-topic-timer">
-      {{radio-button id="private-topic-timer" name="topic-timer" value="false" selection=isPublic}}
-      <b>{{i18n "topic.topic_status_update.private_timer_types"}}</b>
-    </label>
-  </div>
-
   {{edit-topic-timer-form
     topic=model
     topicTimer=topicTimer
-    timerTypes=selections
+    timerTypes=publicTimerTypes
     updateTime=updateTime
     onChangeStatusType=(action "onChangeStatusType")
     onChangeInput=(action "onChangeInput")
diff --git a/app/assets/javascripts/discourse/app/templates/topic.hbs b/app/assets/javascripts/discourse/app/templates/topic.hbs
index f0715b2..af02200 100644
--- a/app/assets/javascripts/discourse/app/templates/topic.hbs
+++ b/app/assets/javascripts/discourse/app/templates/topic.hbs
@@ -286,15 +286,6 @@
                 </div>
               {{/if}}
 
-              {{#if model.private_topic_timer.execute_at}}
-                {{topic-timer-info
-                    topicClosed=model.closed
-                    statusType=model.private_topic_timer.status_type
-                    executeAt=model.private_topic_timer.execute_at
-                    duration=model.private_topic_timer.duration
-                    removeTopicTimer=(action "removeTopicTimer" model.private_topic_timer.status_type "private_topic_timer")}}
-              {{/if}}
-
               {{topic-timer-info
                   topicClosed=model.closed
                   statusType=model.topic_timer.status_type
diff --git a/app/jobs/regular/topic_reminder.rb b/app/jobs/regular/topic_reminder.rb
index a9eed2b..e4b103d 100644
--- a/app/jobs/regular/topic_reminder.rb
+++ b/app/jobs/regular/topic_reminder.rb
@@ -4,6 +4,9 @@ module Jobs
   class TopicReminder < ::Jobs::Base
 
     def execute(args)
+      # noop, TODO(martin 2021-03-11): Remove this after timers migrated and outstanding jobs cancelled
+      return
+
       topic_timer = TopicTimer.find_by(id: args[:topic_timer_id])
 
       topic = topic_timer&.topic
diff --git a/app/models/topic.rb b/app/models/topic.rb
index 598d434..3e8c333 100644
--- a/app/models/topic.rb
+++ b/app/models/topic.rb

[... diff too long, it was truncated ...]

GitHub sha: 5268568d

This commit appears in #10474 which was merged by martin.