FEATURE: Penalty history improvements (#13359)

FEATURE: Penalty history improvements (#13359)

  • FEATURE: add penalty history when silencing a user

Display penalty history (last 6 months) when silencing/suspending a user

  • FEATURE: allow default penalty values to be chosen

Adds a site setting that designates default penalty values in hours.

Silence/suspend modals will auto-fill in the default values, but otherwise will still allow moderators to pick and overwrite values as normal.

First silence/suspend: first value Second silence/suspend: second value etc.

Penalty counts are forgiven at the same rate as tl3 promotion requirements do.

Co-authored-by: jjaffeux j.jaffeux@gmail.com

diff --git a/app/assets/javascripts/admin/addon/components/admin-penalty-history.js b/app/assets/javascripts/admin/addon/components/admin-penalty-history.js
new file mode 100644
index 0000000..32c288b
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/components/admin-penalty-history.js
@@ -0,0 +1,22 @@
+import Component from "@ember/component";
+import discourseComputed from "discourse-common/utils/decorators";
+
+export default Component.extend({
+  classNames: ["penalty-history"],
+
+  @discourseComputed("user.penalty_counts.suspended")
+  suspendedCountClass(count) {
+    if (count > 0) {
+      return "danger";
+    }
+    return "";
+  },
+
+  @discourseComputed("user.penalty_counts.silenced")
+  silencedCountClass(count) {
+    if (count > 0) {
+      return "danger";
+    }
+    return "";
+  },
+});
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js
index b76565f..5065986 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-silence-user.js
@@ -12,6 +12,10 @@ export default Controller.extend(PenaltyController, {
     this.setProperties({ silenceUntil: null, silencing: false });
   },
 
+  finishedSetup() {
+    this.set("silenceUntil", this.user?.next_penalty);
+  },
+
   @discourseComputed("silenceUntil", "reason", "silencing")
   submitDisabled(silenceUntil, reason, silencing) {
     return silencing || isEmpty(silenceUntil) || !reason || reason.length < 1;
diff --git a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js b/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js
index 2712b57..c82a562 100644
--- a/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js
+++ b/app/assets/javascripts/admin/addon/controllers/modals/admin-suspend-user.js
@@ -12,6 +12,10 @@ export default Controller.extend(PenaltyController, {
     this.setProperties({ suspendUntil: null, suspending: false });
   },
 
+  finishedSetup() {
+    this.set("suspendUntil", this.user?.next_penalty);
+  },
+
   @discourseComputed("suspendUntil", "reason", "suspending")
   submitDisabled(suspendUntil, reason, suspending) {
     return suspending || isEmpty(suspendUntil) || !reason || reason.length < 1;
diff --git a/app/assets/javascripts/admin/addon/services/admin-tools.js b/app/assets/javascripts/admin/addon/services/admin-tools.js
index 8928af1..fe282fe 100644
--- a/app/assets/javascripts/admin/addon/services/admin-tools.js
+++ b/app/assets/javascripts/admin/addon/services/admin-tools.js
@@ -48,7 +48,6 @@ export default Service.extend({
 
   _showControlModal(type, user, opts) {
     opts = opts || {};
-
     let controller = showModal(`admin-${type}-user`, {
       admin: true,
       modalClass: `${type}-user-modal`,
@@ -65,6 +64,8 @@ export default Service.extend({
         before: opts.before,
         successCallback: opts.successCallback,
       });
+
+      controller.finishedSetup();
     });
   },
 
diff --git a/app/assets/javascripts/admin/addon/templates/components/admin-penalty-history.hbs b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-history.hbs
new file mode 100644
index 0000000..6c62271
--- /dev/null
+++ b/app/assets/javascripts/admin/addon/templates/components/admin-penalty-history.hbs
@@ -0,0 +1,8 @@
+<div class="suspended-count {{suspendedCountClass}}" title={{i18n "admin.user.last_six_months"}}>
+  <label>{{i18n "admin.user.suspended_count"}}</label>
+  <span>{{user.penalty_counts.suspended}}</span>
+</div>
+<div class="silenced-count {{silencedCountClass}}" title={{i18n "admin.user.last_six_months"}}>
+  <label>{{i18n "admin.user.silenced_count"}}</label>
+  <span>{{user.penalty_counts.silenced}}</span>
+</div>
diff --git a/app/assets/javascripts/admin/addon/templates/modal/admin-silence-user.hbs b/app/assets/javascripts/admin/addon/templates/modal/admin-silence-user.hbs
index db3a184..6c7ad17 100644
--- a/app/assets/javascripts/admin/addon/templates/modal/admin-silence-user.hbs
+++ b/app/assets/javascripts/admin/addon/templates/modal/admin-silence-user.hbs
@@ -5,6 +5,8 @@
       <div class="alert alert-error">{{errorMessage}}</div>
     {{/if}}
 
+    {{admin-penalty-history user=user}}
+
     <div class="until-controls">
       <label>
         {{future-date-input
diff --git a/app/assets/javascripts/admin/addon/templates/modal/admin-suspend-user.hbs b/app/assets/javascripts/admin/addon/templates/modal/admin-suspend-user.hbs
index 34c2685..361f283 100644
--- a/app/assets/javascripts/admin/addon/templates/modal/admin-suspend-user.hbs
+++ b/app/assets/javascripts/admin/addon/templates/modal/admin-suspend-user.hbs
@@ -6,6 +6,8 @@
     {{/if}}
 
     {{#if user.canSuspend}}
+      {{admin-penalty-history user=user}}
+
       <div class="until-controls">
         <label>
           {{future-date-input
diff --git a/app/assets/javascripts/discourse/app/components/future-date-input.js b/app/assets/javascripts/discourse/app/components/future-date-input.js
index b76c31d..75a3316 100644
--- a/app/assets/javascripts/discourse/app/components/future-date-input.js
+++ b/app/assets/javascripts/discourse/app/components/future-date-input.js
@@ -14,9 +14,10 @@ export default Component.extend({
   displayLabel: null,
   labelClasses: null,
 
+  timeInputDisabled: empty("date"),
+
   init() {
     this._super(...arguments);
-
     if (this.input) {
       const datetime = moment(this.input);
       this.setProperties({
@@ -27,8 +28,6 @@ export default Component.extend({
     }
   },
 
-  timeInputDisabled: empty("date"),
-
   @observes("date", "time")
   _updateInput() {
     if (!this.date) {
diff --git a/app/assets/stylesheets/common/admin/suspend.scss b/app/assets/stylesheets/common/admin/suspend.scss
index 4bc18ef..b142448 100644
--- a/app/assets/stylesheets/common/admin/suspend.scss
+++ b/app/assets/stylesheets/common/admin/suspend.scss
@@ -35,4 +35,24 @@
       height: 10em;
     }
   }
+  .penalty-history {
+    margin-bottom: 1em;
+    padding-bottom: 0.5em;
+    border-bottom: 1px solid var(--primary-low);
+    display: flex;
+    & > * {
+      flex-basis: 100%;
+      text-align: center;
+      padding: 1em 0;
+      font-weight: 600;
+      label {
+        font-weight: 600;
+        justify-content: center;
+      }
+    }
+    .danger {
+      background-color: var(--danger);
+      color: var(--secondary);
+    }
+  }
 }
diff --git a/app/serializers/admin_detailed_user_serializer.rb b/app/serializers/admin_detailed_user_serializer.rb
index 6dc9413..b1c4c5b 100644
--- a/app/serializers/admin_detailed_user_serializer.rb
+++ b/app/serializers/admin_detailed_user_serializer.rb
@@ -22,6 +22,8 @@ class AdminDetailedUserSerializer < AdminUserSerializer
              :full_suspend_reason,
              :suspended_till,
              :silence_reason,
+             :penalty_counts,
+             :next_penalty,
              :primary_group_id,
              :badge_count,
              :warnings_received_count,
@@ -96,6 +98,20 @@ class AdminDetailedUserSerializer < AdminUserSerializer
     object.silence_reason
   end
 
+  def penalty_counts
+    TrustLevel3Requirements.new(object).penalty_counts
+  end
+
+  def next_penalty
+    step_number = penalty_counts.total
+    steps = SiteSetting.penalty_step_hours.split('|')
+    step_number = [step_number, steps.length].min
+    penalty_hours = steps[step_number]
+    Integer(penalty_hours, 10).hours.from_now
+  rescue
+    nil
+  end
+
   def silenced_by
     object.silenced_record.try(:acting_user)
   end
diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml
index 8cb9cd7..1b5c96f 100644
--- a/config/locales/client.en.yml
+++ b/config/locales/client.en.yml
@@ -4858,6 +4858,7 @@ en:
         penalty_post_edit: "Edit the post"
         penalty_post_none: "Do nothing"

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

GitHub sha: d87a0216bbd106056db06e4d0cd85d090804c427

This commit appears in #13359 which was approved by romanrizzi. It was merged by featheredtoast.