Skip to content
This repository has been archived by the owner on Aug 13, 2020. It is now read-only.

Commit

Permalink
Email threading, cleanup and inbox (#1304)
Browse files Browse the repository at this point in the history
  • Loading branch information
Eric Berry authored May 29, 2020
1 parent a261f59 commit 12e8686
Show file tree
Hide file tree
Showing 39 changed files with 1,124 additions and 39 deletions.
1 change: 1 addition & 0 deletions Gemfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ gem "cable_ready", "~> 4.1.0"
gem "camo", "~> 0.1.0"
gem "chroma", "~> 0.2.0"
gem "chronic", "~> 0.10.2"
gem "closure_tree", "~> 7.1"
gem "cloudflare-rails", "~> 0.6.0", group: :production
gem "consolidated_screening_list", "~> 0.0.2"
gem "countries", "~> 3.0.0"
Expand Down
6 changes: 6 additions & 0 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,9 @@ GEM
chronic (0.10.2)
chunky_png (1.3.11)
cliver (0.3.2)
closure_tree (7.1.0)
activerecord (>= 4.2.10)
with_advisory_lock (>= 4.0.0)
cloudflare-rails (0.6.0)
httparty
rails (>= 5.0, < 6.1.0)
Expand Down Expand Up @@ -590,6 +593,8 @@ GEM
websocket-driver (0.7.1)
websocket-extensions (>= 0.1.0)
websocket-extensions (0.1.4)
with_advisory_lock (4.6.0)
activerecord (>= 4.2)
xpath (3.2.0)
nokogiri (~> 1.8)
yard (0.9.25)
Expand All @@ -614,6 +619,7 @@ DEPENDENCIES
capybara (>= 2.15)
chroma (~> 0.2.0)
chronic (~> 0.10.2)
closure_tree (~> 7.1)
cloudflare-rails (~> 0.6.0)
codecov
consolidated_screening_list (~> 0.0.2)
Expand Down
6 changes: 3 additions & 3 deletions app/components/page_component.html.erb
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
<div class="page<%= " has-sidebar has-sidebar-expand-xl" if sidebar %>">
<%= content_tag("div", class: classes) do %>
<%= tag.div(class: "sidebar-backdrop") if sidebar %>
<div class="page-inner" data-controller="auto-height">
<%= header %>
<%= render(tabs) if tabs %>
<%= tag.hr(class: "my-3") if !tabs %>
<%= tag.hr(class: "my-3") if !tabs && header %>
<div class="page-section"><%= body %></div>
<%= render "/#{subject_view_directory}/sidebar", subject: subject if sidebar %>
</div>
</div>
<% end %>
12 changes: 10 additions & 2 deletions app/components/page_component.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
class PageComponent < ApplicationComponent
with_content_areas :header, :body

def initialize(subject: nil, tabs: false, sidebar: false)
def initialize(subject: nil, tabs: false, sidebar: false, classes: nil)
@subject = subject
@tabs = tabs
@sidebar = sidebar
@class_names = classes
end

def subject_view_directory
Expand All @@ -14,5 +15,12 @@ def subject_view_directory

private

attr_reader :subject, :sidebar, :tabs
attr_reader :subject, :sidebar, :tabs, :class_names

def classes
classes = ["page"]
classes << "has-sidebar has-sidebar-expand-xl" if sidebar
classes << class_names if class_names
classes.compact
end
end
Empty file.
28 changes: 17 additions & 11 deletions app/controllers/emails_controller.rb
Original file line number Diff line number Diff line change
@@ -1,26 +1,32 @@
class EmailsController < ApplicationController
before_action :authenticate_user!
before_action :authorize_view!
before_action :set_user
before_action :set_email, only: :show

def index
emails = @user.emails.order(delivered_at: :desc)
emails = current_user.emails
@current_filter = params[:filter] || "All"
session[:email_date_format] ||= "default"

emails =
case @current_filter
when "Unread" then emails.inbound.unread_by(current_user)
when "Sent" then emails.outbound
else emails.inbound
end

emails = emails.order(delivered_at: :desc)
@pagy, @emails = pagy(emails)
end

private

def set_user
@user = if authorized_user.can_admin_system?
User.find(params[:user_id])
else
current_user
end
def show
@email.mark_read_for_user!(current_user)
end

private

def set_email
@email = @user.emails.find(params[:id])
@email = current_user.emails.find(params[:id])
end

def authorize_view!
Expand Down
3 changes: 2 additions & 1 deletion app/javascript/themes/current/stylesheets/components.scss
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@
@import './components/checkbox_tree';
@import './components/creative_preview';
@import './components/dropdown';
@import './components/emails';
@import './components/navigation';
@import './components/page';
@import './components/select2';
@import './components/sparkline';
@import './components/tables';
@import './components/trix';
@import './components/emails';
29 changes: 29 additions & 0 deletions app/javascript/themes/current/stylesheets/components/page.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
.page-fixed {
display: flex;
flex-direction: column;
height: 100%;

.page-fixed-header {
position: relative;
padding: 0.5rem 0.5rem 0.5rem 0.25rem;
display: flex;
align-items: center;
height: 3.5rem;
background-color: #fff;
box-shadow: 0 1px 0 0 rgba(20, 20, 31, 0.075);
z-index: 5;
border-bottom: 1px solid #ecedf1;
}
.page-fixed-body {
padding: 0rem;
flex: 1;
overflow-y: auto;
}
.page-fixed-footer {
position: relative;
padding: 0.5rem;
background-color: #fff;
box-shadow: 0 -1px 0 0 rgba(20, 20, 31, 0.075);
z-index: 1;
}
}
5 changes: 5 additions & 0 deletions app/mailboxes/incoming_mailbox.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,15 @@ def process
inbound_email.created_at
end

parent_email_id = Email.find_by(message_id: mail.in_reply_to)&.id

email = Email.create! \
action_mailbox_inbound_email_id: inbound_email.id,
sender: mail.from.first,
recipients: (mail.to.to_a + mail.cc.to_a).uniq.compact.sort,
message_id: mail.message_id,
parent_id: parent_email_id,
in_reply_to: mail.in_reply_to,
subject: mail.subject,
snippet: snippet,
body: body,
Expand Down
11 changes: 11 additions & 0 deletions app/models/concerns/emails/presentable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
module Emails
module Presentable
extend ActiveSupport::Concern
include ActionView::Helpers::DateHelper

def human_delivered_at
from_time = Time.now
distance_of_time_in_words(from_time, delivered_at, scope: "datetime.distance_in_words.email") # => "2H"
end
end
end
4 changes: 2 additions & 2 deletions app/models/concerns/users/presentable.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ def hashed_email
Digest::MD5.hexdigest(email.downcase)
end

def gravatar_url(d = "identicon")
"https://www.gravatar.com/avatar/#{hashed_email}?s=300&d=#{d}"
def gravatar_url(d = "identicon", s = "300")
"https://www.gravatar.com/avatar/#{hashed_email}?s=#{s}&d=#{d}"
end

def display_region
Expand Down
50 changes: 49 additions & 1 deletion app/models/email.rb
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,30 @@
# delivered_at :datetime not null
# delivered_at_date :date not null
# direction :string default("inbound"), not null
# in_reply_to :string
# recipients :string default([]), not null, is an Array
# sender :string
# snippet :text
# subject :text
# created_at :datetime not null
# updated_at :datetime not null
# action_mailbox_inbound_email_id :bigint not null
# message_id :string
# parent_id :bigint
#
# Indexes
#
# index_emails_on_delivered_at_date (delivered_at_date)
# index_emails_on_delivered_at_hour (date_trunc('hour'::text, delivered_at))
# index_emails_on_parent_id (parent_id)
# index_emails_on_recipients (recipients) USING gin
# index_emails_on_sender (sender)
#
class Email < ApplicationRecord
# extends ...................................................................

# includes ..................................................................
include Emails::Presentable

# relationships .............................................................
has_many :email_users
Expand All @@ -35,17 +41,33 @@ class Email < ApplicationRecord
validates :recipients, presence: true
validates :sender, presence: true
validates :direction, presence: true, inclusion: {in: ENUMS::EMAIL_DIRECTIONS.values}
validates :message_id, presence: true, uniqueness: true

# callbacks .................................................................

# scopes ....................................................................
scope :inbound, -> { where(direction: ENUMS::EMAIL_DIRECTIONS::INBOUND) }
scope :outbound, -> { where(direction: ENUMS::EMAIL_DIRECTIONS::OUTBOUND) }
scope :unread_by, ->(user) { where(email_users: {user: user, read_at: nil}) }
scope :read_by, ->(user) { where(email_users: {user: user}).where.not(email_users: {read_at: nil}) }

# additional config (i.e. accepts_nested_attribute_for etc...) ..............
has_closure_tree # see https://github.com/ClosureTree/closure_tree#accessing-data
has_rich_text :body
has_many_attached :attachments

# class methods .............................................................

# public instance methods ...................................................

def inbound?
direction == ENUMS::EMAIL_DIRECTIONS::INBOUND
end

def outbound?
direction == ENUMS::EMAIL_DIRECTIONS::OUTBOUND
end

def participant_addresses
[sender, recipients].flatten.compact.sort
end
Expand All @@ -55,7 +77,33 @@ def participant_users
end

def sending_user
@sender ||= User.find_by(email: sender)
@sending_user ||= User.find_by(email: sender)
end

def non_admin_users
participant_users.non_administrators
end

def participating_organizations
non_admin_users.map(&:default_organization).compact.sort
end

def inbound_email
ActionMailbox::InboundEmail.find_by(id: action_mailbox_inbound_email_id)
end

def mark_read_for_user!(user)
return unless user
email_users.find_by(user: user).update!(read_at: Time.current)
end

def mark_unread_for_user!(user)
return unless user
email_users.find_by(user: user).update!(read_at: nil)
end

def read_by?(user)
email_users.find_by(user: user).read_at
end

# protected instance methods ................................................
Expand Down
1 change: 1 addition & 0 deletions app/models/email_user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# Table name: email_users
#
# id :bigint not null, primary key
# read_at :datetime
# email_id :bigint not null
# user_id :bigint not null
#
Expand Down
2 changes: 1 addition & 1 deletion app/models/user.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ def codefund_bot
def referral_code(user_id)
where(id: user_id).limit(1).pluck(:referral_code).first
end
end
end

# public instance methods ...................................................

Expand Down
19 changes: 19 additions & 0 deletions app/reflexes/emails_reflex.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class EmailsReflex < ApplicationReflex
def mark_read
email.mark_read_for_user! current_user
end

def mark_unread
email.mark_unread_for_user! current_user
end

def toggle_date_format
session[:email_date_format] = element.dataset["date-format"] == "default" ? "human" : "default"
end

private

def email
@email ||= Email.find_by(id: element.dataset["email-id"])
end
end
Loading

0 comments on commit 12e8686

Please sign in to comment.