From eb1502d4e07ea5b8e263642cbf5ae1ec141f5d10 Mon Sep 17 00:00:00 2001 From: Reinier Balt Date: Tue, 7 Jan 2014 21:01:55 +0100 Subject: [PATCH] Merge branch 'master' into new-gui Conflicts: Gemfile.lock --- .travis.yml | 7 +- Gemfile.lock | 156 +++++++++--------- app/controllers/application_controller.rb | 2 +- app/controllers/todos_controller.rb | 108 ++++++------ app/models/message_gateway.rb | 8 +- app/models/project.rb | 3 +- app/models/todo.rb | 2 +- app/services/rich_message_extractor.rb | 60 ++++++- app/services/todo_from_rich_message.rb | 20 ++- app/views/integrations/index.de.html.erb | 2 +- app/views/integrations/index.en.html.erb | 32 ++++ app/views/projects/show.m.erb | 2 +- app/views/projects/update_status.js.rjs | 8 +- app/views/todos/show.m.erb | 34 ++-- config/application.rb | 3 + config/locales/en.yml | 3 + .../046_fix_incorrectly_hidden_todos.rb | 2 +- doc/installation.textile | 2 +- .../preferences_controller_test.rb | 1 - test/controllers/projects_controller_test.rb | 4 +- .../recurring_todos_controller_test.rb | 2 +- test/fixtures/recurring_todos.yml | 2 +- test/models/preference_test.rb | 1 - test/models/project_test.rb | 22 +-- test/models/rich_message_extractor_test.rb | 65 +++++++- test/models/todo_from_rich_message_test.rb | 19 +++ test/models/todo_test.rb | 26 +-- test/models/user_test.rb | 10 +- 28 files changed, 385 insertions(+), 221 deletions(-) diff --git a/.travis.yml b/.travis.yml index a8879b52..5eeee3cb 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,9 +1,15 @@ language: ruby + rvm: - 1.9.3 - 2.0.0 + - 2.1 + +bundler_args: --without development before_install: +before_install: + - "gem install bundler -v=1.5.1" - "mysql -e 'create database tracks_test;'" - "export DISPLAY=:99.0" - "sh -e /etc/init.d/xvfb start" @@ -12,7 +18,6 @@ before_install: script: "CODECLIMATE_REPO_TOKEN=5c52fdd2bbcd0734d56ddb2c3cbaac782da345273e8689d25f54a065ccc3397c bundle exec rake ci RACK_ENV=test" -bundler_args: --without development notifications: email: false diff --git a/Gemfile.lock b/Gemfile.lock index 12b4e500..9106e1c7 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,6 +1,6 @@ GIT remote: https://github.com/cucumber/aruba - revision: adbfc240d69254d7b525876b4c5bff6b721b7d65 + revision: 7afbc5c0cbae9c9a946d70c4c2735ccb86e00f08 specs: aruba (0.5.3) childprocess (>= 0.3.6) @@ -9,35 +9,35 @@ GIT GIT remote: https://github.com/rails/actionpack-xml_parser - revision: 246653ab3670f329176c1e77e6cd1a632466f06e + revision: e1516064761ea26502cd79b283f6af0fa2b1edf5 specs: - actionpack-xml_parser (1.0.0) - actionpack (>= 4.0.0.rc1, < 4.1) + actionpack-xml_parser (1.0.1) + actionpack (>= 4.0.0, < 5) GEM remote: https://rubygems.org/ specs: RedCloth (4.2.9) - aasm (3.0.22) - actionmailer (4.0.0) - actionpack (= 4.0.0) - mail (~> 2.5.3) - actionpack (4.0.0) - activesupport (= 4.0.0) + aasm (3.0.25) + actionmailer (4.0.2) + actionpack (= 4.0.2) + mail (~> 2.5.4) + actionpack (4.0.2) + activesupport (= 4.0.2) builder (~> 3.1.0) erubis (~> 2.7.0) rack (~> 1.5.2) rack-test (~> 0.6.2) - activemodel (4.0.0) - activesupport (= 4.0.0) + activemodel (4.0.2) + activesupport (= 4.0.2) builder (~> 3.1.0) - activerecord (4.0.0) - activemodel (= 4.0.0) + activerecord (4.0.2) + activemodel (= 4.0.2) activerecord-deprecated_finders (~> 1.0.2) - activesupport (= 4.0.0) + activesupport (= 4.0.2) arel (~> 4.0.0) activerecord-deprecated_finders (1.0.3) - activesupport (4.0.0) + activesupport (4.0.2) i18n (~> 0.6, >= 0.6.4) minitest (~> 4.2) multi_json (~> 1.3) @@ -45,16 +45,17 @@ GEM tzinfo (~> 0.3.37) acts_as_list (0.3.0) activerecord (>= 3.0) - arel (4.0.0) + arel (4.0.1) atomic (1.1.14) bcrypt-ruby (3.0.1) builder (3.1.4) - bullet (4.6.0) - uniform_notifier + bullet (4.7.1) + activesupport + uniform_notifier (>= 1.4.0) cache_digests (0.3.1) actionpack (>= 3.2) thread_safe - capybara (2.1.0) + capybara (2.2.1) mime-types (>= 1.16) nokogiri (>= 1.3.3) rack (>= 1.0.0) @@ -62,20 +63,20 @@ GEM xpath (~> 2.0) childprocess (0.3.9) ffi (~> 1.0, >= 1.0.11) - codeclimate-test-reporter (0.1.1) + codeclimate-test-reporter (0.2.0) simplecov (>= 0.7.1, < 1.0.0) - coffee-rails (4.0.0) + coffee-rails (4.0.1) coffee-script (>= 2.2.0) - railties (>= 4.0.0.beta, < 5.0) + railties (>= 4.0.0, < 5.0) coffee-script (2.2.0) coffee-script-source execjs coffee-script-source (1.6.3) commonjs (0.2.7) - cucumber (1.3.8) + cucumber (1.3.10) builder (>= 2.1.2) diff-lcs (>= 1.1.3) - gherkin (~> 2.12.1) + gherkin (~> 2.12) multi_json (>= 1.7.5, < 2.0) multi_test (>= 0.0.2) cucumber-rails (1.4.0) @@ -83,25 +84,26 @@ GEM cucumber (>= 1.2.0) nokogiri (>= 1.5.0) rails (>= 3.0.0) - database_cleaner (1.1.1) - diff-lcs (1.2.4) + database_cleaner (1.2.0) + diff-lcs (1.2.5) + docile (1.1.1) erubis (2.7.0) - execjs (2.0.1) - factory_girl (4.2.0) + execjs (2.0.2) + factory_girl (4.3.0) activesupport (>= 3.0.0) - factory_girl_rails (4.2.1) - factory_girl (~> 4.2.0) + factory_girl_rails (4.3.0) + factory_girl (~> 4.3.0) railties (>= 3.0.0) - ffi (1.9.0) - gherkin (2.12.1) + ffi (1.9.3) + gherkin (2.12.2) multi_json (~> 1.3) hike (1.2.3) htmlentities (4.3.1) - i18n (0.6.5) + i18n (0.6.9) jquery-rails (3.0.4) railties (>= 3.0, < 5.0) thor (>= 0.14, < 2.0) - json (1.8.0) + json (1.8.1) less (2.4.0) commonjs (~> 0.2.7) less-rails (2.4.2) @@ -112,66 +114,69 @@ GEM mime-types (~> 1.16) treetop (~> 1.4.8) metaclass (0.0.1) - mime-types (1.25) - mini_portile (0.5.1) + mime-types (1.25.1) + mini_portile (0.5.2) minitest (4.7.5) mocha (0.14.0) metaclass (~> 0.0.1) - mousetrap-rails (0.0.12) - multi_json (1.8.0) - multi_test (0.0.2) - mysql2 (0.3.13) - nokogiri (1.6.0) + mousetrap-rails (1.4.6) + multi_json (1.8.2) + multi_test (0.0.3) + mysql2 (0.3.14) + nokogiri (1.6.1) mini_portile (~> 0.5.0) polyglot (0.3.3) + protected_attributes (1.0.5) + activemodel (>= 4.0.1, < 5.0) rack (1.5.2) - rack-mini-profiler (0.1.31) + rack-mini-profiler (0.9.0) rack (>= 1.1.3) rack-test (0.6.2) rack (>= 1.0) - rails (4.0.0) - actionmailer (= 4.0.0) - actionpack (= 4.0.0) - activerecord (= 4.0.0) - activesupport (= 4.0.0) + rails (4.0.2) + actionmailer (= 4.0.2) + actionpack (= 4.0.2) + activerecord (= 4.0.2) + activesupport (= 4.0.2) bundler (>= 1.3.0, < 2.0) - railties (= 4.0.0) + railties (= 4.0.2) sprockets-rails (~> 2.0.0) - rails_autolink (1.1.4) + rails_autolink (1.1.5) rails (> 3.1) - railties (4.0.0) - actionpack (= 4.0.0) - activesupport (= 4.0.0) + railties (4.0.2) + actionpack (= 4.0.2) + activesupport (= 4.0.2) rake (>= 0.8.7) thor (>= 0.18.1, < 2.0) - rake (10.1.0) + rake (10.1.1) ref (1.0.5) - rspec-expectations (2.14.3) + rspec-expectations (2.14.4) diff-lcs (>= 1.1.3, < 2.0) - rubyzip (0.9.9) + rubyzip (1.1.0) safe_yaml (0.9.7) sanitize (2.0.6) nokogiri (>= 1.4.4) - sass (3.2.10) - sass-rails (4.0.0) - railties (>= 4.0.0.beta, < 5.0) + sass (3.2.13) + sass-rails (4.0.1) + railties (>= 4.0.0, < 5.0) sass (>= 3.1.10) sprockets-rails (~> 2.0.0) - selenium-webdriver (2.35.1) + selenium-webdriver (2.39.0) childprocess (>= 0.2.5) multi_json (~> 1.0) - rubyzip (< 1.0.0) + rubyzip (~> 1.0) websocket (~> 1.0.4) - simplecov (0.7.1) - multi_json (~> 1.0) - simplecov-html (~> 0.7.1) - simplecov-html (0.7.1) - sprockets (2.10.0) + simplecov (0.8.2) + docile (~> 1.1.0) + multi_json + simplecov-html (~> 0.8.0) + simplecov-html (0.8.0) + sprockets (2.10.1) hike (~> 1.2) multi_json (~> 1.0) rack (~> 1.0) tilt (~> 1.1, != 1.3.0) - sprockets-rails (2.0.0) + sprockets-rails (2.0.1) actionpack (>= 3.0) activesupport (>= 3.0) sprockets (~> 2.8) @@ -187,31 +192,32 @@ GEM atomic tilt (1.4.1) timecop (0.6.3) - tolk (1.3.11) + tolk (1.4.00) + protected_attributes safe_yaml (~> 0.8) will_paginate treetop (1.4.15) polyglot polyglot (>= 0.3.1) - turbolinks (1.3.0) + turbolinks (2.1.0) coffee-rails twitter-bootstrap-rails (2.2.8) actionpack (>= 3.1) execjs rails (>= 3.1) railties (>= 3.1) - tzinfo (0.3.37) - uglifier (2.2.1) + tzinfo (0.3.38) + uglifier (2.4.0) execjs (>= 0.3.0) - multi_json (~> 1.0, >= 1.0.2) - uniform_notifier (1.3.0) + json (>= 1.8.0) + uniform_notifier (1.4.0) websocket (1.0.7) will_paginate (3.0.5) - will_paginate-bootstrap (0.2.5) + will_paginate-bootstrap (1.0.0) will_paginate (>= 3.0.3) xpath (2.0.0) nokogiri (~> 1.3) - yard (0.8.7.2) + yard (0.8.7.3) PLATFORMS ruby diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index f98ac793..a5694db6 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -95,7 +95,7 @@ class ApplicationController < ActionController::Base if todos_parent.nil? count = 0 elsif (todos_parent.is_a?(Project) && todos_parent.hidden?) - count = eval "@project_project_hidden_todo_counts[#{todos_parent.id}]" + count = @project_project_hidden_todo_counts[todos_parent.id] else count = eval "@#{todos_parent.class.to_s.downcase}_not_done_counts[#{todos_parent.id}]" end diff --git a/app/controllers/todos_controller.rb b/app/controllers/todos_controller.rb index 6350d16d..24eafe55 100644 --- a/app/controllers/todos_controller.rb +++ b/app/controllers/todos_controller.rb @@ -747,34 +747,29 @@ class TodosController < ApplicationController end end + def get_not_completed_for_predecessor(relation, todo_id=nil) + items = relation.todos.not_completed. + where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%") + items = items.where("AND NOT(todos.id=?)", todo_id) unless todo_id.nil? + + items. + includes(:context, :project). + reorder('description ASC'). + limit(10) + end + def auto_complete_for_predecessor unless params['id'].nil? get_todo_from_params # Begin matching todos in current project, excluding @todo itself - @items = @todo.project.todos.not_completed. - where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). - includes(:context, :project). - reorder('description ASC'). - limit(10) unless @todo.project.nil? + @items = get_not_completed_for_predecessor(@todo.project, @todo.id) unless @todo.project.nil? # Then look in the current context, excluding @todo itself - @items = @todo.context.todos.not_completed. - where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). - includes(:context, :project). - reorder('description ASC'). - limit(10) unless !@items.empty? || @todo.context.nil? + @items = get_not_completed_for_predecessor(@todo.context, @todo.id) unless !@items.empty? || @todo.context.nil? # Match todos in other projects, excluding @todo itself - @items = current_user.todos.not_completed. - where('(LOWER(todos.description) LIKE ?) AND NOT(todos.id=?)', "%#{params[:term].downcase}%", @todo.id). - includes(:context, :project). - reorder('description ASC'). - limit(10) unless !@items.empty? + @items = get_not_completed_for_predecessor(current_user, @todo.id) unless !@items.empty? else # New todo - TODO: Filter on current project in project view - @items = current_user.todos.not_completed. - where('(LOWER(todos.description) LIKE ?)', "%#{params[:term].downcase}%"). - includes(:context, :project). - reorder('description ASC'). - limit(10) + @items = get_not_complete_for_predecessor(current_user) end render :inline => format_dependencies_as_json_for_auto_complete(@items) end @@ -1020,27 +1015,23 @@ end end end + def find_completed(relation, id, include_hidden) + todos = relation.find(id).todos.completed + todos = todos.not_hidden if !include_hidden + return todos + end + def determine_completed_count + todos=nil + source_view do |from| - from.todo do - @completed_count = current_user.todos.not_hidden.completed.count - end - from.context do - todos = current_user.contexts.find(@todo.context_id).todos.completed - todos = todos.not_hidden if !@todo.context.hidden? - @completed_count = todos.count - end - from.project do - unless @todo.project_id == nil - todos = current_user.projects.find(@todo.project_id).todos.completed - todos = todos.not_hidden if !@todo.project.hidden? - @completed_count = todos.count - end - end - from.tag do - @completed_count = current_user.todos.with_tag(@tag.id).completed.count - end + from.todo { todos = current_user.todos.not_hidden.completed } + from.context { todos = find_completed(current_user.contexts, @todo.context_id, @todo.context.hidden?) } + from.project { todos = find_completed(current_user.projects, @todo.project_id, @todo.project.hidden?) unless @todo.project_id.nil? } + from.tag { todos = current_user.todos.with_tag(@tag.id).completed } end + + @completed_count = todos.nil? ? 0 : todos.count end def determine_deferred_tag_count(tag_name) @@ -1195,23 +1186,20 @@ end end end + def parse_date_for_update(date, error_msg) + begin + parse_date_per_user_prefs(date) + rescue + @todo.errors[:base] << error_msg + end + end + + def update_date_for_update(key) + params['todo'][key] = params["todo"].has_key?(key) ? parse_date_for_update(params["todo"][key], t("todos.error.invalid_#{key}_date")) : "" + end + def update_due_and_show_from_dates - if params["todo"].has_key?("due") - begin - params["todo"]["due"] = parse_date_per_user_prefs(params["todo"]["due"]) - rescue - @todo.errors[:base] << "Invalid due date" - end - else - params["todo"]["due"] = "" - end - if params['todo']['show_from'] - begin - params['todo']['show_from'] = parse_date_per_user_prefs(params['todo']['show_from']) - rescue - @todo.errors[:base] << "Invalid show from date" - end - end + %w{ due show_from }.each {|date| update_date_for_update(date) } end def update_completed_state @@ -1283,18 +1271,18 @@ end completed_todos.completed_after(start_of_this_day).includes(includes[:include]) end + def get_done_in_period(before, after, includes = {:include => Todo::DEFAULT_INCLUDES}) + completed_todos.completed_before(before).completed_after(after).includes(includes[:include]) + end + # all completed todos [begin_of_week, start_of_today] def get_done_rest_of_week(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES}) - start_of_this_week = Time.zone.now.beginning_of_week - start_of_this_day = Time.zone.now.beginning_of_day - completed_todos.completed_before(start_of_this_day).completed_after(start_of_this_week).includes(includes[:include]) + get_done_in_period(Time.zone.now.beginning_of_day, Time.zone.now.beginning_of_week) end # all completed todos [begin_of_month, begin_of_week] def get_done_rest_of_month(completed_todos, includes = {:include => Todo::DEFAULT_INCLUDES}) - start_of_this_month = Time.zone.now.beginning_of_month - start_of_this_week = Time.zone.now.beginning_of_week - completed_todos.completed_before(start_of_this_week).completed_after(start_of_this_month).includes(includes[:include]) + get_done_in_period(Time.zone.now.beginning_of_week, Time.zone.now.beginning_of_month) end def get_not_done_todos diff --git a/app/models/message_gateway.rb b/app/models/message_gateway.rb index fb42dc92..b020c736 100644 --- a/app/models/message_gateway.rb +++ b/app/models/message_gateway.rb @@ -1,6 +1,4 @@ class MessageGateway < ActionMailer::Base - include ActionView::Helpers::SanitizeHelper - extend ActionView::Helpers::SanitizeHelper::ClassMethods def receive(email) user = get_receiving_user_from_email_address(email) @@ -85,11 +83,11 @@ class MessageGateway < ActionMailer::Base end def get_text_or_nil(text) - return text ? sanitize(text.strip) : nil + return text ? text.strip : nil end def get_decoded_text_or_nil(text) - return text ? sanitize(text.decoded.strip) : nil + return text ? text.decoded.strip : nil end def get_first_text_plain_part(email) @@ -99,7 +97,7 @@ class MessageGateway < ActionMailer::Base # remove all parts that are not text/plain parts.reject{|part| !part.content_type.start_with?("text/plain") } - return parts.count > 0 ? sanitize(parts[0].decoded.strip) : "" + return parts.count > 0 ? parts[0].decoded.strip : "" end def get_all_parts(parts) diff --git a/app/models/project.rb b/app/models/project.rb index 685c51c1..f60718ed 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -12,6 +12,7 @@ class Project < ActiveRecord::Base scope :uncompleted, -> { where("NOT(state = ?)", 'completed') } scope :with_name_or_description, lambda { |body| where("name LIKE ? OR description LIKE ?", body, body) } + scope :with_namepart, lambda { |body| where("name LIKE ?", body + '%') } validates_presence_of :name validates_length_of :name, :maximum => 255 @@ -88,7 +89,7 @@ class Project < ActiveRecord::Base # as a result of acts_as_state_machine calling state=() to update the attribute def transition_to(candidate_state) case candidate_state.to_sym - when aasm_current_state + when aasm.current_state return when :hidden hide! diff --git a/app/models/todo.rb b/app/models/todo.rb index 66b9550a..2c37cf4b 100644 --- a/app/models/todo.rb +++ b/app/models/todo.rb @@ -66,7 +66,7 @@ class Todo < ActiveRecord::Base # state machine include AASM - aasm_initial_state Proc.new { |t| (t.show_from && t.user && (t.show_from > t.user.date)) ? :deferred : :active} + aasm_initial_state = Proc.new { |t| (t.show_from && t.user && (t.show_from > t.user.date)) ? :deferred : :active} aasm :column => :state do diff --git a/app/services/rich_message_extractor.rb b/app/services/rich_message_extractor.rb index a357b66d..e248ad1e 100644 --- a/app/services/rich_message_extractor.rb +++ b/app/services/rich_message_extractor.rb @@ -1,28 +1,72 @@ +require 'date' class RichMessageExtractor + include ActionView::Helpers::SanitizeHelper + extend ActionView::Helpers::SanitizeHelper::ClassMethods - RICH_MESSAGE_FIELDS_REGEX = /([^>@]*)@?([^>]*)>?(.*)/ + PROJECT_MARKER = '~' + CONTEXT_MARKER = '@' + TICKLER_MARKER = '>' + DUE_MARKER = '<' + TAG_MARKER = '#' + STAR_MARKER = '*' + + ALL_MARKERS = [ + PROJECT_MARKER, + CONTEXT_MARKER, + TICKLER_MARKER, + DUE_MARKER, + TAG_MARKER, + STAR_MARKER + ] def initialize(message) @message = message end def description - fields[1].strip + desc = select_for('') + desc.blank? ? '' : sanitize(desc[1].strip) end def context - fields[2].strip + context = select_for(CONTEXT_MARKER) + context.blank? ? '' : sanitize(context[1].strip) end def project - stripped = fields[3].strip - stripped.blank? ? nil : stripped + project = select_for PROJECT_MARKER + project.blank? ? nil : sanitize(project[1].strip) + end + + def tags + string = @message.dup + tags = [] + # Regex only matches one tag, so recurse until we have them all + while string.match /#(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/ + tags << sanitize($1) + string.gsub!(/##{$1}/,'') + end + tags.empty? ? nil : tags + end + + def due + due = select_for DUE_MARKER + due.blank? ? nil : Date.parse(due[1].strip) + end + + def show_from + show_from = select_for TICKLER_MARKER + show_from.blank? ? nil : Date.parse(show_from[1].strip) + end + + def starred? + @message.include? '*' end private - def fields - @message.match(RICH_MESSAGE_FIELDS_REGEX) - end + def select_for symbol + @message.match /#{symbol}(.*?)(?=[#{ALL_MARKERS.join}]|\Z)/ + end end diff --git a/app/services/todo_from_rich_message.rb b/app/services/todo_from_rich_message.rb index f919b0bb..b1a28f22 100644 --- a/app/services/todo_from_rich_message.rb +++ b/app/services/todo_from_rich_message.rb @@ -14,6 +14,10 @@ class TodoFromRichMessage description = extractor.description context = extractor.context project = extractor.project + show_from = extractor.show_from + due = extractor.due + tags = extractor.tags + star = extractor.starred? context_id = default_context_id if context.present? @@ -33,17 +37,21 @@ class TodoFromRichMessage found_project.name = project[4..259].strip found_project.save! else - found_project = user.projects.active.find_by_namepart(project) - found_project = user.projects.find_by_namepart(project) if found_project.nil? + found_project = user.projects.active.with_namepart(project).first + found_project = user.projects.with_namepart(project).first if found_project.nil? end project_id = found_project.id unless found_project.nil? end - todo = user.todos.build + todo = user.todos.build todo.description = description - todo.raw_notes = notes - todo.context_id = context_id - todo.project_id = project_id unless project_id.nil? + todo.raw_notes = notes + todo.context_id = context_id + todo.project_id = project_id unless project_id.nil? + todo.show_from = show_from if show_from.is_a? Date + todo.due = due if due.is_a? Date + todo.tag_with tags unless tags.nil? || tags.empty? + todo.starred = star todo end end diff --git a/app/views/integrations/index.de.html.erb b/app/views/integrations/index.de.html.erb index 04850541..efaa2136 100644 --- a/app/views/integrations/index.de.html.erb +++ b/app/views/integrations/index.de.html.erb @@ -131,5 +131,5 @@
  • Enable the "Add any gadget by URL" feature. You will find it at bottom of the list. Select Enable radio button and click Save Changes button.
  • Now you can see Gadgets tab added to Gmail Settings. Go to the Gadgets tab
  • Paste following link to the Add a gadget by its URL: and then click Add button:
    -
    <%= integrations_url + "/google_gadget" %>
  • +
    <%= integrations_url + "/google_gadget.xml" %>
    \ No newline at end of file diff --git a/app/views/integrations/index.en.html.erb b/app/views/integrations/index.en.html.erb index 6fd212b3..0dc900a2 100644 --- a/app/views/integrations/index.en.html.erb +++ b/app/views/integrations/index.en.html.erb @@ -14,6 +14,7 @@
  • Automatically Email Yourself Upcoming Actions
  • Integrate Tracks with an email server to be able to send an action through email to Tracks
  • Send emails to Tracks with Mailgun +
  • Rich Todo Message email format
  • Add Tracks as a Google Gmail gadget

  • Do you have one of your own to add? @@ -122,6 +123,7 @@

    Send emails to Tracks with Mailgun

    +

    If you want to email tasks to Tracks, but cannot run a mailserver on the same host, you could use the Mailgun support built in to Tracks.

    @@ -155,6 +157,36 @@ mailmap:

    All the comments about the email format from the section above apply to the Mailgun handling, as the data is processed the same way

    + +

    Rich Todo Message Format

    +

    For both of the above methods, the follow format can be used:

    +
    my awesome todo @context ~project <131012 >131009 #tag1 #tag2 *
    +

    The fields are:

    + + + + + + + + + + + + + + + + + + + + + + +
    SymbolMeaning
    @The context to place the Todo in
    ~The project to place the Todo in
    <The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)
    >The due date for the Todo (may be 2 digits for day, 4 digits for month-day, or 6 digits for yeah-month-day)
    #A tag to apply to the Todo - may be used multiple times
    *Flag to star the Todo
    +

    All symbols are optional, and text up to the first symbol (or end of string) is used as the description of the todo

    +

    Add Tracks as a Google Gmail gadget

    diff --git a/app/views/projects/show.m.erb b/app/views/projects/show.m.erb index 8250b462..f289ca28 100644 --- a/app/views/projects/show.m.erb +++ b/app/views/projects/show.m.erb @@ -28,4 +28,4 @@ <% else -%><%= render :partial => "notes/notes_summary", :collection => @project.notes %> <% end -%>

    <%= t('projects.settings') %>

    -<%= t('projects.state', :state => project.aasm_current_state.to_s) %>. <%= @project_default_context %> +<%= t('projects.state', :state => project.aasm.current_state.to_s) %>. <%= @project_default_context %> diff --git a/app/views/projects/update_status.js.rjs b/app/views/projects/update_status.js.rjs index c15a4acb..28d62c60 100644 --- a/app/views/projects/update_status.js.rjs +++ b/app/views/projects/update_status.js.rjs @@ -1,11 +1,11 @@ # TODO: is this dead code? page.select('#project_status .active span').each do |element| - element.className = @project.aasm_current_state == :active ? 'active_state' : 'inactive_state' + element.className = @project.aasm.current_state == :active ? 'active_state' : 'inactive_state' end page.select('#project_status .hidden span').each do |element| - element.className = @project.aasm_current_state == :hidden ? 'active_state' : 'inactive_state' + element.className = @project.aasm.current_state == :hidden ? 'active_state' : 'inactive_state' end page.select('#project_status .completed span').each do |element| - element.className = @project.aasm_current_state == :completed ? 'active_state' : 'inactive_state' + element.className = @project.aasm.current_state == :completed ? 'active_state' : 'inactive_state' end -page.notify :notice, "Set project status to #{@project.aasm_current_state}", 5.0 +page.notify :notice, "Set project status to #{@project.aasm.current_state}", 5.0 diff --git a/app/views/todos/show.m.erb b/app/views/todos/show.m.erb index 30275735..3a29573d 100644 --- a/app/views/todos/show.m.erb +++ b/app/views/todos/show.m.erb @@ -4,36 +4,42 @@

    <%= t('common.actions') %>

    - - + +
    - - + + + <%= token_tag %>
    - - + + + <%= token_tag %>
    - - + + + <%= token_tag %>
    - - + + + <%= token_tag %>
    - - + + + <%= token_tag %>
    - - + + + <%= token_tag %>
    diff --git a/config/application.rb b/config/application.rb index 6915bc75..50084086 100644 --- a/config/application.rb +++ b/config/application.rb @@ -68,5 +68,8 @@ module Tracksapp config.action_view.sanitized_allowed_protocols = 'onenote', 'message' config.middleware.insert_after ActionDispatch::ParamsParser, ActionDispatch::XmlParamsParser + + # if a locale is invalid, the Rails app will raise an error + config.i18n.enforce_available_locales = true end end diff --git a/config/locales/en.yml b/config/locales/en.yml index bd8d1525..a8737ab1 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -471,6 +471,9 @@ en: project_completed: Completed actions in this project context_completed: Completed actions in this context tag_hidden: "Hidden actions tagged with '%{param}'" + error: + invalid_due_date: "Invalid due date" + invalid_show_from_date: "Invalid show from date" completed_today: Completed today completed_rest_of_week: Completed in the rest of this week completed_rest_of_month: Completed in the rest of this month diff --git a/db/migrate/046_fix_incorrectly_hidden_todos.rb b/db/migrate/046_fix_incorrectly_hidden_todos.rb index 1261954f..fca599f7 100644 --- a/db/migrate/046_fix_incorrectly_hidden_todos.rb +++ b/db/migrate/046_fix_incorrectly_hidden_todos.rb @@ -3,7 +3,7 @@ class FixIncorrectlyHiddenTodos < ActiveRecord::Migration hidden_todos_without_project = Todo.where(:state => 'project_hidden', :project_id => nil) - active_projects = Project.where(:state => 'active') + active_projects = Project.where(:state => 'active').select("id") hidden_todos_in_active_projects = Todo.where(:state => 'project_hidden').where("project_id IN (?)", active_projects) diff --git a/doc/installation.textile b/doc/installation.textile index b9dc4388..0ae21f9e 100644 --- a/doc/installation.textile +++ b/doc/installation.textile @@ -10,7 +10,7 @@ There are two methods of downloading Tracks: # If you want to live on the edge, you can get the latest development version from GitHub using git (bear in mind that this may be less stable than the released versions): bc. cd ~/Sites -git clone git://github.com/tracksapp/tracks.git +git clone https://github.com/TracksApp/tracks.git cd tracks diff --git a/test/controllers/preferences_controller_test.rb b/test/controllers/preferences_controller_test.rb index 695a9ee9..9d55e83e 100644 --- a/test/controllers/preferences_controller_test.rb +++ b/test/controllers/preferences_controller_test.rb @@ -4,7 +4,6 @@ class PreferencesControllerTest < ActionController::TestCase def setup super - assert_equal "test", Rails.env assert_equal "change-me", Tracks::Config.salt end diff --git a/test/controllers/projects_controller_test.rb b/test/controllers/projects_controller_test.rb index 72ee3f8d..1b2b557f 100644 --- a/test/controllers/projects_controller_test.rb +++ b/test/controllers/projects_controller_test.rb @@ -48,7 +48,7 @@ class ProjectsControllerTest < ActionController::TestCase login_as(:admin_user) xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"} todos.each do |t| - assert_equal :project_hidden, t.reload().aasm_current_state + assert_equal :project_hidden, t.reload().aasm.current_state end assert p.reload().hidden? end @@ -60,7 +60,7 @@ class ProjectsControllerTest < ActionController::TestCase xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"hidden"} xhr :post, :update, :id => 1, "project"=>{"name"=>p.name, "description"=>p.description, "state"=>"active"} todos.each do |t| - assert_equal :active, t.reload().aasm_current_state + assert_equal :active, t.reload().aasm.current_state end assert p.reload().active? end diff --git a/test/controllers/recurring_todos_controller_test.rb b/test/controllers/recurring_todos_controller_test.rb index d9e91395..30c456a7 100644 --- a/test/controllers/recurring_todos_controller_test.rb +++ b/test/controllers/recurring_todos_controller_test.rb @@ -84,7 +84,7 @@ class RecurringTodosControllerTest < ActionController::TestCase xhr :post, :toggle_check, :id=>1, :_source_view=>"" recurring_todo_1 = RecurringTodo.find(1) # reload seems to not work - assert recurring_todo_1.active?, "recurring todo should be active but is #{recurring_todo_1.aasm_current_state}" + assert recurring_todo_1.active?, "recurring todo should be active but is #{recurring_todo_1.aasm.current_state}" # by making active, a new todo should be created from the pattern assert_equal todo_count+1, Todo.count diff --git a/test/fixtures/recurring_todos.yml b/test/fixtures/recurring_todos.yml index 6b22bde7..ebbb188f 100644 --- a/test/fixtures/recurring_todos.yml +++ b/test/fixtures/recurring_todos.yml @@ -20,7 +20,7 @@ def two_weeks_hence end def way_back - Time.zone.local(2008,1,1) + Time.zone.local(2008,1,1).to_s(:db) end %> diff --git a/test/models/preference_test.rb b/test/models/preference_test.rb index d0c065fe..2fe8e273 100644 --- a/test/models/preference_test.rb +++ b/test/models/preference_test.rb @@ -4,7 +4,6 @@ class PreferenceTest < ActiveSupport::TestCase fixtures :users, :preferences def setup - assert_equal "test", ENV['RAILS_ENV'] assert_equal "change-me", Tracks::Config.salt @admin_user = User.find(1) @other_user = User.find(2) diff --git a/test/models/project_test.rb b/test/models/project_test.rb index 47ba1a5f..0605291c 100644 --- a/test/models/project_test.rb +++ b/test/models/project_test.rb @@ -48,36 +48,36 @@ class ProjectTest < ActiveSupport::TestCase # state machine def test_project_initial_state_is_active - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state assert @timemachine.active? end def test_hide_project @timemachine.hide! - assert_equal :hidden, @timemachine.aasm_current_state + assert_equal :hidden, @timemachine.aasm.current_state assert @timemachine.hidden? end def test_activate_project @timemachine.activate! - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state assert @timemachine.active? end def test_transition_to_another_state - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state @timemachine.transition_to(:hidden) - assert_equal :hidden, @timemachine.aasm_current_state + assert_equal :hidden, @timemachine.aasm.current_state @timemachine.transition_to(:completed) - assert_equal :completed, @timemachine.aasm_current_state + assert_equal :completed, @timemachine.aasm.current_state @timemachine.transition_to(:active) - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state end def test_transition_to_same_state - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state @timemachine.transition_to(:active) - assert_equal :active, @timemachine.aasm_current_state + assert_equal :active, @timemachine.aasm.current_state end # other tests @@ -95,7 +95,7 @@ class ProjectTest < ActiveSupport::TestCase def test_complete_project assert_nil @timemachine.completed_at @timemachine.complete! - assert_equal :completed, @timemachine.aasm_current_state + assert_equal :completed, @timemachine.aasm.current_state assert @timemachine.completed? assert_not_nil @timemachine.completed_at, "completed_at not expected to be nil" assert_in_delta Time.now, @timemachine.completed_at, 1 @@ -150,7 +150,7 @@ class ProjectTest < ActiveSupport::TestCase first_todo = @moremoney.todos[0] first_todo.show_from = Time.zone.now + 1.week first_todo.save! - assert_equal :deferred, @moremoney.todos[0].aasm_current_state + assert_equal :deferred, @moremoney.todos[0].aasm.current_state assert_equal 1, @moremoney.todos.deferred.count end diff --git a/test/models/rich_message_extractor_test.rb b/test/models/rich_message_extractor_test.rb index 5c264685..3376c289 100644 --- a/test/models/rich_message_extractor_test.rb +++ b/test/models/rich_message_extractor_test.rb @@ -1,3 +1,4 @@ +require 'date' require 'test/unit' require 'active_support/core_ext/object/blank' require_relative '../../app/services/rich_message_extractor.rb' @@ -5,11 +6,15 @@ require_relative '../../app/services/rich_message_extractor.rb' class RichMessageExtractorTest < Test::Unit::TestCase def test_message_with_all_options - message = "ohai@some-context>in-this-project" + message = "ohai@some-context~this-project>131012<131014#tag1#tag2*" extractor = RichMessageExtractor.new(message) assert_equal "ohai", extractor.description assert_equal "some-context", extractor.context - assert_equal "in-this-project", extractor.project + assert_equal "this-project", extractor.project + assert_equal "2013-10-12", extractor.show_from.to_s + assert_equal "2013-10-14", extractor.due.to_s + assert_equal ["tag1","tag2"], extractor.tags + assert extractor.starred? end def test_message_without_project @@ -20,12 +25,12 @@ class RichMessageExtractorTest < Test::Unit::TestCase assert_equal nil, extractor.project end - def test_message_without_project - message = " ohai @ some-context" + def test_message_without_context + message = " ohai ~ some-project" extractor = RichMessageExtractor.new(message) assert_equal "ohai", extractor.description - assert_equal "some-context", extractor.context - assert_equal nil, extractor.project + assert_equal "", extractor.context + assert_equal "some-project", extractor.project end def test_message_without_project_or_context @@ -52,4 +57,52 @@ class RichMessageExtractorTest < Test::Unit::TestCase assert_equal nil, extractor.project end + def test_message_with_tags + message = "some tags#tag 1#tag2" + extractor = RichMessageExtractor.new(message) + assert_equal ["tag 1","tag2"], extractor.tags + end + + def test_message_with_no_tags + message = "no tags" + extractor = RichMessageExtractor.new(message) + assert_equal nil, extractor.tags + end + + def test_message_with_due_date + message = "datetest<141013" + extractor = RichMessageExtractor.new(message) + assert_equal "2014-10-13", extractor.due.to_s + end + + def test_message_with_no_due_date + message = "no date" + extractor = RichMessageExtractor.new(message) + assert_equal nil, extractor.due + end + + def test_message_with_show_from + message = "datetest>161013" + extractor = RichMessageExtractor.new(message) + assert_equal "2016-10-13", extractor.show_from.to_s + end + + def test_message_with_no_show_from + message = "no tickler" + extractor = RichMessageExtractor.new(message) + assert_equal nil, extractor.show_from + end + + def test_message_with_star + message = "star test*" + extractor = RichMessageExtractor.new(message) + assert extractor.starred? + end + + def test_message_with_no_star + message = "no star test" + extractor = RichMessageExtractor.new(message) + refute extractor.starred? + end + end diff --git a/test/models/todo_from_rich_message_test.rb b/test/models/todo_from_rich_message_test.rb index f9206624..2b57da38 100644 --- a/test/models/todo_from_rich_message_test.rb +++ b/test/models/todo_from_rich_message_test.rb @@ -18,4 +18,23 @@ class TodoFromRichMessageTest < ActiveSupport::TestCase assert_equal default_context_id, new_todo.context_id end + def test_from_rich_message_adds_all_fields + user = @completed.user + context = Context.create(:name => 'context') + project = Project.create(:name => 'project') + message = "description@context~project>131014<131017#tag1#tag2*" + builder = TodoFromRichMessage.new(user, context.id, message, "notes") + new_todo = builder.construct + + assert_not_nil new_todo + assert_equal "description", new_todo.description + assert_equal "notes", new_todo.notes + assert_equal context.id, new_todo.context_id + assert_equal project.id, new_todo.project_id + assert_equal "2013-10-14 00:00:00 +0100", new_todo.show_from.to_s + assert_equal "2013-10-17 00:00:00 +0100", new_todo.due.to_s + assert_equal "starred, tag1, tag2", new_todo.tags.to_s + assert new_todo.starred? + end + end diff --git a/test/models/todo_test.rb b/test/models/todo_test.rb index 5d87210c..100a86a6 100644 --- a/test/models/todo_test.rb +++ b/test/models/todo_test.rb @@ -97,10 +97,10 @@ class TodoTest < ActiveSupport::TestCase def test_defer_an_existing_todo @not_completed2 - assert_equal :active, @not_completed2.aasm_current_state + assert_equal :active, @not_completed2.aasm.current_state @not_completed2.show_from = Time.zone.now + 1.week assert @not_completed2.save, "should have saved successfully" + @not_completed2.errors.to_xml - assert_equal :deferred, @not_completed2.aasm_current_state + assert_equal :deferred, @not_completed2.aasm.current_state end def test_create_a_new_deferred_todo @@ -110,41 +110,41 @@ class TodoTest < ActiveSupport::TestCase todo.context_id = 1 todo.description = 'foo' assert todo.save, "should have saved successfully" + todo.errors.to_xml - assert_equal :deferred, todo.aasm_current_state + assert_equal :deferred, todo.aasm.current_state end def test_create_a_new_deferred_todo_by_passing_attributes user = users(:other_user) todo = user.todos.build(:show_from => next_week, :context_id => 1, :description => 'foo') assert todo.save, "should have saved successfully" + todo.errors.to_xml - assert_equal :deferred, todo.aasm_current_state + assert_equal :deferred, todo.aasm.current_state end def test_toggle_completion t = @not_completed1 - assert_equal :active, t.aasm_current_state + assert_equal :active, t.aasm.current_state t.toggle_completion! - assert_equal :completed, t.aasm_current_state + assert_equal :completed, t.aasm.current_state t.toggle_completion! - assert_equal :active, t.aasm_current_state + assert_equal :active, t.aasm.current_state end def test_toggle_completion_with_show_from_in_future t = @not_completed1 t.show_from= 1.week.from_now t.save! - assert_equal :deferred, t.aasm_current_state + assert_equal :deferred, t.aasm.current_state t.toggle_completion! - assert_equal :completed, t.aasm_current_state + assert_equal :completed, t.aasm.current_state end def test_toggle_completion_with_show_from_in_past t = @not_completed1 t.update_attribute(:show_from, 1.week.ago) - assert_equal :active, t.aasm_current_state + assert_equal :active, t.aasm.current_state assert t.toggle_completion!, "shoud be able to mark active todo complete even if show_from is set in the past" - assert_equal :completed, t.aasm_current_state + assert_equal :completed, t.aasm.current_state end def test_activate_also_saves @@ -225,7 +225,7 @@ class TodoTest < ActiveSupport::TestCase t.context_id = 1 t.save! t.reload - assert_equal :active, t.aasm_current_state + assert_equal :active, t.aasm.current_state end def test_initial_state_is_deferred_when_show_from_in_future @@ -236,7 +236,7 @@ class TodoTest < ActiveSupport::TestCase t.show_from = 1.week.from_now.to_date t.save! t.reload - assert_equal :deferred, t.aasm_current_state + assert_equal :deferred, t.aasm.current_state end def test_todo_is_not_starred diff --git a/test/models/user_test.rb b/test/models/user_test.rb index 42c8c472..e308f84e 100644 --- a/test/models/user_test.rb +++ b/test/models/user_test.rb @@ -1,10 +1,10 @@ require File.expand_path(File.dirname(__FILE__) + '/../test_helper') +require 'timecop' class UserTest < ActiveSupport::TestCase fixtures :users, :preferences, :projects, :contexts, :todos, :recurring_todos def setup - assert_equal "test", ENV['RAILS_ENV'] assert_equal "change-me", Tracks::Config.salt @admin_user = User.find(1) @other_user = User.find(2) @@ -374,16 +374,16 @@ class UserTest < ActiveSupport::TestCase # test group counts for projects and contexts project_counts = u.todos.count_by_group(:project_id) - assert_equal "6,3,4,4", project_counts.map{|pc|pc[1]}.join(",") + assert_equal [6,3,4,4], project_counts.values context_counts = u.todos.count_by_group(:context_id) - assert_equal "7,3,1,3,1,2", context_counts.map{|cc|cc[1]}.join(",") + assert_equal [7,3,1,3,1,2], context_counts.values # add a todo to the first context and check that the count is increased u.todos.create!(:description => "yet another todo", :context => u.contexts.first) context_counts = u.todos.reload.count_by_group(:context_id) - assert_equal "8,3,1,3,1,2", context_counts.map{|cc|cc[1]}.join(",") + assert_equal [8,3,1,3,1,2], context_counts.values end protected @@ -393,4 +393,4 @@ class UserTest < ActiveSupport::TestCase User.create({ :login => 'quire', :password => 'quire', :password_confirmation => 'quire' }.merge(options)) end -end \ No newline at end of file +end