FEATURE: Track how many user flags are agreed/disagreed/ignored
Display the percentage when reviewing flags.
This commit is contained in:
parent
ceafcbc898
commit
ec91450aae
|
@ -0,0 +1,53 @@
|
|||
import computed from "ember-addons/ember-computed-decorators";
|
||||
|
||||
export default Ember.Component.extend({
|
||||
tagName: "",
|
||||
|
||||
@computed("percentage")
|
||||
showPercentage(percentage) {
|
||||
return percentage.total >= 3;
|
||||
},
|
||||
|
||||
// We do a little logic to choose which icon to display and which text
|
||||
@computed("user.flags_agreed", "user.flags_disagreed", "user.flags_ignored")
|
||||
percentage(agreed, disagreed, ignored) {
|
||||
let total = agreed + disagreed + ignored;
|
||||
let result = { total };
|
||||
|
||||
if (total > 0) {
|
||||
result.agreed = Math.round((agreed / total) * 100);
|
||||
result.disagreed = Math.round((disagreed / total) * 100);
|
||||
result.ignored = Math.round((ignored / total) * 100);
|
||||
}
|
||||
|
||||
let highest = Math.max(agreed, disagreed, ignored);
|
||||
if (highest === agreed) {
|
||||
result.icon = "thumbs-up";
|
||||
result.className = "agreed";
|
||||
result.label = `${result.agreed}%`;
|
||||
} else if (highest === disagreed) {
|
||||
result.icon = "thumbs-down";
|
||||
result.className = "disagreed";
|
||||
result.label = `${result.disagreed}%`;
|
||||
} else {
|
||||
result.icon = "external-link";
|
||||
result.className = "ignored";
|
||||
result.label = `${result.ignored}%`;
|
||||
}
|
||||
|
||||
result.title = I18n.t("admin.flags.user_percentage.summary", {
|
||||
agreed: I18n.t("admin.flags.user_percentage.agreed", {
|
||||
count: result.agreed
|
||||
}),
|
||||
disagreed: I18n.t("admin.flags.user_percentage.disagreed", {
|
||||
count: result.disagreed
|
||||
}),
|
||||
ignored: I18n.t("admin.flags.user_percentage.disagreed", {
|
||||
count: result.ignored
|
||||
}),
|
||||
count: total
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
});
|
|
@ -8,6 +8,7 @@
|
|||
<div class='flagger-flag-type'>
|
||||
{{post-action-title postAction.post_action_type_id postAction.name_key}}
|
||||
</div>
|
||||
{{user-flag-percentage user=postAction.user}}
|
||||
{{/flag-user}}
|
||||
{{/each}}
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{{#if showPercentage}}
|
||||
<div class='user-flag-percentage' title={{percentage.title}}>
|
||||
<span class="percentage-label {{percentage.className}}">{{percentage.label}}</span>
|
||||
{{d-icon percentage.icon}}
|
||||
</div>
|
||||
{{/if}}
|
|
@ -107,10 +107,32 @@
|
|||
.flag-user-date {
|
||||
color: $primary-medium;
|
||||
}
|
||||
|
||||
.flag-user-avatar {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
.flag-user-extra {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.user-flag-percentage {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-left: 0.5em;
|
||||
|
||||
.percentage-label {
|
||||
margin-right: 0.25em;
|
||||
&.agreed {
|
||||
color: $success;
|
||||
}
|
||||
&.disagreed {
|
||||
color: $danger;
|
||||
}
|
||||
&.ignored {
|
||||
color: $primary-medium;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flag-conversation {
|
||||
|
|
|
@ -164,6 +164,9 @@ class PostAction < ActiveRecord::Base
|
|||
trigger_spam = true if action.post_action_type_id == PostActionType.types[:spam]
|
||||
end
|
||||
|
||||
# Update the flags_agreed user stat
|
||||
UserStat.where(user_id: actions.map(&:user_id)).update_all("flags_agreed = flags_agreed + 1")
|
||||
|
||||
DiscourseEvent.trigger(:confirmed_spam_post, post) if trigger_spam
|
||||
|
||||
if actions.first.present?
|
||||
|
@ -183,8 +186,7 @@ class PostAction < ActiveRecord::Base
|
|||
PostActionType.notify_flag_type_ids
|
||||
end
|
||||
|
||||
actions = PostAction.where(post_id: post.id)
|
||||
.where(post_action_type_id: action_type_ids)
|
||||
actions = PostAction.active.where(post_id: post.id).where(post_action_type_id: action_type_ids)
|
||||
|
||||
actions.each do |action|
|
||||
action.disagreed_at = Time.zone.now
|
||||
|
@ -194,6 +196,9 @@ class PostAction < ActiveRecord::Base
|
|||
action.add_moderator_post_if_needed(moderator, :disagreed)
|
||||
end
|
||||
|
||||
# Update the flags_disagreed user stat
|
||||
UserStat.where(user_id: actions.map(&:user_id)).update_all("flags_disagreed = flags_disagreed + 1")
|
||||
|
||||
# reset all cached counters
|
||||
cached = {}
|
||||
action_type_ids.each do |atid|
|
||||
|
|
|
@ -1207,18 +1207,16 @@ class Report
|
|||
u.username,
|
||||
u.uploaded_avatar_id as avatar_id,
|
||||
CASE WHEN u.silenced_till IS NOT NULL THEN 't' ELSE 'f' END as silenced,
|
||||
SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END) as disagreed_flags,
|
||||
SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END) as agreed_flags,
|
||||
ROUND(SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END)::numeric / SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END)::numeric, 2) as ratio,
|
||||
SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END) - SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END) spread,
|
||||
ROUND((1-(SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END)::numeric / SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END)::numeric)) *
|
||||
(SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END) - SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END)), 2) as score
|
||||
FROM post_actions AS pa
|
||||
INNER JOIN users AS u ON u.id = pa.user_id
|
||||
WHERE pa.post_action_type_id IN (#{PostActionType.flag_types.values.join(', ')})
|
||||
AND pa.user_id <> -1
|
||||
GROUP BY u.id, u.username, u.silenced_till
|
||||
HAVING SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END) > SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END)
|
||||
us.flags_disagreed AS disagreed_flags,
|
||||
us.flags_agreed AS agreed_flags,
|
||||
ROUND(us.flags_agreed::numeric / us.flags_disagreed::numeric, 2) as ratio,
|
||||
us.flags_disagreed - us.flags_agreed AS spread,
|
||||
ROUND((1-(us.flags_agreed::numeric / us.flags_disagreed::numeric)) *
|
||||
(us.flags_disagreed - us.flags_agreed)) AS score
|
||||
FROM users AS u
|
||||
INNER JOIN user_stats AS us ON us.user_id = u.id
|
||||
WHERE u.id <> -1
|
||||
AND flags_disagreed > flags_agreed
|
||||
ORDER BY score DESC
|
||||
LIMIT 20
|
||||
SQL
|
||||
|
|
|
@ -4,7 +4,10 @@ class FlaggedUserSerializer < BasicUserSerializer
|
|||
:post_count,
|
||||
:topic_count,
|
||||
:ip_address,
|
||||
:custom_fields
|
||||
:custom_fields,
|
||||
:flags_agreed,
|
||||
:flags_disagreed,
|
||||
:flags_ignored
|
||||
|
||||
def can_delete_all_posts
|
||||
scope.can_delete_all_posts?(object)
|
||||
|
@ -18,6 +21,18 @@ class FlaggedUserSerializer < BasicUserSerializer
|
|||
object.ip_address.try(:to_s)
|
||||
end
|
||||
|
||||
def flags_agreed
|
||||
object.user_stat.flags_agreed
|
||||
end
|
||||
|
||||
def flags_disagreed
|
||||
object.user_stat.flags_disagreed
|
||||
end
|
||||
|
||||
def flags_ignored
|
||||
object.user_stat.flags_ignored
|
||||
end
|
||||
|
||||
def custom_fields
|
||||
fields = User.whitelisted_user_custom_fields(scope)
|
||||
|
||||
|
|
|
@ -2931,6 +2931,21 @@ en:
|
|||
was_edited: "Post was edited after the first flag"
|
||||
previous_flags_count: "This post has already been flagged {{count}} times."
|
||||
show_details: "Show flag details"
|
||||
|
||||
user_percentage:
|
||||
summary:
|
||||
one: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} total flag)"
|
||||
other: "{{agreed}}, {{disagreed}}, {{ignored}} ({{count}} total flags)"
|
||||
agreed:
|
||||
one: "{{count}}% agree"
|
||||
other: "{{count}}% agree"
|
||||
disagreed:
|
||||
one: "{{count}}% disagree"
|
||||
other: "{{count}}% disagree"
|
||||
ignored:
|
||||
one: "{{count}}% ignore"
|
||||
other: "{{count}}% ignore"
|
||||
|
||||
details: "details"
|
||||
|
||||
flagged_topics:
|
||||
|
|
|
@ -0,0 +1,34 @@
|
|||
class AddFlagStatsToUser < ActiveRecord::Migration[5.2]
|
||||
def up
|
||||
add_column :user_stats, :flags_agreed, :integer, default: 0, null: false
|
||||
add_column :user_stats, :flags_disagreed, :integer, default: 0, null: false
|
||||
add_column :user_stats, :flags_ignored, :integer, default: 0, null: false
|
||||
|
||||
sql = <<~SQL
|
||||
UPDATE user_stats
|
||||
SET flags_agreed = x.flags_agreed,
|
||||
flags_disagreed = x.flags_disagreed,
|
||||
flags_ignored = x.flags_ignored
|
||||
FROM (
|
||||
SELECT u.id AS user_id,
|
||||
SUM(CASE WHEN pa.disagreed_at IS NOT NULL THEN 1 ELSE 0 END) as flags_disagreed,
|
||||
SUM(CASE WHEN pa.agreed_at IS NOT NULL THEN 1 ELSE 0 END) as flags_agreed,
|
||||
SUM(CASE WHEN pa.deferred_at IS NOT NULL THEN 1 ELSE 0 END) as flags_ignored
|
||||
FROM post_actions AS pa
|
||||
INNER JOIN users AS u ON u.id = pa.user_id
|
||||
WHERE pa.post_action_type_id IN (#{PostActionType.notify_flag_types.values.join(', ')})
|
||||
AND pa.user_id > 0
|
||||
GROUP BY u.id
|
||||
) AS x
|
||||
WHERE x.user_id = user_stats.user_id
|
||||
SQL
|
||||
|
||||
execute sql
|
||||
end
|
||||
|
||||
def down
|
||||
remove_column :user_stats, :flags_agreed
|
||||
remove_column :user_stats, :flags_disagreed
|
||||
remove_column :user_stats, :flags_ignored
|
||||
end
|
||||
end
|
|
@ -206,7 +206,7 @@ module FlagQuery
|
|||
results = PostAction
|
||||
.flags
|
||||
.active
|
||||
.includes(post: [:user, :topic])
|
||||
.includes(post: [{ user: :user_stat }, :topic])
|
||||
.references(:post)
|
||||
.where("posts.user_id > 0")
|
||||
.order('post_actions.created_at DESC')
|
||||
|
|
|
@ -79,12 +79,17 @@ describe PostAction do
|
|||
# Acting on the flag should not post an automated status message (since a moderator already replied)
|
||||
expect(topic.posts.count).to eq(2)
|
||||
PostAction.agree_flags!(post, admin)
|
||||
expect(action.user.user_stat.flags_agreed).to eq(1)
|
||||
expect(action.user.user_stat.flags_disagreed).to eq(0)
|
||||
|
||||
topic.reload
|
||||
expect(topic.posts.count).to eq(2)
|
||||
|
||||
# Clearing the flags should not post an automated status message
|
||||
PostAction.act(mod, post, PostActionType.types[:notify_moderators], message: "another special message")
|
||||
new_action = PostAction.act(mod, post, PostActionType.types[:notify_moderators], message: "another special message")
|
||||
PostAction.clear_flags!(post, admin)
|
||||
expect(new_action.user.user_stat.flags_agreed).to eq(0)
|
||||
expect(new_action.user.user_stat.flags_disagreed).to eq(1)
|
||||
topic.reload
|
||||
expect(topic.posts.count).to eq(2)
|
||||
|
||||
|
@ -95,6 +100,9 @@ describe PostAction do
|
|||
|
||||
expect(topic.posts.count).to eq(1)
|
||||
PostAction.agree_flags!(another_post, admin)
|
||||
expect(action.user.user_stat.flags_agreed).to eq(2)
|
||||
expect(action.user.user_stat.flags_disagreed).to eq(0)
|
||||
|
||||
topic.reload
|
||||
expect(topic.posts.count).to eq(2)
|
||||
expect(topic.posts.last.post_type).to eq(Post.types[:moderator_action])
|
||||
|
@ -361,7 +369,7 @@ describe PostAction do
|
|||
|
||||
# If a flag is dismissed
|
||||
PostAction.clear_flags!(post, admin)
|
||||
expect(PostAction.flag_counts_for(post.id)).to eq([8, 0])
|
||||
expect(PostAction.flag_counts_for(post.id)).to eq([0, 8])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -689,6 +697,7 @@ describe PostAction do
|
|||
|
||||
SiteSetting.auto_respond_to_flag_actions = false
|
||||
PostAction.agree_flags!(post, admin)
|
||||
expect(action.user.user_stat.flags_agreed).to eq(1)
|
||||
|
||||
topic.reload
|
||||
expect(topic.posts.count).to eq(1)
|
||||
|
@ -704,6 +713,7 @@ describe PostAction do
|
|||
|
||||
SiteSetting.auto_respond_to_flag_actions = true
|
||||
PostAction.agree_flags!(post, admin)
|
||||
expect(action.user.user_stat.flags_agreed).to eq(1)
|
||||
|
||||
user_notifications = user.notifications
|
||||
expect(user_notifications.count).to eq(1)
|
||||
|
@ -715,11 +725,12 @@ describe PostAction do
|
|||
post = Fabricate(:post)
|
||||
user = Fabricate(:user)
|
||||
action = PostAction.act(user, post, PostActionType.types[:notify_user], message: "WAT")
|
||||
topic = action.reload.related_post.topic
|
||||
action.reload.related_post.topic
|
||||
expect(user.notifications.count).to eq(0)
|
||||
|
||||
SiteSetting.auto_respond_to_flag_actions = true
|
||||
PostAction.agree_flags!(post, admin)
|
||||
expect(action.user.user_stat.flags_agreed).to eq(0)
|
||||
|
||||
user_notifications = user.notifications
|
||||
expect(user_notifications.count).to eq(0)
|
||||
|
|
|
@ -404,6 +404,7 @@ describe WebHook do
|
|||
payload = JSON.parse(job_args["payload"])
|
||||
expect(payload["id"]).to eq(post_action.id)
|
||||
|
||||
post_action = PostAction.act(Fabricate(:user), post, PostActionType.types[:spam])
|
||||
PostAction.clear_flags!(post, moderator)
|
||||
job_args = Jobs::EmitWebHookEvent.jobs.last["args"].first
|
||||
|
||||
|
|
|
@ -59,6 +59,7 @@ RSpec.describe Admin::FlagsController do
|
|||
|
||||
post_action.reload
|
||||
expect(post_action.agreed_by_id).to eq(admin.id)
|
||||
expect(user.user_stat.reload.flags_agreed).to eq(1)
|
||||
|
||||
post_1.reload
|
||||
expect(post_1.deleted_at).to eq(nil)
|
||||
|
@ -77,6 +78,7 @@ RSpec.describe Admin::FlagsController do
|
|||
post_action.reload
|
||||
|
||||
expect(post_action.agreed_by_id).to eq(admin.id)
|
||||
expect(user.user_stat.reload.flags_agreed).to eq(1)
|
||||
|
||||
agree_post = Topic.joins(:topic_allowed_users).where('topic_allowed_users.user_id = ?', user.id).order(:id).last.posts.last
|
||||
expect(agree_post.raw).to eq(I18n.with_locale(:en) { I18n.t('flags_dispositions.agreed') })
|
||||
|
|
Loading…
Reference in New Issue