1
0

Implement email search

This commit is contained in:
Tim Kächele 2020-12-11 20:00:57 +01:00
parent 7bd547141b
commit c644ddbe8d
7 changed files with 172 additions and 53 deletions

View File

@ -1,6 +1,12 @@
class EmailsController < ApplicationController class EmailsController < ApplicationController
before_action :set_email, only: [:show, :mark_as_read, :body_preview] before_action :set_email, only: [:show, :mark_as_read, :body_preview]
def index
@emails = Email.where(mailbox_id: params[:mailbox_id])
@emails = @emails.search(params[:q]) if params[:q].present?
render(layout: false)
end
def show def show
@selected = params[:selected] @selected = params[:selected]
@email.mark_as_read! unless @email.read? @email.mark_as_read! unless @email.read?

View File

@ -0,0 +1,47 @@
import { Controller } from "stimulus"
export default class extends Controller {
static targets = [ "query", "output" ]
static values = { url: String }
connect() {
this.requests = [];
}
search() {
this.resetTimeout();
this.cancelPreviousRequests();
this.searchTimeout = setTimeout(() => {
let abortController = new AbortController();
this.requests.push(abortController)
let signal = abortController.signal;
let url = this.urlValue + "&q=" + this.queryTarget.value;
fetch(url, { signal })
.then((response) => {
return response.text();
})
.then((html) => {
this.outputTarget.innerHTML = html;
})
.catch(e => {
console.log(e)
})
}, 75)
}
resetTimeout() {
if(this.searchTimeout) {
clearTimeout(this.searchTimeout);
}
}
cancelPreviousRequests() {
this.requests.forEach(request => {
request.abort();
});
this.requests = [];
}
}

View File

@ -9,6 +9,10 @@ class Email < ApplicationRecord
validates :rendered_html, presence: true validates :rendered_html, presence: true
validates :rendered_plain_text, presence: true validates :rendered_plain_text, presence: true
def self.search(query)
Search::EmailSearch.new.search(self, query)
end
def self.unread def self.unread
where(read_at: nil) where(read_at: nil)
end end

View File

@ -0,0 +1,32 @@
module Search
class EmailSearch
def call(collection, search_query)
collection.where(where_statement,
search_query_parts: search_query_parts(search_query))
end
alias search call
private
def search_query_parts(search_query)
return [] if search_query.blank?
search_query.split(/\s+/)
.map { |query| "%#{query}%" }
end
def where_statement
<<~SQL
request_payload->'personalizations'->>personalization_id
ILIKE
ANY(ARRAY[:search_query_parts])
OR
subject ILIKE ANY(ARRAY[:search_query_parts])
OR
template_id ILIKE ANY(ARRAY[:search_query_parts])
OR
rendered_plain_text ILIKE ANY(ARRAY[:search_query_parts])
SQL
end
end
end

View File

@ -0,0 +1,18 @@
<% if @emails.any? %>
<div class="email email--no-chrome">
<b class="email__from">
From
</b>
<b class="email__to">To</b>
<b class="email__subject">Subject</b>
<b class="email__created-at">Received At</b>
</div>
<% @emails.each do |email| %>
<%= render(EmailListEntryComponent.new(email: email)) %>
<% end %>
<% else %>
<div class="text-center">
<p>¯\_(ツ)_/¯</p>
<p>We couldn't find any emails.</p>
</div>
<% end %>

View File

@ -1,4 +1,5 @@
<section> <div data-controller="search" data-search-url-value="<%= emails_path(mailbox_id: @mailbox.id) %>">
<section>
<header class="header header--with-border"> <header class="header header--with-border">
<div class="header--justified-header"> <div class="header--justified-header">
<h1><%= @mailbox.name %> (<%= @mailbox.unread_count %>)</h1> <h1><%= @mailbox.name %> (<%= @mailbox.unread_count %>)</h1>
@ -27,9 +28,18 @@
</p> </p>
<% end %> <% end %>
</header> </header>
</section> </section>
<div class="form-group">
<% if @emails.any? %> <input class="form-control"
data-search-target="query"
data-action="keyup->search#search"
placeholder="Search..."
type="text"
class="form-control"
/>
</div>
<div data-search-target="output">
<% if @emails.any? %>
<div class="email email--no-chrome"> <div class="email email--no-chrome">
<b class="email__from"> <b class="email__from">
From From
@ -41,7 +51,7 @@
<% @emails.each do |email| %> <% @emails.each do |email| %>
<%= render(EmailListEntryComponent.new(email: email)) %> <%= render(EmailListEntryComponent.new(email: email)) %>
<% end %> <% end %>
<% else %> <% else %>
<div class="text-center"> <div class="text-center">
<p>¯\_(ツ)_/¯</p> <p>¯\_(ツ)_/¯</p>
<p>You don't have any emails yet.</p> <p>You don't have any emails yet.</p>
@ -53,4 +63,6 @@
</div> </div>
</div> </div>
</div> </div>
<% end %> <% end %>
</div>
</div>

View File

@ -7,7 +7,7 @@ Rails.application.routes.draw do
post 'clear_mailbox', to: 'mailboxes#clear_mailbox' post 'clear_mailbox', to: 'mailboxes#clear_mailbox'
end end
end end
resources :emails, only: [:show] do resources :emails, only: [:index, :show] do
member do member do
get '/preview', to: 'emails#body_preview' get '/preview', to: 'emails#body_preview'
end end