diff --git a/app/assets/stylesheets/notifications.scss b/app/assets/stylesheets/notifications.scss index ccd6db2f9..8910aa68e 100644 --- a/app/assets/stylesheets/notifications.scss +++ b/app/assets/stylesheets/notifications.scss @@ -67,11 +67,21 @@ margin: 10px auto; width: 90%; color:#666666; + &.milestone-emojis{ + font-size: xx-large; + padding-top: 10px; + padding-bottom: 0px; + } } img.badge-image{ margin:10px 0px 20px; height: 150px; } + img.milestone-gif{ + margin: 10px 0px 10px; + border-radius: 8px; + width: 300px; + } p.badge-reward-message{ margin: 8px auto; width: 90%; diff --git a/app/decorators/notification_decorator.rb b/app/decorators/notification_decorator.rb index c2be08aed..6e994653f 100644 --- a/app/decorators/notification_decorator.rb +++ b/app/decorators/notification_decorator.rb @@ -10,4 +10,12 @@ class NotificationDecorator < Draper::Decorator end struct.new(json_data[type]["class"]["name"], json_data[type]["id"]) end + + def milestone_type + action.split("::")[1] + end + + def milestone_count + action.split("::")[2] + end end diff --git a/app/labor/article_analytics_fetcher.rb b/app/labor/article_analytics_fetcher.rb index 0f98690fc..be6215f5e 100644 --- a/app/labor/article_analytics_fetcher.rb +++ b/app/labor/article_analytics_fetcher.rb @@ -19,9 +19,11 @@ class ArticleAnalyticsFetcher page_views_obj = pageviews.to_h chunk.each do |article| article.update_columns(previous_positive_reactions_count: article.positive_reactions_count) + Notification.send_milestone_notification(type: "Reaction", article: article) next if article.page_views_count > page_views_obj[article.id].to_i article.update_columns(page_views_count: page_views_obj[article.id].to_i) + Notification.send_milestone_notification(type: "View", article: article) end end end diff --git a/app/labor/random_gif.rb b/app/labor/random_gif.rb new file mode 100644 index 000000000..d042455d4 --- /dev/null +++ b/app/labor/random_gif.rb @@ -0,0 +1,27 @@ +class RandomGif + def initialize(_action = "congratulations") + @random_gifs = { + "xjZtu4qi1biIo" => { aspect_ratio: 1.000 }, + "12P29BwtrvsbbW" => { aspect_ratio: 0.760 }, + "9PyhoXey73EpW" => { aspect_ratio: 0.753 }, + "OcZp0maz6ALok" => { aspect_ratio: 1.000 }, + "Is1O1TWV0LEJi" => { aspect_ratio: 0.565 }, + "xjZtu4qi1biIo" => { aspect_ratio: 1.0000 }, + "lz24Z42jLcTa8" => { aspect_ratio: 0.776 }, + "g9582DNuQppxC" => { aspect_ratio: 0.562 }, + "l4HodBpDmoMA5p9bG" => { aspect_ratio: 1.000 }, + "3oxOCfV7z28QtXXAtO" => { aspect_ratio: 0.750 }, + "y8Mz1yj13s3kI" => { aspect_ratio: 0.750 }, + "111ebonMs90YLu" => { aspect_ratio: 0.750 }, + "Sk5uipPXyBjfW" => { aspect_ratio: 0.422 } + } + end + + def random_id + @random_gifs.keys.sample + end + + def get_aspect_ratio(id) + (@random_gifs[id] || "xjZtu4qi1biIo")[:aspect_ratio] + end +end diff --git a/app/models/notification.rb b/app/models/notification.rb index 457d5866c..b332ceda4 100644 --- a/app/models/notification.rb +++ b/app/models/notification.rb @@ -254,6 +254,29 @@ class Notification < ApplicationRecord end handle_asynchronously :send_tag_adjustment_notification + def send_milestone_notification(milestone_hash) + milestone_hash[:next_milestone] = next_milestone(milestone_hash) + return unless should_send_milestone?(milestone_hash) + + Notification.create!( + user_id: milestone_hash[:article].user_id, + notifiable_id: milestone_hash[:article].id, + notifiable_type: "Article", + json_data: { article: article_data(milestone_hash[:article]), gif_id: RandomGif.new.random_id }, + action: "Milestone::#{milestone_hash[:type]}::#{milestone_hash[:next_milestone]}", + ) + if milestone_hash[:article].organization_id + Notification.create!( + organization_id: milestone_hash[:article].organization_id, + notifiable_id: milestone_hash[:article].id, + notifiable_type: "Article", + json_data: { article: article_data(milestone_hash[:article]) }, + action: "Milestone::#{milestone_hash[:type]}::#{milestone_hash[:next_milestone]}", + ) + end + end + handle_asynchronously :send_milestone_notification + def remove_all(notifiable_hash) Notification.where( notifiable_id: notifiable_hash[:notifiable_id], @@ -348,6 +371,41 @@ class Notification < ApplicationRecord } end + def should_send_milestone?(milestone_hash) + return if milestone_hash[:article].published_at < DateTime.new(2019, 2, 25) + + last_milestone_notification = Notification.find_by( + user_id: milestone_hash[:article].user_id, + notifiable_type: "Article", + notifiable_id: milestone_hash[:article].id, + action: "Milestone::#{milestone_hash[:type]}::#{milestone_hash[:next_milestone]}", + ) + + if milestone_hash[:type] == "View" + last_milestone_notification.blank? && milestone_hash[:article].page_views_count > milestone_hash[:next_milestone] + elsif milestone_hash[:type] == "Reaction" + last_milestone_notification.blank? && milestone_hash[:article].positive_reactions_count > milestone_hash[:next_milestone] + end + end + + def next_milestone(milestone_hash) + case milestone_hash[:type] + when "View" + milestones = [1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576] + milestone_count = milestone_hash[:article].page_views_count + when "Reaction" + milestones = [64, 128, 256, 512, 1024, 2048, 4096, 8192] + milestone_count = milestone_hash[:article].positive_reactions_count + end + + closest_number = milestones.min_by { |num| (milestone_count - num).abs } + if milestone_count > closest_number + closest_number + else + milestones[milestones.index(closest_number) - 1] + end + end + def send_push_notifications(user_id, title, body, path) return unless ApplicationConfig["PUSHER_BEAMS_KEY"] && ApplicationConfig["PUSHER_BEAMS_KEY"].size == 64 diff --git a/app/views/notifications/_article.html.erb b/app/views/notifications/_article.html.erb index a3be60d4c..a84bc1b71 100644 --- a/app/views/notifications/_article.html.erb +++ b/app/views/notifications/_article.html.erb @@ -1,16 +1,18 @@ <% if notification.action == "Reaction" %> <%= render "reaction", notification: notification %> +<% elsif notification.action.include? "Milestone" %> + <%= render "milestone", notification: notification %> <% else %> <% json_data = notification.json_data %> <% if json_data["organization"] %>
" class="article-organization-headline-inner"> - " alt="link to <%= json_data["organization"]["name"] %>"> <%= json_data["organization"]["name"] %> + " alt="link to <%= json_data["organization"]["name"] %>"> <%= json_data["organization"]["name"] %> "> 
<% end %> - <% cache "activity-profile-pic-#{json_data["user"]["id"]}-#{json_data["user"]["profile_image_90"]}" do %> + <% cache "activity-profile-pic-#{json_data['user']['id']}-#{json_data['user']['profile_image_90']}" do %> " class="small-pic-link-wrapper">
" alt="link to <%= json_data["user"]["username"] %>'s profile"> diff --git a/app/views/notifications/_milestone.html.erb b/app/views/notifications/_milestone.html.erb new file mode 100644 index 000000000..e9e52142c --- /dev/null +++ b/app/views/notifications/_milestone.html.erb @@ -0,0 +1,17 @@ +<% json_data = notification.json_data %> +
+
+ Your post "><%= json_data["article"]["title"] %> just passed <%= notification.milestone_count %> <%= notification.milestone_type.pluralize.downcase %>! +
+

🎉🎉🎉🎉🎉🎉🎉🎉

+ + " rel="noopener noreferrer" target="_blank"> + px;" class="milestone-gif" src="https://media.giphy.com/media/<%= json_data["gif_id"] %>/giphy-downsized.gif" alt="A random celebratory gif!"> + +
+ + + +
diff --git a/spec/models/notification_spec.rb b/spec/models/notification_spec.rb index 1ece0be09..95bde4ef2 100644 --- a/spec/models/notification_spec.rb +++ b/spec/models/notification_spec.rb @@ -5,7 +5,7 @@ RSpec.describe Notification, type: :model do let(:user2) { create(:user) } let(:user3) { create(:user) } let(:organization) { create(:organization) } - let(:article) { create(:article, user_id: user.id) } + let(:article) { create(:article, user_id: user.id, page_views_count: 4000, positive_reactions_count: 70) } let(:follow_instance) { user.follow(user2) } describe "#send_new_follower_notification" do @@ -240,6 +240,66 @@ RSpec.describe Notification, type: :model do end end + describe "#send_milestone_notification" do + # milestones = [64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144] + let(:view_milestone_hash) { { type: "View", article: article } } + let(:reaction_milestone_hash) { { type: "Reaction", article: article } } + + context "when a user has never received a milestone notification" do + it "sends the appropriate level view milestone notification" do + Notification.send_milestone_notification_without_delay(view_milestone_hash) + expect(user.notifications.first.action).to include "2048" + end + + it "sends the appropriate level reaction milestone notification" do + Notification.send_milestone_notification_without_delay(reaction_milestone_hash) + expect(user.notifications.first.action).to include "64" + end + end + + context "when a user has received a milestone notification before" do + def mock_previous_view_milestone_notification + Notification.send_milestone_notification_without_delay(view_milestone_hash) + article.update_column(:page_views_count, 9001) + Notification.send_milestone_notification_without_delay(view_milestone_hash) + end + + def mock_previous_reaction_milestone_notification + Notification.send_milestone_notification_without_delay(reaction_milestone_hash) + article.update_column(:positive_reactions_count, 150) + Notification.send_milestone_notification_without_delay(reaction_milestone_hash) + end + + it "sends the appropriate level view milestone notification" do + mock_previous_view_milestone_notification + expect(user.notifications.second.action).to include "8192" + end + + it "sends the appropriate level reaction milestone notification" do + mock_previous_reaction_milestone_notification + expect(user.notifications.last.action).to include "128" + end + + it "adds an additional view milestone notification" do + mock_previous_view_milestone_notification + expect(user.notifications.count).to eq 2 + end + + it "does not the same view milestone notification if called again" do + mock_previous_view_milestone_notification + Notification.send_milestone_notification_without_delay(view_milestone_hash) + expect(user.notifications.count).to eq 2 + end + + it "does not send a view milestone notification again if the latest number of views is not past the next milestone" do + mock_previous_view_milestone_notification + article.update_column(:page_views_count, rand(9002..16383)) + Notification.send_milestone_notification_without_delay(view_milestone_hash) + expect(user.notifications.count).to eq 2 + end + end + end + describe "#update_notifications" do context "when there are no notifications to begin with" do it "returns nil" do diff --git a/yarn.lock b/yarn.lock index bc932ab1b..d2560afca 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10472,7 +10472,7 @@ tweetnacl@^1.0.0: resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-1.0.0.tgz#713d8b818da42068740bf68386d0479e66fc8a7b" integrity sha1-cT2LgY2kIGh0C/aDhtBHnmb8ins= -twilio-video@^1.15.1: +twilio-video@^1.15.2: version "1.15.2" resolved "https://registry.yarnpkg.com/twilio-video/-/twilio-video-1.15.2.tgz#da3379c0e256def317689d9a980c683277f61baf" integrity sha512-C3KQcHmqqJObFWU2I+bMVqwIEUISXV+DSFJHdPrrw9Z+tDE7Y5SNNj+r4e9ArABx7mtWZlEUa4+Ela/dOQLEjA==