From 9b5836aa1db71973569f3efe18f807e2aecaa45d Mon Sep 17 00:00:00 2001 From: Michelle Bueno Saquetim Vendrame Date: Thu, 2 Dec 2021 17:11:55 +0000 Subject: [PATCH] Add three reports (#14338) * Add report top_users_by_received_likes * Add report top_users_by_received_likes_from_inferior_trust_level * Add report top_users_by_likes_received_from_a_variety_of_people * Add test to report_top_users_by_received_likes * add top_users_by_likes_received_from_a_variety_of_people report test * add top_users_by_likes_received_from_inferior_trust_level report tests --- .../reports/top_users_by_likes_received.rb | 58 ++++++++++ ...likes_received_from_a_variety_of_people.rb | 60 ++++++++++ ...ikes_received_from_inferior_trust_level.rb | 71 ++++++++++++ app/models/report.rb | 3 + config/locales/server.en.yml | 19 ++++ install-imagemagick | 86 +++++++++++++++ spec/models/report_spec.rb | 104 ++++++++++++++++++ 7 files changed, 401 insertions(+) create mode 100644 app/models/concerns/reports/top_users_by_likes_received.rb create mode 100644 app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb create mode 100644 app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb create mode 100755 install-imagemagick diff --git a/app/models/concerns/reports/top_users_by_likes_received.rb b/app/models/concerns/reports/top_users_by_likes_received.rb new file mode 100644 index 00000000000..2f1b7c11329 --- /dev/null +++ b/app/models/concerns/reports/top_users_by_likes_received.rb @@ -0,0 +1,58 @@ +# frozen_string_literal: true + +module Reports::TopUsersByLikesReceived + extend ActiveSupport::Concern + + class_methods do + def report_top_users_by_likes_received(report) + report.icon = 'heart' + report.data = [] + + report.modes = [:table] + + report.dates_filtering = true + + report.labels = [ + { + type: :user, + properties: { + id: :user_id, + username: :username, + avatar: :user_avatar_template, + }, + title: I18n.t("reports.top_users_by_likes_received.labels.user") + }, + { + type: :number, + property: :qtt_like, + title: I18n.t("reports.top_users_by_likes_received.labels.qtt_like") + }, + ] + + sql = <<~SQL + SELECT + ua.user_id AS user_id, + u.username as username, + u.uploaded_avatar_id as uploaded_avatar_id, + COUNT(*) qtt_like + FROM user_actions ua + INNER JOIN users u on ua.user_id = u.id + WHERE ua.created_at::date BETWEEN :start_date AND :end_date + AND ua.action_type = 2 + GROUP BY ua.user_id, u.username, u.uploaded_avatar_id + ORDER BY qtt_like DESC + LIMIT 10 + SQL + + DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + qtt_like: row.qtt_like, + } + end + + end + end +end diff --git a/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb b/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb new file mode 100644 index 00000000000..0bf6a7348c4 --- /dev/null +++ b/app/models/concerns/reports/top_users_by_likes_received_from_a_variety_of_people.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module Reports::TopUsersByLikesReceivedFromAVarietyOfPeople + extend ActiveSupport::Concern + + class_methods do + def report_top_users_by_likes_received_from_a_variety_of_people(report) + report.icon = 'heart' + report.data = [] + + report.modes = [:table] + + report.dates_filtering = true + + report.labels = [ + { + type: :user, + properties: { + id: :user_id, + username: :username, + avatar: :user_avatar_template, + }, + title: I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.user") + }, + { + type: :number, + property: :qtt_like, + title: I18n.t("reports.top_users_by_likes_received_from_a_variety_of_people.labels.qtt_like") + }, + ] + + sql = <<~SQL + SELECT + p.user_id, + u.username as username, + u.uploaded_avatar_id as uploaded_avatar_id, + COUNT(DISTINCT ua.user_id) qtt_like + FROM user_actions ua + INNER JOIN posts p ON p.id = ua.target_post_id + INNER JOIN users u on p.user_id = u.id + WHERE ua.created_at::date BETWEEN :start_date AND :end_date + AND ua.action_type = 1 + AND p.user_id > 0 + GROUP BY p.user_id, u.username, u.uploaded_avatar_id + ORDER BY qtt_like DESC + LIMIT 10 + SQL + + DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + qtt_like: row.qtt_like, + } + end + + end + end +end diff --git a/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb b/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb new file mode 100644 index 00000000000..d0b41d57d63 --- /dev/null +++ b/app/models/concerns/reports/top_users_by_likes_received_from_inferior_trust_level.rb @@ -0,0 +1,71 @@ +# frozen_string_literal: true + +module Reports::TopUsersByLikesReceivedFromInferiorTrustLevel + extend ActiveSupport::Concern + + class_methods do + def report_top_users_by_likes_received_from_inferior_trust_level(report) + report.icon = 'heart' + report.data = [] + + report.modes = [:table] + + report.dates_filtering = true + + report.labels = [ + { + type: :user, + properties: { + id: :user_id, + username: :username, + avatar: :user_avatar_template, + }, + title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.user") + }, + { + type: :number, + property: :trust_level, + title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.trust_level") + }, + { + type: :number, + property: :qtt_like, + title: I18n.t("reports.top_users_by_likes_received_from_inferior_trust_level.labels.qtt_like") + }, + ] + + sql = <<~SQL + WITH user_liked_tl_lower AS ( + SELECT + users.id user_id, + users.username as username, + users.uploaded_avatar_id as uploaded_avatar_id, + users.trust_level, + COUNT(*) qtt_like, + rank() OVER (PARTITION BY users.trust_level ORDER BY COUNT(*) DESC) + FROM users + INNER JOIN posts p ON p.user_id = users.id + INNER JOIN user_actions ua ON ua.target_post_id = p.id AND ua.action_type = 1 + INNER JOIN users u_liked ON ua.user_id = u_liked.id AND u_liked.trust_level < users.trust_level + WHERE ua.created_at::date BETWEEN :start_date AND :end_date + GROUP BY users.id + ORDER BY trust_level DESC, qtt_like DESC + ) + + SELECT * FROM user_liked_tl_lower + WHERE rank <= 10 + SQL + + DB.query(sql, start_date: report.start_date, end_date: report.end_date).each do |row| + report.data << { + user_id: row.user_id, + username: row.username, + user_avatar_template: User.avatar_template(row.username, row.uploaded_avatar_id), + trust_level: row.trust_level, + qtt_like: row.qtt_like, + } + end + + end + end +end diff --git a/app/models/report.rb b/app/models/report.rb index 4bd185641a1..91a6a808b67 100644 --- a/app/models/report.rb +++ b/app/models/report.rb @@ -46,6 +46,9 @@ class Report include Reports::ModeratorWarningPrivateMessages include Reports::ProfileViews include Reports::TopUploads + include Reports::TopUsersByLikesReceived + include Reports::TopUsersByLikesReceivedFromInferiorTrustLevel + include Reports::TopUsersByLikesReceivedFromAVarietyOfPeople attr_accessor :type, :data, :total, :prev30Days, :start_date, :end_date, :labels, :prev_period, :facets, :limit, :average, diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 14301fe56a7..1baf352b266 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -1420,6 +1420,25 @@ en: ignores_count: Ignores count mutes_count: Mutes count description: "Users who have been muted and/or ignored by many other users." + top_users_by_likes_received: + title: "Top Users by likes received" + labels: + user: User + qtt_like: Likes Received + description: "Top 10 users who have been received more likes." + top_users_by_likes_received_from_inferior_trust_level: + title: "Top Users by likes received from a user with a lower trust level" + labels: + user: User + trust_level: Trust level + qtt_like: Likes Received + description: "Top 10 users in a higher trust level being liked by people in a lower trust level." + top_users_by_likes_received_from_a_variety_of_people: + title: "Top Users by likes received from a variety of people" + labels: + user: User + qtt_like: Likes Received + description: "Top 10 users who have had the likes from a wide range of people." dashboard: rails_env_warning: "Your server is running in %{env} mode." diff --git a/install-imagemagick b/install-imagemagick new file mode 100755 index 00000000000..71226135475 --- /dev/null +++ b/install-imagemagick @@ -0,0 +1,86 @@ +#!/bin/bash +set -e + +# version check: https://github.com/ImageMagick/ImageMagick/releases +IMAGE_MAGICK_VERSION="7.0.11-13" +IMAGE_MAGICK_HASH="fc454be622724c6224fa6c8230bb9c50191a05fbf05b9c9c25aa3e5497090b83" + +# version check: https://github.com/strukturag/libheif/releases +LIBHEIF_VERSION="1.12.0" +LIBHEIF_HASH="086145b0d990182a033b0011caadb1b642da84f39ab83aa66d005610650b3c65" + +# version check: https://aomedia.googlesource.com/aom +LIB_AOM_VERSION="3.1.0" + +# We use debian, but GitHub CI is stuck on Ubuntu Bionic, so this must be compatible with both +LIBJPEGTURBO=$(cat /etc/issue | grep -qi Debian && echo 'libjpeg62-turbo libjpeg62-turbo-dev' || echo 'libjpeg-turbo8 libjpeg-turbo8-dev') + +PREFIX=/usr/local +WDIR=/tmp/imagemagick + +# Install build deps +apt -y -q remove imagemagick +apt -y -q install git make gcc pkg-config autoconf curl g++ \ + yasm cmake \ + libde265-0 libde265-dev ${LIBJPEGTURBO} x265 libx265-dev libtool \ + libpng16-16 libpng-dev ${LIBJPEGTURBO} libwebp6 libwebp-dev libgomp1 libwebpmux3 libwebpdemux2 ghostscript libxml2-dev libxml2-utils \ + libbz2-dev gsfonts libtiff-dev libfreetype6-dev libjpeg-dev + +mkdir -p $WDIR +cd $WDIR + +# Building libaom +git clone https://aomedia.googlesource.com/aom +cd aom && git checkout v${LIB_AOM_VERSION} && cd .. +mkdir build_aom +cd build_aom +cmake ../aom/ -DENABLE_TESTS=0 -DBUILD_SHARED_LIBS=1 && make && make install +ldconfig /usr/local/lib +cd .. +rm -rf aom +rm -rf build_aom + +# Build and install libheif +cd $WDIR +wget -O $WDIR/libheif.tar.gz "https://github.com/strukturag/libheif/archive/v$LIBHEIF_VERSION.tar.gz" +sha256sum $WDIR/libheif.tar.gz +echo "$LIBHEIF_HASH $WDIR/libheif.tar.gz" | sha256sum -c +tar -xzvf $WDIR/libheif.tar.gz +cd libheif-$LIBHEIF_VERSION +./autogen.sh +./configure +make && make install + +# Build and install ImageMagick +wget -O $WDIR/ImageMagick.tar.gz "https://github.com/ImageMagick/ImageMagick/archive/$IMAGE_MAGICK_VERSION.tar.gz" +sha256sum $WDIR/ImageMagick.tar.gz +echo "$IMAGE_MAGICK_HASH $WDIR/ImageMagick.tar.gz" | sha256sum -c +IMDIR=$WDIR/$(tar tzf $WDIR/ImageMagick.tar.gz --wildcards "ImageMagick-*/configure" |cut -d/ -f1) +tar zxf $WDIR/ImageMagick.tar.gz -C $WDIR +cd $IMDIR +PKG_CONF_LIBDIR=$PREFIX/lib LDFLAGS=-L$PREFIX/lib CFLAGS=-I$PREFIX/include ./configure \ + --prefix=$PREFIX \ + --enable-static \ + --enable-bounds-checking \ + --enable-hdri \ + --enable-hugepages \ + --with-threads \ + --with-modules \ + --with-quantum-depth=16 \ + --without-magick-plus-plus \ + --with-bzlib \ + --with-zlib \ + --without-autotrace \ + --with-freetype \ + --with-jpeg \ + --without-lcms \ + --with-lzma \ + --with-png \ + --with-tiff \ + --with-heic \ + --with-webp +make all && make install + +cd $HOME +rm -rf $WDIR +ldconfig /usr/local/lib diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb index 6c5d22d6129..ccb1caef6e2 100644 --- a/spec/models/report_spec.rb +++ b/spec/models/report_spec.rb @@ -1325,4 +1325,108 @@ describe Report do end end end + + describe 'top_users_by_likes_received' do + let(:report) { Report.find('top_users_by_likes_received') } + + include_examples 'no data' + + context 'with data' do + before do + user_1 = Fabricate(:user, username: "jonah") + user_2 = Fabricate(:user, username: "jake") + user_3 = Fabricate(:user, username: "john") + + 3.times { UserAction.create!(user_id: user_1.id, action_type: UserAction::WAS_LIKED) } + 9.times { UserAction.create!(user_id: user_2.id, action_type: UserAction::WAS_LIKED) } + 6.times { UserAction.create!(user_id: user_3.id, action_type: UserAction::WAS_LIKED) } + end + + it "with category filtering" do + report = Report.find('top_users_by_likes_received') + + expect(report.data.length).to eq(3) + expect(report.data[0][:username]).to eq("jake") + expect(report.data[1][:username]).to eq("john") + expect(report.data[2][:username]).to eq("jonah") + end + end + end + + describe 'top_users_by_likes_received_from_a_variety_of_people' do + let(:report) { Report.find('top_users_by_likes_received_from_a_variety_of_people') } + + include_examples 'no data' + + context 'with data' do + before do + user_1 = Fabricate(:user, username: "jonah") + user_2 = Fabricate(:user, username: "jake") + user_3 = Fabricate(:user, username: "john") + user_4 = Fabricate(:user, username: "joseph") + user_5 = Fabricate(:user, username: "joanne") + user_6 = Fabricate(:user, username: "jerome") + + topic_1 = Fabricate(:topic, user: user_1) + topic_2 = Fabricate(:topic, user: user_2) + topic_3 = Fabricate(:topic, user: user_3) + + post_1 = Fabricate(:post, topic: topic_1, user: user_1) + post_2 = Fabricate(:post, topic: topic_2, user: user_2) + post_3 = Fabricate(:post, topic: topic_3, user: user_3) + + 3.times { UserAction.create!(user_id: user_4.id, target_post_id: post_1.id, action_type: UserAction::LIKE) } + 6.times { UserAction.create!(user_id: user_5.id, target_post_id: post_2.id, action_type: UserAction::LIKE) } + 9.times { UserAction.create!(user_id: user_6.id, target_post_id: post_3.id, action_type: UserAction::LIKE) } + + end + + it "with category filtering" do + report = Report.find('top_users_by_likes_received_from_a_variety_of_people') + + expect(report.data.length).to eq(3) + expect(report.data[0][:username]).to eq("jonah") + expect(report.data[1][:username]).to eq("jake") + expect(report.data[2][:username]).to eq("john") + end + end + end + + describe 'top_users_by_likes_received_from_inferior_trust_level' do + let(:report) { Report.find('top_users_by_likes_received_from_inferior_trust_level') } + + include_examples 'no data' + + context 'with data' do + before do + user_1 = Fabricate(:user, username: "jonah", trust_level: 2) + user_2 = Fabricate(:user, username: "jake", trust_level: 2) + user_3 = Fabricate(:user, username: "john", trust_level: 2) + user_4 = Fabricate(:user, username: "joseph", trust_level: 1) + user_5 = Fabricate(:user, username: "joanne", trust_level: 1) + user_6 = Fabricate(:user, username: "jerome", trust_level: 2) + + topic_1 = Fabricate(:topic, user: user_1) + topic_2 = Fabricate(:topic, user: user_2) + topic_3 = Fabricate(:topic, user: user_3) + + post_1 = Fabricate(:post, topic: topic_1, user: user_1) + post_2 = Fabricate(:post, topic: topic_2, user: user_2) + post_3 = Fabricate(:post, topic: topic_3, user: user_3) + + 3.times { UserAction.create!(user_id: user_4.id, target_post_id: post_1.id, action_type: UserAction::LIKE) } + 6.times { UserAction.create!(user_id: user_5.id, target_post_id: post_2.id, action_type: UserAction::LIKE) } + 9.times { UserAction.create!(user_id: user_6.id, target_post_id: post_3.id, action_type: UserAction::LIKE) } + + end + + it "with category filtering" do + report = Report.find('top_users_by_likes_received_from_inferior_trust_level') + + expect(report.data.length).to eq(2) + expect(report.data[0][:username]).to eq("jake") + expect(report.data[1][:username]).to eq("jonah") + end + end + end end