Fix /connect bug and add email-if-inactive functionality (#482)

* Add basic functionality for connect message emails

* Fix lint

* Add connect messages email permission
This commit is contained in:
Ben Halpern 2018-08-27 14:29:53 -04:00 committed by GitHub
parent cea3fc7f26
commit 01e43a99e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 123 additions and 8 deletions

View file

@ -20,6 +20,7 @@ class EmailSubscriptionsController < ApplicationController
email_comment_notifications: "comment notifications",
email_follower_notifications: "follower notifications",
email_mention_notifications: "mention notifications",
email_connect_messages: "connect messages",
email_unread_notifications: "unread notifications",
}
emails_type[given_email_type]

View file

@ -21,6 +21,7 @@ class UsersController < ApplicationController
@tab_list = @user.settings_tab_list
@tab = params["user"]["tab"] || "profile"
authorize @user
# raise permitted_attributes(@user).to_s
if @user.update(permitted_attributes(@user))
RssReader.new.delay.fetch_user(@user) if @user.feed_url.present?
notice = "Your profile was successfully updated."

View file

@ -55,6 +55,13 @@ class NotifyMailer < ApplicationMailer
mail(to: @user.email, subject: "Thank you for your report")
end
def new_message_email(message)
@message = message
@user = message.direct_receiver
subject = "#{message.user.name} just messaged you"
mail(to: @user.email, subject: subject)
end
def reporter_resolution_email(report)
@feedback_message = report
@user = report.reporter

View file

@ -33,6 +33,14 @@ class ChatChannel < ApplicationRecord
channel_type == "open"
end
def direct?
channel_type == "direct"
end
def invite_only?
channel_type == "invite_only"
end
def clear_channel
messages.each(&:destroy!)
Pusher.trigger(pusher_channels, "channel-cleared", { chat_channel_id: id }.to_json)
@ -84,9 +92,9 @@ class ChatChannel < ApplicationRecord
end
def pusher_channels
if channel_type == "invite_only"
if invite_only?
"presence-channel-#{id}"
elsif channel_type == "open"
elsif open?
"open-channel-#{id}"
else
chat_channel_memberships.pluck(:user_id).map { |id| "private-message-notifications-#{id}" }
@ -95,7 +103,7 @@ class ChatChannel < ApplicationRecord
def adjusted_slug(user = nil, caller_type = "reciever")
user ||= current_user
if channel_type == "direct" && caller_type == "reciever"
if direct? && caller_type == "reciever"
"@" + slug.gsub("/#{user.username}", "").gsub("#{user.username}/", "")
elsif caller_type == "sender"
"@" + user.username

View file

@ -11,7 +11,7 @@ class ChatChannelMembership < ApplicationRecord
private
def permission
if chat_channel.channel_type == "direct" && chat_channel.slug.split("/").exclude?(user.username)
if chat_channel.direct? && chat_channel.slug.split("/").exclude?(user.username)
errors.add(:user_id, "is not allowed in chat")
end
# To be possibly implemented in future

View file

@ -8,6 +8,7 @@ class Message < ApplicationRecord
before_save :determine_user_validity
before_validation :evaluate_markdown
after_create :send_email_if_appropriate
after_create :update_chat_channel_last_message_at
after_create :update_all_has_unopened_messages_statuses
@ -40,6 +41,11 @@ class Message < ApplicationRecord
end
end
def direct_receiver
return if chat_channel.channel_type != "direct"
chat_channel.users.where.not(id: user.id).first
end
private
def update_chat_channel_last_message_at
@ -67,7 +73,7 @@ class Message < ApplicationRecord
doc = Nokogiri::HTML(html)
rich_style = "border: 1px solid #0a0a0a; border-radius: 3px; padding: 8px;"
doc.css("a").each do |a|
if a["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/") && article = Article.find_by_slug(a["href"].split("/")[4].split("?")[0])
if article = rich_link_article(a)
html = html + "<a style='color: #0a0a0a' href='#{article.path}'
target='_blank' data-content='articles/#{article.id}'>
<h1 style='#{rich_style}' data-content='articles/#{article.id}'>
@ -94,4 +100,22 @@ class Message < ApplicationRecord
membership = sub.user.chat_channel_memberships.order("last_opened_at DESC").first
membership.last_opened_at > 40.seconds.ago
end
def rich_link_article(a)
if a["href"].include?("//#{ApplicationConfig['APP_DOMAIN']}/") && a["href"].split("/")[4]
Article.find_by_slug(a["href"].split("/")[4].split("?")[0])
end
end
def send_email_if_appropriate
recipient = direct_receiver
return if !chat_channel.direct? ||
recipient.updated_at > 2.hours.ago ||
recipient.chat_channel_memberships.order("last_opened_at DESC").
first.last_opened_at > 18.hours.ago ||
chat_channel.last_message_at > 45.minutes.ago ||
recipient.email_connect_messages == false
NotifyMailer.new_message_email(self).deliver
end
end

View file

@ -56,6 +56,7 @@ class UserPolicy < ApplicationPolicy
email_follower_notifications
email_membership_newsletter
email_mention_notifications
email_connect_messages
email_newsletter
email_public
email_unread_notifications

View file

@ -3,5 +3,5 @@
</td></tr><tr><td style='padding:20px;padding-bottom:30px;border-radius:3px;font-size:19px;background:#f4f4f4'>
<%= @mentionable.processed_html.html_safe %>
<a style="background:#3c7dc1;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@mention.mentionable.path%>'>read & reply</a>
<a style="background:#3c7dc1;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@mention.mentionable.path%>'>view on dev.to</a>
</td></tr></table>

View file

@ -0,0 +1,10 @@
<h2 style="font-size:25px;"><%= link_to(@message.user.name, "https://dev.to/#{@message.user.username}") %> sent you a message on DEV Connect</h2>
<table><tr><td style='color:#c3c3c3;font-size:14px;padding-bottom:8px'>
</td></tr><tr><td style='padding:20px;padding-bottom:30px;border-radius:3px;font-size:19px;background:#f4f4f4'>
<%= @message.message_html %>
<br/><br/>
<a style="background:#3c7dc1;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to/connect/@<%= @message.user.username %>'>view on dev.to</a>
</td></tr></table>
<br/>
<em>You only get emails like this if you have not been active on <a href="https://dev.to/connect">dev.to/connect</a> in the past day. You may disable this type of email in your settings.

View file

@ -0,0 +1,3 @@
<%= ActionController::Base.helpers.strip_tags(@message.message_html.html_safe) %>
View now: https://dev.to/connect/@<%= @message.user.username %>

View file

@ -6,5 +6,5 @@
</td></tr><tr><td style='padding:20px;padding-bottom:30px;border-radius:3px;font-size:19px;background:#f4f4f4'>
<h2 style="font-size:22px;color:rgb(107, 107, 107)">re: <em><%= @comment.commentable.title %></em></h2>
<%= @comment.processed_html.html_safe %>
<a style="background:#3c7dc1;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@comment.path%>'>read & reply</a> <a style="background:#1f1f1f;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@comment.commentable.path%>'>read original post</a>
<a style="background:#3c7dc1;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@comment.path%>'>view on dev.to</a> <a style="background:#1f1f1f;color:white;padding:5px 14px;border-radius:3px;text-decoration:none;display:inline-block;margin-top:4px;" href='https://dev.to<%=@comment.commentable.path%>'>read original post</a>
</td></tr></table>

View file

@ -31,6 +31,10 @@
<%= f.check_box :email_badge_notifications %>
<%= f.label :email_badge_notifications, "Send me an email when I receive a badge" %>
</div>
<div class="sub-field">
<%= f.check_box :email_connect_messages %>
<%= f.label :email_connect_messages, "Send me an email when I receive a direct message (while inactive)" %>
</div>
<div class="sub-field">
<%= f.check_box :email_unread_notifications %>
<%= f.label :email_unread_notifications, "Send me occasional reminders that I have unread notifications on dev.to" %>

View file

@ -0,0 +1,5 @@
class AddEmailUnreadMessagesToUsers < ActiveRecord::Migration[5.1]
def change
add_column :users, :email_connect_messages, :boolean, default:true
end
end

View file

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 20180806142338) do
ActiveRecord::Schema.define(version: 20180826174411) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -378,6 +378,10 @@ ActiveRecord::Schema.define(version: 20180806142338) do
create_table "messages", force: :cascade do |t|
t.bigint "chat_channel_id", null: false
t.datetime "created_at", null: false
t.text "encrypted_message_html"
t.text "encrypted_message_html_iv"
t.text "encrypted_message_markdown"
t.text "encrypted_message_markdown_iv"
t.string "message_html", null: false
t.string "message_markdown", null: false
t.datetime "updated_at", null: false
@ -632,6 +636,7 @@ ActiveRecord::Schema.define(version: 20180806142338) do
t.string "email", default: "", null: false
t.boolean "email_badge_notifications", default: true
t.boolean "email_comment_notifications", default: true
t.boolean "email_connect_messages", default: true
t.boolean "email_digest_periodic", default: true, null: false
t.boolean "email_follower_notifications", default: true
t.boolean "email_membership_newsletter", default: false

View file

@ -27,4 +27,8 @@ class NotifyMailerPreview < ActionMailer::Preview
def new_report_email
NotifyMailer.new_report_email(FeedbackMessage.first)
end
def new_message_email
NotifyMailer.new_message_email(Message.last)
end
end

View file

@ -38,4 +38,46 @@ RSpec.describe Message, type: :model do
message_markdown: "hello")
expect(message).to be_valid
end
it "creates rich link in connect with proper link" do
article = create(:article)
message = create(:message, chat_channel_id: chat_channel.id, user_id: user.id,
message_markdown: "hello http://#{ApplicationConfig['APP_DOMAIN']}#{article.path}")
expect(message.message_html).to include(article.title)
expect(message.message_html).to include("data-content")
end
it "creates rich link in connect with non-rich link" do
message = create(:message, chat_channel_id: chat_channel.id, user_id: user.id,
message_markdown: "hello http://#{ApplicationConfig['APP_DOMAIN']}/report-abuse")
expect(message.message_html).not_to include("data-content")
end
it "sends email if user not recently active on /connect" do
chat_channel.add_users([user, user2])
chat_channel.update_column(:channel_type, "direct")
user2.update_column(:updated_at, 1.day.ago)
user2.chat_channel_memberships.last.update_column(:last_opened_at, 2.days.ago)
create(:message, chat_channel_id: chat_channel.id, user_id: user.id,
message_markdown: "hello http://#{ApplicationConfig['APP_DOMAIN']}/report-abuse")
expect(EmailMessage.last.subject).to start_with("#{user.name} just messaged you")
end
it "does not send email if user has been recently active" do
chat_channel.add_users([user, user2])
create(:message, chat_channel_id: chat_channel.id, user_id: user.id,
message_markdown: "hello http://#{ApplicationConfig['APP_DOMAIN']}/report-abuse")
expect(EmailMessage.all.size).to eq(0)
end
it "does not send email if user has email_messages turned off" do
chat_channel.add_users([user, user2])
chat_channel.update_column(:channel_type, "direct")
user2.update_column(:updated_at, 1.day.ago)
user2.update_column(:email_connect_messages, false)
user2.chat_channel_memberships.last.update_column(:last_opened_at, 2.days.ago)
create(:message, chat_channel_id: chat_channel.id, user_id: user.id,
message_markdown: "hello http://#{ApplicationConfig['APP_DOMAIN']}/report-abuse")
expect(EmailMessage.all.size).to eq(0)
end
end