diff --git a/app/assets/javascripts/admin/controllers/admin-dashboard-next-moderation.js.es6 b/app/assets/javascripts/admin/controllers/admin-dashboard-next-moderation.js.es6 index 059bcd61766..a16faa821bf 100644 --- a/app/assets/javascripts/admin/controllers/admin-dashboard-next-moderation.js.es6 +++ b/app/assets/javascripts/admin/controllers/admin-dashboard-next-moderation.js.es6 @@ -12,6 +12,16 @@ export default Ember.Controller.extend(PeriodComputationMixin, { }; }, + @computed + mostDisagreedFlaggersOptions() { + return { + table: { + total: false, + perPage: 10 + } + }; + }, + @computed("startDate", "endDate") filters(startDate, endDate) { return { startDate, endDate }; diff --git a/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs b/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs index 5a5eb153e36..d9bec93bf2c 100644 --- a/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs +++ b/app/assets/javascripts/admin/templates/dashboard_next_moderation.hbs @@ -33,6 +33,11 @@ dataSourceName="post_edits" filters=lastWeekfilters}} + {{admin-report + dataSourceName="most_disagreed_flaggers" + filters=lastWeekfilters + reportOptions=mostDisagreedFlaggersOptions}} + {{plugin-outlet name="admin-dashboard-moderation-bottom"}} diff --git a/app/models/report.rb b/app/models/report.rb index 2a5943b0ef0..ba9a045fc5e 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -1170,6 +1170,72 @@ class Report end end + def self.report_most_disagreed_flaggers(report) + report.data = [] + + report.modes = [:table] + + report.labels = [ + { + type: :user, + properties: { + username: :username, + id: :user_id, + avatar: :avatar_template, + }, + title: I18n.t("reports.most_disagreed_flaggers.labels.user") + }, + { + type: :number, + property: :disagreed_flags, + title: I18n.t("reports.most_disagreed_flaggers.labels.disagreed_flags") + }, + { + type: :number, + property: :agreed_flags, + title: I18n.t("reports.most_disagreed_flaggers.labels.agreed_flags") + }, + { + type: :number, + property: :score, + title: I18n.t("reports.most_disagreed_flaggers.labels.score") + }, + ] + + sql = <<~SQL + SELECT u.id, + 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) + ORDER BY score DESC + LIMIT 20 + SQL + + DB.query(sql).each do |row| + flagger = {} + flagger[:user_id] = row.id + flagger[:username] = row.username + flagger[:avatar_template] = User.avatar_template(row.username, row.avatar_id) + flagger[:disagreed_flags] = row.disagreed_flags + flagger[:agreed_flags] = row.agreed_flags + flagger[:score] = row.score + + report.data << flagger + end + end + private def hex_to_rgbs(hex_color) diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index b592e968614..345a76e16e3 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -898,6 +898,13 @@ en: editor: Editor author: Author edit_reason: Reason + most_disagreed_flaggers: + title: "Most disagreed flaggers" + labels: + user: User + agreed_flags: Agreed flags + disagreed_flags: Disagreed flags + score: Score moderators_activity: title: "Moderators activity" labels: @@ -2725,7 +2732,7 @@ en: This is an automated message. The new user [%{username}](%{user_url}) tried to create multiple posts with links to %{domains}, but those posts were blocked to avoid spam. The user is still able to create new posts that do not link to %{domains}. - + Please [review the user](%{user_url}). This can be modified via the `newuser_spam_host_threshold` and `white_listed_spam_host_domains` site settings. Consider adding %{domains} to the whitelist if they should be exempt. diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 7fb350a7194..b67693062f3 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -935,4 +935,42 @@ describe Report do end end end + + describe 'most_disagreed_flaggers' do + let(:joffrey) { Fabricate(:user, username: "joffrey") } + let(:robin) { Fabricate(:user, username: "robin") } + let(:moderator) { Fabricate(:moderator) } + + context 'with data' do + it "it works" do + 10.times do + post_disagreed = Fabricate(:post) + PostAction.act(joffrey, post_disagreed, PostActionType.types[:spam]) + PostAction.clear_flags!(post_disagreed, moderator) + end + + 3.times do + post_disagreed = Fabricate(:post) + PostAction.act(robin, post_disagreed, PostActionType.types[:spam]) + PostAction.clear_flags!(post_disagreed, moderator) + end + post_agreed = Fabricate(:post) + PostAction.act(robin, post_agreed, PostActionType.types[:off_topic]) + PostAction.agree_flags!(post_agreed, moderator) + + report = Report.find('most_disagreed_flaggers') + + first = report.data[0] + expect(first[:username]).to eq("joffrey") + expect(first[:score]).to eq(10) + expect(first[:agreed_flags]).to eq(0) + expect(first[:disagreed_flags]).to eq(10) + + second = report.data[1] + expect(second[:username]).to eq("robin") + expect(second[:agreed_flags]).to eq(1) + expect(second[:disagreed_flags]).to eq(3) + end + end + end end