diff --git a/Gemfile b/Gemfile index 8ea5c90f..ea3f3eef 100644 --- a/Gemfile +++ b/Gemfile @@ -68,7 +68,6 @@ group :test do gem "mocha", :require => false gem "aruba", git: 'https://github.com/cucumber/aruba', :require => false # need 0.5.4 for piping files; 0.5.3 is latest - gem "simplecov" gem "timecop", "~> 0.6.2" # Note that > 2.14 has problems, see: @@ -82,6 +81,7 @@ group :test do #gem "capybara-screenshot" #gem "launchy" + gem "simplecov" # get test coverage info on codeclimate gem "codeclimate-test-reporter", group: :test, require: nil end diff --git a/Gemfile.lock b/Gemfile.lock index 0fd7d18e..12b4e500 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -146,7 +146,7 @@ GEM thor (>= 0.18.1, < 2.0) rake (10.1.0) ref (1.0.5) - rspec-expectations (2.14.2) + rspec-expectations (2.14.3) diff-lcs (>= 1.1.3, < 2.0) rubyzip (0.9.9) safe_yaml (0.9.7) diff --git a/app/controllers/search_controller.rb b/app/controllers/search_controller.rb index f4cbb776..42e43e67 100644 --- a/app/controllers/search_controller.rb +++ b/app/controllers/search_controller.rb @@ -5,19 +5,12 @@ class SearchController < ApplicationController def results @source_view = params['_source_view'] || 'search' @page_title = "TRACKS::Search Results for #{params[:search]}" - terms = "%#{params[:search]}%" - @found_not_complete_todos = incomplete_todos(terms) - @found_complete_todos = complete_todos(terms) - @found_todos = @found_not_complete_todos + @found_complete_todos + searcher = Search::SearchResults.new(current_user, params[:search]) + searcher.search - @found_projects = current_user.projects.with_name_or_description(terms); - @found_notes = current_user.notes.with_body(terms); - @found_contexts = current_user.contexts.with_name(terms) - - # TODO: limit search to tags on todos - @found_tags = todo_tags_by_name(current_user, terms) - @count = @found_todos.size + @found_projects.size + @found_notes.size + @found_contexts.size + @found_tags.size + @results = searcher.results + @count = searcher.number_of_finds init_not_done_counts init_project_hidden_todo_counts @@ -26,30 +19,4 @@ class SearchController < ApplicationController def index @page_title = "TRACKS::Search" end - -private - def incomplete_todos(terms) - current_user.todos. - where("(todos.description LIKE ? OR todos.notes LIKE ?) AND todos.completed_at IS NULL", terms, terms). - includes(Todo::DEFAULT_INCLUDES). - reorder("todos.due IS NULL, todos.due ASC, todos.created_at ASC") - end - - def complete_todos(terms) - current_user.todos. - where("(todos.description LIKE ? OR todos.notes LIKE ?) AND NOT (todos.completed_at IS NULL)", terms, terms). - includes(Todo::DEFAULT_INCLUDES). - reorder("todos.completed_at DESC") - end - - def todo_tags_by_name(current_user, terms) - Tagging.find_by_sql([ - "SELECT DISTINCT tags.name as name "+ - "FROM tags "+ - "LEFT JOIN taggings ON tags.id = taggings.tag_id "+ - "LEFT JOIN todos ON taggings.taggable_id = todos.id "+ - "WHERE todos.user_id=? "+ - "AND tags.name LIKE ? ", current_user.id, terms]) - - end end diff --git a/app/helpers/feedlist_helper.rb b/app/helpers/feedlist_helper.rb index 1cb0da32..507e219b 100644 --- a/app/helpers/feedlist_helper.rb +++ b/app/helpers/feedlist_helper.rb @@ -27,12 +27,16 @@ module FeedlistHelper return html.html_safe end + def all_feed_links(object, symbol) + feed_links([:rss, :txt, :ical], { :controller=> 'todos', :action => 'index', symbol => object.to_param }, content_tag(:strong, object.name)) + end + def all_feed_links_for_project(project) - feed_links([:rss, :txt, :ical], { :controller=> 'todos', :action => 'index', :project_id => project.to_param }, content_tag(:strong, project.name)) + all_feed_links(project, :project_id) end def all_feed_links_for_context(context) - feed_links([:rss, :txt, :ical], { :controller=> 'todos', :action => 'index', :context_id => context.to_param }, content_tag(:strong, context.name)) + all_feed_links(context, :context_id) end protected @@ -44,5 +48,6 @@ module FeedlistHelper def user_token_hash { :token => current_user.token } end + end diff --git a/app/models/message_gateway.rb b/app/models/message_gateway.rb index e6c3855d..fb42dc92 100644 --- a/app/models/message_gateway.rb +++ b/app/models/message_gateway.rb @@ -3,27 +3,14 @@ class MessageGateway < ActionMailer::Base extend ActionView::Helpers::SanitizeHelper::ClassMethods def receive(email) - user = get_user_from_email_address(email) + user = get_receiving_user_from_email_address(email) return false if user.nil? + return false unless check_sender_is_in_mailmap(user, email) context = user.prefs.sms_context - description = nil - notes = nil - - if email.multipart? - description = get_text_or_nil(email.subject) - notes = get_first_text_plain_part(email) - else - if email.subject.blank? - description = get_decoded_text_or_nil(email.body) - notes = nil - else - description = get_text_or_nil(email.subject) - notes = get_decoded_text_or_nil(email.body) - end - end + todo_params = get_todo_params(email) - todo_builder = TodoFromRichMessage.new(user, context.id, description, notes) + todo_builder = TodoFromRichMessage.new(user, context.id, todo_params[:description], todo_params[:notes]) todo = todo_builder.construct todo.save! Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}" @@ -32,32 +19,60 @@ class MessageGateway < ActionMailer::Base private - def get_address(email) - return SITE_CONFIG['email_dispatch'] == 'to' ? email.to[0] : email.from[0] - end - - def get_user_from_env_setting + def get_todo_params(email) + params = {} + + if email.multipart? + params[:description] = get_text_or_nil(email.subject) + params[:notes] = get_first_text_plain_part(email) + else + if email.subject.blank? + params[:description] = get_decoded_text_or_nil(email.body) + params[:notes] = nil + else + params[:description] = get_text_or_nil(email.subject) + params[:notes] = get_decoded_text_or_nil(email.body) + end + end + params + end + + def get_receiving_user_from_email_address(email) + SITE_CONFIG['email_dispatch'] == 'single_user' ? get_receiving_user_from_env_setting : get_receiving_user_from_mail_header(email) + end + + def get_receiving_user_from_env_setting Rails.logger.info "All received email goes to #{ENV['TRACKS_MAIL_RECEIVER']}" user = User.where(:login => ENV['TRACKS_MAIL_RECEIVER']).first Rails.logger.info "WARNING: Unknown user set for TRACKS_MAIL_RECEIVER (#{ENV['TRACKS_MAIL_RECEIVER']})" if user.nil? return user end - def get_user_from_mail_header(email) - address = get_address(email) - Rails.logger.info "Looking for user with email #{address}" - user = User.where("preferences.sms_email" => address.strip).includes(:preference).first - if user.nil? - user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first - end - if user.present? and !sender_is_in_mailmap?(user,email) - Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}" - return nil - end - Rails.logger.info(!user.nil? ? "Email belongs to #{user.login}" : "User unknown") + def get_receiving_user_from_mail_header(email) + user = get_receiving_user_from_sms_email( get_address(email) ) + Rails.logger.info(user.nil? ? "User unknown": "Email belongs to #{user.login}") return user end + def get_address(email) + return SITE_CONFIG['email_dispatch'] == 'to' ? email.to[0] : email.from[0] + end + + def get_receiving_user_from_sms_email(address) + Rails.logger.info "Looking for user with email #{address}" + user = User.where("preferences.sms_email" => address.strip).includes(:preference).first + user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first if user.nil? + return user + end + + def check_sender_is_in_mailmap(user, email) + if user.present? and !sender_is_in_mailmap?(user,email) + Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}" + return false + end + return true + end + def sender_is_in_mailmap?(user,email) if SITE_CONFIG['mailmap'].is_a? Hash and SITE_CONFIG['email_dispatch'] == 'to' # Look for the sender in the map of allowed senders @@ -69,11 +84,6 @@ class MessageGateway < ActionMailer::Base end end - - def get_user_from_email_address(email) - SITE_CONFIG['email_dispatch'] == 'single_user' ? get_user_from_env_setting : get_user_from_mail_header(email) - end - def get_text_or_nil(text) return text ? sanitize(text.strip) : nil end diff --git a/app/models/search/search_results.rb b/app/models/search/search_results.rb new file mode 100644 index 00000000..692d0fd0 --- /dev/null +++ b/app/models/search/search_results.rb @@ -0,0 +1,54 @@ +module Search + + class SearchResults + attr_reader :results + + def initialize(user, terms) + @user = user + @terms = "%#{terms}%" + @results = {} + end + + def search + results[:not_complete_todos] = incomplete_todos(@terms) + results[:complete_todos] = complete_todos(@terms) + results[:todos] = results[:not_complete_todos] + results[:complete_todos] + results[:projects] = @user.projects.with_name_or_description(@terms) + results[:notes] = @user.notes.with_body(@terms) + results[:contexts] = @user.contexts.with_name(@terms) + results[:tags] = todo_tags_by_name(@terms) + end + + def number_of_finds + results[:todos].size + results[:projects].size + results[:notes].size + results[:contexts].size + results[:tags].size + end + + private + + def incomplete_todos(terms) + @user.todos. + where("(todos.description LIKE ? OR todos.notes LIKE ?) AND todos.completed_at IS NULL", terms, terms). + includes(Todo::DEFAULT_INCLUDES). + reorder("todos.due IS NULL, todos.due ASC, todos.created_at ASC") + end + + def complete_todos(terms) + @user.todos. + where("(todos.description LIKE ? OR todos.notes LIKE ?) AND NOT (todos.completed_at IS NULL)", terms, terms). + includes(Todo::DEFAULT_INCLUDES). + reorder("todos.completed_at DESC") + end + + def todo_tags_by_name(terms) + Tagging.find_by_sql([ + "SELECT DISTINCT tags.name as name "+ + "FROM tags "+ + "LEFT JOIN taggings ON tags.id = taggings.tag_id "+ + "LEFT JOIN todos ON taggings.taggable_id = todos.id "+ + "WHERE todos.user_id=? "+ + "AND tags.name LIKE ? ", @user.id, terms]) + end + + end + +end diff --git a/app/views/search/results.html.erb b/app/views/search/results.html.erb index c73d9761..e97431ca 100644 --- a/app/views/search/results.html.erb +++ b/app/views/search/results.html.erb @@ -2,24 +2,24 @@
<% else -%> - <%= render :layout => 'show_results_collection', :object => @found_todos, :locals => { :collection_name => "found-todos", :collection_title => t('search.todos_matching_query')} do %> - <%= render :partial => "todos/todo", :collection => @found_todos, :locals => { :parent_container_type => 'search', :suppress_context => false, :suppress_project => false, :suppress_edit_button => false } %> + <%= render :layout => 'show_results_collection', :object => @results[:todos], :locals => { :collection_name => "found-todos", :collection_title => t('search.todos_matching_query')} do %> + <%= render :partial => "todos/todo", :collection => @results[:todos], :locals => { :parent_container_type => 'search', :suppress_context => false, :suppress_project => false, :suppress_edit_button => false } %> <% end -%> - <%= render :layout => 'show_results_collection', :object => @found_projects, :locals => { :collection_name => "found-project", :collection_title => t('search.projects_matching_query')} do %> - <%= render :partial => "projects/project_listing", :collection => @found_projects, :locals => { :suppress_drag_handle => true, :suppress_edit_button => true, :suppress_delete_button => true } %> + <%= render :layout => 'show_results_collection', :object => @results[:projects], :locals => { :collection_name => "found-project", :collection_title => t('search.projects_matching_query')} do %> + <%= render :partial => "projects/project_listing", :collection => @results[:projects], :locals => { :suppress_drag_handle => true, :suppress_edit_button => true, :suppress_delete_button => true } %> <% end -%> - <%= render :layout => 'show_results_collection', :object => @found_notes, :locals => { :collection_name => "found-notes", :collection_title => t('search.notes_matching_query')} do %> - <%= render :partial => "notes/notes_summary", :collection=> @found_notes %> + <%= render :layout => 'show_results_collection', :object => @results[:notes], :locals => { :collection_name => "found-notes", :collection_title => t('search.notes_matching_query')} do %> + <%= render :partial => "notes/notes_summary", :collection=> @results[:notes] %> <% end -%> - <%= render :layout => 'show_results_collection', :object => @found_contexts, :locals => { :collection_name => "found-contexts", :collection_title => t('search.contexts_matching_query')} do %> - <%= render :partial => "contexts/context_listing", :collection => @found_contexts, :locals => { :suppress_drag_handle => true, :suppress_edit_button => true } %> + <%= render :layout => 'show_results_collection', :object => @results[:contexts], :locals => { :collection_name => "found-contexts", :collection_title => t('search.contexts_matching_query')} do %> + <%= render :partial => "contexts/context_listing", :collection => @results[:contexts], :locals => { :suppress_drag_handle => true, :suppress_edit_button => true } %> <% end -%> - <%= render :layout => 'show_results_collection', :object => @found_tags, :locals => { :collection_name => "found-tags", :collection_title => t('search.tags_matching_query')} do %> -