From 36c333640ff4d6cc777cc5bae2c3da44619e667c Mon Sep 17 00:00:00 2001 From: Denis Novikov Date: Sat, 4 Jun 2011 10:22:12 +0200 Subject: [PATCH 01/26] Fixes an issue with no saving user_id into comments table when task is creating with empty body and assigned user. --- app/controllers/tasks_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 58c3d441a3..d46f34b598 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -33,6 +33,7 @@ def create authorize! :make_tasks, @current_project @task = @task_list.tasks.build_by_user(current_user, params[:task]) @task.is_private = (params[:task][:is_private]||false) if params[:task] + @task.updating_user = current_user @task.save respond_to do |f| From 8126b81b31ba3d74462c4cf1e99db8a2a8f735a7 Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Thu, 18 Aug 2011 21:06:27 +0100 Subject: [PATCH 02/26] Allow tasks to be included in the task list reply via ?include= --- app/controllers/api_v1/task_lists_controller.rb | 4 ++-- app/models/task_list/conversions.rb | 4 ++++ .../api_v1/task_lists_controller_spec.rb | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/controllers/api_v1/task_lists_controller.rb b/app/controllers/api_v1/task_lists_controller.rb index bd1b60ad6e..9a3c19764c 100644 --- a/app/controllers/api_v1/task_lists_controller.rb +++ b/app/controllers/api_v1/task_lists_controller.rb @@ -15,7 +15,7 @@ def index limit(api_limit). order('task_lists.id DESC') - api_respond @task_lists, :references => true + api_respond @task_lists, :references => true, :include => api_include end def show @@ -138,6 +138,6 @@ def api_scope end def api_include - [:tasks, :comments] & (params[:include]||{}).map(&:to_sym) + [:tasks, :unarchived_tasks, :archived_tasks] & (params[:include]||{}).map(&:to_sym) end end \ No newline at end of file diff --git a/app/models/task_list/conversions.rb b/app/models/task_list/conversions.rb index 4f79afd22d..55629d9603 100644 --- a/app/models/task_list/conversions.rb +++ b/app/models/task_list/conversions.rb @@ -39,6 +39,10 @@ def to_api_hash(options = {}) if Array(options[:include]).include? :tasks base[:tasks] = tasks.map {|t| t.to_api_hash(options)} + elsif Array(options[:include]).include? :unarchived_tasks + base[:tasks] = tasks.unarchived.map {|t| t.to_api_hash(options)} + elsif Array(options[:include]).include? :archived_tasks + base[:tasks] = tasks.archived.map {|t| t.to_api_hash(options)} end base diff --git a/spec/controllers/api_v1/task_lists_controller_spec.rb b/spec/controllers/api_v1/task_lists_controller_spec.rb index 0a707c507e..67aa27eb58 100644 --- a/spec/controllers/api_v1/task_lists_controller_spec.rb +++ b/spec/controllers/api_v1/task_lists_controller_spec.rb @@ -15,6 +15,8 @@ describe "#index" do it "shows task lists in the project" do login_as @user + @project.create_task(@owner,@task_list,{:name => 'Something TODO'}).save! + @project.create_task(@owner,@other_task_list,{:name => 'Something Else TODO'}).save! get :index, :project_id => @project.permalink response.should be_success @@ -22,11 +24,24 @@ data = JSON.parse(response.body) references = data['references'].map{|r| "#{r['id']}_#{r['type']}"} data['objects'].length.should == 2 + data['objects'].each{|o| o['tasks'].should == nil} references.include?("#{@project.id}_Project").should == true references.include?("#{@task_list.user_id}_User").should == true references.include?("#{@other_task_list.user_id}_User").should == true end + + it "shows task lists with tasks with include=tasks" do + login_as @user + @project.create_task(@owner,@task_list,{:name => 'Something TODO'}).save! + @project.create_task(@owner,@other_task_list,{:name => 'Something Else TODO'}).save! + + get :index, :project_id => @project.permalink, :include => 'tasks' + response.should be_success + + data = JSON.parse(response.body) + data['objects'].each{|o| o['tasks'].length.should == 1} + end it "shows task lists as JSON when requested with the :text format" do login_as @user From 47d2aafe21b927e1a28e42424de1239f461ab918 Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Fri, 19 Aug 2011 19:12:04 +0100 Subject: [PATCH 03/26] Filter out objects from archived projects on global api routes --- app/controllers/api_v1/comments_controller.rb | 2 +- app/controllers/api_v1/conversations_controller.rb | 2 +- app/controllers/api_v1/dividers_controller.rb | 2 +- app/controllers/api_v1/notes_controller.rb | 2 +- app/controllers/api_v1/pages_controller.rb | 2 +- app/controllers/api_v1/task_lists_controller.rb | 2 +- app/controllers/api_v1/tasks_controller.rb | 2 +- app/controllers/api_v1/uploads_controller.rb | 2 +- spec/controllers/api_v1/comments_controller_spec.rb | 10 ++++++++++ .../api_v1/conversations_controller_spec.rb | 10 ++++++++++ spec/controllers/api_v1/pages_controller_spec.rb | 10 ++++++++++ spec/controllers/api_v1/task_lists_controller_spec.rb | 10 ++++++++++ spec/controllers/api_v1/tasks_controller_spec.rb | 10 ++++++++++ spec/controllers/api_v1/uploads_controller_spec.rb | 10 ++++++++++ 14 files changed, 68 insertions(+), 8 deletions(-) diff --git a/app/controllers/api_v1/comments_controller.rb b/app/controllers/api_v1/comments_controller.rb index 840de47c8b..5fcc8e7625 100644 --- a/app/controllers/api_v1/comments_controller.rb +++ b/app/controllers/api_v1/comments_controller.rb @@ -6,7 +6,7 @@ def index authorize! :show, @target||current_user context = @target ? @target.comments.where(api_scope) : - Comment.where(:project_id => current_user.project_ids).where(api_scope) + Comment.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) @comments = context.except(:order). where(api_range('comments')). diff --git a/app/controllers/api_v1/conversations_controller.rb b/app/controllers/api_v1/conversations_controller.rb index d476dd1d5e..32a150d093 100644 --- a/app/controllers/api_v1/conversations_controller.rb +++ b/app/controllers/api_v1/conversations_controller.rb @@ -7,7 +7,7 @@ def index context = if @current_project @current_project.conversations.where(api_scope) else - Conversation.where(:project_id => current_user.project_ids).where(api_scope) + Conversation.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) end @conversations = context.except(:order). diff --git a/app/controllers/api_v1/dividers_controller.rb b/app/controllers/api_v1/dividers_controller.rb index ad9a4db8de..8ce1b441c1 100644 --- a/app/controllers/api_v1/dividers_controller.rb +++ b/app/controllers/api_v1/dividers_controller.rb @@ -8,7 +8,7 @@ def index context = if target target.dividers else - Divider.where(:project_id => current_user.project_ids) + Divider.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}) end.joins(:page) @dividers = context.except(:order). diff --git a/app/controllers/api_v1/notes_controller.rb b/app/controllers/api_v1/notes_controller.rb index 1dccb8c4e1..d9e79c5635 100644 --- a/app/controllers/api_v1/notes_controller.rb +++ b/app/controllers/api_v1/notes_controller.rb @@ -8,7 +8,7 @@ def index context = if target target.notes else - Note.where(:project_id => current_user.project_ids) + Note.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}) end.joins(:page) @notes = context.except(:order). diff --git a/app/controllers/api_v1/pages_controller.rb b/app/controllers/api_v1/pages_controller.rb index 3d2fecd4bb..57d392309b 100644 --- a/app/controllers/api_v1/pages_controller.rb +++ b/app/controllers/api_v1/pages_controller.rb @@ -7,7 +7,7 @@ def index context = if @current_project @current_project.pages.where(api_scope) else - Page.where(:project_id => current_user.project_ids).where(api_scope) + Page.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) end @pages = context.except(:order). diff --git a/app/controllers/api_v1/task_lists_controller.rb b/app/controllers/api_v1/task_lists_controller.rb index 9a3c19764c..addaf95c92 100644 --- a/app/controllers/api_v1/task_lists_controller.rb +++ b/app/controllers/api_v1/task_lists_controller.rb @@ -7,7 +7,7 @@ def index context = if @current_project @current_project.task_lists.where(api_scope) else - TaskList.where(:project_id => current_user.project_ids).where(api_scope) + TaskList.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) end @task_lists = context.except(:order). diff --git a/app/controllers/api_v1/tasks_controller.rb b/app/controllers/api_v1/tasks_controller.rb index b30b93abfb..85e3b7bdc1 100644 --- a/app/controllers/api_v1/tasks_controller.rb +++ b/app/controllers/api_v1/tasks_controller.rb @@ -8,7 +8,7 @@ def index context = if @current_project (@task_list || @current_project).tasks.where(api_scope) else - Task.where(:project_id => current_user.project_ids).where(api_scope) + Task.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) end @tasks = context.except(:order). diff --git a/app/controllers/api_v1/uploads_controller.rb b/app/controllers/api_v1/uploads_controller.rb index 5cef20ce8b..c1e4fbd2f9 100644 --- a/app/controllers/api_v1/uploads_controller.rb +++ b/app/controllers/api_v1/uploads_controller.rb @@ -8,7 +8,7 @@ def index context = if target target.uploads.where(api_scope) else - Upload.where(:project_id => current_user.project_ids).where(api_scope) + Upload.joins(:project).where(:project_id => current_user.project_ids, :projects => {:archived => false}).where(api_scope) end @uploads = context.except(:order). diff --git a/spec/controllers/api_v1/comments_controller_spec.rb b/spec/controllers/api_v1/comments_controller_spec.rb index 34ca38d290..b5207468f5 100644 --- a/spec/controllers/api_v1/comments_controller_spec.rb +++ b/spec/controllers/api_v1/comments_controller_spec.rb @@ -49,6 +49,16 @@ JSON.parse(response.body)['objects'].length.should == 2 end + it "shows no comments for archived projects" do + login_as @user + @project.update_attribute :archived, true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows comments created by a user" do login_as @user diff --git a/spec/controllers/api_v1/conversations_controller_spec.rb b/spec/controllers/api_v1/conversations_controller_spec.rb index 0e0cbc7205..db6affa8ed 100644 --- a/spec/controllers/api_v1/conversations_controller_spec.rb +++ b/spec/controllers/api_v1/conversations_controller_spec.rb @@ -55,6 +55,16 @@ JSON.parse(response.body)['objects'].length.should == 3 end + it "shows no conversations for archived projects" do + login_as @user + @project.update_attribute :archived, true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows conversations created by a user" do login_as @user diff --git a/spec/controllers/api_v1/pages_controller_spec.rb b/spec/controllers/api_v1/pages_controller_spec.rb index 78ef3663e7..160e3bf468 100644 --- a/spec/controllers/api_v1/pages_controller_spec.rb +++ b/spec/controllers/api_v1/pages_controller_spec.rb @@ -48,6 +48,16 @@ JSON.parse(response.body)['objects'].length.should == 2 end + it "shows no pages for archived projects" do + login_as @user + @project.update_attribute :archived, true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows pages created by a user" do login_as @user diff --git a/spec/controllers/api_v1/task_lists_controller_spec.rb b/spec/controllers/api_v1/task_lists_controller_spec.rb index 67aa27eb58..2368ee106f 100644 --- a/spec/controllers/api_v1/task_lists_controller_spec.rb +++ b/spec/controllers/api_v1/task_lists_controller_spec.rb @@ -74,6 +74,16 @@ JSON.parse(response.body)['objects'].length.should == 3 end + it "shows no task lists for archived projects" do + login_as @user + @project.update_attribute :archived, true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows task lists created by a user" do login_as @user diff --git a/spec/controllers/api_v1/tasks_controller_spec.rb b/spec/controllers/api_v1/tasks_controller_spec.rb index 4bbbfd81ab..18483299d6 100644 --- a/spec/controllers/api_v1/tasks_controller_spec.rb +++ b/spec/controllers/api_v1/tasks_controller_spec.rb @@ -27,6 +27,16 @@ JSON.parse(response.body)['objects'].length.should == 2 end + it "shows no tasks in archived projects" do + login_as @user + @project.update_attributes :archived => true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows tasks with a JSONP callback" do login_as @user diff --git a/spec/controllers/api_v1/uploads_controller_spec.rb b/spec/controllers/api_v1/uploads_controller_spec.rb index 6d5f560c7a..5b52818f56 100644 --- a/spec/controllers/api_v1/uploads_controller_spec.rb +++ b/spec/controllers/api_v1/uploads_controller_spec.rb @@ -64,6 +64,16 @@ JSON.parse(response.body)['objects'].length.should == 3 end + it "shows no uploads for archived projects" do + login_as @user + @project.update_attribute :archived, true + + get :index + response.should be_success + + JSON.parse(response.body)['objects'].length.should == 0 + end + it "shows uploads created by a user" do login_as @user From 2ee6a49f7a2651b7b69b77ac4e68f2a594a95208 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:06:02 +0200 Subject: [PATCH 04/26] #447452: add urgent flags to tasks and comments --- ...18091722_add_urgent_flag_to_tasks_and_comments.rb | 12 ++++++++++++ db/schema.rb | 5 ++++- 2 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 db/migrate/20110818091722_add_urgent_flag_to_tasks_and_comments.rb diff --git a/db/migrate/20110818091722_add_urgent_flag_to_tasks_and_comments.rb b/db/migrate/20110818091722_add_urgent_flag_to_tasks_and_comments.rb new file mode 100644 index 0000000000..7818138964 --- /dev/null +++ b/db/migrate/20110818091722_add_urgent_flag_to_tasks_and_comments.rb @@ -0,0 +1,12 @@ +class AddUrgentFlagToTasksAndComments < ActiveRecord::Migration + def self.up + add_column :tasks, :urgent, :boolean, :default => false, :null => false + add_column :comments, :urgent, :boolean, :default => false, :null => false + add_column :comments, :previous_urgent, :boolean, :default => false, :null => false + end + + def self.down + remove_column :tasks, :urgent + remove_column :comments, :urgent, :previous_urgent + end +end diff --git a/db/schema.rb b/db/schema.rb index 40df074787..fe149a3000 100644 --- a/db/schema.rb +++ b/db/schema.rb @@ -10,7 +10,7 @@ # # It's strongly recommended to check this file into your version control system. -ActiveRecord::Schema.define(:version => 20110812102452) do +ActiveRecord::Schema.define(:version => 20110818091722) do create_table "activities", :force => true do |t| t.integer "user_id" @@ -104,6 +104,8 @@ t.integer "uploads_count", :default => 0 t.boolean "deleted", :default => false, :null => false t.boolean "is_private", :default => false, :null => false + t.boolean "urgent", :default => false, :null => false + t.boolean "previous_urgent", :default => false, :null => false end add_index "comments", ["created_at"], :name => "index_comments_on_created_at" @@ -466,6 +468,7 @@ t.boolean "deleted", :default => false, :null => false t.boolean "is_private", :default => false, :null => false t.string "google_calendar_url_token" + t.boolean "urgent", :default => false, :null => false end add_index "tasks", ["assigned_id"], :name => "index_tasks_on_assigned_id" From 90308259dd3863293c1d2298218babf31056ba36 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:13:49 +0200 Subject: [PATCH 05/26] #447452: models: manager urgent flag (transitions, previous setting, validations) --- app/models/comment.rb | 3 ++- app/models/comment/conversions.rb | 2 ++ app/models/comment/tasks.rb | 8 ++++++-- app/models/task.rb | 16 +++++++++++++--- app/models/user.rb | 2 +- spec/models/task_spec.rb | 8 ++++++++ spec/models/user_spec.rb | 11 +++++++++++ 7 files changed, 43 insertions(+), 7 deletions(-) diff --git a/app/models/comment.rb b/app/models/comment.rb index 5f9e746a85..02ea6a443f 100644 --- a/app/models/comment.rb +++ b/app/models/comment.rb @@ -39,7 +39,7 @@ def previous_assigned :reject_if => lambda { |google_docs| google_docs['title'].blank? || google_docs['url'].blank? } attr_accessible :body, :status, :assigned, :hours, :human_hours, :billable, - :upload_ids, :uploads_attributes, :due_on, :google_docs_attributes, :private_ids, :is_private + :upload_ids, :uploads_attributes, :due_on, :urgent, :google_docs_attributes, :private_ids, :is_private attr_accessor :is_importing attr_accessor :private_ids @@ -246,6 +246,7 @@ def cleanup_task if @last_comment_in_task self.target.assigned_id = previous_assigned_id self.target.due_on = previous_due_on + self.target.urgent = previous_urgent self.target.status = previous_status || Task::STATUSES[:open] self.target.save! end diff --git a/app/models/comment/conversions.rb b/app/models/comment/conversions.rb index 24fac910d8..302c7c6f0b 100644 --- a/app/models/comment/conversions.rb +++ b/app/models/comment/conversions.rb @@ -54,6 +54,8 @@ def to_api_hash(options = {}) base[:status] = status base[:due_on] = due_on base[:previous_due_on] = previous_due_on + base[:urgent] = urgent + base[:previous_urgent] = previous_urgent end if Array(options[:include]).include?(:uploads) && uploads.any? diff --git a/app/models/comment/tasks.rb b/app/models/comment/tasks.rb index c7510ff1c9..9f6e567f78 100644 --- a/app/models/comment/tasks.rb +++ b/app/models/comment/tasks.rb @@ -5,7 +5,7 @@ def previously_closed? end def transition? - status_transition? || assigned_transition? || due_on_change? + status_transition? || assigned_transition? || due_on_change? || urgent_change? end def initial_status? @@ -20,6 +20,10 @@ def due_on_change? due_on != previous_due_on end + def urgent_change? + urgent != previous_urgent + end + def assigned_transition? assigned_id != previous_assigned_id end @@ -60,4 +64,4 @@ def previous_status_name Task::STATUS_NAMES[previous_status] end -end \ No newline at end of file +end diff --git a/app/models/task.rb b/app/models/task.rb index a65af2969d..262af41ac3 100644 --- a/app/models/task.rb +++ b/app/models/task.rb @@ -24,7 +24,7 @@ class Task < RoleRecord accepts_nested_attributes_for :comments, :allow_destroy => false, :reject_if => lambda { |comment| %w[is_private body hours human_hours uploads_attributes google_docs_attributes].all? { |k| comment[k].blank? } } - attr_accessible :name, :assigned_id, :status, :due_on, :comments_attributes, :user, :task_list_id + attr_accessible :name, :assigned_id, :status, :due_on, :comments_attributes, :user, :task_list_id, :urgent validates_presence_of :user validates_presence_of :task_list @@ -49,6 +49,7 @@ class Task < RoleRecord before_save :save_completed_at before_validation :remember_comment_created, :on => :update before_save :update_google_calendar_event, :if => lambda {|t| t.assigned.try(:user) || !t.google_calendar_url_token.blank? } + before_validation :nilize_due_on_for_urgent_tasks def assigned @assigned ||= assigned_id ? Person.with_deleted.find_by_id(assigned_id) : nil @@ -56,7 +57,7 @@ def assigned def track_changes? (new_record? and not status_new?) or - (updating_user and (status_changed? or assigned_id_changed? or due_on_changed?)) + (updating_user and (status_changed? or assigned_id_changed? or due_on_changed? or urgent_changed?)) end def archived? @@ -299,7 +300,7 @@ def set_comments_author # before_save end def remember_comment_created # before_update - @comment_created = comments.any?(&:new_record?) || assigned_id_changed? || status_changed? || due_on_changed? + @comment_created = comments.any?(&:new_record?) || assigned_id_changed? || status_changed? || due_on_changed? || urgent_changed? true end @@ -331,6 +332,11 @@ def save_changes_to_comment # before_save comment.previous_due_on = self.due_on_was if due_on_changed? end + if urgent_changed? or self.new_record? + comment.urgent = self.urgent + comment.previous_urgent = self.urgent_was if urgent_changed? + end + @saved_changes_to_comment = true true end @@ -465,4 +471,8 @@ def delete_old_events_if_required self.google_calendar_url_token = nil end end + + def nilize_due_on_for_urgent_tasks + self.due_on = nil if self.urgent? + end end diff --git a/app/models/user.rb b/app/models/user.rb index 61cc6ff3ac..d326acfbde 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -260,7 +260,7 @@ def pending_tasks Rails.cache.fetch("pending_tasks.#{id}") do active_project_ids.empty? ? [] : Task.where(:status => Task::ACTIVE_STATUS_CODES).where(:assigned_id => active_project_ids).order('ID desc').includes(:project). - sort { |a,b| (a.due_on || 1.week.from_now.to_date) <=> (b.due_on || 1.year.from_now.to_date) } + sort { |a,b| [a.urgent? ? 0 : 1, (a.due_on || 1.week.from_now.to_date)] <=> [b.urgent? ? 0 : 1, (b.due_on || 1.year.from_now.to_date)] } end end diff --git a/spec/models/task_spec.rb b/spec/models/task_spec.rb index 8425156c13..cd281e99a0 100644 --- a/spec/models/task_spec.rb +++ b/spec/models/task_spec.rb @@ -57,6 +57,14 @@ task.status_name = 'silly' }.should raise_error(ArgumentError) end + + it "should nilizie due_on only when urgent flag is set" do + task1 = Factory(:task, :due_on => Time.now, :urgent => false) + task1.due_on.should_not be_nil + + task2 = Factory(:task, :due_on => Time.now, :urgent => true) + task2.due_on.should be_nil + end describe "assigning tasks" do before do diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index adf3be52d9..e6b24f3534 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -449,6 +449,17 @@ @task.project.update_attribute :archived, true @user.pending_tasks.should be_empty end + it "should list active tasks sorted by (urgent, due_on ASC)" do + @task.assign_to(@user) + @task2 = Factory.create(:task, :due_on => 1.minute.from_now) + @task3 = Factory.create(:task, :due_on => 2.days.from_now) + @task4 = Factory.create(:task, :urgent => true) + [@task2, @task3, @task4].each do |task| + task.project.add_user(@user) + task.assign_to(@user) + end + @user.pending_tasks.should == [@task4, @task2, @task3, @task] + end end describe "#assigned_tasks_count" do From 70289177212b9e6c0b302bdabe1f1ca9862201ef Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:37:25 +0200 Subject: [PATCH 06/26] #447452: add urgent flag in task form --- app/helpers/tasks_helper.rb | 27 ++++++-- app/javascripts/date_picker.js | 61 ++++++++++++++++--- .../calendar_date_select_urgent_header.haml | 7 +++ app/views/comments/_comment.haml | 1 + config/locales/en.yml | 7 +++ config/teambox.yml | 1 + 6 files changed, 92 insertions(+), 12 deletions(-) create mode 100644 app/templates/tasks/calendar_date_select_urgent_header.haml diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 82fbcc90b8..034d259eed 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -62,6 +62,13 @@ def comment_task_due_on(comment) end end + def comment_task_urgent(comment) + if comment.urgent_change? + text = comment.urgent? ? t('tasks.urgent.set') : t('tasks.urgent.unset') + content_tag(:p, text, :class => "urgent_transition") + end + end + def task_status(task,status_type) status_for_column = status_type == :column ? "task_status_#{task.status_name}" : "task_counter" out = %() @@ -127,12 +134,22 @@ def time_tracking_doc end def date_picker(f, field, options = {}, html_options = {}) - selected_date = f.object.send(field.to_sym) ? localize(f.object.send(field.to_sym), :format => :long) : '' - - content_tag :div, :class => "date_picker", :id => "#{f.object.class.to_s.underscore}_#{f.object.id}_#{field}" do + selected_date = f.object.send(field.to_sym) ? localize(f.object.send(field.to_sym), :format => :long) : '' + show_urgent_flag = f.object.is_a?(Task) + datepicker_info = if show_urgent_flag && f.object.urgent? + t('date_picker.urgent.short') + elsif selected_date.blank? + t('date_picker.no_date_assigned') + else + selected_date + end + + classes = ["date_picker", ("show_urgent" if show_urgent_flag)].compact + content_tag :div, :class => classes.join(" "), :id => "#{f.object.class.to_s.underscore}_#{f.object.id}_#{field}" do [ image_tag('/images/calendar_date_select/calendar.gif', :class => :calendar_date_select_popup_icon), - content_tag(:span, selected_date.blank? ? t('date_picker.no_date_assigned') : selected_date, :class => 'localized_date'), - f.hidden_field(field, html_options.reverse_merge!(:class => :datepicker)) + content_tag(:span, datepicker_info, :class => 'datepicker_info'), + f.hidden_field(field, html_options.reverse_merge!(:class => :datepicker)), + (f.hidden_field("urgent", :class => "urgent") if show_urgent_flag), ].join.html_safe end end diff --git a/app/javascripts/date_picker.js b/app/javascripts/date_picker.js index 6370c9be4c..aa48d5e3bf 100644 --- a/app/javascripts/date_picker.js +++ b/app/javascripts/date_picker.js @@ -2,12 +2,15 @@ document.on('click', 'div.date_picker', function(e, element) { var field = element.down('input') var label = element.down('span') var parentDiv = element.up('div') - DatePicker.initialize(field, label, parentDiv) -}) + var date_picker = DatePicker.initialize(field, label, parentDiv); + if (element.hasClassName("show_urgent")) { + DatePicker.add_urgent_box(date_picker, element, field, label, parentDiv); + } +}); DatePicker = { initialize: function(field, label, parentDiv) { - new CalendarDateSelect(field, { + return(new CalendarDateSelect(field, { buttons: true, time: false, year_range: 10, @@ -26,9 +29,53 @@ DatePicker = { } else { localized_time = this.value } + label.update(localized_time || I18n.translations.date_picker.no_date_assigned); + } + })); + }, + + add_urgent_box: function(date_picker, element, field, label, parentDiv) { + // Render custom urgent box on calendar's top DIV + var urgent_field = field.parentNode.down("input.urgent"); + var html = Mustache.to_html(Templates.tasks.calendar_date_select_urgent_header, { + task_id: element.id.split("_")[1] + }); + date_picker.top_div.update(html); - label.update(localized_time) + // Link toggler for help info + date_picker.top_div.down(".show-help").observe("click", function(event) { + date_picker.top_div.down(".help").toggle(); + event.stop(); + }); + + // On urgent checkbox changes update task[urgent] and show/hide sections accordingly + var update_urgent_box = function (date_picker, input_urgent, user_action) { + urgent_field.value = input_urgent.checked ? "1" : "0"; + + if (input_urgent.checked) { + label.update(I18n.translations.date_picker.urgent.short); + } else { + date_picker.clearDate(); + date_picker.callback("onchange"); + label.update(I18n.translations.date_picker.no_date_assigned); } - }) - } -} \ No newline at end of file + + if (user_action && input_urgent.checked) { + date_picker.close(); + } else { + date_picker.calendar_div.select("> div").each(function(div) { + if (!div.hasClassName("cds_top")) { + div[input_urgent.checked ? "hide" : "show"](); + } + }); + } + } + + var input_urgent = date_picker.top_div.down("input.urgent") + input_urgent.checked = (urgent_field.value == "1"); + update_urgent_box(date_picker, input_urgent, false); + input_urgent.observe("click", function() { + update_urgent_box(date_picker, this, true); + }); + } +} diff --git a/app/templates/tasks/calendar_date_select_urgent_header.haml b/app/templates/tasks/calendar_date_select_urgent_header.haml new file mode 100644 index 0000000000..902eedfb7a --- /dev/null +++ b/app/templates/tasks/calendar_date_select_urgent_header.haml @@ -0,0 +1,7 @@ +.urgent + %input.urgent{:type => 'checkbox', :value => '1', :id => 'task_{{task_id}}_urgent'} + %label{:for => 'task_{{task_id}}_urgent'} + =t('date_picker.urgent.long') + %a{:href => '#', :class => 'show-help text_actions'} [?] + .help{:style => 'display: none'} + =t('date_picker.urgent.info') diff --git a/app/views/comments/_comment.haml b/app/views/comments/_comment.haml index 4308871f1f..f9502ac98c 100644 --- a/app/views/comments/_comment.haml +++ b/app/views/comments/_comment.haml @@ -30,6 +30,7 @@ = t('tasks.assigned.assigned_to', :user => comment.assigned.user.name) - else = t('tasks.assigned.unassigned') + = comment_task_urgent(comment) ~raw comment.body_html diff --git a/config/locales/en.yml b/config/locales/en.yml index eb16f47852..836f36701d 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -920,6 +920,9 @@ en: due_on_html: Needs to be done before... optional assigned_to: Who is responsible? assigned_to_nobody: Nobody + urgent: + set: Urgent flag set + unset: Urgent flag unset assigned: assigned_to: "Assigned to %{user}" myself: "Assign to myself (%{user})" @@ -2033,6 +2036,10 @@ en: date_picker: no_date_assigned: No date assigned + urgent: + long: "Urgent, to be done as soon as possible" + short: "Urgent" + info: Will show up on "tasks due for today" for the assigned user oauth: authentication_failure: Authentication failure, '%{message}' diff --git a/config/teambox.yml b/config/teambox.yml index 3dfd8883fb..184a999a85 100644 --- a/config/teambox.yml +++ b/config/teambox.yml @@ -144,6 +144,7 @@ defaults: &defaults - "*.tasks" - "*.projects" - "*.conversations" + - "*.date_picker" development: <<: *defaults From 51ab841f0f2ae60d56d076584a9e6b9ca850649d Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:38:44 +0200 Subject: [PATCH 07/26] #447452: navitation: show urgent in today tasks --- app/views/shared/_navigation.html.haml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/views/shared/_navigation.html.haml b/app/views/shared/_navigation.html.haml index b8f5a22030..5d5902e4e9 100644 --- a/app/views/shared/_navigation.html.haml +++ b/app/views/shared/_navigation.html.haml @@ -11,7 +11,7 @@ -# My Tasks - near_tasks = current_user.nearest_pending_tasks - - today_tasks = near_tasks.select { |t| t.due_on && (t.due_on <= Time.now.to_date) } + - today_tasks = near_tasks.select { |t| t.urgent? || (t.due_on && (t.due_on <= Time.now.to_date)) } - late_tasks = near_tasks.select { |t| t.due_on && (t.due_on < Time.now.to_date) } - pending_tasks = near_tasks - today_tasks - if today_tasks.any? From 2101ebdbbf7c506deafabf04b3eacc3abe40efae Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:41:24 +0200 Subject: [PATCH 08/26] web_steps.rb: [fix] make xpath selector relative to scope node --- features/step_definitions/web_steps.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/features/step_definitions/web_steps.rb b/features/step_definitions/web_steps.rb index 271c5c373a..ad5509d373 100644 --- a/features/step_definitions/web_steps.rb +++ b/features/step_definitions/web_steps.rb @@ -97,7 +97,7 @@ def with_css_scope(selector) When /^(?:|I )click the element that contain "([^\"]*)"(?: within "([^\"]*)")?$/ do |text, selector| with_css_scope(selector) do |node| - node.find(:xpath,"//*[.='#{text}']").click + node.find(:xpath,".//*[.='#{text}']").click end end From 08678bf1613f4b5db85500c1067c0bc517397a0c Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 10:54:03 +0200 Subject: [PATCH 09/26] #447452: add basic urgent feature --- features/step_definitions/task_steps.rb | 4 ++++ features/task_urgent.feature | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+) create mode 100644 features/task_urgent.feature diff --git a/features/step_definitions/task_steps.rb b/features/step_definitions/task_steps.rb index 39f516056b..3906e284f7 100644 --- a/features/step_definitions/task_steps.rb +++ b/features/step_definitions/task_steps.rb @@ -176,6 +176,10 @@ Then %(I should see "#{text}" within ".task_header h2") end +Then /^I should see "([^"]*)" within the task actions$/ do |text| + Then %(I should see "#{text}" within ".task .actions") +end + When /^(?:|I )select "([^\"]*)" in the "([^\"]*)" calendar?$/ do |number, calendar| with_css_scope("div[id$='_#{calender}_on']") do |node| find(:css,"table div[contains(#{number})]").click diff --git a/features/task_urgent.feature b/features/task_urgent.feature new file mode 100644 index 0000000000..92277f7824 --- /dev/null +++ b/features/task_urgent.feature @@ -0,0 +1,18 @@ +@javascript @tasks +Feature: Setting tasks as urgent + + Background: + Given @charles exists and is logged in + And I am in the project called "Teambox" + And the task list called "Bugs" belongs to the project called "Teambox" + And the following task with associations exist: + | name | task_list | project | + | Fix major bug | Bugs | Teambox | + And I go to the "Teambox" tasks page + + Scenario: I set the urgent flag for the task + When I follow "Fix major bug" + And I wait for 1 second + And I click the element that contain "No date assigned" within ".task .date_picker" + And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" + Then I should see "Urgent" within the task actions From 70b59eade4da04c42bb7a773d567fd0f0a93717f Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 12:41:19 +0200 Subject: [PATCH 10/26] #447452: fix urgent task order + styles for calendar --- app/helpers/tasks_helper.rb | 37 +++++++++++++++++++++++--------- app/javascripts/date_picker.js | 2 +- app/models/user.rb | 2 +- app/views/comments/_comment.haml | 1 - app/views/tasks/_task.haml | 2 ++ config/locales/en.yml | 1 + spec/models/user_spec.rb | 7 +++--- 7 files changed, 36 insertions(+), 16 deletions(-) diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 034d259eed..f5c3249854 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -9,6 +9,7 @@ def task_classes(task) classes << 'due_3weeks' if task.due_in?(3.weeks) classes << 'due_month' if task.due_in?(1.months) classes << 'overdue' if task.overdue? + classes << 'urgent' if task.urgent? classes << 'unassigned_date' if task.due_on.nil? classes << "status_#{task.status_name}" classes << 'status_notopen' if !task.open? @@ -27,7 +28,11 @@ def sidebar_tasks(tasks) end def render_due_on(task,user) - content_tag(:span, due_on(task), :class => 'due_on') + if task.urgent? + content_tag(:span, "★".html_safe, :class => 'urgent') + else + content_tag(:span, due_on(task), :class => 'due_on') + end end def render_assignment(task,user) @@ -51,15 +56,27 @@ def comment_task_status(comment) end def comment_task_due_on(comment) - if comment.due_on_change? - [].tap { |out| - if comment.due_on_transition? - out << span_for_due_date(comment.previous_due_on) - out << content_tag(:span, '→'.html_safe, :class => "arr due_on_arr") - end - out << span_for_due_date(comment.due_on) - }.join(' ').html_safe - end + if comment.urgent_change? + [ + comment.urgent? ? span_for_due_date(comment.previous_due_on) : span_for_urgent(comment), + content_tag(:span, '→'.html_safe, :class => "arr due_on_arr"), + comment.urgent? ? span_for_urgent(comment) : span_for_due_date(comment.due_on) , + ] + elsif comment.due_on_change? + [ + ([ + span_for_due_date(comment.previous_due_on), + content_tag(:span, '→'.html_safe, :class => "arr due_on_arr"), + ] if comment.due_on_transition?), + span_for_due_date(comment.due_on), + ] + else + [] + end.compact.flatten.join(' ').html_safe + end + + def span_for_urgent(comment) + content_tag(:span, t("tasks.urgent.caption"), :class => 'urgent') end def comment_task_urgent(comment) diff --git a/app/javascripts/date_picker.js b/app/javascripts/date_picker.js index aa48d5e3bf..b67a692ebc 100644 --- a/app/javascripts/date_picker.js +++ b/app/javascripts/date_picker.js @@ -54,7 +54,7 @@ DatePicker = { if (input_urgent.checked) { label.update(I18n.translations.date_picker.urgent.short); - } else { + } else if (user_action) { date_picker.clearDate(); date_picker.callback("onchange"); label.update(I18n.translations.date_picker.no_date_assigned); diff --git a/app/models/user.rb b/app/models/user.rb index d326acfbde..b0882598c0 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -260,7 +260,7 @@ def pending_tasks Rails.cache.fetch("pending_tasks.#{id}") do active_project_ids.empty? ? [] : Task.where(:status => Task::ACTIVE_STATUS_CODES).where(:assigned_id => active_project_ids).order('ID desc').includes(:project). - sort { |a,b| [a.urgent? ? 0 : 1, (a.due_on || 1.week.from_now.to_date)] <=> [b.urgent? ? 0 : 1, (b.due_on || 1.year.from_now.to_date)] } + sort { |a,b| [a.urgent? ? 1 : 0, (a.due_on || 1.week.from_now.to_date)] <=> [b.urgent? ? 1 : 0, (b.due_on || 1.year.from_now.to_date)] } end end diff --git a/app/views/comments/_comment.haml b/app/views/comments/_comment.haml index f9502ac98c..4308871f1f 100644 --- a/app/views/comments/_comment.haml +++ b/app/views/comments/_comment.haml @@ -30,7 +30,6 @@ = t('tasks.assigned.assigned_to', :user => comment.assigned.user.name) - else = t('tasks.assigned.unassigned') - = comment_task_urgent(comment) ~raw comment.body_html diff --git a/app/views/tasks/_task.haml b/app/views/tasks/_task.haml index bd4b1911dd..c69d9659d2 100644 --- a/app/views/tasks/_task.haml +++ b/app/views/tasks/_task.haml @@ -13,6 +13,8 @@ .lock_icon = link_to h(task), [project, task], :class => :name %span.assigned_date= task.due_on ? due_on(task) : '' + - if task.urgent? + %span.urgent= t("tasks.urgent.caption") %span.assigned_user= task.assigned ? link_to(h(task.assigned.short_name), user_path(task.assigned.user)) : '' - unless @current_project .extended diff --git a/config/locales/en.yml b/config/locales/en.yml index 836f36701d..21b0bb58b0 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -921,6 +921,7 @@ en: assigned_to: Who is responsible? assigned_to_nobody: Nobody urgent: + caption: Urgent set: Urgent flag set unset: Urgent flag unset assigned: diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb index e6b24f3534..931dbf89fa 100644 --- a/spec/models/user_spec.rb +++ b/spec/models/user_spec.rb @@ -451,14 +451,15 @@ end it "should list active tasks sorted by (urgent, due_on ASC)" do @task.assign_to(@user) - @task2 = Factory.create(:task, :due_on => 1.minute.from_now) + @task.update_attribute(:due_on, 1.minute.from_now) + @task2 = Factory.create(:task, :due_on => 10.minutes.from_now) @task3 = Factory.create(:task, :due_on => 2.days.from_now) @task4 = Factory.create(:task, :urgent => true) [@task2, @task3, @task4].each do |task| task.project.add_user(@user) task.assign_to(@user) - end - @user.pending_tasks.should == [@task4, @task2, @task3, @task] + end + @user.pending_tasks.should == [@task, @task2, @task3, @task4] end end From 1033a2030f6c61eddac9546097b30750e98f37d8 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 13:30:54 +0200 Subject: [PATCH 11/26] #447452: styles for urgent flag in tasks and calendar_date_select --- app/styles/_calendar_date_select.sass | 12 +++++++++++- app/styles/_tasks.sass | 12 ++++++++++-- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/styles/_calendar_date_select.sass b/app/styles/_calendar_date_select.sass index 843efcec1f..b4a789c984 100644 --- a/app/styles/_calendar_date_select.sass +++ b/app/styles/_calendar_date_select.sass @@ -7,7 +7,7 @@ -webkit-box-shadow: 0 0 5px rgb(153, 153, 153) background: white display: block - width: 250px + width: 280px th font-size: 11px text-align: center @@ -29,6 +29,16 @@ border: right: solid 1px rgb(140, 140, 140) bottom: solid 1px rgb(140, 140, 140) + .cds_top + .urgent + margin: 10px 0 + .help + margin: 5px 0 + color: #777 + font-size: 12px + .show-help + position: relative + top: 3px .cds_header clear: both text-align: center diff --git a/app/styles/_tasks.sass b/app/styles/_tasks.sass index 81b1876fc0..1127c3596d 100644 --- a/app/styles/_tasks.sass +++ b/app/styles/_tasks.sass @@ -190,7 +190,7 @@ form.new_task, form.edit_task, .task_list_form padding-left: 4px .lock_icon display: none - span.assigned_date, span.assigned_user + span.assigned_date, span.assigned_user, span.urgent display: none color: rgb(100,100,100) padding: 1px 5px @@ -223,6 +223,14 @@ form.new_task, form.edit_task, .task_list_form font-weight: bold span.assigned_date, span.assigned_user color: rgb(200,0,0) + &.urgent + .taskName + a.name + color: rgb(150,0,0) + font-weight: bold + span.urgent + color: rgb(150,0,0) + display: inline-block .task.status_open.mine background: rgb(255,255,210) .taskName @@ -363,7 +371,7 @@ p.assigned_transition text-decoration: line-through color: #999 -.assigned_date +.assigned_date, .comment span.urgent, .taskName span.urgent color: rgb(100,100,100) padding: 0px 5px margin-right: 5px From 0b2f8b4a804213d9c72ea5496b33b1c05861f10f Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 13:31:52 +0200 Subject: [PATCH 12/26] #447452: Task: Add urgent flag in API response --- app/models/task/conversions.rb | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/models/task/conversions.rb b/app/models/task/conversions.rb index 6db96a86f5..0df2ce7392 100644 --- a/app/models/task/conversions.rb +++ b/app/models/task/conversions.rb @@ -12,6 +12,7 @@ def to_xml(options = {}) xml.tag! 'assigned-id', assigned_id xml.tag! 'status', status xml.tag! 'due-on', due_on.to_s(:db) if due_on + xml.tag! 'urgent', urgent? xml.tag! 'created-at', created_at.to_s(:db) xml.tag! 'updated-at', updated_at.to_s(:db) xml.tag! 'completed-at', completed_at.to_s(:db) if completed_at @@ -41,6 +42,7 @@ def to_api_hash(options = {}) base[:type] = self.class.to_s if options[:emit_type] base[:due_on] = due_on.to_s(:db) if due_on + base[:urgent] = urgent? base[:completed_at] = completed_at.to_s(:db) if completed_at if Array(options[:include]).include? :task_list @@ -70,4 +72,4 @@ def to_api_hash(options = {}) base end -end \ No newline at end of file +end From 6d0455845fa0aa7770f71021c8e32877efe0bf9c Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 16:01:30 +0200 Subject: [PATCH 13/26] #447452: move urgent edit to task_edit and add story to task_create --- features/task_create.feature | 11 +++++++++++ features/task_edit.feature | 10 ++++++++++ features/task_urgent.feature | 18 ------------------ 3 files changed, 21 insertions(+), 18 deletions(-) delete mode 100644 features/task_urgent.feature diff --git a/features/task_create.feature b/features/task_create.feature index 3d923bf846..c3f835b948 100644 --- a/features/task_create.feature +++ b/features/task_create.feature @@ -26,6 +26,17 @@ Feature: Creating a task And I wait for 1 second And I should see "Ohhh upload" as a task name + Scenario: Mislav creates a valid task with urgent flag + When I go to the "Awesome Ruby Yahh" task list page of the "Ruby Rockstars" project + When I follow "+ Add Task" + And I fill in "Task title" with "Ohhh upload" + And I click the element that contain "No date assigned" within "#new_task" + And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" + And I press "Add Task" + And I wait for 1 second + And I should see "Ohhh upload" as a task name + And I should see "Urgent" within "span.urgent" + Scenario: Fails to create a task without a title When I go to the "Awesome Ruby Yahh" task list page of the "Ruby Rockstars" project And I follow "+ Add Task" diff --git a/features/task_edit.feature b/features/task_edit.feature index 8890f4ad52..7981dda444 100644 --- a/features/task_edit.feature +++ b/features/task_edit.feature @@ -19,3 +19,13 @@ Feature: Editing a task And I press "Update Task" And I wait for 1 second Then I should see "Fix minor bug" within the task header + + Scenario: I set the urgent flag for the task + When I follow "Fix major bug" + And I wait for 1 second + And I click the element that contain "No date assigned" within ".task .date_picker" + And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" + And I press "Save" + Then I should see "Urgent" within the task actions + And I should see "No due date" within "span.assigned_date" + And I should see "Urgent" within "span.urgent" diff --git a/features/task_urgent.feature b/features/task_urgent.feature deleted file mode 100644 index 92277f7824..0000000000 --- a/features/task_urgent.feature +++ /dev/null @@ -1,18 +0,0 @@ -@javascript @tasks -Feature: Setting tasks as urgent - - Background: - Given @charles exists and is logged in - And I am in the project called "Teambox" - And the task list called "Bugs" belongs to the project called "Teambox" - And the following task with associations exist: - | name | task_list | project | - | Fix major bug | Bugs | Teambox | - And I go to the "Teambox" tasks page - - Scenario: I set the urgent flag for the task - When I follow "Fix major bug" - And I wait for 1 second - And I click the element that contain "No date assigned" within ".task .date_picker" - And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" - Then I should see "Urgent" within the task actions From f609b2f33aca6d7f008788f1e51b207a622869fc Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Fri, 19 Aug 2011 16:25:03 +0200 Subject: [PATCH 14/26] #447452: use char ! as the urgent task badge --- app/helpers/tasks_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index f5c3249854..98ae5ca1ec 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -29,7 +29,7 @@ def sidebar_tasks(tasks) def render_due_on(task,user) if task.urgent? - content_tag(:span, "★".html_safe, :class => 'urgent') + content_tag(:span, "!".html_safe, :class => 'urgent') else content_tag(:span, due_on(task), :class => 'due_on') end From 25d80caebfffdcb3bf0dabcc920fbb2a9d53ed10 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Sun, 21 Aug 2011 12:17:52 +0200 Subject: [PATCH 15/26] #447452: refactor TasksHelper#comment_task_due_on --- app/helpers/tasks_helper.rb | 37 +++++++++++-------------------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 98ae5ca1ec..64bdf5781a 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -56,36 +56,17 @@ def comment_task_status(comment) end def comment_task_due_on(comment) - if comment.urgent_change? + if comment.urgent_change? || comment.due_on_change? [ - comment.urgent? ? span_for_due_date(comment.previous_due_on) : span_for_urgent(comment), + comment.previous_urgent? ? span_for_urgent(comment) : + span_for_due_date(comment.previous_due_on), content_tag(:span, '→'.html_safe, :class => "arr due_on_arr"), - comment.urgent? ? span_for_urgent(comment) : span_for_due_date(comment.due_on) , - ] - elsif comment.due_on_change? - [ - ([ - span_for_due_date(comment.previous_due_on), - content_tag(:span, '→'.html_safe, :class => "arr due_on_arr"), - ] if comment.due_on_transition?), - span_for_due_date(comment.due_on), - ] - else - [] - end.compact.flatten.join(' ').html_safe - end - - def span_for_urgent(comment) - content_tag(:span, t("tasks.urgent.caption"), :class => 'urgent') - end - - def comment_task_urgent(comment) - if comment.urgent_change? - text = comment.urgent? ? t('tasks.urgent.set') : t('tasks.urgent.unset') - content_tag(:p, text, :class => "urgent_transition") + comment.urgent? ? span_for_urgent(comment) : + span_for_due_date(comment.due_on), + ].join(' ').html_safe end end - + def task_status(task,status_type) status_for_column = status_type == :column ? "task_status_#{task.status_name}" : "task_counter" out = %() @@ -181,6 +162,10 @@ def span_for_due_date(due_date) content_tag(:span, task_due_on(due_date), :class => "assigned_date") end + + def span_for_urgent(comment) + content_tag(:span, t("tasks.urgent.caption"), :class => 'urgent') + end def span_for_thread_due_date(task) content_tag(:span, due_on(task), From 3d3f09ab7ed3f4f378edc7b1e42bc20f26e64391 Mon Sep 17 00:00:00 2001 From: Pablo Villalba Date: Mon, 22 Aug 2011 10:43:34 +0200 Subject: [PATCH 16/26] A bit of extra margin for the Urgent checkbox --- app/styles/_calendar_date_select.sass | 1 + 1 file changed, 1 insertion(+) diff --git a/app/styles/_calendar_date_select.sass b/app/styles/_calendar_date_select.sass index b4a789c984..b31533492b 100644 --- a/app/styles/_calendar_date_select.sass +++ b/app/styles/_calendar_date_select.sass @@ -32,6 +32,7 @@ .cds_top .urgent margin: 10px 0 + text-align: center .help margin: 5px 0 color: #777 From 50b3cb751f580dcd45a6cc7c1f7a15fc19797154 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Mon, 22 Aug 2011 12:09:58 +0200 Subject: [PATCH 17/26] #447452: show urgent flag box for conversation to task conversion form --- app/helpers/tasks_helper.rb | 23 ++++++++++--------- app/javascripts/date_picker.js | 1 + app/models/comment/tasks.rb | 4 ++++ app/models/conversation/tasks.rb | 9 ++++++-- features/conversation_convert_to_task.feature | 15 ++++++++++++ features/task_edit.feature | 1 - 6 files changed, 39 insertions(+), 14 deletions(-) diff --git a/app/helpers/tasks_helper.rb b/app/helpers/tasks_helper.rb index 64bdf5781a..73a280da5e 100644 --- a/app/helpers/tasks_helper.rb +++ b/app/helpers/tasks_helper.rb @@ -57,13 +57,14 @@ def comment_task_status(comment) def comment_task_due_on(comment) if comment.urgent_change? || comment.due_on_change? - [ - comment.previous_urgent? ? span_for_urgent(comment) : - span_for_due_date(comment.previous_due_on), - content_tag(:span, '→'.html_safe, :class => "arr due_on_arr"), - comment.urgent? ? span_for_urgent(comment) : - span_for_due_date(comment.due_on), - ].join(' ').html_safe + [].tap do |out| + if comment.due_on_transition? || comment.urgent_transition? + out << (comment.previous_urgent? ? span_for_urgent(comment) : + span_for_due_date(comment.previous_due_on)) + out << content_tag(:span, '→'.html_safe, :class => "arr due_on_arr") + end + out << (comment.urgent? ? span_for_urgent(comment) : span_for_due_date(comment.due_on)) + end.join(' ').html_safe end end @@ -132,8 +133,8 @@ def time_tracking_doc end def date_picker(f, field, options = {}, html_options = {}) - selected_date = f.object.send(field.to_sym) ? localize(f.object.send(field.to_sym), :format => :long) : '' - show_urgent_flag = f.object.is_a?(Task) + selected_date = f.object.send(field.to_sym) ? localize(f.object.send(field.to_sym), :format => :long) : '' + show_urgent_flag = [Task, Conversation].include?(f.object.class) datepicker_info = if show_urgent_flag && f.object.urgent? t('date_picker.urgent.short') elsif selected_date.blank? @@ -146,8 +147,8 @@ def date_picker(f, field, options = {}, html_options = {}) content_tag :div, :class => classes.join(" "), :id => "#{f.object.class.to_s.underscore}_#{f.object.id}_#{field}" do [ image_tag('/images/calendar_date_select/calendar.gif', :class => :calendar_date_select_popup_icon), content_tag(:span, datepicker_info, :class => 'datepicker_info'), - f.hidden_field(field, html_options.reverse_merge!(:class => :datepicker)), - (f.hidden_field("urgent", :class => "urgent") if show_urgent_flag), + f.hidden_field(field, html_options.reverse_merge(:class => :datepicker)), + (f.hidden_field("urgent", html_options.reverse_merge(:class => :urgent)) if show_urgent_flag), ].join.html_safe end end diff --git a/app/javascripts/date_picker.js b/app/javascripts/date_picker.js index b67a692ebc..5922a70940 100644 --- a/app/javascripts/date_picker.js +++ b/app/javascripts/date_picker.js @@ -51,6 +51,7 @@ DatePicker = { // On urgent checkbox changes update task[urgent] and show/hide sections accordingly var update_urgent_box = function (date_picker, input_urgent, user_action) { urgent_field.value = input_urgent.checked ? "1" : "0"; + urgent_field.removeAttribute("disabled"); if (input_urgent.checked) { label.update(I18n.translations.date_picker.urgent.short); diff --git a/app/models/comment/tasks.rb b/app/models/comment/tasks.rb index 9f6e567f78..00c26d18b4 100644 --- a/app/models/comment/tasks.rb +++ b/app/models/comment/tasks.rb @@ -24,6 +24,10 @@ def urgent_change? urgent != previous_urgent end + def urgent_transition? + urgent? != previous_urgent? and previous_urgent? + end + def assigned_transition? assigned_id != previous_assigned_id end diff --git a/app/models/conversation/tasks.rb b/app/models/conversation/tasks.rb index 193f059d40..a7ceaca0f8 100644 --- a/app/models/conversation/tasks.rb +++ b/app/models/conversation/tasks.rb @@ -1,7 +1,11 @@ class Conversation - attr_accessor :due_on, :status, :assigned_id, :task_list_id - attr_accessible :due_on, :status, :assigned_id, :task_list_id + attr_accessor :due_on, :urgent, :status, :assigned_id, :task_list_id + attr_accessible :due_on, :urgent, :status, :assigned_id, :task_list_id + def urgent? + !!urgent + end + def convert_to_task! task_list = task_list_id.blank? ? nil : TaskList.find(task_list_id) task_list ||= TaskList.find_or_create_by_name_and_project_id_and_user_id('Inbox', project.id, user.id) @@ -11,6 +15,7 @@ def convert_to_task! t.name = name t.status = status.blank? ? 0 : status t.due_on = due_on + t.urgent = urgent || false t.user = user t.updating_user = updating_user t.task_list = task_list diff --git a/features/conversation_convert_to_task.feature b/features/conversation_convert_to_task.feature index 45b91bcee5..027323f346 100644 --- a/features/conversation_convert_to_task.feature +++ b/features/conversation_convert_to_task.feature @@ -109,3 +109,18 @@ Feature: Converting a conversation to a task And I wait for 3 seconds And I am going to the bookmarked link Then I should see "An exciting task for you" in the task thread title + + Scenario: I can set the urgent flag for the task being created + Given I started a simple conversation + When I go to the home page + And I click the conversation's comment box + And I follow "Convert to task" + And I wait for 2 seconds + And I fill in "conversation_name" with "Give git course" + And I click the element that contain "No date assigned" within ".date_picker" + And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" + Then I should see "Urgent" within ".fields" + When I press "Convert" + And I wait for 3 seconds + Then I should see "Give git course" in the task thread title + And I should see 'Urgent' diff --git a/features/task_edit.feature b/features/task_edit.feature index 7981dda444..b88a5d2937 100644 --- a/features/task_edit.feature +++ b/features/task_edit.feature @@ -27,5 +27,4 @@ Feature: Editing a task And I check "Urgent, to be done as soon as possible" within ".calendar_date_select" And I press "Save" Then I should see "Urgent" within the task actions - And I should see "No due date" within "span.assigned_date" And I should see "Urgent" within "span.urgent" From 07461513fd91f7fe4e47e3f8b16e03ab36b60d90 Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Mon, 22 Aug 2011 15:26:57 +0200 Subject: [PATCH 18/26] #447452: convert_to_task: set urgent flag to false by default --- app/models/conversation/tasks.rb | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/app/models/conversation/tasks.rb b/app/models/conversation/tasks.rb index a7ceaca0f8..12df99aaf3 100644 --- a/app/models/conversation/tasks.rb +++ b/app/models/conversation/tasks.rb @@ -15,7 +15,8 @@ def convert_to_task! t.name = name t.status = status.blank? ? 0 : status t.due_on = due_on - t.urgent = urgent || false + t.urgent = urgent + t.urgent = t.urgent.nil? ? false : t.urgent t.user = user t.updating_user = updating_user t.task_list = task_list From dcb9e48e13ff344ba3f989a5eb05803a3acdb85c Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Mon, 22 Aug 2011 17:33:26 +0100 Subject: [PATCH 19/26] Better upload insertion in the activity stream --- .../api_v1/activities_controller.rb | 8 +++-- .../api_v1/activities_controller_spec.rb | 36 +++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/app/controllers/api_v1/activities_controller.rb b/app/controllers/api_v1/activities_controller.rb index ce837ef92b..b17c839001 100644 --- a/app/controllers/api_v1/activities_controller.rb +++ b/app/controllers/api_v1/activities_controller.rb @@ -13,7 +13,7 @@ def index limit(api_limit(:hard => true)) @activities = @activities.threads.except(:order).by_thread if params[:threads] - api_respond @activities, :references => true + api_respond @activities, :references => true, :include => api_include end def show @@ -21,7 +21,7 @@ def show authorize!(:show, @activity) if @activity if @activity - api_respond @activity, :references => true, :include => [:uploads] + api_respond @activity, :references => true, :include => api_include([:uploads]) else api_error :not_found, :type => 'ObjectNotFound', :message => 'Not found' end @@ -47,4 +47,8 @@ def get_target api_error :not_found, :type => 'ObjectNotFound', :message => 'Target not found' end end + + def api_include(default=[]) + [:user, :google_docs, :uploads] & ((params[:include]||[])+default).map(&:to_sym) + end end diff --git a/spec/controllers/api_v1/activities_controller_spec.rb b/spec/controllers/api_v1/activities_controller_spec.rb index 11d9ecafe9..5bb30c8e56 100644 --- a/spec/controllers/api_v1/activities_controller_spec.rb +++ b/spec/controllers/api_v1/activities_controller_spec.rb @@ -17,6 +17,42 @@ JSON.parse(response.body)['objects'].map{|a| a['id'].to_i}.sort.should == (@project.activity_ids+@other_project.activity_ids).sort end + + it "shows uploads in comments when requested" do + login_as @user + + @conversation = Factory.create(:conversation, :project => @project, :body => 'Test conversation') + @task = Factory.create(:conversation, :project => @project, :body => 'Test conversation') + @task.updating_user = @task.user + @task.update_attributes(:comments_attributes => {'0' => {'body' => 'Test'}}) + + @upload = @project.uploads.new({:asset => mock_uploader('semicolons.js', 'application/javascript', "alert('what?!')")}) + @upload.comment = @conversation.comments.first + @upload.user = @user + @upload.save! + + @other_upload = @project.uploads.new({:asset => mock_uploader('jquery.js', 'application/javascript', ";")}) + @other_upload.comment = @task.comments(true).first + @other_upload.user = @user + @other_upload.save! + + get :index, :include => [:uploads, :google_docs] + response.should be_success + + data = JSON.parse(response.body) + references = data['references'].map{|r| "#{r['id']}_#{r['type']}"} + references.include?("#{@upload.id}_Upload").should == true + references.include?("#{@other_upload.id}_Upload").should == true + + comment_ids = [@task.comments(true).first.id, @conversation.comments.first.id] + comments = data['references'].reject{ |o| !(o['type'] == 'Comment' && comment_ids.include?(o['id'])) } + comments.length.should == 2 + + comments.each do |comment| + comment['uploads'].should_not == nil + comment['upload_ids'].should_not == nil + end + end it "shows activities as JSON when requested with :text format" do login_as @user From 61d9dabbb82375fd17db59fa941894f14673dc6c Mon Sep 17 00:00:00 2001 From: Artur Szwed Date: Tue, 23 Aug 2011 15:39:46 +0200 Subject: [PATCH 20/26] Add a "More..." dropdown by files in comments Links to Share this File and Download --- app/javascripts/task.js | 2 + app/javascripts/upload.js | 18 ++++++++ app/styles/_uploads.sass | 60 +++++++++++++++---------- app/views/uploads/_file.haml | 3 +- app/views/uploads/_more.haml | 5 +++ app/views/uploads/_thumbnail.haml | 3 +- app/views/uploads/_upload.haml | 4 +- features/step_definitions/task_steps.rb | 22 +++++++++ features/upload_more_options.feature | 15 +++++++ 9 files changed, 105 insertions(+), 27 deletions(-) create mode 100644 app/views/uploads/_more.haml create mode 100644 features/upload_more_options.feature diff --git a/app/javascripts/task.js b/app/javascripts/task.js index ae6c66116d..41a7c9d84d 100644 --- a/app/javascripts/task.js +++ b/app/javascripts/task.js @@ -26,6 +26,7 @@ document.on('ajax:success', '.task_header + form.edit_task', function(e, form) { }) Task = { + sortableChange: function(draggable) { this.currentDraggable = draggable }, @@ -344,3 +345,4 @@ document.on('ajax:success', '.task_list form.new_task', function(e, form) { Form.reset(form).focusFirstElement().up('.task_list').down('.tasks').insert(response) }) + diff --git a/app/javascripts/upload.js b/app/javascripts/upload.js index 527d8df5f8..66dd43dac7 100644 --- a/app/javascripts/upload.js +++ b/app/javascripts/upload.js @@ -62,3 +62,21 @@ document.on('click', '.uploads .upload .header .file a', function(e, el) { document.on('click', '.uploads .upload .header', function(e, el) { toggle_task_row(el); }); + +var toggle_upload_more = function(el) { + var reference = el.up('.upload_file') ? el.up('.upload_file').down('.reference') : el.up('.upload_thumbnail').down('.reference'); + if(reference) { + if (reference.visible()) { + reference.hide(); + } else { + $$('.upload_file .reference').invoke('hide'); + $$('.upload_thumbnail .reference').invoke('hide'); + reference.show(); + } + } + return false; +}; + +document.on('click', '.more span', function(e, el) { + toggle_upload_more(el); +}); diff --git a/app/styles/_uploads.sass b/app/styles/_uploads.sass index 6588b3e54c..b4ab36d042 100644 --- a/app/styles/_uploads.sass +++ b/app/styles/_uploads.sass @@ -53,26 +53,6 @@ $upload_icon_width: 16px font-size: 10px &:hover .more display: inline-block - .reference - display: inline-block - position: absolute - border: 1px solid #aaa - border-top: 0 - top: 25px - right: -1px - background-color: #ffe - z-index: 100 - a - display: block - padding: 5px 15px - color: black - &:hover - background-color: $navbar - color: black - text-decoration: none - cursor: pointer - a[data-method="delete"] - color: red .uploads_current, .upload_images margin: 5px @@ -95,11 +75,15 @@ $upload_icon_width: 16px img margin-right: 5px padding-top: 2px - span + span, div.more + display: inline-block position: relative top: -2px - .size color: rgb(120, 120, 120) + a.label + color: rgb(120, 120, 120) + text-decoration: underline + cursor: pointer .activity .upload_list @@ -146,4 +130,34 @@ $upload_icon_width: 16px margin: 25px 0 font-size: 12px font-weight: bold - a + +.upload_file, .file_upload + .reference + display: inline-block + position: absolute + min-width: 250px + border: 1px solid #aaa + border-top: 0 + top: 25px + right: -1px + background-color: #ffe + z-index: 100 + a + display: block + padding: 5px 15px + color: black + &:hover + background-color: $navbar + color: black + text-decoration: none + cursor: pointer + a[data-method="delete"] + color: red + +div.more:hover + .reference + display: block !important + top: 14px + right: auto + border: 1px solid #aaa + +box-shadow(#777 1px 2px 10px) diff --git a/app/views/uploads/_file.haml b/app/views/uploads/_file.haml index f817cb1fdb..1d53d9909d 100644 --- a/app/views/uploads/_file.haml +++ b/app/views/uploads/_file.haml @@ -3,4 +3,5 @@ %span.filename = link_to_upload(upload, upload.file_name) %span.size - = '(' + number_to_human_size(upload.size).to_s + ')' \ No newline at end of file + = '(' + number_to_human_size(upload.size).to_s + ')' + = render :partial => "uploads/more", :locals => {:upload => upload} \ No newline at end of file diff --git a/app/views/uploads/_more.haml b/app/views/uploads/_more.haml new file mode 100644 index 0000000000..9941aaccaa --- /dev/null +++ b/app/views/uploads/_more.haml @@ -0,0 +1,5 @@ +.more + %a.label More... + .reference{ :style => 'display: none;' } + = link_to t('uploads.public_download.link.anchor'), public_download_project_upload_path(upload.project, upload), :rel => 'facebox', :title => t('uploads.public_download.link.title') + = link_to_upload upload, t('.download') diff --git a/app/views/uploads/_thumbnail.haml b/app/views/uploads/_thumbnail.haml index 9a14d9bbf7..687fd3f59c 100644 --- a/app/views/uploads/_thumbnail.haml +++ b/app/views/uploads/_thumbnail.haml @@ -1,2 +1,3 @@ .upload_thumbnail{:id => "upload_#{upload.id}"} - = upload_link_with_thumbnail(upload, :small) \ No newline at end of file + = upload_link_with_thumbnail(upload, :small) + = render :partial => "uploads/more", :locals => {:upload => upload} \ No newline at end of file diff --git a/app/views/uploads/_upload.haml b/app/views/uploads/_upload.haml index 733826f4cd..adee4cd308 100644 --- a/app/views/uploads/_upload.haml +++ b/app/views/uploads/_upload.haml @@ -15,8 +15,8 @@ = link_to_upload upload, t('.download'), {:rel => nil} = link_to t('uploads.public_download.link.anchor'), public_download_project_upload_path(upload.project, upload), :rel => 'facebox', :title => t('uploads.public_download.link.title') - if can?(:update, upload) # and request.format != :m - = link_to t('.remove'), [upload.project, upload], :'data-remote' => true, - :'data-method' => 'delete', :'data-confirm' => t('confirm.delete_upload') = link_to t('common.rename'), [:edit, upload.project, upload], :remote => true, :class => "link_rename" + = link_to t('.remove'), [upload.project, upload], :'data-remote' => true, + :'data-method' => 'delete', :'data-confirm' => t('confirm.delete_upload') diff --git a/features/step_definitions/task_steps.rb b/features/step_definitions/task_steps.rb index 3906e284f7..0eb9dbaf7a 100644 --- a/features/step_definitions/task_steps.rb +++ b/features/step_definitions/task_steps.rb @@ -8,6 +8,28 @@ @task = project.create_task(@current_user, task_list, {:name => name}) end +Given /^I have a task called "([^"]*)" with a comment including upload "([^"]*)"$/ do |task_name, file_name| + + Given %(I have a task called "#{task_name}") + @comment = @task.comments.create :body => "Something to say" + + path = File.join(Rails.root, "spec/fixtures/#{file_name}") + if File.exists?(path) + @upload = Factory.create(:upload, { + :asset => open(path), + :asset_file_name => file_name, + :asset_file_size => nil, + :asset_content_type => nil, + :project => @current_project, + :comment => @comment + }) + + else + Factory.create(:upload, :asset_file_name => file_name, :project => @current_project, :comment => @comment) + end + +end + ## FIXME: it's better for 'givens' to set tasks up directly in the db: Given /^the following tasks? with associations exists?:?$/ do |table| diff --git a/features/upload_more_options.feature b/features/upload_more_options.feature new file mode 100644 index 0000000000..593b89074c --- /dev/null +++ b/features/upload_more_options.feature @@ -0,0 +1,15 @@ +@uploads +Feature: Uploading a file + + Background: + Given a project exists with name: "Ruby Rockstars" + And a confirmed user exists with login: "mislav", first_name: "Mislav", last_name: "Marohnić" + And I am logged in as @mislav + And I am currently in the project ruby_rockstars + And I have a task called "Railscast Theme" with a comment including upload "tb-space.jpg" + + Scenario: Upload has more options in activity feed + When I go to the project page + And I click the element that contain "More..." + Then I should see "Download" within ".reference" + And I should see "Send this file to somebody..." within ".reference" \ No newline at end of file From 2daac7d6e51072ef8a768213ae5eafc36f29c315 Mon Sep 17 00:00:00 2001 From: Jordi Romero Date: Wed, 24 Aug 2011 13:14:31 +0200 Subject: [PATCH 21/26] fix project digests when args are encoded in JSON When an emailer is enqueued in Resque its arguments are encoded in JSON and then decoded, thus hashes must be converted to HashWithIndifferentAccess. --- app/models/emailer.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/models/emailer.rb b/app/models/emailer.rb index 384c41d55e..adc41a8751 100644 --- a/app/models/emailer.rb +++ b/app/models/emailer.rb @@ -240,6 +240,7 @@ def project_digest(user_id, person_id, project_id, target_types_and_ids, comment @person = Person.find(person_id) @project = Project.find(project_id) @targets = target_types_and_ids.map do |target| + target = target.with_indifferent_access target[:target_type].constantize.find_by_id target[:target_id] end.compact.uniq @comments = Comment.where(:id => comment_ids) From 39b4791f414969b3786d06d95b54f0ae5d141366 Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Wed, 24 Aug 2011 17:54:22 +0100 Subject: [PATCH 22/26] Added project route for tasks#create, adds tasks to the Inbox --- app/controllers/api_v1/tasks_controller.rb | 12 +++++++++++- config/routes.rb | 2 +- .../api_v1/tasks_controller_spec.rb | 19 +++++++++++++++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/app/controllers/api_v1/tasks_controller.rb b/app/controllers/api_v1/tasks_controller.rb index 85e3b7bdc1..f54962858d 100644 --- a/app/controllers/api_v1/tasks_controller.rb +++ b/app/controllers/api_v1/tasks_controller.rb @@ -1,5 +1,6 @@ class ApiV1::TasksController < ApiV1::APIController - before_filter :load_task_list, :only => [:index, :create, :show, :reorder] + before_filter :load_task_list, :only => [:index, :show, :reorder] + before_filter :load_or_create_task_list, :only => [:create] before_filter :load_task, :except => [:index, :create, :reorder] def index @@ -103,6 +104,15 @@ def load_task api_error :not_found, :type => 'ObjectNotFound', :message => 'Task not found' if @task_list && @task.task_list_id != @task_list.id end + def load_or_create_task_list + if params[:task_list_id] or @current_project.nil? + load_task_list + else + # make or load inbox + @task_list = TaskList.find_or_create_by_name_and_project_id_and_user_id('Inbox', @current_project.id, @current_project.user_id) + end + end + def api_scope conditions = {} unless params[:status].nil? diff --git a/config/routes.rb b/config/routes.rb index 0f5475a9b7..cfddf04fbd 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -298,7 +298,7 @@ def self.matches?(request) resources :tasks, :except => [:new, :edit] end - resources :tasks, :except => [:new, :edit, :create] do + resources :tasks, :except => [:new, :edit] do member do put :watch put :unwatch diff --git a/spec/controllers/api_v1/tasks_controller_spec.rb b/spec/controllers/api_v1/tasks_controller_spec.rb index 18483299d6..7a6e563f46 100644 --- a/spec/controllers/api_v1/tasks_controller_spec.rb +++ b/spec/controllers/api_v1/tasks_controller_spec.rb @@ -267,6 +267,25 @@ @task_list.tasks(true).length.should == 2 @task_list.tasks(true).last.name.should == 'Another TODO!' end + + it "should create an inbox in the desired project if no task list is specified" do + login_as @user + + post :create, :project_id => @project.permalink, :id => @task_list.id, :name => 'Another TODO!' + response.should be_success + + data = JSON.parse(response.body) + references = data['references'].map{|r| "#{r['id']}_#{r['type']}"} + + task = Task.find_by_id(data['id']) + task.should_not == nil + references.include?("#{@project.id}_Project").should == true + references.include?("#{task.user_id}_User").should == true + + task_list = TaskList.find_by_id(data['task_list_id']) + task_list.name.should == 'Inbox' + task_list.tasks.first.should == task + end it "should not allow observers to create tasks" do login_as @observer From e33d7047c713a62e1e770d33795ac36dd634362f Mon Sep 17 00:00:00 2001 From: Arnau Sanchez Date: Wed, 24 Aug 2011 19:31:46 +0200 Subject: [PATCH 23/26] #458071: redraw calendar box to show it nearer to the link --- app/javascripts/date_picker.js | 1 + 1 file changed, 1 insertion(+) diff --git a/app/javascripts/date_picker.js b/app/javascripts/date_picker.js index 5922a70940..27ee864f97 100644 --- a/app/javascripts/date_picker.js +++ b/app/javascripts/date_picker.js @@ -70,6 +70,7 @@ DatePicker = { } }); } + date_picker.positionCalendarDiv(); } var input_urgent = date_picker.top_div.down("input.urgent") From 5c2206d7d2f92506cbd349223929e0290522e780 Mon Sep 17 00:00:00 2001 From: James Urquhart Date: Wed, 24 Aug 2011 21:34:12 +0100 Subject: [PATCH 24/26] Change name limits for project and organization names --- app/models/organization.rb | 2 +- app/models/project/validation.rb | 5 ++--- spec/models/organization_spec.rb | 32 +++++++++++++++++++++++++++++++- spec/models/project_spec.rb | 25 ++++++++++++++++--------- 4 files changed, 50 insertions(+), 14 deletions(-) diff --git a/app/models/organization.rb b/app/models/organization.rb index e31a5354ff..23d31ed91c 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -13,7 +13,7 @@ class Organization < ActiveRecord::Base has_many :task_list_templates - validates_length_of :name, :minimum => 4 + validates_length_of :name, :minimum => 1 validates_presence_of :permalink validates_uniqueness_of :permalink, :case_sensitive => false, :scope => :deleted diff --git a/app/models/project/validation.rb b/app/models/project/validation.rb index 341eda77e3..fea03a8cb7 100644 --- a/app/models/project/validation.rb +++ b/app/models/project/validation.rb @@ -1,8 +1,7 @@ class Project - validates_length_of :name, :minimum => 5, :on => :create # New projects - validates_length_of :name, :minimum => 5, :on => :update, :if => :name_changed? # Changing the name - validates_length_of :name, :minimum => 3, :on => :update # Legacy validation for existing projects + validates_length_of :name, :minimum => 1, :on => :create + validates_length_of :name, :minimum => 1, :on => :update validates_uniqueness_of :permalink, :case_sensitive => false, :scope => :deleted validates_length_of :permalink, :minimum => 5 validates_format_of :permalink, :with => /^[a-z0-9_\-]{5,}$/, :if => :permalink_length_valid? diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb index 1df0fe100a..1e43662066 100644 --- a/spec/models/organization_spec.rb +++ b/spec/models/organization_spec.rb @@ -7,7 +7,7 @@ it { should have_many(:task_list_templates) } #it { should validate_presence_of(:permalink) } - it { should validate_length_of(:name, :minimum => 4) } + it { should validate_length_of(:name, :minimum => 1) } it { should validate_length_of(:permalink, :minimum => 4) } describe "permalink" do @@ -22,6 +22,36 @@ end end + describe "validating length of name and permalink" do + it "should fail on create if the name is shorter than 1 chars" do + organization = Factory.build(:organization, :name => "") + organization.should be_invalid + organization.should have(1).error_on(:name) + end + + it "should allow existent organizations to have a name at least 1 chars if they don't change it" do + organization = Factory.build(:organization, :name => "a", :permalink => "abcdefg") + organization.save(:validate => false) + organization.should be_valid + end + + it "should not allow permalinks with less than 4 chars" do + organization = Factory.build(:organization, :name => "a", :permalink => "abcdefg") + organization.save(:validate => false) + organization.should be_valid + organization.permalink = "2" + organization.save + (organization.reload.permalink.length >= 4).should == true + end + + it "should fail if the name is updated and shorter than 1 chars" do + organization = Factory.create(:organization, :name => "abc123") + organization.name = "" + organization.should be_invalid + end + + end + describe "domain" do it "should allow multiple organizations with nil domain" do Factory(:organization, :domain => nil) diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index a90da88efc..beee4ef7bb 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -16,7 +16,7 @@ it { should have_many(:activities) } it { should validate_presence_of(:user) } - it { should validate_length_of(:name, :minimum => 3) } + it { should validate_length_of(:name, :minimum => 1) } it { should validate_length_of(:permalink, :minimum => 5) } describe "creating a project" do @@ -36,23 +36,30 @@ @owner = Factory.create(:user) end - it "should fail on create if the name is shorter than 5 chars" do - project = Factory.build(:project, :user => @owner, :name => "abcd") + it "should fail on create if the name is shorter than 1 chars" do + project = Factory.build(:project, :user => @owner, :name => "") project.should be_invalid project.should have(1).error_on(:name) end - it "should allow existent projects to have a name between 3 and 5 chars if they don't change it" do - project = Factory.build(:project, :user => @owner, :name => "abcd", :permalink => "abcdefg") + it "should allow existent projects to have a name at least 1 chars if they don't change it" do + project = Factory.build(:project, :user => @owner, :name => "a", :permalink => "abcdefg") project.save(:validate => false) project.should be_valid - project.permalink = "#{project.permalink}2" + end + + it "should not allow permalinks with less than 5 chars" do + project = Factory.build(:project, :user => @owner, :name => "a", :permalink => "abcdefg") + project.save(:validate => false) project.should be_valid + project.permalink = "2" + project.save + (project.reload.permalink.length >= 5).should == true end - it "should fail if the name is updated and shorter than 5 chars" do - project = Factory.create(:project, :name => "abcde") - project.name = "abc" + it "should fail if the name is updated and shorter than 1 chars" do + project = Factory.create(:project, :name => "abc123") + project.name = "" project.should be_invalid end From 9481a2b2879e59f7197c6b5898541364775fceb9 Mon Sep 17 00:00:00 2001 From: Charles Barbier Date: Thu, 25 Aug 2011 16:11:31 -0400 Subject: [PATCH 25/26] When deleting a project, associated watchable tag should be removed. nil proof the controller, add model association and spec --- app/controllers/watchers_controller.rb | 7 +++++-- app/models/project/associations.rb | 1 + spec/models/watcher_spec.rb | 15 +++++++++++++++ 3 files changed, 21 insertions(+), 2 deletions(-) diff --git a/app/controllers/watchers_controller.rb b/app/controllers/watchers_controller.rb index 1b4da75c09..8801af8118 100644 --- a/app/controllers/watchers_controller.rb +++ b/app/controllers/watchers_controller.rb @@ -2,7 +2,10 @@ class WatchersController < ApplicationController skip_before_filter :load_project def index - @watch_list_by_project = current_user.watchers.includes(:watchable).group_by { |t| t.project.name } + @watch_list_by_project = current_user.watchers.includes(:watchable).reject { |t| + t.project.nil? }.group_by { |t| + t.project.name + } end def unwatch @@ -10,4 +13,4 @@ def unwatch @watch.destroy head :ok end -end \ No newline at end of file +end.nil? \ No newline at end of file diff --git a/app/models/project/associations.rb b/app/models/project/associations.rb index 117123252b..4813cee706 100644 --- a/app/models/project/associations.rb +++ b/app/models/project/associations.rb @@ -12,6 +12,7 @@ class Project delete.has_many :folders delete.has_many :notes delete.has_many :dividers + delete.has_many :watcher_tags, :class_name => 'Watcher' delete.with_options :order => 'id DESC' do |ordered| ordered.has_many :conversations diff --git a/spec/models/watcher_spec.rb b/spec/models/watcher_spec.rb index 91b26c95c5..c2b09837b6 100644 --- a/spec/models/watcher_spec.rb +++ b/spec/models/watcher_spec.rb @@ -2,6 +2,21 @@ describe Watcher do + describe "in projects" do + before do + @user = Factory.create(:confirmed_user) + @project = Factory.create(:project) + end + + it "should destroy the watcher after project deletion" do + Watcher.create(:user => @user, :project => @project) + + lambda { + @project.destroy + }.should change(Watcher, :count).by(-1) + end + end + describe "in conversations" do before do @user1 = Factory.create(:confirmed_user) From b947e3f93c7b32ee5b1d4659e5dd87fbe5ae3856 Mon Sep 17 00:00:00 2001 From: Denis Novikov Date: Sat, 4 Jun 2011 10:22:12 +0200 Subject: [PATCH 26/26] Fixes an issue with no saving user_id into comments table when task is creating with empty body and assigned user. --- app/controllers/tasks_controller.rb | 1 + 1 file changed, 1 insertion(+) diff --git a/app/controllers/tasks_controller.rb b/app/controllers/tasks_controller.rb index 58c3d441a3..d46f34b598 100644 --- a/app/controllers/tasks_controller.rb +++ b/app/controllers/tasks_controller.rb @@ -33,6 +33,7 @@ def create authorize! :make_tasks, @current_project @task = @task_list.tasks.build_by_user(current_user, params[:task]) @task.is_private = (params[:task][:is_private]||false) if params[:task] + @task.updating_user = current_user @task.save respond_to do |f|