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.
- data/.gitignore +28 -0
- data/.rspec +3 -0
- data/.rvmrc.template +2 -0
- data/.travis.yml +20 -0
- data/CHANGELOG +32 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +110 -0
- data/LICENSE.txt +15 -0
- data/README.md +292 -0
- data/Rakefile +8 -0
- data/config/config.template.yml +6 -0
- data/config/config.yml.trackinguser_for_test +7 -0
- data/config/mongoid.template.yml +24 -0
- data/lib/patches/trello/card.rb +19 -0
- data/lib/patches/trello/member.rb +20 -0
- data/lib/startup_trello.rb +2 -0
- data/lib/tasks/rspec.rake +17 -0
- data/lib/tasks/tasks.rake +55 -0
- data/lib/tracco.rb +30 -0
- data/lib/tracco/effort.rb +35 -0
- data/lib/tracco/estimate.rb +25 -0
- data/lib/tracco/google_docs_exporter.rb +67 -0
- data/lib/tracco/member.rb +61 -0
- data/lib/tracco/mongoid_helper.rb +13 -0
- data/lib/tracco/tracked_card.rb +121 -0
- data/lib/tracco/tracking/base.rb +82 -0
- data/lib/tracco/tracking/card_done_tracking.rb +10 -0
- data/lib/tracco/tracking/effort_tracking.rb +45 -0
- data/lib/tracco/tracking/estimate_tracking.rb +23 -0
- data/lib/tracco/tracking/invalid_tracking.rb +19 -0
- data/lib/tracco/tracking_factory.rb +22 -0
- data/lib/tracco/trello_authorize.rb +16 -0
- data/lib/tracco/trello_configuration.rb +39 -0
- data/lib/tracco/trello_tracker.rb +44 -0
- data/lib/tracco/version.rb +3 -0
- data/script/ci/before_script.sh +1 -0
- data/script/ci/run_build.sh +2 -0
- data/script/crontab.template +8 -0
- data/script/mate.sh +1 -0
- data/spec/effort_spec.rb +59 -0
- data/spec/estimate_spec.rb +38 -0
- data/spec/integration/trello_authorization_spec.rb +12 -0
- data/spec/integration/trello_tracker_spec.rb +66 -0
- data/spec/member_spec.rb +81 -0
- data/spec/patches/trello/card_spec.rb +25 -0
- data/spec/spec_helper.rb +66 -0
- data/spec/support/database_cleaner.rb +12 -0
- data/spec/tracked_card_spec.rb +336 -0
- data/spec/tracking/card_done_tracking_spec.rb +18 -0
- data/spec/tracking/effort_tracking_spec.rb +114 -0
- data/spec/tracking/estimate_tracking_spec.rb +44 -0
- data/spec/tracking_factory_spec.rb +42 -0
- data/spec/trello_authorize_spec.rb +65 -0
- data/spec/trello_configuration_spec.rb +43 -0
- data/spec/trello_tracker_spec.rb +26 -0
- data/tracco.gemspec +41 -0
- data/tracco.sublime-project +10 -0
- 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,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 @@
|
|
1
|
+
cp -f config/mongoid.template.yml config/mongoid.yml
|
@@ -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
|
data/script/mate.sh
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
mate $(ls -a | grep -v '\.\.$' | grep -v '\.$' | grep -v '\.git$'| grep -v '\.DS_Store'| grep -v '\.tmtags' | grep -v '\tags')
|
data/spec/effort_spec.rb
ADDED
@@ -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
|