REFACTOR: Replace score bonuses with low/med/high priorities

We removed score from the UX so it makes more sense to have sites set
priorities instead of score bonuses.
This commit is contained in:
Robin Ward 2019-05-22 17:23:45 -04:00
parent 30961dd875
commit e74cd54fc6
21 changed files with 142 additions and 61 deletions

View File

@ -1,19 +1,32 @@
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import computed from "ember-addons/ember-computed-decorators";
export default Ember.Controller.extend({
saving: false,
saved: false,
@computed
reviewablePriorities() {
return [
{ id: 0, name: "low" },
{ id: 5, name: "medium" },
{ id: 10, name: "high" }
];
},
actions: {
save() {
let bonuses = {};
let priorities = {};
this.get("settings.reviewable_score_types").forEach(st => {
bonuses[st.id] = parseFloat(st.score_bonus);
priorities[st.id] = parseFloat(st.reviewable_priority);
});
this.set("saving", true);
ajax("/review/settings", { method: "PUT", data: { bonuses } })
ajax("/review/settings", {
method: "PUT",
data: { reviewable_priorities: priorities }
})
.then(() => {
this.set("saved", true);
})

View File

@ -1,12 +1,11 @@
<div class='reviewable-settings'>
<h4>{{i18n "review.settings.score_bonuses.title"}}</h4>
<p class='description'>{{i18n "review.settings.score_bonuses.description"}}</p>
<h4>{{i18n "review.settings.priorities.title"}}</h4>
{{#each settings.reviewable_score_types as |rst|}}
<div class='reviewable-score-type'>
<div class='title'>{{rst.title}}</div>
<div class='field'>
{{input value=rst.score_bonus}}
{{combo-box value=rst.reviewable_priority content=settings.reviewable_priorities}}
</div>
</div>
{{/each}}

View File

@ -51,8 +51,9 @@
}
.reviewable-settings {
p.description {
margin-bottom: 2em;
h4 {
margin-top: 1em;
margin-bottom: 1em;
}
.saved {
@ -60,7 +61,11 @@
}
.reviewable-score-type {
display: flex;
margin-bottom: 0.5em;
.select-kit {
min-width: 10em;
}
.title {
width: 30%;
}

View File

@ -188,14 +188,21 @@ class ReviewablesController < ApplicationController
raise Discourse::InvalidAccess.new unless current_user.admin?
post_action_types = PostActionType.where(id: PostActionType.flag_types.values).order('id')
data = { reviewable_score_types: post_action_types }
if request.put?
params[:bonuses].each do |id, bonus|
PostActionType.where(id: id).update_all(score_bonus: bonus.to_f)
params[:reviewable_priorities].each do |id, priority|
if !priority.nil? && Reviewable.priorities.has_value?(priority.to_i)
# For now, the score bonus is equal to the priority. In the future we might want
# to calculate it a different way.
PostActionType.where(id: id).update_all(
reviewable_priority: priority.to_i,
score_bonus: priority.to_f
)
end
end
end
data = { reviewable_score_types: post_action_types }
render_serialized(data, ReviewableSettingsSerializer, rest_serializer: true)
end

View File

@ -12,6 +12,8 @@ class Jobs::ReviewablePriorities < Jobs::Scheduled
FROM reviewables
SQL
Reviewable.set_priorities(medium: res[0], high: res[1])
medium, high = res
Reviewable.set_priorities(medium: medium, high: high)
end
end

View File

@ -127,12 +127,12 @@ end
#
# Table name: post_action_types
#
# name_key :string(50) not null
# is_flag :boolean default(FALSE), not null
# icon :string(20)
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# position :integer default(0), not null
# score_bonus :float default(0.0), not null
# name_key :string(50) not null
# is_flag :boolean default(FALSE), not null
# icon :string(20)
# created_at :datetime not null
# updated_at :datetime not null
# id :integer not null, primary key
# position :integer default(0), not null
# reviewable_priority :integer
#

View File

@ -41,6 +41,15 @@ class Reviewable < ActiveRecord::Base
Jobs.enqueue(:notify_reviewable, reviewable_id: self.id) if pending?
end
# The gaps are in case we want more accuracy in the future
def self.priorities
@priorities ||= Enum.new(
low: 0,
medium: 5,
high: 10
)
end
def self.statuses
@statuses ||= Enum.new(
pending: 0,
@ -157,15 +166,18 @@ class Reviewable < ActiveRecord::Base
rs
end
def self.set_priorities(medium: nil, high: nil)
PluginStore.set('reviewables', 'priority_medium', medium) if medium
PluginStore.set('reviewables', 'priority_high', high) if high
def self.set_priorities(values)
values.each do |k, v|
id = Reviewable.priorities[k]
PluginStore.set('reviewables', "priority_#{id}", v) unless id.nil?
end
end
def self.min_score_for_priority(priority = nil)
priority ||= SiteSetting.reviewable_default_visibility
return 0.0 unless ['medium', 'high'].include?(priority)
return PluginStore.get('reviewables', "priority_#{priority}").to_f
id = Reviewable.priorities[priority.to_sym]
return 0.0 if id.nil?
return PluginStore.get('reviewables', "priority_#{id}").to_f
end
def history
@ -502,6 +514,7 @@ end
# created_by_id :integer not null
# reviewable_by_moderator :boolean default(FALSE), not null
# reviewable_by_group_id :integer
# claimed_by_id :integer
# category_id :integer
# topic_id :integer
# score :float default(0.0), not null

View File

@ -296,6 +296,7 @@ end
# created_by_id :integer not null
# reviewable_by_moderator :boolean default(FALSE), not null
# reviewable_by_group_id :integer
# claimed_by_id :integer
# category_id :integer
# topic_id :integer
# score :float default(0.0), not null

View File

@ -147,6 +147,7 @@ end
# created_by_id :integer not null
# reviewable_by_moderator :boolean default(FALSE), not null
# reviewable_by_group_id :integer
# claimed_by_id :integer
# category_id :integer
# topic_id :integer
# score :float default(0.0), not null

View File

@ -103,6 +103,7 @@ end
# created_by_id :integer not null
# reviewable_by_moderator :boolean default(FALSE), not null
# reviewable_by_group_id :integer
# claimed_by_id :integer
# category_id :integer
# topic_id :integer
# score :float default(0.0), not null

View File

@ -17,6 +17,5 @@ end
#
# Indexes
#
# idx_user_custom_fields_last_reminded_at (name,user_id) UNIQUE WHERE ((name)::text = 'last_reminded_at'::text)
# index_user_custom_fields_on_user_id_and_name (user_id,name)
#

View File

@ -1,9 +0,0 @@
# frozen_string_literal: true
class ReviewableScoreBonusSerializer < ApplicationSerializer
attributes :id, :name, :score_bonus
def name
I18n.t("post_action_types.#{object.name_key}.title")
end
end

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ReviewableScoreTypeSerializer < ApplicationSerializer
attributes :id, :title, :score_bonus, :icon
attributes :id, :title, :reviewable_priority, :icon
# Allow us to share post action type translations for backwards compatibility
def title
@ -9,12 +9,12 @@ class ReviewableScoreTypeSerializer < ApplicationSerializer
I18n.t("reviewable_score_types.#{ReviewableScore.types[id]}.title")
end
def score_bonus
object.score_bonus.to_f
def reviewable_priority
object.reviewable_priority.to_i
end
def include_score_bonus?
object.respond_to?(:score_bonus)
def include_reviewable_priority?
object.respond_to?(:reviewable_priority)
end
def icon

View File

@ -1,7 +1,7 @@
# frozen_string_literal: true
class ReviewableSettingsSerializer < ApplicationSerializer
attributes :id
attributes :id, :reviewable_priorities
has_many :reviewable_score_types, serializer: ReviewableScoreTypeSerializer
@ -12,4 +12,10 @@ class ReviewableSettingsSerializer < ApplicationSerializer
def reviewable_score_types
object[:reviewable_score_types]
end
def reviewable_priorities
Reviewable.priorities.map do |p|
{ id: p[1], name: I18n.t("reviewables.priorities.#{p[0]}") }
end
end
end

View File

@ -377,9 +377,8 @@ en:
saved: "Saved"
save_changes: "Save Changes"
title: "Settings"
score_bonuses:
title: "Score Bonuses"
description: "Bonuses allow certain types to be scored higher than others so they can be prioritized. Note: changing these values will not apply to previously scored items."
priorities:
title: "Reviewable Priorities"
moderation_history: "Moderation History"
view_all: "View All"

View File

@ -4414,6 +4414,11 @@ en:
webhook_deactivation_reason: "Your webhook has been automatically deactivated. We received multiple '%{status}' HTTP status failure responses."
reviewables:
priorities:
low: "Low"
medium: "Medium"
high: "High"
must_claim: "You must claim items before acting on them."
user_claimed: "This item has been claimed by another user."
missing_version: "You must supply a version parameter"

View File

@ -0,0 +1,19 @@
# frozen_string_literal: true
class AddPriorityToPostActionTypes < ActiveRecord::Migration[5.2]
def up
add_column :post_action_types, :reviewable_priority, :integer, default: 0, null: false
execute(<<~SQL)
UPDATE post_action_types
SET reviewable_priority = CASE
WHEN score_bonus > 5 THEN 10
WHEN score_bonus > 0 THEN 5
ELSE 0
END
SQL
end
def down
remove_column :post_action_types, :reviewable_priority
end
end

View File

@ -8,16 +8,17 @@ describe Jobs::ReviewablePriorities do
(1..6).each { |i| Fabricate(:reviewable, score: i) }
Jobs::ReviewablePriorities.new.execute({})
expect(Reviewable.min_score_for_priority('low')).to eq(0.0)
expect(Reviewable.min_score_for_priority(:low)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:medium)).to eq(3.0)
expect(Reviewable.min_score_for_priority('medium')).to eq(3.0)
expect(Reviewable.min_score_for_priority('high')).to eq(6.0)
expect(Reviewable.min_score_for_priority(:high)).to eq(6.0)
end
it "will return 0 if no reviewables exist" do
Jobs::ReviewablePriorities.new.execute({})
expect(Reviewable.min_score_for_priority('low')).to eq(0.0)
expect(Reviewable.min_score_for_priority('medium')).to eq(0.0)
expect(Reviewable.min_score_for_priority('high')).to eq(0.0)
expect(Reviewable.min_score_for_priority(:low)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:medium)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:high)).to eq(0.0)
end
end

View File

@ -284,20 +284,20 @@ RSpec.describe Reviewable, type: :model do
context "priorities" do
it "returns 0 for unknown priorities" do
expect(Reviewable.min_score_for_priority('wat')).to eq(0.0)
expect(Reviewable.min_score_for_priority(:wat)).to eq(0.0)
end
it "returns 0 for all by default" do
expect(Reviewable.min_score_for_priority('low')).to eq(0.0)
expect(Reviewable.min_score_for_priority('medium')).to eq(0.0)
expect(Reviewable.min_score_for_priority('high')).to eq(0.0)
expect(Reviewable.min_score_for_priority(:low)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:medium)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:high)).to eq(0.0)
end
it "can be set manually with `set_priorities`" do
Reviewable.set_priorities(medium: 12.5, high: 123.45)
expect(Reviewable.min_score_for_priority('low')).to eq(0.0)
expect(Reviewable.min_score_for_priority('medium')).to eq(12.5)
expect(Reviewable.min_score_for_priority('high')).to eq(123.45)
expect(Reviewable.min_score_for_priority(:low)).to eq(0.0)
expect(Reviewable.min_score_for_priority(:medium)).to eq(12.5)
expect(Reviewable.min_score_for_priority(:high)).to eq(123.45)
end
it "will return the default priority if none supplied" do

View File

@ -387,9 +387,23 @@ describe ReviewablesController do
end
it "allows the settings to be updated" do
put "/review/settings.json", params: { bonuses: { 8 => 3.45 } }
put "/review/settings.json", params: { reviewable_priorities: { 8 => Reviewable.priorities[:medium] } }
expect(response.code).to eq("200")
expect(PostActionType.find_by(id: 8).score_bonus).to eq(3.45)
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:medium])
expect(pa.score_bonus).to eq(5.0)
put "/review/settings.json", params: { reviewable_priorities: { 8 => Reviewable.priorities[:low] } }
expect(response.code).to eq("200")
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:low])
expect(pa.score_bonus).to eq(0.0)
put "/review/settings.json", params: { reviewable_priorities: { 8 => Reviewable.priorities[:high] } }
expect(response.code).to eq("200")
pa = PostActionType.find_by(id: 8)
expect(pa.reviewable_priority).to eq(Reviewable.priorities[:high])
expect(pa.score_bonus).to eq(10.0)
end
end

View File

@ -95,17 +95,22 @@ export default function(helpers) {
{
id: 3,
title: "Off-Topic",
score_bonus: 0.0
reviewable_priority: 0
},
{
id: 4,
title: "Inappropriate",
score_bonus: 0.0
reviewable_priority: 5
}
],
reviewable_settings: {
id: 13870,
reviewable_score_type_ids: [3, 4]
reviewable_score_type_ids: [3, 4],
reviewable_priorities: [
{ id: 0, name: "Low" },
{ id: 5, name: "Medium" },
{ id: 10, name: "High" }
]
},
__rest_serializer: "1"
});