mirror of
https://github.com/TracksApp/tracks.git
synced 2025-12-26 12:08:47 +01:00
Merge branch 'master' into new-gui
Conflicts: Gemfile.lock
This commit is contained in:
commit
463f5e922d
37 changed files with 1083 additions and 501 deletions
|
|
@ -10,7 +10,7 @@ before_install:
|
|||
- "cp config/site.yml.tmpl config/site.yml"
|
||||
- "cp config/database.yml.tmpl config/database.yml"
|
||||
|
||||
script: "bundle exec rake ci"
|
||||
script: "CODECLIMATE_REPO_TOKEN=5c52fdd2bbcd0734d56ddb2c3cbaac782da345273e8689d25f54a065ccc3397c bundle exec rake ci RACK_ENV=test"
|
||||
|
||||
bundler_args: --without development
|
||||
notifications:
|
||||
|
|
|
|||
5
Gemfile
5
Gemfile
|
|
@ -67,7 +67,7 @@ group :test do
|
|||
gem "database_cleaner"
|
||||
gem "mocha", :require => false
|
||||
|
||||
gem "aruba", :require => false
|
||||
gem "aruba", git: 'https://github.com/cucumber/aruba', :require => false # need 0.5.4 for piping files; 0.5.3 is latest
|
||||
gem "simplecov"
|
||||
gem "timecop", "~> 0.6.2"
|
||||
|
||||
|
|
@ -81,4 +81,7 @@ group :test do
|
|||
# uncomment to be able to make screenshots from scenarios
|
||||
#gem "capybara-screenshot"
|
||||
#gem "launchy"
|
||||
|
||||
# get test coverage info on codeclimate
|
||||
gem "codeclimate-test-reporter", group: :test, require: nil
|
||||
end
|
||||
|
|
|
|||
94
Gemfile.lock
94
Gemfile.lock
|
|
@ -1,3 +1,12 @@
|
|||
GIT
|
||||
remote: https://github.com/cucumber/aruba
|
||||
revision: adbfc240d69254d7b525876b4c5bff6b721b7d65
|
||||
specs:
|
||||
aruba (0.5.3)
|
||||
childprocess (>= 0.3.6)
|
||||
cucumber (>= 1.1.1)
|
||||
rspec-expectations (>= 2.7.0)
|
||||
|
||||
GIT
|
||||
remote: https://github.com/rails/actionpack-xml_parser
|
||||
revision: 246653ab3670f329176c1e77e6cd1a632466f06e
|
||||
|
|
@ -9,7 +18,11 @@ GEM
|
|||
remote: https://rubygems.org/
|
||||
specs:
|
||||
RedCloth (4.2.9)
|
||||
<<<<<<< HEAD
|
||||
aasm (3.0.20)
|
||||
=======
|
||||
aasm (3.0.22)
|
||||
>>>>>>> master
|
||||
actionmailer (4.0.0)
|
||||
actionpack (= 4.0.0)
|
||||
mail (~> 2.5.3)
|
||||
|
|
@ -37,11 +50,15 @@ GEM
|
|||
acts_as_list (0.3.0)
|
||||
activerecord (>= 3.0)
|
||||
arel (4.0.0)
|
||||
<<<<<<< HEAD
|
||||
aruba (0.5.3)
|
||||
childprocess (>= 0.3.6)
|
||||
cucumber (>= 1.1.1)
|
||||
rspec-expectations (>= 2.7.0)
|
||||
atomic (1.1.12)
|
||||
=======
|
||||
atomic (1.1.14)
|
||||
>>>>>>> master
|
||||
bcrypt-ruby (3.0.1)
|
||||
builder (3.1.4)
|
||||
bullet (4.6.0)
|
||||
|
|
@ -57,6 +74,8 @@ GEM
|
|||
xpath (~> 2.0)
|
||||
childprocess (0.3.9)
|
||||
ffi (~> 1.0, >= 1.0.11)
|
||||
codeclimate-test-reporter (0.0.11)
|
||||
simplecov (>= 0.7.1, < 1.0.0)
|
||||
coffee-rails (4.0.0)
|
||||
coffee-script (>= 2.2.0)
|
||||
railties (>= 4.0.0.beta, < 5.0)
|
||||
|
|
@ -64,61 +83,80 @@ GEM
|
|||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.6.3)
|
||||
<<<<<<< HEAD
|
||||
commonjs (0.2.6)
|
||||
cucumber (1.3.6)
|
||||
=======
|
||||
cucumber (1.3.8)
|
||||
>>>>>>> master
|
||||
builder (>= 2.1.2)
|
||||
diff-lcs (>= 1.1.3)
|
||||
gherkin (~> 2.12.0)
|
||||
multi_json (~> 1.7.5)
|
||||
gherkin (~> 2.12.1)
|
||||
multi_json (>= 1.7.5, < 2.0)
|
||||
multi_test (>= 0.0.2)
|
||||
cucumber-rails (1.3.0)
|
||||
cucumber-rails (1.4.0)
|
||||
capybara (>= 1.1.2)
|
||||
cucumber (>= 1.1.8)
|
||||
cucumber (>= 1.2.0)
|
||||
nokogiri (>= 1.5.0)
|
||||
<<<<<<< HEAD
|
||||
=======
|
||||
rails (>= 3.0.0)
|
||||
>>>>>>> master
|
||||
database_cleaner (1.1.1)
|
||||
diff-lcs (1.2.4)
|
||||
erubis (2.7.0)
|
||||
execjs (1.4.0)
|
||||
multi_json (~> 1.0)
|
||||
execjs (2.0.1)
|
||||
factory_girl (4.2.0)
|
||||
activesupport (>= 3.0.0)
|
||||
factory_girl_rails (4.2.1)
|
||||
factory_girl (~> 4.2.0)
|
||||
railties (>= 3.0.0)
|
||||
ffi (1.9.0)
|
||||
gherkin (2.12.0)
|
||||
gherkin (2.12.1)
|
||||
multi_json (~> 1.3)
|
||||
hike (1.2.3)
|
||||
htmlentities (4.3.1)
|
||||
i18n (0.6.4)
|
||||
i18n (0.6.5)
|
||||
jquery-rails (3.0.4)
|
||||
railties (>= 3.0, < 5.0)
|
||||
thor (>= 0.14, < 2.0)
|
||||
json (1.8.0)
|
||||
<<<<<<< HEAD
|
||||
less (2.3.2)
|
||||
commonjs (~> 0.2.6)
|
||||
less-rails (2.3.3)
|
||||
actionpack (>= 3.1)
|
||||
less (~> 2.3.1)
|
||||
libv8 (3.11.8.17)
|
||||
=======
|
||||
libv8 (3.16.14.3)
|
||||
>>>>>>> master
|
||||
mail (2.5.4)
|
||||
mime-types (~> 1.16)
|
||||
treetop (~> 1.4.8)
|
||||
metaclass (0.0.1)
|
||||
mime-types (1.23)
|
||||
mime-types (1.25)
|
||||
mini_portile (0.5.1)
|
||||
minitest (4.7.5)
|
||||
mocha (0.14.0)
|
||||
metaclass (~> 0.0.1)
|
||||
<<<<<<< HEAD
|
||||
mousetrap-rails (0.0.11)
|
||||
multi_json (1.7.8)
|
||||
=======
|
||||
multi_json (1.8.0)
|
||||
>>>>>>> master
|
||||
multi_test (0.0.2)
|
||||
mysql2 (0.3.13)
|
||||
nokogiri (1.6.0)
|
||||
mini_portile (~> 0.5.0)
|
||||
polyglot (0.3.3)
|
||||
rack (1.5.2)
|
||||
<<<<<<< HEAD
|
||||
rack-mini-profiler (0.1.28)
|
||||
=======
|
||||
rack-mini-profiler (0.1.31)
|
||||
>>>>>>> master
|
||||
rack (>= 1.1.3)
|
||||
rack-test (0.6.2)
|
||||
rack (>= 1.0)
|
||||
|
|
@ -130,7 +168,7 @@ GEM
|
|||
bundler (>= 1.3.0, < 2.0)
|
||||
railties (= 4.0.0)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
rails_autolink (1.1.0)
|
||||
rails_autolink (1.1.3)
|
||||
rails (> 3.1)
|
||||
railties (4.0.0)
|
||||
actionpack (= 4.0.0)
|
||||
|
|
@ -139,10 +177,17 @@ GEM
|
|||
thor (>= 0.18.1, < 2.0)
|
||||
rake (10.1.0)
|
||||
ref (1.0.5)
|
||||
<<<<<<< HEAD
|
||||
rspec-expectations (2.14.1)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rubyzip (0.9.9)
|
||||
safe_yaml (0.9.5)
|
||||
=======
|
||||
rspec-expectations (2.14.2)
|
||||
diff-lcs (>= 1.1.3, < 2.0)
|
||||
rubyzip (0.9.9)
|
||||
safe_yaml (0.9.7)
|
||||
>>>>>>> master
|
||||
sanitize (2.0.6)
|
||||
nokogiri (>= 1.4.4)
|
||||
sass (3.2.10)
|
||||
|
|
@ -150,10 +195,14 @@ GEM
|
|||
railties (>= 4.0.0.beta, < 5.0)
|
||||
sass (>= 3.1.10)
|
||||
sprockets-rails (~> 2.0.0)
|
||||
<<<<<<< HEAD
|
||||
selenium-webdriver (2.34.0)
|
||||
=======
|
||||
selenium-webdriver (2.35.1)
|
||||
>>>>>>> master
|
||||
childprocess (>= 0.2.5)
|
||||
multi_json (~> 1.0)
|
||||
rubyzip
|
||||
rubyzip (< 1.0.0)
|
||||
websocket (~> 1.0.4)
|
||||
simplecov (0.7.1)
|
||||
multi_json (~> 1.0)
|
||||
|
|
@ -168,22 +217,22 @@ GEM
|
|||
actionpack (>= 3.0)
|
||||
activesupport (>= 3.0)
|
||||
sprockets (~> 2.8)
|
||||
sqlite3 (1.3.7)
|
||||
sqlite3 (1.3.8)
|
||||
swf_fu (2.0.4)
|
||||
coffee-script
|
||||
rails (>= 3.1)
|
||||
therubyracer (0.11.4)
|
||||
libv8 (~> 3.11.8.12)
|
||||
therubyracer (0.12.0)
|
||||
libv8 (~> 3.16.14.0)
|
||||
ref
|
||||
thor (0.18.1)
|
||||
thread_safe (0.1.2)
|
||||
thread_safe (0.1.3)
|
||||
atomic
|
||||
tilt (1.4.1)
|
||||
timecop (0.6.3)
|
||||
tolk (1.3.11)
|
||||
safe_yaml (~> 0.8)
|
||||
will_paginate
|
||||
treetop (1.4.14)
|
||||
treetop (1.4.15)
|
||||
polyglot
|
||||
polyglot (>= 0.3.1)
|
||||
turbolinks (1.3.0)
|
||||
|
|
@ -194,17 +243,21 @@ GEM
|
|||
rails (>= 3.1)
|
||||
railties (>= 3.1)
|
||||
tzinfo (0.3.37)
|
||||
uglifier (2.1.2)
|
||||
uglifier (2.2.1)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (~> 1.0, >= 1.0.2)
|
||||
uniform_notifier (1.2.0)
|
||||
uniform_notifier (1.3.0)
|
||||
websocket (1.0.7)
|
||||
<<<<<<< HEAD
|
||||
will_paginate (3.0.4)
|
||||
will_paginate-bootstrap (0.2.3)
|
||||
will_paginate (>= 3.0.3)
|
||||
=======
|
||||
will_paginate (3.0.5)
|
||||
>>>>>>> master
|
||||
xpath (2.0.0)
|
||||
nokogiri (~> 1.3)
|
||||
yard (0.8.7)
|
||||
yard (0.8.7.2)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
|
|
@ -214,11 +267,12 @@ DEPENDENCIES
|
|||
aasm
|
||||
actionpack-xml_parser!
|
||||
acts_as_list
|
||||
aruba
|
||||
aruba!
|
||||
bcrypt-ruby (~> 3.0.0)
|
||||
bullet
|
||||
cache_digests
|
||||
capybara
|
||||
codeclimate-test-reporter
|
||||
coffee-rails (~> 4.0.0)
|
||||
cucumber-rails
|
||||
database_cleaner
|
||||
|
|
|
|||
|
|
@ -16,16 +16,29 @@ class DataController < ApplicationController
|
|||
redirect_to :back
|
||||
else
|
||||
@import_to = params[:import_to]
|
||||
|
||||
#get column headers and formart as [['name', column_number]...]
|
||||
i = -1
|
||||
@headers = import_headers(params[:file].path).collect { |v| [v, i+=1] }
|
||||
@headers.unshift ['',i]
|
||||
|
||||
begin
|
||||
#get column headers and format as [['name', column_number]...]
|
||||
i = -1
|
||||
@headers = import_headers(params[:file].path).collect { |v| [v, i+=1] }
|
||||
@headers.unshift ['',i]
|
||||
rescue Exception => e
|
||||
flash[:error] = "Invalid CVS: could not read headers: #{e}"
|
||||
redirect_to :back
|
||||
return
|
||||
end
|
||||
|
||||
#save file for later
|
||||
directory = "public/uploads/csv"
|
||||
@path = File.join(directory, params[:file].original_filename)
|
||||
File.open(@path, "wb") { |f| f.write(params[:file].read) }
|
||||
begin
|
||||
uploaded_file = params[:file]
|
||||
@filename = Tracks::Utils.sanitize_filename(uploaded_file.original_filename)
|
||||
path_and_file = Rails.root.join('public', 'uploads', 'csv', @filename)
|
||||
File.open(path_and_file, "wb") { |f| f.write(uploaded_file.read) }
|
||||
rescue Exception => e
|
||||
flash[:error] = "Could not save uploaded CSV (#{path_and_file}). Can Tracks write to the upload directory? #{e}"
|
||||
redirect_to :back
|
||||
return
|
||||
end
|
||||
|
||||
case @import_to
|
||||
when 'projects'
|
||||
|
|
@ -44,12 +57,14 @@ class DataController < ApplicationController
|
|||
|
||||
def csv_import
|
||||
begin
|
||||
filename = Tracks::Utils.sanitize_filename(params[:file])
|
||||
path_and_file = Rails.root.join('public', 'uploads', 'csv', filename)
|
||||
case params[:import_to]
|
||||
when 'projects'
|
||||
count = Project.import params, current_user
|
||||
count = Project.import path_and_file, params, current_user
|
||||
flash[:notice] = "#{count} Projects imported"
|
||||
when 'todos'
|
||||
count = Todo.import params, current_user
|
||||
count = Todo.import path_and_file, params, current_user
|
||||
flash[:notice] = "#{count} Todos imported"
|
||||
else
|
||||
flash[:error] = t('data.invalid_import_destination')
|
||||
|
|
@ -57,11 +72,11 @@ class DataController < ApplicationController
|
|||
rescue Exception => e
|
||||
flash[:error] = t('data.invalid_import_destination') + ": #{e}"
|
||||
end
|
||||
File.delete(params[:file])
|
||||
File.delete(path_and_file)
|
||||
redirect_to import_data_path
|
||||
end
|
||||
|
||||
def import_headers file
|
||||
def import_headers(file)
|
||||
CSV.foreach(file, headers: false) do |row|
|
||||
return row
|
||||
end
|
||||
|
|
@ -105,13 +120,14 @@ class DataController < ApplicationController
|
|||
send_data(result, :filename => "tracks_backup.yml", :type => 'text/plain')
|
||||
end
|
||||
|
||||
# export all actions as csv
|
||||
def csv_actions
|
||||
content_type = 'text/csv'
|
||||
CSV::Writer.generate(result = "") do |csv|
|
||||
CSV.generate(result = "") do |csv|
|
||||
csv << ["id", "Context", "Project", "Description", "Notes", "Tags",
|
||||
"Created at", "Due", "Completed at", "User ID", "Show from",
|
||||
"state"]
|
||||
current_user.todos.include(:context, :project).all.each do |todo|
|
||||
current_user.todos.includes(:context, :project, :taggings, :tags).each do |todo|
|
||||
csv << [todo.id, todo.context.name,
|
||||
todo.project_id.nil? ? "" : todo.project.name,
|
||||
todo.description,
|
||||
|
|
@ -127,7 +143,7 @@ class DataController < ApplicationController
|
|||
send_data(result, :filename => "todos.csv", :type => content_type)
|
||||
end
|
||||
|
||||
|
||||
# export all notes as csv
|
||||
def csv_notes
|
||||
content_type = 'text/csv'
|
||||
CSV.generate(result = "") do |csv|
|
||||
|
|
|
|||
38
app/controllers/mailgun_controller.rb
Normal file
38
app/controllers/mailgun_controller.rb
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
require 'openssl'
|
||||
|
||||
class MailgunController < ApplicationController
|
||||
|
||||
skip_before_filter :login_required, :only => [:mailgun]
|
||||
before_filter :verify, :only => [:mailgun]
|
||||
protect_from_forgery with: :null_session
|
||||
|
||||
def mailgun
|
||||
unless params.include? 'body-mime'
|
||||
Rails.logger.info "Cannot process Mailgun request, no body-mime sent"
|
||||
render_failure "Unacceptable body-mime", 406
|
||||
return
|
||||
end
|
||||
|
||||
todo = MessageGateway.receive(params['body-mime'])
|
||||
if todo
|
||||
render :xml => todo.to_xml( *todo_xml_params )
|
||||
else
|
||||
render_failure "Todo not saved", 406
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def verify
|
||||
unless params['signature'] == OpenSSL::HMAC.hexdigest(
|
||||
OpenSSL::Digest::Digest.new('sha256'),
|
||||
SITE_CONFIG['mailgun_api_key'],
|
||||
'%s%s' % [params['timestamp'], params['token']]
|
||||
)
|
||||
Rails.logger.info "Cannot verify Mailgun signature"
|
||||
render_failure "Access denied", 406
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -137,8 +137,8 @@ class ProjectsController < ApplicationController
|
|||
limit(current_user.prefs.show_number_completed).
|
||||
includes(Todo::DEFAULT_INCLUDES) unless @max_completed == 0
|
||||
|
||||
@count = @not_done_todos.size
|
||||
@down_count = @count + @deferred_todos.size + @pending_todos.size
|
||||
@down_count = @not_done_todos.size + @deferred_todos.size + @pending_todos.size
|
||||
@count=@down_count
|
||||
@next_project = current_user.projects.next_from(@project)
|
||||
@previous_project = current_user.projects.previous_from(@project)
|
||||
@default_tags = @project.default_tags
|
||||
|
|
|
|||
|
|
@ -87,7 +87,8 @@ class TodosController < ApplicationController
|
|||
p.parse_dates() unless mobile?
|
||||
tag_list = p.tag_list
|
||||
|
||||
@todo = current_user.todos.build(p.attributes)
|
||||
@todo = current_user.todos.build
|
||||
@todo.assign_attributes(p.attributes)
|
||||
p.add_errors(@todo)
|
||||
|
||||
if @todo.errors.empty?
|
||||
|
|
@ -125,6 +126,8 @@ class TodosController < ApplicationController
|
|||
determine_down_count
|
||||
@contexts = current_user.contexts
|
||||
@projects = current_user.projects
|
||||
@context = @todo.context
|
||||
@project = @todo.project
|
||||
@initial_context_name = params['default_context_name']
|
||||
@initial_project_name = params['default_project_name']
|
||||
@initial_tags = params['initial_tag_list']
|
||||
|
|
@ -1170,18 +1173,18 @@ end
|
|||
def update_context
|
||||
@context_changed = false
|
||||
if params['todo']['context_id'].blank? && params['context_name'].present?
|
||||
context = current_user.contexts.where(:name => params['context_name'].strip).first
|
||||
unless context
|
||||
@context = current_user.contexts.where(:name => params['context_name'].strip).first
|
||||
if @context.nil?
|
||||
@new_context = current_user.contexts.build
|
||||
@new_context.name = params['context_name'].strip
|
||||
@new_context.save
|
||||
@new_context_created = true
|
||||
@new_container = @new_context
|
||||
@not_done_todos = [@todo]
|
||||
context = @new_context
|
||||
@context = @new_context
|
||||
end
|
||||
params["todo"]["context_id"] = context.id
|
||||
@context_changed = @original_item_context_id != params["todo"]["context_id"] = context.id
|
||||
params["todo"]["context_id"] = @context.id
|
||||
@context_changed = @original_item_context_id != params["todo"]["context_id"] = @context.id
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
|||
|
|
@ -565,6 +565,13 @@ module TodosHelper
|
|||
return false
|
||||
end
|
||||
|
||||
def should_show_empty_container
|
||||
source_view do |page|
|
||||
page.context { return @remaining_in_context == 0 }
|
||||
end
|
||||
return @down_count==0
|
||||
end
|
||||
|
||||
def project_container_id(todo)
|
||||
return "p#{todo.project_id}" unless todo.project.nil?
|
||||
return "without_project_container"
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ class MessageGateway < ActionMailer::Base
|
|||
todo = todo_builder.construct
|
||||
todo.save!
|
||||
Rails.logger.info "Saved email as todo for user #{user.login} in context #{context.name}"
|
||||
todo
|
||||
end
|
||||
|
||||
private
|
||||
|
|
@ -49,10 +50,26 @@ class MessageGateway < ActionMailer::Base
|
|||
if user.nil?
|
||||
user = User.where("preferences.sms_email" => address.strip[1.100]).includes(:preference).first
|
||||
end
|
||||
if user.present? and !sender_is_in_mailmap?(user,email)
|
||||
Rails.logger.warn "#{email.from[0]} not found in mailmap for #{user.login}"
|
||||
return nil
|
||||
end
|
||||
Rails.logger.info(!user.nil? ? "Email belongs to #{user.login}" : "User unknown")
|
||||
return user
|
||||
end
|
||||
|
||||
|
||||
def sender_is_in_mailmap?(user,email)
|
||||
if SITE_CONFIG['mailmap'].is_a? Hash and SITE_CONFIG['email_dispatch'] == 'to'
|
||||
# Look for the sender in the map of allowed senders
|
||||
SITE_CONFIG['mailmap'][user.preference.sms_email].include? email.from[0]
|
||||
else
|
||||
# We can't check the map if it's not defined, or if the lookup is the
|
||||
# wrong way round, so just allow it
|
||||
true
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
def get_user_from_email_address(email)
|
||||
SITE_CONFIG['email_dispatch'] == 'single_user' ? get_user_from_env_setting : get_user_from_mail_header(email)
|
||||
end
|
||||
|
|
|
|||
|
|
@ -138,9 +138,9 @@ class Project < ActiveRecord::Base
|
|||
@age_in_days ||= ((Time.now.utc - created_at).to_i / 1.day) + 1
|
||||
end
|
||||
|
||||
def self.import(params, user)
|
||||
def self.import(filename, params, user)
|
||||
count = 0
|
||||
CSV.foreach(params[:file], headers: true) do |row|
|
||||
CSV.foreach(filename, headers: true) do |row|
|
||||
unless find_by_name_and_user_id row[params[:name].to_i], user.id
|
||||
project = new
|
||||
project.name = row[params[:name].to_i]
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def daily_every_x_days=(x)
|
||||
every_other1 = x if recurring_period=='daily'
|
||||
self.every_other1 = x if recurring_period=='daily'
|
||||
end
|
||||
|
||||
def daily_every_x_days
|
||||
|
|
@ -489,7 +489,7 @@ class RecurringTodo < ActiveRecord::Base
|
|||
|
||||
case self.recurrence_selector
|
||||
when 0 # specific day of the month
|
||||
if start.mday > day
|
||||
if (previous && start.mday >= day) || (previous.nil? && start.mday > day)
|
||||
# there is no next day n in this month, search in next month
|
||||
#
|
||||
# start += n.months
|
||||
|
|
|
|||
|
|
@ -399,11 +399,11 @@ class Todo < ActiveRecord::Base
|
|||
end
|
||||
end
|
||||
|
||||
def self.import(params, user)
|
||||
default_context = Context.where(:user_id=>user.id).order('id').first
|
||||
def self.import(filename, params, user)
|
||||
default_context = user.contexts.order('id').first
|
||||
|
||||
count = 0
|
||||
CSV.foreach(params[:file], headers: true) do |row|
|
||||
CSV.foreach(filename, headers: true) do |row|
|
||||
unless find_by_description_and_user_id row[params[:description].to_i], user.id
|
||||
todo = new
|
||||
todo.user = user
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@
|
|||
<%= select_tag(l, options_for_select(@headers) ) %>
|
||||
<br>
|
||||
<% end %>
|
||||
<%= hidden_field_tag :file, @path %>
|
||||
<%= hidden_field_tag :file, @filename %>
|
||||
<%= hidden_field_tag :import_to, @import_to %>
|
||||
<%= submit_tag "Import" %>
|
||||
<% end %>
|
||||
|
|
@ -42,7 +42,7 @@ function remove_todo(next_steps) {
|
|||
|
||||
function add_to_existing_container(next_steps) {
|
||||
$('#<%= item_container_id(@todo) %>_items').append(html_for_todo());
|
||||
<% if source_view_is_one_of(:project,:calendar) -%>
|
||||
<% if source_view_is_one_of(:calendar) -%>
|
||||
next_steps.go();
|
||||
<% if (@target_context_count==1) || ( (@todo.deferred? || @todo.pending?) && @remaining_deferred_or_pending_count == 1) -%>
|
||||
$("#<%= empty_container_msg_div_id %>").slideUp(100);
|
||||
|
|
@ -83,7 +83,7 @@ function highlight_updated_todo(next_steps) {
|
|||
}
|
||||
|
||||
function update_empty_container(next_steps) {
|
||||
<% if @down_count==0 -%>
|
||||
<% if should_show_empty_container -%>
|
||||
$('div#no_todos_in_view').slideDown(400, function(){ next_steps.go(); });
|
||||
<% else -%>
|
||||
$('div#no_todos_in_view').fadeOut(100, function(){ next_steps.go(); });
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ Tracksapp::Application.routes.draw do
|
|||
|
||||
root :to => 'todos#index'
|
||||
|
||||
post 'mailgun/mime' => 'mailgun#mailgun'
|
||||
|
||||
post 'login' => 'login#login'
|
||||
get 'login' => 'login#login'
|
||||
get 'login/check_expiry' => 'login#check_expiry'
|
||||
|
|
|
|||
|
|
@ -58,7 +58,20 @@ open_signups: false
|
|||
# (see http://docs.cloudmailin.com/validating_the_sender)
|
||||
# cloudmailin: asdasd
|
||||
|
||||
# Mailgun api key - used to verify incoming HTTP requests from Mailgun.org
|
||||
# mailgun_api_key: key-abcdef1234567890
|
||||
|
||||
# change this to reflect the email address of the admin that you want to show
|
||||
# on the signup page
|
||||
admin_email: my.email@domain.com
|
||||
admin_email: my.email@domain.com
|
||||
|
||||
|
||||
# Map of allowed incoming email addresses to real users
|
||||
# Requires email_dispatch == 'to'
|
||||
# This allows you to specify _who_ can send email Todos to your list
|
||||
# The format is your incoming email (as per preferences page) for the key, and
|
||||
# an array-list of acceptable senders for that account
|
||||
#mailmap:
|
||||
# 'user@preferences.from.value':
|
||||
# - 'acceptable1@personal.org'
|
||||
# - 'acceptable2@work.com'
|
||||
|
|
|
|||
63
doc/tracks_cli/tracks_api.rb
Normal file
63
doc/tracks_cli/tracks_api.rb
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
require 'net/https'
|
||||
require File.expand_path(File.dirname(__FILE__) + '/tracks_xml_builder')
|
||||
|
||||
module TracksCli
|
||||
|
||||
class TracksAPI
|
||||
def initialize(options)
|
||||
@options = options
|
||||
end
|
||||
|
||||
def get_http(uri)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
if uri.scheme == "https" # enable SSL/TLS
|
||||
http.use_ssl = true
|
||||
http.ca_path = "/etc/ssl/certs/" # Debian based path
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.verify_depth = 5
|
||||
end
|
||||
|
||||
http
|
||||
end
|
||||
|
||||
def context_uri_for(context_id)
|
||||
URI.parse(@options[:context_prefix] + context_id.to_s + ".xml")
|
||||
end
|
||||
|
||||
def todo_uri
|
||||
URI.parse(@options[:uri])
|
||||
end
|
||||
|
||||
def project_uri
|
||||
URI.parse(@options[:projects_uri])
|
||||
end
|
||||
|
||||
def post(uri, body)
|
||||
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
|
||||
req.basic_auth @options[:login], @options[:password]
|
||||
req.body = body
|
||||
get_http(uri).request(req)
|
||||
end
|
||||
|
||||
def get(uri)
|
||||
req = Net::HTTP::Get.new(uri.path)
|
||||
req.basic_auth @options[:login], @options[:password]
|
||||
get_http(uri).request(req)
|
||||
end
|
||||
|
||||
def post_todo(todo)
|
||||
post(todo_uri, TracksXmlBuilder.new.build_todo_xml(todo))
|
||||
end
|
||||
|
||||
def post_project(project)
|
||||
post(project_uri, TracksXmlBuilder.new.build_project_xml(project))
|
||||
end
|
||||
|
||||
def get_context(context_id)
|
||||
get(context_uri_for(context_id))
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
66
doc/tracks_cli/tracks_xml_builder.rb
Normal file
66
doc/tracks_cli/tracks_xml_builder.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
require 'active_support/time_with_zone'
|
||||
|
||||
module TracksCli
|
||||
|
||||
class TracksXmlBuilder
|
||||
|
||||
def xml_for_description(description)
|
||||
"<description>#{description}</description>"
|
||||
end
|
||||
|
||||
def xml_for_project_id(project_id)
|
||||
"<project_id>#{project_id}</project_id>"
|
||||
end
|
||||
|
||||
def xml_for_show_from(show_from)
|
||||
show_from.nil? ? "" : "<show-from type=\"datetime\">#{Time.at(show_from).xmlschema}</show-from>"
|
||||
end
|
||||
|
||||
def xml_for_notes(notes)
|
||||
notes.nil? ? "" : "<notes>#{notes}</notes>"
|
||||
end
|
||||
|
||||
def xml_for_taglist(taglist)
|
||||
unless taglist.nil?
|
||||
tags = taglist.split(",")
|
||||
if tags.length() > 0
|
||||
tags = tags.collect { |tag| "<tag><name>#{tag.strip}</name></tag>" unless tag.strip.empty?}.join('')
|
||||
return "<tags>#{tags}</tags>"
|
||||
end
|
||||
else
|
||||
return ""
|
||||
end
|
||||
end
|
||||
|
||||
def xml_for_context(context_name, context_id)
|
||||
if context_name && !context_name.empty?
|
||||
return "<context><name>#{context_name}</name></context>"
|
||||
else
|
||||
return "<context_id>#{context_id}</context_id>"
|
||||
end
|
||||
end
|
||||
|
||||
def xml_for_predecessor(dependend, predecessor)
|
||||
dependend ? "<predecessor_dependencies><predecessor>#{predecessor}</predecessor></predecessor_dependencies>" : ""
|
||||
end
|
||||
|
||||
def build_todo_xml(todo)
|
||||
props = [
|
||||
xml_for_description(todo[:description]),
|
||||
xml_for_project_id(todo[:project_id]),
|
||||
xml_for_show_from(todo[:show_from]),
|
||||
xml_for_notes(todo[:notes]),
|
||||
xml_for_taglist(todo[:taglist]),
|
||||
xml_for_context(todo[:context_name], todo[:context_id]),
|
||||
xml_for_predecessor(todo[:is_dependend], todo[:predecessor])
|
||||
]
|
||||
|
||||
"<todo>#{props.join("")}</todo>"
|
||||
end
|
||||
|
||||
def build_project_xml(project)
|
||||
"<project><name>#{project[:description]}</name><default-context-id>#{project[:default_context_id]}</default-context-id></project>"
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
@ -1,138 +1,109 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
#
|
||||
# Author: Vitalie Lazu <vitalie.lazu@gmail.com>
|
||||
# Date: Sat, 10 Jan 2009 19:12:43 +0200
|
||||
#
|
||||
|
||||
# CLI ruby client for Tracks: rails application for GTD methodology
|
||||
# CLI ruby client for Tracks: rails application for GTD methodology (First author: Vitalie Lazu <vitalie.lazu@gmail.com>)
|
||||
# http://www.getontracks.org/development/
|
||||
# You need to set ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
|
||||
# and set GTD_TODOS_URL to your tracks install. It defaults to 'http://localhost:3000/todos.xml'
|
||||
#
|
||||
# Example:
|
||||
# $ echo "todo 1\ntodo2" | GTD_LOGIN=username GTD_PASSWORD=secret ruby tracks_cli_client.rb -c 123 -p 456
|
||||
# This will post todo 1 and todo 2 to localhost:3000 using username and secret als credentials
|
||||
|
||||
require 'net/https'
|
||||
require 'optparse'
|
||||
require 'cgi'
|
||||
require 'time'
|
||||
require File.expand_path(File.dirname(__FILE__) + '/tracks_cli/tracks_api')
|
||||
|
||||
class Hash
|
||||
def to_query_string
|
||||
map { |k, v|
|
||||
if v.instance_of?(Hash)
|
||||
v.map { |sk, sv|
|
||||
"#{k}[#{sk}]=#{sv}"
|
||||
}.join('&')
|
||||
else
|
||||
"#{k}=#{v}"
|
||||
end
|
||||
}.join('&')
|
||||
class PostLineAsTodo
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
@tracks = TracksCli::TracksAPI.new(
|
||||
uri: ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml',
|
||||
login: ENV['GTD_LOGIN'],
|
||||
password: ENV['GTD_PASSWORD'])
|
||||
@context_id = options[:context_id] ? options[:context_id].to_i : 1
|
||||
@project_id = options[:project_id] ? options[:project_id].to_i : 1
|
||||
end
|
||||
|
||||
def post(lines)
|
||||
lines.each_line do |l|
|
||||
l.chomp!
|
||||
next if l.strip.empty?
|
||||
|
||||
resp = @tracks.post_todo(
|
||||
description: CGI.escapeHTML(l),
|
||||
context_id: @context_id,
|
||||
project_id: @project_id,
|
||||
show_from: @options[:show_from])
|
||||
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location']
|
||||
else
|
||||
p resp.body
|
||||
raise Error
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module Gtd
|
||||
class API
|
||||
GTD_URI = ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml'
|
||||
class Error < StandardError; end
|
||||
class InvalidParser < StandardError; end
|
||||
|
||||
def post(lines, options = {})
|
||||
uri = URI.parse(GTD_URI)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
class ConsoleOptions
|
||||
attr_reader :parser, :options
|
||||
|
||||
if uri.scheme == "https" # enable SSL/TLS
|
||||
http.use_ssl = true
|
||||
http.ca_path = "/etc/ssl/certs/" # Debian based path
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.verify_depth = 5
|
||||
def initialize
|
||||
@options = {}
|
||||
|
||||
@parser = OptionParser.new do |cmd|
|
||||
cmd.banner = "Ruby Gtd CLI - takes todos input from STDIN"
|
||||
|
||||
cmd.separator ''
|
||||
|
||||
cmd.on('-h', '--help', 'Displays this help message') do
|
||||
puts @parser
|
||||
exit
|
||||
end
|
||||
|
||||
lines.each_line do |l|
|
||||
l.chomp!
|
||||
next if l.strip.empty?
|
||||
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
|
||||
@options[:project_id] = v
|
||||
end
|
||||
|
||||
description = CGI.escapeHTML(l)
|
||||
context_id = options[:context_id] ? options[:context_id].to_i : 1
|
||||
project_id = options[:project_id] ? options[:project_id].to_i : 1
|
||||
props = "<description>#{description}</description><project_id>#{project_id}</project_id><context_id>#{context_id}</context_id>"
|
||||
cmd.on('-c [N]', Integer, 'context id to set') do |v|
|
||||
@options[:context_id] = v
|
||||
end
|
||||
|
||||
if options[:show_from]
|
||||
props << "<show-from type=\"datetime\">#{Time.at(options[:show_from]).xmlschema}</show-from>"
|
||||
end
|
||||
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
|
||||
end
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
|
||||
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
|
||||
req.body = "<todo>#{props}</todo>"
|
||||
|
||||
resp = http.request(req)
|
||||
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location']
|
||||
else
|
||||
p resp.body
|
||||
raise Gtd::Error
|
||||
end
|
||||
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Error < StandardError; end
|
||||
class InvalidParser < StandardError; end
|
||||
def run(args)
|
||||
@parser.parse!(args)
|
||||
lines = STDIN.read
|
||||
|
||||
class ConsoleOptions
|
||||
attr_reader :parser, :options
|
||||
|
||||
def initialize
|
||||
@options = {}
|
||||
|
||||
@parser = OptionParser.new do |cmd|
|
||||
cmd.banner = "Ruby Gtd CLI - takes todos input from STDIN"
|
||||
|
||||
cmd.separator ''
|
||||
|
||||
cmd.on('-h', '--help', 'Displays this help message') do
|
||||
puts @parser
|
||||
exit
|
||||
end
|
||||
|
||||
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
|
||||
@options[:project_id] = v
|
||||
end
|
||||
|
||||
cmd.on('-c [N]', Integer, 'context id to set') do |v|
|
||||
@options[:context_id] = v
|
||||
end
|
||||
|
||||
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
|
||||
end
|
||||
|
||||
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run(args)
|
||||
@parser.parse!(args)
|
||||
lines = STDIN.read
|
||||
|
||||
if lines.strip.empty?
|
||||
puts "Please pipe in some content to tracks on STDIN."
|
||||
exit 1
|
||||
end
|
||||
|
||||
gtd = API.new
|
||||
gtd.post(lines, @options)
|
||||
exit 0
|
||||
rescue InvalidParser
|
||||
puts "Please specify a valid format parser."
|
||||
exit 1
|
||||
rescue Error
|
||||
puts "An unknown error occurred"
|
||||
if lines.strip.empty?
|
||||
puts "Please pipe in some content to tracks on STDIN."
|
||||
exit 1
|
||||
end
|
||||
|
||||
PostLineAsTodo.new(@options).post(lines)
|
||||
exit 0
|
||||
rescue InvalidParser
|
||||
puts "Please specify a valid format parser."
|
||||
exit 1
|
||||
rescue Error
|
||||
puts "An unknown error occurred"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
app = Gtd::ConsoleOptions.new
|
||||
app.run(ARGV)
|
||||
ConsoleOptions.new.run(ARGV)
|
||||
end
|
||||
|
|
|
|||
486
doc/tracks_template_cli.rb
Executable file → Normal file
486
doc/tracks_template_cli.rb
Executable file → Normal file
|
|
@ -1,7 +1,5 @@
|
|||
#!/usr/bin/env ruby
|
||||
|
||||
# Version 0.4 (Dec 17, 2011)
|
||||
|
||||
#
|
||||
# Based on the tracks_cli by Vitalie Lazu (https://gist.github.com/45537)
|
||||
#
|
||||
|
|
@ -67,313 +65,247 @@
|
|||
# .Set a reminder to check for reimbursement for [location]
|
||||
# .Mail folder to secretary
|
||||
|
||||
# Instantiate this template: ./tracks_template_cli -c 1 -f template_file.txt
|
||||
# Instantiate this template: ruby tracks_template_cli -c 1 -f template_file.txt
|
||||
|
||||
require 'net/https'
|
||||
require 'optparse'
|
||||
require 'cgi'
|
||||
require 'time'
|
||||
require 'readline'
|
||||
require File.expand_path(File.dirname(__FILE__) + '/tracks_cli/tracks_api')
|
||||
|
||||
class Hash
|
||||
def to_query_string
|
||||
map { |k, v|
|
||||
if v.instance_of?(Hash)
|
||||
v.map { |sk, sv|
|
||||
"#{k}[#{sk}]=#{sv}"
|
||||
}.join('&')
|
||||
else
|
||||
"#{k}=#{v}"
|
||||
end
|
||||
}.join('&')
|
||||
class TemplateParser
|
||||
|
||||
def initialize
|
||||
@keywords = {}
|
||||
end
|
||||
|
||||
def parse_keyword(token)
|
||||
print "Input required for "+token+": "
|
||||
@keywords[token]=gets.chomp
|
||||
end
|
||||
|
||||
def replace_tokens_in(line)
|
||||
@keywords.each{ |key,val| line=line.sub(key,val) }
|
||||
line
|
||||
end
|
||||
|
||||
def parse_todo(line)
|
||||
options = {}
|
||||
|
||||
# first char is . or ^ the latter meaning this todo is dependent on the previous one
|
||||
options[:depend]= line[0].chr == "^" ? true : false;
|
||||
line = line[1..line.length] # remove first char
|
||||
|
||||
tmp= line.split("|")
|
||||
if tmp.length > 5
|
||||
puts "Formatting error: found too many |"
|
||||
exit 1
|
||||
end
|
||||
|
||||
tmp[0].chomp!
|
||||
options[:description]=tmp[0]
|
||||
|
||||
tmp.each_with_index do |t,idx|
|
||||
t=t.strip.chomp
|
||||
t=nil if t.empty?
|
||||
tmp[idx]=t
|
||||
end
|
||||
|
||||
options[:context]=tmp[1]
|
||||
options[:taglist]=tmp[2]
|
||||
options[:notes] =tmp[3]
|
||||
options
|
||||
end
|
||||
|
||||
def parse(file, poster)
|
||||
while line = file.gets
|
||||
line = line.strip
|
||||
|
||||
# skip line if empty or comment
|
||||
next if (line.empty? || line[0].chr == "#")
|
||||
|
||||
# check if line defines a token
|
||||
if (line.split(' ')[0] == "token")
|
||||
parse_keyword line.split(' ')[1]
|
||||
next
|
||||
end
|
||||
|
||||
# replace defined tokes in current line
|
||||
line = replace_tokens_in line
|
||||
|
||||
# line is either todo/dependency or project
|
||||
if (line[0].chr == "." ) || (line[0].chr == "^")
|
||||
if @last_project_id.nil?
|
||||
puts "Warning: no project specified for task \"#{line}\". Using default project."
|
||||
end
|
||||
poster.postTodo(parse_todo(line), @last_project_id)
|
||||
else
|
||||
@last_project_id = poster.postProject(line)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
module Gtd
|
||||
class API
|
||||
GTD_URI_TODOS = ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml'
|
||||
GTD_URI_PROJECTS = ENV['GTD_PROJECTS_URL'] || 'http://localhost:3000/projects.xml'
|
||||
GTD_URI_CONTEXTS_PREFIX = ENV['GTD_CONTEXT_URL_PREFIX'] || 'http://localhost:3000/contexts/'
|
||||
GTD_URI_CONTEXTS = ENV['GTD_CONTEXT_URL'] || 'http://localhost:3000/contexts.xml'
|
||||
|
||||
def postTodo(l, options = {})
|
||||
uri = URI.parse(GTD_URI_TODOS)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
if uri.scheme == "https" # enable SSL/TLS
|
||||
http.use_ssl = true
|
||||
http.ca_path = "/etc/ssl/certs/" # Debian based path
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.verify_depth = 5
|
||||
end
|
||||
|
||||
l.chomp!
|
||||
|
||||
description = CGI.escapeHTML(l)
|
||||
context_id = options[:context_id] ? options[:context_id].to_i : 1
|
||||
project_id = options[:project_id] ? options[:project_id].to_i : 1
|
||||
props = "<description>#{description}</description><project_id>#{project_id}</project_id>"
|
||||
|
||||
if options[:show_from]
|
||||
props << "<show-from type=\"datetime\">#{Time.at(options[:show_from]).xmlschema}</show-from>"
|
||||
end
|
||||
|
||||
if options[:note]
|
||||
props << "<notes>#{options[:note]}</notes>"
|
||||
end
|
||||
|
||||
if options[:taglist]
|
||||
tags = options[:taglist].split(",")
|
||||
if tags.length() > 0
|
||||
tags = tags.collect { |tag| "<tag><name>#{tag.strip}</name></tag>" unless tag.strip.empty?}.join('')
|
||||
props << "<tags>#{tags}</tags>"
|
||||
end
|
||||
end
|
||||
|
||||
if not (options[:context].nil? || options[:context].empty?)
|
||||
props << "<context><name>#{options[:context]}</name></context>"
|
||||
else
|
||||
## use the default context
|
||||
props << "<context_id>#{context_id}</context_id>"
|
||||
end
|
||||
|
||||
if options[:depend]
|
||||
props << "<predecessor_dependencies><predecessor>#{options[:last_todo_id]}</predecessor></predecessor_dependencies>"
|
||||
end
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
|
||||
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
|
||||
req.body = "<todo>#{props}</todo>"
|
||||
|
||||
puts req.body if options[:verbose]
|
||||
|
||||
resp = http.request(req)
|
||||
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location'] if options[:verbose]
|
||||
|
||||
# return the todo id
|
||||
return resp['location'].split("/").last
|
||||
else
|
||||
p resp.body
|
||||
raise Gtd::Error
|
||||
end
|
||||
end
|
||||
|
||||
def postProject(l, options = {})
|
||||
uri = URI.parse(GTD_URI_PROJECTS)
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
if uri.scheme == "https" # enable SSL/TLS
|
||||
http.use_ssl = true
|
||||
http.ca_path = "/etc/ssl/certs/" # Debian based path
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.verify_depth = 5
|
||||
end
|
||||
|
||||
l.chomp!
|
||||
|
||||
description = CGI.escapeHTML(l)
|
||||
props = "<name>#{l}</name><default-context-id>#{options[:context_id]}</default-context-id>"
|
||||
|
||||
req = Net::HTTP::Post.new(uri.path, "Content-Type" => "text/xml")
|
||||
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
|
||||
req.body = "<project>#{props}</project>"
|
||||
|
||||
resp = http.request(req)
|
||||
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location'] if options[:verbose]
|
||||
|
||||
# return the project id
|
||||
return resp['location'].split("/").last
|
||||
else
|
||||
p resp.body
|
||||
raise Gtd::Error
|
||||
end
|
||||
end
|
||||
|
||||
def queryContext(contextID)
|
||||
return false unless contextID.is_a? Integer
|
||||
|
||||
uri = URI.parse(GTD_URI_CONTEXTS_PREFIX + contextID.to_s + ".xml")
|
||||
http = Net::HTTP.new(uri.host, uri.port)
|
||||
|
||||
if uri.scheme == "https" # enable SSL/TLS
|
||||
http.use_ssl = true
|
||||
http.ca_path = "/etc/ssl/certs/" # Debian based path
|
||||
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
|
||||
http.verify_depth = 5
|
||||
end
|
||||
|
||||
req = Net::HTTP::Get.new(uri.path)
|
||||
req.basic_auth ENV['GTD_LOGIN'], ENV['GTD_PASSWORD']
|
||||
resp = http.request(req)
|
||||
|
||||
case resp
|
||||
when Net::HTTPSuccess
|
||||
return true
|
||||
else
|
||||
return false
|
||||
end
|
||||
end
|
||||
class TemplatePoster
|
||||
|
||||
def initialize(options)
|
||||
@options = options
|
||||
@tracks = TracksCli::TracksAPI.new({
|
||||
uri: ENV['GTD_TODOS_URL'] || 'http://localhost:3000/todos.xml',
|
||||
login: ENV['GTD_LOGIN'],
|
||||
password: ENV['GTD_PASSWORD'],
|
||||
projects_uri: ENV['GTD_PROJECTS_URL'] || 'http://localhost:3000/projects.xml',
|
||||
contexts_uri: ENV['GTD_CONTEXT_URL'] || 'http://localhost:3000/contexts.xml',
|
||||
context_prefix: ENV['GTD_CONTEXT_URL_PREFIX'] || 'http://localhost:3000/contexts/'})
|
||||
@context_id = options[:context_id] ? options[:context_id].to_i : 1
|
||||
@project_id = options[:project_id] ? options[:project_id].to_i : 1
|
||||
end
|
||||
|
||||
def postTodo(parsed_todo, project_id)
|
||||
resp = @tracks.post_todo(
|
||||
description: CGI.escapeHTML(parsed_todo[:description]),
|
||||
context_name: parsed_todo[:context],
|
||||
context_id: @context_id,
|
||||
project_id: project_id || @project_id,
|
||||
show_from: parsed_todo[:show_from],
|
||||
notes: parsed_todo[:notes],
|
||||
is_dependend: parsed_todo[:depend],
|
||||
predecessor: @last_posted_todo_id)
|
||||
|
||||
class Error < StandardError; end
|
||||
class InvalidParser < StandardError; end
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location'] if @options[:verbose]
|
||||
|
||||
# return the todo id
|
||||
@last_posted_todo_id = resp['location'].split("/").last
|
||||
return @last_posted_todo_id
|
||||
else
|
||||
p resp.body
|
||||
raise Error
|
||||
end
|
||||
end
|
||||
|
||||
class ConsoleOptions
|
||||
attr_reader :parser, :options, :keywords
|
||||
def postProject(project_description)
|
||||
project_description.chomp!
|
||||
|
||||
def initialize
|
||||
@options = {}
|
||||
@keywords = {}
|
||||
resp = @tracks.post_project(
|
||||
description: CGI.escapeHTML(project_description),
|
||||
default_context_id: @context_id)
|
||||
|
||||
@parser = OptionParser.new do |cmd|
|
||||
cmd.banner = "Ruby Gtd Templates CLI"
|
||||
if resp.code == '302' || resp.code == '201'
|
||||
puts resp['location'] if @options[:verbose]
|
||||
|
||||
cmd.separator ''
|
||||
# return the project id
|
||||
return resp['location'].split("/").last
|
||||
else
|
||||
p resp.body
|
||||
raise Error
|
||||
end
|
||||
end
|
||||
|
||||
cmd.on('-h', '--help', 'Displays this help message') do
|
||||
puts @parser
|
||||
exit
|
||||
end
|
||||
def queryContext(context_id)
|
||||
return false unless context_id.is_a? Integer
|
||||
|
||||
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
|
||||
@options[:project_id] = v
|
||||
end
|
||||
resp = @tracks.get_context(context_id)
|
||||
|
||||
cmd.on('-k [S]', "keyword to be replaced") do |v|
|
||||
@keywords[v.split("=")[0]] = v.split("=")[1]
|
||||
end
|
||||
return resp.code == '200'
|
||||
end
|
||||
|
||||
cmd.on('-v', "verbose on") do |v|
|
||||
@options[:verbose] = true
|
||||
end
|
||||
end
|
||||
|
||||
cmd.on('-f [S]', "filename of the template") do |v|
|
||||
@filename = v
|
||||
end
|
||||
class Error < StandardError; end
|
||||
class InvalidParser < StandardError; end
|
||||
|
||||
cmd.on('-c [N]', Integer, 'default context id to set for new projects') do |v|
|
||||
@options[:context_id] = v
|
||||
end
|
||||
class ConsoleOptions
|
||||
attr_reader :parser, :options, :keywords
|
||||
|
||||
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
|
||||
end
|
||||
def initialize
|
||||
@options = {}
|
||||
@keywords = {}
|
||||
|
||||
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
|
||||
end
|
||||
@parser = OptionParser.new do |cmd|
|
||||
cmd.banner = "Ruby Gtd Templates CLI"
|
||||
|
||||
cmd.separator ''
|
||||
|
||||
cmd.on('-h', '--help', 'Displays this help message') do
|
||||
puts @parser
|
||||
exit
|
||||
end
|
||||
|
||||
cmd.on('-p [N]', Integer, "project id to set for new todo") do |v|
|
||||
@options[:project_id] = v
|
||||
end
|
||||
|
||||
cmd.on('-k [S]', "keyword to be replaced") do |v|
|
||||
@keywords[v.split("=")[0]] = v.split("=")[1]
|
||||
end
|
||||
|
||||
cmd.on('-v', "verbose on") do |v|
|
||||
@options[:verbose] = true
|
||||
end
|
||||
|
||||
cmd.on('-f [S]', "filename of the template") do |v|
|
||||
@filename = v
|
||||
end
|
||||
|
||||
cmd.on('-c [N]', Integer, 'default context id to set for new projects') do |v|
|
||||
@options[:context_id] = v
|
||||
end
|
||||
|
||||
cmd.on('-w [N]', Integer, 'Postpone task for N weeks') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * (v || 1)
|
||||
end
|
||||
|
||||
cmd.on('-m [N]', Integer, 'Postpone task for N months') do |v|
|
||||
@options[:show_from] = Time.now.to_i + 24 * 3600 * 7 * 4 * (v || 1)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def run(args)
|
||||
@parser.parse!(args)
|
||||
# lines = STDIN.read
|
||||
gtd = API.new
|
||||
def run(args)
|
||||
@parser.parse!(args)
|
||||
@poster = TemplatePoster.new(@options)
|
||||
|
||||
if @filename != nil and not File.exist?(@filename)
|
||||
puts "ERROR: file #{@filename} doesn't exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ENV['GTD_LOGIN'] == nil
|
||||
puts "ERROR: no GTD_LOGIN environment variable set"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ENV['GTD_PASSWORD'] == nil
|
||||
puts "ERROR: no GTD_PASSWORD environment variable set"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if @filename == nil
|
||||
file = STDIN
|
||||
else
|
||||
file = File.open(@filename)
|
||||
end
|
||||
|
||||
## check for existence of the context
|
||||
if !@options[:context_id]
|
||||
puts "ERROR: need to specify a context_id with -c option. Go here to find one: #{API::GTD_URI_CONTEXTS}"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if !gtd.queryContext(@options[:context_id])
|
||||
puts "Error: context_id #{options[:context_id]} doesn't exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
#lines.each_line do |line|
|
||||
while line = file.gets
|
||||
line = line.strip
|
||||
next if (line.empty? || line[0].chr == "#")
|
||||
|
||||
if (line.split(' ')[0] == "token")
|
||||
## defining a new token; ask for input
|
||||
|
||||
newtok=line.split(' ')[1]
|
||||
|
||||
print "Input required for "+newtok+": "
|
||||
@keywords[newtok]=gets.chomp
|
||||
next
|
||||
end
|
||||
|
||||
# replace tokens
|
||||
@keywords.each do |key,val|
|
||||
line=line.sub(key,val)
|
||||
end
|
||||
|
||||
# decide whether project or task
|
||||
if (line[0].chr == "." ) || (line[0].chr == "^")
|
||||
@options[:depend]= line[0].chr == "^" ? true : false;
|
||||
line = line[1..line.length]
|
||||
|
||||
# find notes
|
||||
tmp= line.split("|")
|
||||
if tmp.length > 5
|
||||
puts "Formatting error: found too many |"
|
||||
exit 1
|
||||
end
|
||||
|
||||
line=tmp[0]
|
||||
|
||||
tmp.each_with_index do |t,idx|
|
||||
t=t.strip.chomp
|
||||
t=nil if t.empty?
|
||||
tmp[idx]=t
|
||||
end
|
||||
|
||||
@options[:context]=tmp[1]
|
||||
@options[:taglist]=tmp[2]
|
||||
@options[:note]=tmp[3]
|
||||
|
||||
if !@options[:project_id]
|
||||
puts "Warning: no project specified for task \"#{line}\". Using default project."
|
||||
end
|
||||
|
||||
@options[:last_todo_id]=gtd.postTodo(line, @options)
|
||||
else
|
||||
@options[:project_id]=gtd.postProject(line, @options)
|
||||
end
|
||||
end
|
||||
|
||||
exit 0
|
||||
rescue InvalidParser
|
||||
puts "Please specify a valid format parser."
|
||||
exit 1
|
||||
rescue Error
|
||||
puts "An unknown error occurred"
|
||||
if !@filename.nil? and not File.exist?(@filename)
|
||||
puts "ERROR: file #{@filename} doesn't exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ENV['GTD_LOGIN'] == nil
|
||||
puts "ERROR: no GTD_LOGIN environment variable set"
|
||||
exit 1
|
||||
end
|
||||
|
||||
if ENV['GTD_PASSWORD'] == nil
|
||||
puts "ERROR: no GTD_PASSWORD environment variable set"
|
||||
exit 1
|
||||
end
|
||||
|
||||
file = @filename.nil? ? STDIN : File.open(@filename)
|
||||
|
||||
## check for existence of the context
|
||||
if @options[:context_id].nil?
|
||||
puts "ERROR: need to specify a context_id with -c option."
|
||||
exit 1
|
||||
end
|
||||
|
||||
if !@poster.queryContext(@options[:context_id])
|
||||
puts "Error: context_id #{options[:context_id]} doesn't exist"
|
||||
exit 1
|
||||
end
|
||||
|
||||
TemplateParser.new.parse(file, @poster)
|
||||
|
||||
exit 0
|
||||
rescue InvalidParser
|
||||
puts "Please specify a valid format parser."
|
||||
exit 1
|
||||
rescue Error
|
||||
puts "An unknown error occurred"
|
||||
exit 1
|
||||
end
|
||||
end
|
||||
|
||||
if $0 == __FILE__
|
||||
app = Gtd::ConsoleOptions.new
|
||||
app.run(ARGV)
|
||||
ConsoleOptions.new.run(ARGV)
|
||||
end
|
||||
|
|
|
|||
36
features/add_todo_from_cli.feature
Normal file
36
features/add_todo_from_cli.feature
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
Feature: Add a todo to Tracks on console
|
||||
In order to be able to add a todo from the command line
|
||||
As a user who has installed Tracks with console access
|
||||
I want to run the script to add a todo
|
||||
|
||||
These scenarios are tagged javascript so that there is a Tracks server running
|
||||
to use from the command line script
|
||||
|
||||
Background:
|
||||
Given the following user records
|
||||
| login | password | is_admin |
|
||||
| testuser | secret | false |
|
||||
| admin | secret | true |
|
||||
And I have logged in as "testuser" with password "secret"
|
||||
And I have a context called "Context A"
|
||||
And I have a project called "Project A"
|
||||
|
||||
@javascript @aruba
|
||||
Scenario: Create a single todo
|
||||
Given a console input that looks like
|
||||
"""
|
||||
a new todo
|
||||
"""
|
||||
When I execute the add-todo script
|
||||
Then I should have 1 todo in project "Project A"
|
||||
|
||||
@javascript @aruba
|
||||
Scenario: Create multiple todos
|
||||
Given a console input that looks like
|
||||
"""
|
||||
todo 1
|
||||
todo 2
|
||||
|
||||
"""
|
||||
When I execute the add-todo script
|
||||
Then I should have 2 todo in project "Project A"
|
||||
|
|
@ -12,17 +12,16 @@ Feature: Edit a context
|
|||
And I have a project called "test project"
|
||||
And I have 2 todos in project "test project" in context "@pc" with tags "starred" prefixed by "test_project "
|
||||
|
||||
@javascript
|
||||
@javascript
|
||||
Scenario: In place edit of context name
|
||||
Given I have a context called "Errands"
|
||||
When I go to the context page for "Errands"
|
||||
When I go to the context page for "@pc"
|
||||
And I edit the context name in place to be "OutAndAbout"
|
||||
Then I should see the context name is "OutAndAbout"
|
||||
When I go to the contexts page
|
||||
Then I should see that a context named "Errands" is not present
|
||||
And I should see that a context named "OutAndAbout" is present
|
||||
|
||||
@javascript
|
||||
@javascript
|
||||
Scenario: Editing the context of a todo will remove the todo
|
||||
When I go to the the context page for "@pc"
|
||||
Then the badge should show 2
|
||||
|
|
@ -37,7 +36,7 @@ Feature: Edit a context
|
|||
Then I should not see the todo "test_project todo 1"
|
||||
And I should see "changed"
|
||||
|
||||
@javascript
|
||||
@javascript
|
||||
Scenario: Editing the context of the last todo will remove the todo and show empty message
|
||||
When I go to the the context page for "@pc"
|
||||
And I edit the context of "test_project todo 1" to "@laptop"
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
Feature: Create project from template
|
||||
Feature: Create project from template on console
|
||||
In order to be able to create a project from a template
|
||||
As a user who has installed Tracks with console access
|
||||
I want to run the script to add projects and actions from a template
|
||||
|
|
@ -21,7 +21,7 @@ Feature: Create project from template
|
|||
My first project
|
||||
.My first task in this project
|
||||
"""
|
||||
When I execute the script
|
||||
When I execute the template script
|
||||
Then I should have a project called "My first project"
|
||||
And I should have 1 todo in project "My first project"
|
||||
|
||||
|
|
@ -33,5 +33,5 @@ Feature: Create project from template
|
|||
.Todo
|
||||
^Dependent
|
||||
"""
|
||||
When I execute the script
|
||||
When I execute the template script
|
||||
Then the successors of "Todo" should include "Dependent"
|
||||
|
|
|
|||
59
features/step_definitions/console_steps.rb
Normal file
59
features/step_definitions/console_steps.rb
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
Given /^a template that looks like$/ do |template|
|
||||
steps %{
|
||||
Given a file named "template.txt" with:
|
||||
"""
|
||||
#{template}
|
||||
"""
|
||||
}
|
||||
end
|
||||
|
||||
Given /^a console input that looks like$/ do |input|
|
||||
steps %{
|
||||
Given a file named "todo.txt" with:
|
||||
"""
|
||||
#{input}
|
||||
"""
|
||||
}
|
||||
end
|
||||
|
||||
When /^I execute the template script$/ do
|
||||
step "I cd to \"../..\""
|
||||
|
||||
context_id = @current_user.contexts.first.id
|
||||
port = Capybara.current_session.server.port
|
||||
|
||||
# assumes there is a context with id=1
|
||||
cli = "ruby doc/tracks_template_cli.rb -c #{context_id} -f tmp/aruba/template.txt"
|
||||
|
||||
set_env('GTD_LOGIN','testuser')
|
||||
set_env('GTD_PASSWORD', 'secret')
|
||||
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
|
||||
set_env('GTD_PROJECTS_URL', "http://localhost:#{port}/projects.xml")
|
||||
set_env('GTD_CONTEXT_URL_PREFIX', "http://localhost:#{port}/contexts/")
|
||||
set_env("GTD_CONTEXT_URL","http://localhost:#{port}/contexts.xml")
|
||||
|
||||
step "I run `#{cli}`"
|
||||
end
|
||||
|
||||
When /^I execute the add-todo script$/ do
|
||||
step "I cd to \"../..\""
|
||||
|
||||
# assumes there is a context and a project
|
||||
context_id = @current_user.contexts.first.id
|
||||
project_id = @current_user.projects.first.id
|
||||
port = Capybara.current_session.server.port
|
||||
|
||||
cli = "ruby doc/tracks_cli_client.rb -c #{context_id} -p #{project_id}"
|
||||
|
||||
set_env('GTD_LOGIN','testuser')
|
||||
set_env('GTD_PASSWORD', 'secret')
|
||||
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
|
||||
|
||||
step "I run `#{cli}` interactively"
|
||||
step "I pipe in the file \"tmp/aruba/todo.txt\""
|
||||
|
||||
# it seems aruba does not wait for process to end with interactively run command, but
|
||||
# continues anyway which will start cleaning up the database while the process is still running
|
||||
# so wait 2 secs for the process to finish
|
||||
sleep 2
|
||||
end
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
Given /^a template that looks like$/ do |template|
|
||||
steps %{
|
||||
Given a file named "template.txt" with:
|
||||
"""
|
||||
#{template}
|
||||
"""
|
||||
}
|
||||
end
|
||||
|
||||
When /^I execute the script$/ do
|
||||
step "I cd to \"../..\""
|
||||
|
||||
context_id = @current_user.contexts.first.id
|
||||
port = Capybara.current_session.server.port
|
||||
|
||||
# assumes there is a context with id=1
|
||||
cli = "ruby doc/tracks_template_cli.rb -c #{context_id} -f tmp/aruba/template.txt"
|
||||
|
||||
set_env('GTD_LOGIN','testuser')
|
||||
set_env('GTD_PASSWORD', 'secret')
|
||||
set_env('GTD_TODOS_URL', "http://localhost:#{port}/todos.xml")
|
||||
set_env('GTD_PROJECTS_URL', "http://localhost:#{port}/projects.xml")
|
||||
set_env('GTD_CONTEXT_URL_PREFIX', "http://localhost:#{port}/contexts/")
|
||||
set_env("GTD_CONTEXT_URL","http://localhost:#{port}/contexts.xml")
|
||||
|
||||
step "I run `#{cli}`"
|
||||
end
|
||||
|
|
@ -45,7 +45,7 @@ end
|
|||
#
|
||||
# Before('@no-txn,@selenium,@culerity,@celerity,@javascript') do
|
||||
# # { :except => [:widgets] } may not do what you expect here
|
||||
# # as tCucumber::Rails::Database.javascript_strategy overrides
|
||||
# # as Cucumber::Rails::Database.javascript_strategy overrides
|
||||
# # this setting.
|
||||
# DatabaseCleaner.strategy = :truncation
|
||||
# end
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
# IMPORTANT: This file is generated by cucumber-rails - edit at your own peril.
|
||||
# It is recommended to regenerate this file in the future when you upgrade to a
|
||||
# newer version of cucumber-rails. Consider adding your own code to a new file
|
||||
# It is recommended to regenerate this file in the future when you upgrade to a
|
||||
# newer version of cucumber-rails. Consider adding your own code to a new file
|
||||
# instead of editing this one. Cucumber will automatically load all features/**/*.rb
|
||||
# files.
|
||||
|
||||
|
|
@ -14,19 +14,19 @@ begin
|
|||
require 'cucumber/rake/task'
|
||||
|
||||
namespace :cucumber do
|
||||
Cucumber::Rake::Task.new({:ok => 'db:test:prepare'}, 'Run features that should pass') do |t|
|
||||
Cucumber::Rake::Task.new({:ok => 'test:prepare'}, 'Run features that should pass') do |t|
|
||||
t.binary = vendored_cucumber_bin # If nil, the gem's binary is used.
|
||||
t.fork = true # You may get faster startup if you set this to false
|
||||
t.profile = 'default'
|
||||
end
|
||||
|
||||
Cucumber::Rake::Task.new({:wip => 'db:test:prepare'}, 'Run features that are being worked on') do |t|
|
||||
Cucumber::Rake::Task.new({:wip => 'test:prepare'}, 'Run features that are being worked on') do |t|
|
||||
t.binary = vendored_cucumber_bin
|
||||
t.fork = true # You may get faster startup if you set this to false
|
||||
t.profile = 'wip'
|
||||
end
|
||||
|
||||
Cucumber::Rake::Task.new({:rerun => 'db:test:prepare'}, 'Record failing features and run only them if any exist') do |t|
|
||||
Cucumber::Rake::Task.new({:rerun => 'test:prepare'}, 'Record failing features and run only them if any exist') do |t|
|
||||
t.binary = vendored_cucumber_bin
|
||||
t.fork = true # You may get faster startup if you set this to false
|
||||
t.profile = 'rerun'
|
||||
|
|
@ -50,8 +50,8 @@ begin
|
|||
STDERR.puts "*** The 'features' task is deprecated. See rake -T cucumber ***"
|
||||
end
|
||||
|
||||
# In case we don't have ActiveRecord, append a no-op task that we can depend upon.
|
||||
task 'db:test:prepare' do
|
||||
# In case we don't have the generic Rails test:prepare hook, append a no-op task that we can depend upon.
|
||||
task 'test:prepare' do
|
||||
end
|
||||
|
||||
task :stats => 'cucumber:statsetup'
|
||||
|
|
|
|||
|
|
@ -38,6 +38,10 @@ module Tracks
|
|||
RedCloth.new(text).to_html
|
||||
end
|
||||
|
||||
def self.sanitize_filename(filename)
|
||||
filename.gsub(/[^0-9A-z.\-]/, '_')
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def self.helpers
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ class IntegrationsControllerTest < ActionController::TestCase
|
|||
|
||||
def test_cloudmailin_integration_success
|
||||
SITE_CONFIG['cloudmailin'] = "123456789"
|
||||
SITE_CONFIG['email_dispatch'] = 'from'
|
||||
post :cloudmailin, {
|
||||
"html"=>"",
|
||||
"plain"=>"asdasd",
|
||||
|
|
|
|||
66
test/controllers/mailgun_controller_test.rb
Normal file
66
test/controllers/mailgun_controller_test.rb
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
require File.expand_path(File.dirname(__FILE__) + '/../test_helper')
|
||||
|
||||
class MailgunControllerTest < ActionController::TestCase
|
||||
|
||||
def setup
|
||||
@user = users(:sms_user)
|
||||
@inbox = contexts(:inbox)
|
||||
end
|
||||
|
||||
def load_message(filename)
|
||||
File.read(File.join(Rails.root, 'test', 'fixtures', filename))
|
||||
end
|
||||
|
||||
def test_mailgun_signature_verifies
|
||||
SITE_CONFIG['mailgun_api_key'] = "123456789"
|
||||
SITE_CONFIG['email_dispatch'] = 'from'
|
||||
|
||||
post :mailgun, {
|
||||
"timestamp" => "1379539674",
|
||||
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
|
||||
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
|
||||
"body-mime" => load_message('mailgun_message1.txt')
|
||||
}
|
||||
|
||||
assert_response :success
|
||||
end
|
||||
|
||||
def test_mailgun_creates_todo_with_mailmap
|
||||
SITE_CONFIG['mailgun_api_key'] = "123456789"
|
||||
SITE_CONFIG['email_dispatch'] = 'to'
|
||||
SITE_CONFIG['mailmap'] = {
|
||||
'5555555555@tmomail.net' => ['incoming@othermail.com', 'notused@foo.org']
|
||||
}
|
||||
|
||||
todo_count = Todo.count
|
||||
post :mailgun, {
|
||||
"timestamp" => "1379539674",
|
||||
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
|
||||
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
|
||||
"body-mime" => load_message('mailgun_message2.txt')
|
||||
}
|
||||
|
||||
assert_response :success
|
||||
|
||||
assert_equal(todo_count+1, Todo.count)
|
||||
message_todo = Todo.where(:description => "test").first
|
||||
assert_not_nil(message_todo)
|
||||
assert_equal(@inbox, message_todo.context)
|
||||
assert_equal(@user, message_todo.user)
|
||||
end
|
||||
|
||||
def test_mailgun_signature_fails
|
||||
SITE_CONFIG['mailgun_api_key'] = "invalidkey"
|
||||
SITE_CONFIG['email_dispatch'] = 'from'
|
||||
|
||||
post :mailgun, {
|
||||
"timestamp" => "1379539674",
|
||||
"token" => "5km6cwo0e3bfvg78hw4s69znro09xhk1h8u6-s633yasc8hcr5",
|
||||
"signature" => "da92708b8f2c9dcd7ecdc91d52946c01802833e6683e46fc00b3f081920dd5b1",
|
||||
"body-mime" => load_message('mailgun_message1.txt')
|
||||
}
|
||||
|
||||
assert_response 406
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -729,23 +729,11 @@ class TodosControllerTest < ActionController::TestCase
|
|||
recurring_todo_1.every_other2 = 1
|
||||
assert recurring_todo_1.save
|
||||
|
||||
# mark todo_1 as complete by toggle_check, this gets rid of todo_1 that was
|
||||
# not correctly created from the adjusted recurring pattern we defined
|
||||
# above.
|
||||
# mark todo_1 as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => todo_1.id, :_source_view => 'todo'
|
||||
todo_1.reload
|
||||
assert todo_1.completed?
|
||||
|
||||
# locate the new todo. This todo is created from the adjusted recurring
|
||||
# pattern defined in this test
|
||||
new_todo = Todo.where(:recurring_todo_id => recurring_todo_1.id, :state => 'active').first
|
||||
assert !new_todo.nil?
|
||||
|
||||
# mark new_todo as complete by toggle_check
|
||||
xhr :post, :toggle_check, :id => new_todo.id, :_source_view => 'todo'
|
||||
new_todo.reload
|
||||
assert todo_1.completed?
|
||||
|
||||
# locate the new todo in tickler
|
||||
new_todo = Todo.where(:recurring_todo_id => recurring_todo_1.id, :state => 'deferred').first
|
||||
assert !new_todo.nil?
|
||||
|
|
@ -799,6 +787,44 @@ class TodosControllerTest < ActionController::TestCase
|
|||
assert next_todo.due > todo.due
|
||||
end
|
||||
|
||||
def test_check_for_next_todo_monthly
|
||||
login_as :admin_user
|
||||
|
||||
tomorrow = Time.zone.now + 1.day
|
||||
|
||||
# Given a monthly repeat pattern
|
||||
recurring_todo = RecurringTodo.find(5)
|
||||
recurring_todo.target = "due_date"
|
||||
recurring_todo.recurring_period = "monthly"
|
||||
recurring_todo.every_other1 = tomorrow.day
|
||||
recurring_todo.every_other2 = 1
|
||||
recurring_todo.save
|
||||
|
||||
# Given a recurring todo (todo) that belongs to the repeat pattern (recurring_todo) and is due tomorrow
|
||||
todo = Todo.where(:recurring_todo_id => 1).first
|
||||
assert todo.from_recurring_todo?
|
||||
todo.recurring_todo_id = 5 # rewire todo to the repeat pattern above
|
||||
todo.due = tomorrow
|
||||
todo.save!
|
||||
|
||||
# When I mark the todo complete
|
||||
xhr :post, :toggle_check, :id => todo.id, :_source_view => 'todo'
|
||||
todo.reload
|
||||
assert todo.completed?
|
||||
|
||||
# Then there should not be an active todo beloning to the repeat pattern
|
||||
next_todo = Todo.where(:recurring_todo_id => recurring_todo.id, :state => 'active').first
|
||||
assert next_todo.nil?
|
||||
|
||||
# Then there should be one new deferred todo
|
||||
next_todo = Todo.where(:recurring_todo_id => recurring_todo.id, :state => 'deferred').first
|
||||
assert !next_todo.nil?
|
||||
assert !next_todo.show_from.nil?
|
||||
|
||||
# check that the due date of the new todo is later than tomorrow
|
||||
assert next_todo.due > todo.due
|
||||
end
|
||||
|
||||
############
|
||||
# todo notes
|
||||
############
|
||||
|
|
|
|||
27
test/fixtures/mailgun_message1.txt
vendored
Normal file
27
test/fixtures/mailgun_message1.txt
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000
|
||||
X-Envelope-From: <5555555555@tmomail.net>
|
||||
Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC)
|
||||
Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n <test@test.mailgun.org>; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig==
|
||||
Mime-Version: 1.0
|
||||
X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Date: Wed, 18 Sep 2013 21:51:08 +0100
|
||||
Message-Id: <CAE=3ySAxx8r7dcgaixQjbYS3J4mVG7FdEqAwDAcJ4bkHnDiT5w@mail.gmail.com>
|
||||
Subject: test1
|
||||
From: A User <5555555555@tmomail.net>
|
||||
To: test@test.mailgun.org
|
||||
Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\"
|
||||
X-Mailgun-Incoming: Yes
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
|
||||
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
|
||||
<div dir=\"ltr\"><br></div>
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838--
|
||||
27
test/fixtures/mailgun_message2.txt
vendored
Normal file
27
test/fixtures/mailgun_message2.txt
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
Received: by luna.mailgun.net with SMTP mgrt 8745468409521; Wed, 18 Sep 2013\n 20:51:11 +0000
|
||||
X-Envelope-From: <incoming@othermail.com>
|
||||
Received: from mail-pa0-f45.google.com (mail-pa0-f45.google.com\n [209.85.220.45]) by mxa.mailgun.org with ESMTP id\n 523a123d.7f3a16482330-in1; Wed, 18 Sep 2013 20:51:09 -0000 (UTC)
|
||||
Received: by mail-pa0-f45.google.com with SMTP id bg4so8745646pad.18 for\n <5555555555@tmomail.net>; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Dkim-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com;\n s=20120113; h=mime-version:date:message-id:subject:from:to:content-type;\n bh=XwBMHRr9XT/f5w7Vzap//yQJ9VoVaVcjEQ5+xjgSoMk=;\n b=IsSDTe2jY8XucXOxviOrGg1upSm5IOddtOsHFpgcyND4F9glSIU+xixRK60/9Dbqae\n BT5On3C+Aoffpj/WUqnp1+hA89JYDbWhzXcJPhkW8FcFnPvAzQ0G/loV7+CafWLubEnp\n T61owZQAQgdkHsrZMf1ChmpghNMJuDLMJvYmiijIId+z7arBocjErhHqwRY4Y6lmFdTo\n s9uGtVJ7Av1g7m8KyGZY6kzh/XPhbr4B0tjIFpyp+bDFH2Nk290RAB/2garNBfQAPhzy\n hIvvbuz5MLFWSnW17eXdymHAEH6oSbRfar8ocxcY5T+hg++nfsegUJ6sPRG1G63qnsj4 dCig==
|
||||
Mime-Version: 1.0
|
||||
X-Received: by 10.68.17.230 with SMTP id r6mr21790845pbd.112.1379537468893;\n Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Received: by 10.70.43.236 with HTTP; Wed, 18 Sep 2013 13:51:08 -0700 (PDT)
|
||||
Date: Wed, 18 Sep 2013 21:51:08 +0100
|
||||
Message-Id: <CAE=3ySAxx8r7dcgaixQjbYS3J4mVG7FdEqAwDAcJ4bkHnDiT5w@mail.gmail.com>
|
||||
Subject: test
|
||||
From: A User <incoming@othermail.com>
|
||||
To: 5555555555@tmomail.net
|
||||
Content-Type: multipart/alternative; boundary=\"bcaec520ee93c9b26104e6ae9838\"
|
||||
X-Mailgun-Incoming: Yes
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838
|
||||
Content-Type: text/plain; charset=ISO-8859-1
|
||||
|
||||
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838
|
||||
Content-Type: text/html; charset=ISO-8859-1
|
||||
|
||||
<div dir=\"ltr\"><br></div>
|
||||
|
||||
--bcaec520ee93c9b26104e6ae9838--
|
||||
|
|
@ -175,9 +175,6 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
|||
assert_equal @sunday, due_date # june 8th
|
||||
|
||||
due_date = @monthly.get_due_date(@sunday) # june 8th
|
||||
assert_equal Time.zone.local(2008,6,8), due_date # june 8th
|
||||
|
||||
due_date = @monthly.get_due_date(@monday) # june 9th
|
||||
assert_equal Time.zone.local(2008,8,8), due_date # aug 8th
|
||||
end
|
||||
|
||||
|
|
@ -247,7 +244,7 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
|||
# every_day should return start_day if it is in the future
|
||||
@every_day.start_from = @in_three_days
|
||||
due_date = @every_day.get_due_date(nil)
|
||||
assert_equal @in_three_days, due_date
|
||||
assert_equal @in_three_days.to_s(:db), due_date.to_s(:db)
|
||||
due_date = @every_day.get_due_date(@tomorrow)
|
||||
assert_equal @in_three_days, due_date
|
||||
|
||||
|
|
@ -354,6 +351,66 @@ class RecurringTodoTest < ActiveSupport::TestCase
|
|||
assert !@every_month.valid?
|
||||
end
|
||||
|
||||
def test_set_every_n_days_from_form_input
|
||||
todo = RecurringTodo.new({
|
||||
:description => "Task every 2 days",
|
||||
:context => Context.first,
|
||||
:recurring_target => "show_from_date",
|
||||
:start_from => "01/01/01",
|
||||
:ends_on => "no_end_date",
|
||||
:recurring_period => "daily",
|
||||
:daily_selector => "daily_every_x_day",
|
||||
:daily_every_x_days => 2,
|
||||
})
|
||||
assert todo.valid?, todo.errors.full_messages
|
||||
assert_equal 2, todo.every_other1
|
||||
end
|
||||
|
||||
def test_set_every_n_weeks_from_form_input
|
||||
todo = RecurringTodo.new({
|
||||
:description => "Task every 3 weeks",
|
||||
:context => Context.first,
|
||||
:recurring_target => "show_from_date",
|
||||
:start_from => "01/01/01",
|
||||
:ends_on => "no_end_date",
|
||||
:recurring_period => "weekly",
|
||||
:weekly_every_x_week => 3,
|
||||
:weekly_return_monday => "m",
|
||||
})
|
||||
assert todo.valid?, todo.errors.full_messages
|
||||
assert_equal 3, todo.every_other1
|
||||
assert todo.on_monday
|
||||
end
|
||||
|
||||
def test_set_every_n_months_from_form_input
|
||||
todo = RecurringTodo.new({
|
||||
:description => "Task every 4 months",
|
||||
:context => Context.first,
|
||||
:recurring_target => "show_from_date",
|
||||
:start_from => "01/01/01",
|
||||
:ends_on => "no_end_date",
|
||||
:recurring_period => "monthly",
|
||||
:monthly_selector => "monthly_every_x_day",
|
||||
:monthly_every_x_day => 1,
|
||||
:monthly_every_x_month => 4,
|
||||
})
|
||||
assert todo.valid?, todo.errors.full_messages
|
||||
assert_equal 4, todo.every_other2
|
||||
end
|
||||
|
||||
def test_set_yearly_from_form_input
|
||||
todo = RecurringTodo.new({
|
||||
:description => "Task every year in May",
|
||||
:context => Context.first,
|
||||
:recurring_target => "show_from_date",
|
||||
:start_from => "01/01/01",
|
||||
:ends_on => "no_end_date",
|
||||
:recurring_period => "yearly",
|
||||
:yearly_selector => "yearly_every_x_day",
|
||||
:yearly_every_x_day => 15,
|
||||
:yearly_month_of_year => 5,
|
||||
})
|
||||
assert todo.valid?, todo.errors.full_messages
|
||||
assert_equal 5, todo.every_other2
|
||||
end
|
||||
end
|
||||
|
|
|
|||
36
test/models/tracks_cli/tracks_api_test.rb
Normal file
36
test/models/tracks_cli/tracks_api_test.rb
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
require 'net/https'
|
||||
require './test/minimal_test_helper'
|
||||
require './doc/tracks_cli/tracks_api'
|
||||
|
||||
module TracksCli
|
||||
|
||||
class TracksApiTest < Test::Unit::TestCase
|
||||
|
||||
def test_https_detection
|
||||
uri = URI.parse("https://tracks.example.com")
|
||||
http = TracksCli::TracksAPI.new({}).get_http(uri)
|
||||
assert http.use_ssl?, "ssl expected"
|
||||
|
||||
uri = URI.parse("http://tracks.example.com")
|
||||
http = TracksCli::TracksAPI.new({}).get_http(uri)
|
||||
assert !http.use_ssl?, "no ssl expected"
|
||||
end
|
||||
|
||||
def test_context_uri
|
||||
uri = TracksCli::TracksAPI.new({context_prefix: "c"}).context_uri_for(16)
|
||||
assert_equal "c16.xml", uri.path
|
||||
|
||||
uri = TracksCli::TracksAPI.new({context_prefix: "c"}).context_uri_for(18)
|
||||
assert_equal "c18.xml", uri.path
|
||||
end
|
||||
|
||||
def test_static_uris_for_todo_and_project
|
||||
uri = TracksCli::TracksAPI.new({projects_uri: "https//tracks.example.com/projects.xml"}).project_uri
|
||||
assert_equal "https//tracks.example.com/projects.xml", uri.path
|
||||
|
||||
uri = TracksCli::TracksAPI.new({uri: "https//tracks.example.com/todos.xml"}).todo_uri
|
||||
assert_equal "https//tracks.example.com/todos.xml", uri.path
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
81
test/models/tracks_cli/tracks_xml_builder_test.rb
Normal file
81
test/models/tracks_cli/tracks_xml_builder_test.rb
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
require './test/minimal_test_helper'
|
||||
require './doc/tracks_cli/tracks_xml_builder'
|
||||
require 'active_support/time_with_zone'
|
||||
|
||||
module TracksCli
|
||||
|
||||
class TracksXmlBuilderTest < Test::Unit::TestCase
|
||||
def test_all
|
||||
todo = {
|
||||
description: "test action",
|
||||
project_id: 1,
|
||||
show_from: Time.utc(2013,1,1,14,00,00),
|
||||
notes: "action notes",
|
||||
taglist: "one, two",
|
||||
context_name: "@home",
|
||||
is_dependend: true,
|
||||
predecessor: 123
|
||||
}
|
||||
|
||||
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
|
||||
expect = "<todo><description>test action</description>" +
|
||||
"<project_id>1</project_id><show-from type=\"datetime\">#{Time.at(todo[:show_from]).xmlschema}</show-from>" +
|
||||
"<notes>action notes</notes><tags><tag><name>one</name></tag><tag><name>two</name></tag></tags>" +
|
||||
"<context><name>@home</name></context><predecessor_dependencies><predecessor>123</predecessor></predecessor_dependencies></todo>"
|
||||
|
||||
assert_equal expect, xml
|
||||
end
|
||||
|
||||
def test_context_name_is_passed_through
|
||||
todo = {
|
||||
description: "test action",
|
||||
project_id: 1,
|
||||
context_name: "@home",
|
||||
}
|
||||
|
||||
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
|
||||
expect = "<todo><description>test action</description><project_id>1</project_id><context><name>@home</name></context></todo>"
|
||||
|
||||
assert_equal expect, xml
|
||||
end
|
||||
|
||||
def test_context_id_is_used_if_no_context_name_given
|
||||
todo = {
|
||||
description: "test action",
|
||||
project_id: 5,
|
||||
context_id: 16,
|
||||
}
|
||||
|
||||
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
|
||||
expect = "<todo><description>test action</description><project_id>5</project_id><context_id>16</context_id></todo>"
|
||||
|
||||
assert_equal expect, xml, "only context_id given, so that should be included"
|
||||
|
||||
todo = {
|
||||
description: "test action",
|
||||
project_id: 5,
|
||||
context_id: 16,
|
||||
context_name: "@inbox"
|
||||
}
|
||||
|
||||
xml = TracksCli::TracksXmlBuilder.new.build_todo_xml(todo)
|
||||
expect = "<todo><description>test action</description><project_id>5</project_id><context><name>@inbox</name></context></todo>"
|
||||
|
||||
assert_equal expect, xml, "both context_id and context_name given, then context_name should be used"
|
||||
end
|
||||
|
||||
def test_project_xml_all
|
||||
todo = {
|
||||
description: "test project",
|
||||
default_context_id: 16
|
||||
}
|
||||
|
||||
xml = TracksCli::TracksXmlBuilder.new.build_project_xml(todo)
|
||||
expect = "<project><name>test project</name><default-context-id>16</default-context-id></project>"
|
||||
|
||||
assert_equal expect, xml
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
end
|
||||
|
|
@ -1,4 +1,9 @@
|
|||
ENV["RAILS_ENV"] ||= "test"
|
||||
|
||||
# test coverage from codeclimate
|
||||
require "codeclimate-test-reporter"
|
||||
CodeClimate::TestReporter.start
|
||||
|
||||
require File.expand_path('../../config/environment', __FILE__)
|
||||
require 'rails/test_help'
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue