tracco 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. data/.gitignore +28 -0
  2. data/.rspec +3 -0
  3. data/.rvmrc.template +2 -0
  4. data/.travis.yml +20 -0
  5. data/CHANGELOG +32 -0
  6. data/Gemfile +9 -0
  7. data/Gemfile.lock +110 -0
  8. data/LICENSE.txt +15 -0
  9. data/README.md +292 -0
  10. data/Rakefile +8 -0
  11. data/config/config.template.yml +6 -0
  12. data/config/config.yml.trackinguser_for_test +7 -0
  13. data/config/mongoid.template.yml +24 -0
  14. data/lib/patches/trello/card.rb +19 -0
  15. data/lib/patches/trello/member.rb +20 -0
  16. data/lib/startup_trello.rb +2 -0
  17. data/lib/tasks/rspec.rake +17 -0
  18. data/lib/tasks/tasks.rake +55 -0
  19. data/lib/tracco.rb +30 -0
  20. data/lib/tracco/effort.rb +35 -0
  21. data/lib/tracco/estimate.rb +25 -0
  22. data/lib/tracco/google_docs_exporter.rb +67 -0
  23. data/lib/tracco/member.rb +61 -0
  24. data/lib/tracco/mongoid_helper.rb +13 -0
  25. data/lib/tracco/tracked_card.rb +121 -0
  26. data/lib/tracco/tracking/base.rb +82 -0
  27. data/lib/tracco/tracking/card_done_tracking.rb +10 -0
  28. data/lib/tracco/tracking/effort_tracking.rb +45 -0
  29. data/lib/tracco/tracking/estimate_tracking.rb +23 -0
  30. data/lib/tracco/tracking/invalid_tracking.rb +19 -0
  31. data/lib/tracco/tracking_factory.rb +22 -0
  32. data/lib/tracco/trello_authorize.rb +16 -0
  33. data/lib/tracco/trello_configuration.rb +39 -0
  34. data/lib/tracco/trello_tracker.rb +44 -0
  35. data/lib/tracco/version.rb +3 -0
  36. data/script/ci/before_script.sh +1 -0
  37. data/script/ci/run_build.sh +2 -0
  38. data/script/crontab.template +8 -0
  39. data/script/mate.sh +1 -0
  40. data/spec/effort_spec.rb +59 -0
  41. data/spec/estimate_spec.rb +38 -0
  42. data/spec/integration/trello_authorization_spec.rb +12 -0
  43. data/spec/integration/trello_tracker_spec.rb +66 -0
  44. data/spec/member_spec.rb +81 -0
  45. data/spec/patches/trello/card_spec.rb +25 -0
  46. data/spec/spec_helper.rb +66 -0
  47. data/spec/support/database_cleaner.rb +12 -0
  48. data/spec/tracked_card_spec.rb +336 -0
  49. data/spec/tracking/card_done_tracking_spec.rb +18 -0
  50. data/spec/tracking/effort_tracking_spec.rb +114 -0
  51. data/spec/tracking/estimate_tracking_spec.rb +44 -0
  52. data/spec/tracking_factory_spec.rb +42 -0
  53. data/spec/trello_authorize_spec.rb +65 -0
  54. data/spec/trello_configuration_spec.rb +43 -0
  55. data/spec/trello_tracker_spec.rb +26 -0
  56. data/tracco.gemspec +41 -0
  57. data/tracco.sublime-project +10 -0
  58. metadata +316 -0
@@ -0,0 +1,82 @@
1
+ module Tracking
2
+ module Base
3
+ extend Forwardable
4
+ include TrelloConfiguration
5
+
6
+ TIME_CONVERTERS = {
7
+ 'h' => lambda { |estimate| estimate },
8
+ 'd' => lambda { |estimate| estimate * 8 },
9
+ 'g' => lambda { |estimate| estimate * 8 },
10
+ 'p' => lambda { |estimate| estimate / 2 }
11
+ }
12
+
13
+ DURATION_REGEXP = '(\d+\.?\d*[phdg])'
14
+ DATE_REGEXP = /(\d{2})\.(\d{2})\.(\d{4})/
15
+
16
+ # delegate to the trello notification the member_creator method aliased as 'notifier'
17
+ def_delegator :@tracking_notification, :member_creator, :notifier
18
+
19
+ def initialize(tracking_notification)
20
+ @tracking_notification = tracking_notification
21
+ end
22
+
23
+ def date
24
+ Chronic.parse(date_as_string).to_date
25
+ end
26
+
27
+ def to_s
28
+ "[#{date}] From #{notifier.username.color(:green)}\t on card '#{trello_card.name.color(:yellow)}': #{raw_text}"
29
+ end
30
+
31
+ private
32
+
33
+ def tracking_notification
34
+ @tracking_notification
35
+ end
36
+
37
+ def trello_card
38
+ @trello_card ||= tracking_notification.card
39
+ end
40
+
41
+ def notification_date
42
+ Chronic.parse(tracking_notification.date).to_date
43
+ end
44
+
45
+ def raw_tracking
46
+ raw_text.gsub("@#{tracker_username}", "")
47
+ end
48
+
49
+ def raw_text
50
+ tracking_notification.data['text']
51
+ end
52
+
53
+ def convert_to_hours(duration_as_string)
54
+ return if duration_as_string.nil?
55
+
56
+ time_scale = duration_as_string.slice!(-1)
57
+ converter = TIME_CONVERTERS[time_scale]
58
+ converter.call(Float(duration_as_string))
59
+ end
60
+
61
+ def date_as_string
62
+ case raw_tracking
63
+ when DATE_REGEXP
64
+ day, month, year = raw_tracking.scan(DATE_REGEXP).flatten
65
+ "#{year}-#{month}-#{day}"
66
+ when /yesterday\s+\+#{DURATION_REGEXP}/, /\+#{DURATION_REGEXP}\s+yesterday/
67
+ (notification_date - 1).to_s
68
+ else
69
+ tracking_notification.date
70
+ end
71
+ end
72
+
73
+ def extract_match_from_raw_tracking(regexp)
74
+ extracted = nil
75
+ raw_tracking.scan(regexp) do |match|
76
+ extracted = match.first
77
+ end
78
+
79
+ extracted
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,10 @@
1
+ module Tracking
2
+ class CardDoneTracking
3
+ include Base
4
+
5
+ def add_to(card)
6
+ card.done = true
7
+ end
8
+
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ module Tracking
2
+ class EffortTracking
3
+ include Base
4
+
5
+ #TODO: rename to 'amount' ?
6
+ #TODO: avoid recomputing effort every time using a lazy instance variable
7
+ def effort
8
+ effort_amount = convert_to_hours(raw_effort)
9
+ if effort_amount
10
+ total_effort = effort_amount * effort_members.size
11
+ Effort.new(amount: total_effort, date: date, members: effort_members, tracking_notification_id: tracking_notification.id)
12
+ end
13
+ end
14
+
15
+ def add_to(card)
16
+ card.efforts << effort unless card.contains_effort?(effort)
17
+ end
18
+
19
+ private
20
+
21
+ def raw_effort
22
+ extract_match_from_raw_tracking(/\+#{DURATION_REGEXP}/)
23
+ end
24
+
25
+ def effort_members
26
+ @effort_members ||= users_involved_in_the_effort.map do |username|
27
+ Member.build_from(Trello::Member.find(username))
28
+ end
29
+
30
+ @effort_members
31
+ end
32
+
33
+ def users_involved_in_the_effort
34
+ users_involved_in_the_effort = raw_tracking.scan(/@(\w+)/).flatten
35
+ users_involved_in_the_effort << notifier.username unless should_count_only_listed_members?
36
+
37
+ users_involved_in_the_effort
38
+ end
39
+
40
+ def should_count_only_listed_members?
41
+ raw_tracking =~ /\((@\w+\W*\s*)+\)/
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,23 @@
1
+ module Tracking
2
+ class EstimateTracking
3
+ include Base
4
+
5
+ #TODO: rename to 'amount' ?
6
+ #TODO: avoid recomputing estimate every time using a lazy instance variable
7
+ def estimate
8
+ estimate = convert_to_hours(raw_estimate)
9
+ Estimate.new(amount: estimate, date: date, tracking_notification_id: tracking_notification.id)
10
+ end
11
+
12
+ def add_to(card)
13
+ card.estimates << estimate unless card.contains_estimate?(estimate)
14
+ end
15
+
16
+ private
17
+
18
+ def raw_estimate
19
+ extract_match_from_raw_tracking(/\[#{DURATION_REGEXP}\]/)
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,19 @@
1
+ module Tracking
2
+ class InvalidTracking
3
+ include Base
4
+
5
+ def add_to(card)
6
+ # do nothing
7
+ Trello.logger.warn "Ignoring tracking notification: '#{raw_text}'"
8
+ end
9
+
10
+ def estimate
11
+ nil
12
+ end
13
+
14
+ def effort
15
+ nil
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,22 @@
1
+ class TrackingFactory
2
+
3
+ DURATION_REGEXP = '(\d+\.?\d*[phdg])'
4
+
5
+ @match_pairs = {}
6
+
7
+ def self.match(match_pair)
8
+ @match_pairs.merge!(match_pair)
9
+ end
10
+
11
+ def self.build_from(tracking_notification)
12
+ matching_pair = @match_pairs.find { |regexp, tracking_class| tracking_notification.data['text'] =~ regexp }
13
+
14
+ tracking_class = matching_pair ? matching_pair.last : Tracking::InvalidTracking
15
+ tracking_class.new(tracking_notification)
16
+ end
17
+
18
+ match /\[#{DURATION_REGEXP}\]/ => Tracking::EstimateTracking
19
+ match /\+#{DURATION_REGEXP}/ => Tracking::EffortTracking
20
+ match /DONE/ => Tracking::CardDoneTracking
21
+
22
+ end
@@ -0,0 +1,16 @@
1
+ module TrelloAuthorize
2
+ include TrelloConfiguration
3
+ include Trello::Authorization
4
+
5
+ def authorize_on_trello(auth_params={})
6
+ %w{developer_public_key access_token_key}.each do |key|
7
+ auth_params[key.to_sym] ||= ENV[key] || authorization_params_from_config_file[key]
8
+ end
9
+
10
+ Trello.configure do |config|
11
+ config.developer_public_key = auth_params[:developer_public_key]
12
+ config.member_token = auth_params[:access_token_key]
13
+ end
14
+ end
15
+
16
+ end
@@ -0,0 +1,39 @@
1
+ module TrelloConfiguration
2
+
3
+ def tracker_username(forced_tracker_username=nil)
4
+ @tracker_username ||= forced_tracker_username || ENV["tracker_username"] || configuration["tracker_username"]
5
+ end
6
+
7
+ def authorization_params_from_config_file
8
+ begin
9
+ configuration["trello"]
10
+ rescue NoMethodError => e
11
+ Trello.logger.info "Invalid configuration file".color(:red)
12
+ {}
13
+ end
14
+
15
+ end
16
+
17
+ class Database
18
+ def self.load_env(db_env, mongoid_configuration_path=nil)
19
+ ENV['MONGOID_ENV'] = db_env
20
+ Mongoid.load!(mongoid_configuration_path || "config/mongoid.yml", db_env)
21
+ Trello.logger.info "Mongo db env: #{db_env.color(:green)}."
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def db_environment
28
+ ENV['MONGOID_ENV']
29
+ end
30
+
31
+ def configuration
32
+ @configuration ||= load_configuration
33
+ end
34
+
35
+ def load_configuration
36
+ YAML.load_file("config/config.yml")
37
+ end
38
+
39
+ end
@@ -0,0 +1,44 @@
1
+ class TrelloTracker
2
+ include TrelloAuthorize
3
+ include Trello
4
+
5
+ trap("SIGINT") { exit! }
6
+
7
+ def initialize(custom_config_params = {})
8
+ authorize_on_trello(custom_config_params)
9
+ tracker_username(custom_config_params[:tracker_username])
10
+ end
11
+
12
+ def track(starting_date=Date.today)
13
+ notifications = tracker.notifications_from(starting_date)
14
+
15
+ oldest, latest = boundary_dates_in(notifications)
16
+ Trello.logger.info "Processing #{notifications.size} tracking notifications (from #{oldest} to #{latest}) starting from #{starting_date}..."
17
+
18
+ notifications.each do |notification|
19
+ tracking = TrackingFactory.build_from(notification)
20
+ begin
21
+ tracked_card = TrackedCard.update_or_create_with(notification.card)
22
+ tracked_card.add!(tracking)
23
+ Trello.logger.info tracking
24
+
25
+ rescue StandardError => e
26
+ Trello.logger.warn "skipping tracking: #{e.message}".color(:magenta)
27
+ Trello.logger.debug "#{e.backtrace}"
28
+ end
29
+ end
30
+ Trello.logger.info "Done tracking cards!".color(:green)
31
+ end
32
+
33
+ private
34
+
35
+ def tracker
36
+ @tracker ||= Member.find(tracker_username)
37
+ end
38
+
39
+ def boundary_dates_in(notifications)
40
+ dates = notifications.map { |each_notification| Chronic.parse(each_notification.date) }
41
+ [dates.min, dates.max]
42
+ end
43
+
44
+ end
@@ -0,0 +1,3 @@
1
+ class TrelloEffortTracker
2
+ VERSION = '0.0.9'
3
+ end
@@ -0,0 +1 @@
1
+ cp -f config/mongoid.template.yml config/mongoid.yml
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env sh
2
+ bundle exec rake spec:fast
@@ -0,0 +1,8 @@
1
+ SHELL=/Users/pierodibello/.rvm/bin/rvm-shell
2
+
3
+ GEMSET="ruby-1.9.3-p194@spikes"
4
+ PROJECT_PATH="/Users/$USER/Documents/workspace/trello_effort_tracker"
5
+ LC_ALL=en_US.UTF-8
6
+
7
+ # m h dom mon dow command
8
+ */10 * * * * rvm-shell $GEMSET -c "cd $PROJECT_PATH; bundle exec rake run:today[production]" >> /tmp/crontab.out 2>&1
@@ -0,0 +1 @@
1
+ mate $(ls -a | grep -v '\.\.$' | grep -v '\.$' | grep -v '\.git$'| grep -v '\.DS_Store'| grep -v '\.tmtags' | grep -v '\tags')
@@ -0,0 +1,59 @@
1
+ require 'spec_helper'
2
+ require 'mongoid-rspec'
3
+
4
+ describe Effort do
5
+
6
+ it { should have_fields(:amount, :date) }
7
+ it { should be_embedded_in(:tracked_card) }
8
+ it { should embed_many(:members) }
9
+
10
+ describe "validation" do
11
+ it { should validate_presence_of(:amount) }
12
+ it { should validate_presence_of(:date) }
13
+ it { should validate_presence_of(:members) }
14
+ end
15
+
16
+ %w{piero tommaso tom ugo}.each do |username|
17
+ let(username.to_sym) { Member.new(username: username) }
18
+ end
19
+
20
+ describe "#amount_per_member" do
21
+ it "counts the amount spent per single member" do
22
+ effort = Effort.new(amount: 6, members: [piero, tommaso], date: Date.parse("2012-11-09"), )
23
+ effort.amount_per_member.should == 3
24
+ end
25
+ end
26
+
27
+ describe "equality" do
28
+ it "is equal to another effort with same amount, date and members" do
29
+ effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [piero, tommaso])
30
+ same_effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [piero, tommaso])
31
+ yet_same_effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [tommaso, piero])
32
+
33
+ effort.should == same_effort
34
+ effort.should == yet_same_effort
35
+ end
36
+
37
+ it "is not equal when amount differs" do
38
+ effort = Effort.new(amount: 3, date: Date.today, members: [tom, ugo])
39
+ another_effort = Effort.new(amount: 1, date: Date.today, members: [tom, ugo])
40
+
41
+ effort.should_not == another_effort
42
+ end
43
+
44
+ it "is not equal when date differs" do
45
+ effort = Effort.new(amount: 3, date: Date.parse("2012-11-09"), members: [tom, ugo])
46
+ another_effort = Effort.new(amount: 3, date: Date.parse("2011-10-08"), members: [tom, ugo])
47
+
48
+ effort.should_not == another_effort
49
+ end
50
+
51
+ it "is not equal when members differ" do
52
+ effort = Effort.new(amount: 3, date: Date.today, members: [tom, ugo])
53
+ another_effort = Effort.new(amount: 3, date: Date.today, members: [piero, ugo])
54
+
55
+ effort.should_not == another_effort
56
+ end
57
+
58
+ end
59
+ end
@@ -0,0 +1,38 @@
1
+ require 'spec_helper'
2
+ require 'mongoid-rspec'
3
+
4
+ describe Estimate do
5
+
6
+ it { should have_fields(:amount, :date) }
7
+ it { should be_embedded_in(:tracked_card) }
8
+
9
+ describe "validation" do
10
+ it { should validate_presence_of(:amount) }
11
+ it { should validate_presence_of(:date) }
12
+ end
13
+
14
+ describe "equality" do
15
+
16
+ it "is equal to another estimate with same amount and date" do
17
+ estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
18
+ same_estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
19
+
20
+ estimate.should == same_estimate
21
+ end
22
+
23
+ it "is not equal when amount differs" do
24
+ estimate = Estimate.new(amount: 3, date: Date.today)
25
+ another_estimate = Estimate.new(amount: 1, date: Date.today)
26
+
27
+ estimate.should_not == another_estimate
28
+ end
29
+
30
+ it "is not equal when date differs" do
31
+ estimate = Estimate.new(amount: 3, date: Date.parse("2012-11-09"))
32
+ another_estimate = Estimate.new(amount: 3, date: Date.parse("2011-10-08"))
33
+
34
+ estimate.should_not == another_estimate
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,12 @@
1
+ require 'spec_helper'
2
+ require 'trello'
3
+
4
+ describe "TrelloAuthorization" do
5
+ include TrelloAuthorize
6
+
7
+ it "authorizes connection to Trello", :needs_valid_configuration => true do
8
+ authorize_on_trello
9
+
10
+ Trello::Member.find("me").should_not be_nil
11
+ end
12
+ end