tracco 0.0.14 → 0.0.15
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +16 -0
- data/Gemfile.lock +1 -1
- data/README.md +4 -4
- data/lib/patches/trello/card.rb +3 -2
- data/lib/patches/trello/member.rb +4 -2
- data/lib/tasks/tasks.rake +2 -2
- data/lib/tracco/google_docs_exporter.rb +50 -47
- data/lib/tracco/models/effort.rb +40 -0
- data/lib/tracco/models/estimate.rb +29 -0
- data/lib/tracco/models/member.rb +64 -0
- data/lib/tracco/models/tracked_card.rb +148 -0
- data/lib/tracco/tracking/base.rb +68 -65
- data/lib/tracco/tracking/card_done_tracking.rb +9 -6
- data/lib/tracco/tracking/effort_tracking.rb +32 -29
- data/lib/tracco/tracking/estimate_tracking.rb +16 -13
- data/lib/tracco/tracking/factory.rb +18 -16
- data/lib/tracco/tracking/invalid_tracking.rb +15 -13
- data/lib/tracco/trello_tracker.rb +34 -30
- data/lib/tracco/version.rb +2 -2
- data/lib/tracco.rb +4 -4
- data/spec/factories/effort_factory.rb +2 -2
- data/spec/factories/estimate_factory.rb +1 -1
- data/spec/factories/tracked_card_factory.rb +1 -1
- data/spec/integration/trello_tracker_spec.rb +49 -47
- data/spec/models/effort_spec.rb +61 -0
- data/spec/models/estimate_spec.rb +40 -0
- data/spec/models/member_spec.rb +83 -0
- data/spec/models/tracked_card_spec.rb +467 -0
- data/spec/support/spec_helper_methods.rb +7 -1
- data/spec/tracking/card_done_tracking_spec.rb +11 -9
- data/spec/tracking/effort_tracking_spec.rb +77 -75
- data/spec/tracking/estimate_tracking_spec.rb +30 -28
- data/spec/tracking/factory_spec.rb +39 -0
- data/spec/trello_tracker_spec.rb +20 -18
- data/tracco.gemspec +1 -1
- metadata +12 -12
- data/lib/tracco/effort.rb +0 -37
- data/lib/tracco/estimate.rb +0 -26
- data/lib/tracco/member.rb +0 -61
- data/lib/tracco/tracked_card.rb +0 -145
- data/spec/effort_spec.rb +0 -59
- data/spec/estimate_spec.rb +0 -38
- data/spec/member_spec.rb +0 -81
- data/spec/tracked_card_spec.rb +0 -465
- data/spec/tracking_factory_spec.rb +0 -42
data/CHANGELOG
CHANGED
@@ -1,3 +1,19 @@
|
|
1
|
+
|
2
|
+
0.0.15 / 2013-03-01
|
3
|
+
==================
|
4
|
+
* Introducing the Tracco namespace to avoid name collisions. All mongo collections will need to be copied from tracked_cards to tracco_tracked_cards
|
5
|
+
|
6
|
+
All existing collections will need to be renamed from tracked_cards to tracco_tracked cards.
|
7
|
+
> mongo trello_effort_tracker_dev
|
8
|
+
> db.tracked_cards.copyTo("tracco_tracked_cards");
|
9
|
+
|
10
|
+
* Update README.md
|
11
|
+
* Renaming the 'tracking event' into 'tracking notification'
|
12
|
+
* Renaming the 'tracking notification' into 'tracking event'
|
13
|
+
* Moving a spec in the proper folder
|
14
|
+
* Moving the models specs in a proper spec subfolder
|
15
|
+
* Moving all the models (tracked_cards, estimates, efforts and members) in a proper 'models' folder
|
16
|
+
|
1
17
|
0.0.14 / 2013-02-27
|
2
18
|
==================
|
3
19
|
* TrackedCard#contains_effort? is considering all the card's efforts, even the muted ones, so that a muted effort won't be re-added from Trello to Tracco
|
data/Gemfile.lock
CHANGED
data/README.md
CHANGED
@@ -4,8 +4,8 @@
|
|
4
4
|
# Tracco
|
5
5
|
|
6
6
|
## What is Tracco?
|
7
|
-
Tracco is
|
8
|
-
|
7
|
+
Tracco is an effort tracker for Trello: the purpose of Tracco is to extract and track estimates and actual efforts out of the cards on your Trello boards.
|
8
|
+
All you have to do is notify all of your estimates and efforts tracked on your Trello cards using a simple conventional format.
|
9
9
|
Tracco will extract and store these estimates and actual efforts to let you mine useful key metrics (e.g. estimate errors, remaining efforts, pair programming frequencies, and so on).
|
10
10
|
|
11
11
|
## Why Tracco?
|
@@ -96,7 +96,7 @@ Or you may just create a TrelloTracker instance and execute its track method.
|
|
96
96
|
```ruby
|
97
97
|
require 'tracco'
|
98
98
|
|
99
|
-
tracker = TrelloTracker.new
|
99
|
+
tracker = Tracco::TrelloTracker.new
|
100
100
|
tracker.track
|
101
101
|
```
|
102
102
|
|
@@ -148,7 +148,7 @@ Through the following environment variables (ENV object):
|
|
148
148
|
Passing into the constructor a hash containing the auth values:
|
149
149
|
|
150
150
|
```ruby
|
151
|
-
tracker = TrelloTracker.new(
|
151
|
+
tracker = Tracco::TrelloTracker.new(
|
152
152
|
developer_public_key: "487635b55e6fe9021902fa763b4d101a",
|
153
153
|
access_token_key: "33bed56f2a12a49c9ba1c2d6ad3e2002e11a34358c3f3fe260d7fba746a06203",
|
154
154
|
tracker_username: "my_personal_tracker")
|
data/lib/patches/trello/card.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# Reopening the Trello::Card class to add some helper methods
|
1
2
|
module Trello
|
2
|
-
|
3
3
|
class Card
|
4
|
-
|
4
|
+
|
5
|
+
# Tells whether a card is in a list with a name starting with "DONE"
|
5
6
|
def in_done_column?
|
6
7
|
list_name = ""
|
7
8
|
begin
|
@@ -1,7 +1,9 @@
|
|
1
|
+
# Reopening the Trello::Member class to add some helper methods
|
1
2
|
module Trello
|
2
3
|
class Member
|
3
|
-
|
4
|
-
|
4
|
+
|
5
|
+
# Fetch all the direct notifications sent to the 'tracking user' starting from a given date
|
6
|
+
def tracking_notifications_since(starting_date)
|
5
7
|
notifications(limit:1000).select(&greater_than_or_equal_to(starting_date)).select(&tracking_notification?)
|
6
8
|
end
|
7
9
|
|
data/lib/tasks/tasks.rake
CHANGED
@@ -16,7 +16,7 @@ namespace :run do
|
|
16
16
|
args.with_defaults(starting_date: Date.today.to_s, db_env: "development")
|
17
17
|
TrelloConfiguration::Database.load_env(args.db_env)
|
18
18
|
|
19
|
-
tracker = TrelloTracker.new
|
19
|
+
tracker = Tracco::TrelloTracker.new
|
20
20
|
tracker.track(Date.parse(args.starting_date))
|
21
21
|
end
|
22
22
|
|
@@ -33,7 +33,7 @@ namespace :export do
|
|
33
33
|
args.with_defaults(db_env: "development")
|
34
34
|
TrelloConfiguration::Database.load_env(args.db_env)
|
35
35
|
|
36
|
-
exporter = GoogleDocsExporter.new(args.spreadsheet, args.worksheet)
|
36
|
+
exporter = Tracco::GoogleDocsExporter.new(args.spreadsheet, args.worksheet)
|
37
37
|
spreadsheet_url = exporter.export
|
38
38
|
|
39
39
|
puts "[DONE]".color(:green)
|
@@ -1,67 +1,70 @@
|
|
1
1
|
require "google_drive"
|
2
2
|
require 'highline/import'
|
3
3
|
|
4
|
-
|
5
|
-
|
4
|
+
module Tracco
|
5
|
+
class GoogleDocsExporter
|
6
|
+
include TrelloConfiguration
|
6
7
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
8
|
+
def initialize(spreadsheet_name, worksheet_name)
|
9
|
+
@spreadsheet_name = spreadsheet_name || "trello effort tracking"
|
10
|
+
@worksheet_name = worksheet_name || "tracking"
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
13
|
+
def export
|
14
|
+
Trello.logger.info "Running exporter from db env '#{db_environment}' to google docs '#{@spreadsheet_name.color(:green)}##{@worksheet_name.color(:green)}'..."
|
14
15
|
|
15
|
-
|
16
|
-
|
16
|
+
spreadsheet = google_docs_session.spreadsheet_by_title(@spreadsheet_name) || google_docs_session.create_spreadsheet(@spreadsheet_name)
|
17
|
+
worksheet = spreadsheet.worksheet_by_title(@worksheet_name) || spreadsheet.add_worksheet(@worksheet_name)
|
17
18
|
|
18
|
-
|
19
|
-
|
19
|
+
create_header(worksheet)
|
20
|
+
index = 2 # skip the header
|
20
21
|
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
22
|
+
cards = TrackedCard.all_tracked_cards(:method => :first_activity_date, :order => :desc)
|
23
|
+
cards.each do |card|
|
24
|
+
print ".".color(:green)
|
25
|
+
worksheet[index, columns[:user_story_id]] = card.short_id
|
26
|
+
worksheet[index, columns[:user_story_name]] = card.name
|
27
|
+
worksheet[index, columns[:start_date]] = card.working_start_date
|
28
|
+
worksheet[index, columns[:total_effort]] = card.total_effort
|
29
|
+
worksheet[index, columns[:last_estimate_error]] = card.last_estimate_error
|
30
|
+
card.estimates.each_with_index do |estimate, i|
|
31
|
+
worksheet[index, columns[:estimate]+i] = estimate.amount
|
32
|
+
end
|
33
|
+
index += 1
|
31
34
|
end
|
32
|
-
|
35
|
+
|
36
|
+
saved = worksheet.save
|
37
|
+
spreadsheet.human_url if saved
|
33
38
|
end
|
34
39
|
|
35
|
-
|
36
|
-
spreadsheet.human_url if saved
|
37
|
-
end
|
40
|
+
private
|
38
41
|
|
39
|
-
|
42
|
+
def google_docs_session(email=configuration["google_docs_username"])
|
43
|
+
@session ||= login(email)
|
44
|
+
end
|
40
45
|
|
41
|
-
|
42
|
-
|
43
|
-
|
46
|
+
def login(email)
|
47
|
+
username = ask("Enter your google docs username: ") { |q| q.default = email }
|
48
|
+
password = ask("Enter your google docs password: ") { |q| q.echo = false }
|
44
49
|
|
45
|
-
|
46
|
-
|
47
|
-
password = ask("Enter your google docs password: ") { |q| q.echo = false }
|
50
|
+
GoogleDrive.login(username, password)
|
51
|
+
end
|
48
52
|
|
49
|
-
|
50
|
-
|
53
|
+
def columns
|
54
|
+
@columns ||= {
|
55
|
+
user_story_id: 1,
|
56
|
+
user_story_name: 2,
|
57
|
+
start_date: 3,
|
58
|
+
total_effort: 4,
|
59
|
+
last_estimate_error: 5,
|
60
|
+
estimate: 6,
|
61
|
+
}
|
62
|
+
end
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
user_story_name: 2,
|
56
|
-
start_date: 3,
|
57
|
-
total_effort: 4,
|
58
|
-
last_estimate_error: 5,
|
59
|
-
estimate: 6,
|
60
|
-
}
|
61
|
-
end
|
64
|
+
def create_header(worksheet)
|
65
|
+
worksheet.update_cells(1,1, [["ID", "Story Name", "Start Date", "Total Effort (hours)", "Last estimate error (%)", "First Estimate", "2nd estimate", "3rd estimate"]])
|
66
|
+
end
|
62
67
|
|
63
|
-
def create_header(worksheet)
|
64
|
-
worksheet.update_cells(1,1, [["ID", "Story Name", "Start Date", "Total Effort (hours)", "Last estimate error (%)", "First Estimate", "2nd estimate", "3rd estimate"]])
|
65
68
|
end
|
66
69
|
|
67
70
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Tracco
|
2
|
+
class Effort
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
include Mongoid::MultiParameterAttributes
|
6
|
+
|
7
|
+
field :amount, type: BigDecimal
|
8
|
+
field :date, type: Date
|
9
|
+
field :tracking_notification_id
|
10
|
+
field :muted, type: Boolean, default: false
|
11
|
+
|
12
|
+
embeds_many :members
|
13
|
+
embedded_in :tracked_card
|
14
|
+
|
15
|
+
default_scope where(muted: false).asc(:date)
|
16
|
+
|
17
|
+
validates_presence_of :amount, :date, :members
|
18
|
+
|
19
|
+
def amount_per_member
|
20
|
+
amount / members.size
|
21
|
+
end
|
22
|
+
|
23
|
+
def include?(member)
|
24
|
+
members.include?(member)
|
25
|
+
end
|
26
|
+
|
27
|
+
def ==(other)
|
28
|
+
return true if other.equal?(self)
|
29
|
+
return false unless other.kind_of?(self.class)
|
30
|
+
|
31
|
+
amount == other.amount && date == other.date && Set.new(members) == Set.new(other.members)
|
32
|
+
end
|
33
|
+
|
34
|
+
def to_s
|
35
|
+
"[#{date}] spent #{amount} hours by #{members.map(&:at_username).join(", ")}"
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Tracco
|
2
|
+
class Estimate
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
include Mongoid::MultiParameterAttributes
|
6
|
+
|
7
|
+
field :amount, type: BigDecimal
|
8
|
+
field :date, type: Date
|
9
|
+
field :tracking_notification_id
|
10
|
+
|
11
|
+
embedded_in :tracked_card
|
12
|
+
|
13
|
+
default_scope asc(:date)
|
14
|
+
|
15
|
+
validates_presence_of :amount, :date
|
16
|
+
|
17
|
+
def ==(other)
|
18
|
+
return true if other.equal?(self)
|
19
|
+
return false unless other.kind_of?(self.class)
|
20
|
+
|
21
|
+
amount == other.amount && date == other.date
|
22
|
+
end
|
23
|
+
|
24
|
+
def to_s
|
25
|
+
"[#{date}] estimated #{amount} hours"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Tracco
|
2
|
+
class Member
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
|
6
|
+
field :trello_id
|
7
|
+
field :username
|
8
|
+
field :full_name
|
9
|
+
field :avatar_id
|
10
|
+
field :bio
|
11
|
+
field :url
|
12
|
+
|
13
|
+
embedded_in :effort
|
14
|
+
|
15
|
+
validates_presence_of :username
|
16
|
+
|
17
|
+
def self.build_from(trello_member)
|
18
|
+
trello_member_id = trello_member.id
|
19
|
+
trello_member.attributes.delete(:id)
|
20
|
+
new(trello_member.attributes.merge(trello_id: trello_member_id))
|
21
|
+
end
|
22
|
+
|
23
|
+
def at_username
|
24
|
+
"@#{username}"
|
25
|
+
end
|
26
|
+
|
27
|
+
def avatar_url
|
28
|
+
trello_member.avatar_url(size: :small)
|
29
|
+
end
|
30
|
+
|
31
|
+
def effort_spent(from_date=nil)
|
32
|
+
cards = TrackedCard.with_effort_spent_by(username)
|
33
|
+
efforts = cards.map(&:efforts).compact.flatten
|
34
|
+
efforts = efforts.select {|e| e.date >= from_date} if from_date
|
35
|
+
efforts.select { |effort| effort.include?(self) }.inject(0) { |total, effort| total + effort.amount_per_member }
|
36
|
+
end
|
37
|
+
alias_method :effort_spent_since, :effort_spent
|
38
|
+
|
39
|
+
def ==(other)
|
40
|
+
return true if other.equal?(self)
|
41
|
+
return false unless other.kind_of?(self.class)
|
42
|
+
|
43
|
+
username == other.username
|
44
|
+
end
|
45
|
+
|
46
|
+
def eql?(other)
|
47
|
+
return false unless other.instance_of?(self.class)
|
48
|
+
username == other.username
|
49
|
+
end
|
50
|
+
|
51
|
+
def hash
|
52
|
+
username.hash
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def trello_member
|
58
|
+
@trello_member ||= Trello::Member.new("id" => trello_id, "fullName" => full_name, "username" => username,
|
59
|
+
"avatarHash" => avatar_id, "bio" => bio, "url" => url)
|
60
|
+
end
|
61
|
+
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
module Tracco
|
2
|
+
class TrackedCard
|
3
|
+
include Mongoid::Document
|
4
|
+
include Mongoid::Timestamps
|
5
|
+
extend MongoidHelper
|
6
|
+
|
7
|
+
field :name
|
8
|
+
field :description
|
9
|
+
field :short_id, type: Integer
|
10
|
+
field :trello_id
|
11
|
+
field :done, type: Boolean
|
12
|
+
field :due, type: Date
|
13
|
+
field :closed, type: Boolean
|
14
|
+
field :url
|
15
|
+
field :pos
|
16
|
+
|
17
|
+
embeds_many :estimates
|
18
|
+
embeds_many :efforts
|
19
|
+
|
20
|
+
validates_presence_of :name, :short_id, :trello_id
|
21
|
+
validates_numericality_of :short_id
|
22
|
+
|
23
|
+
scope :with_effort_spent_by, ->(username){ where("efforts.members.username" => username) }
|
24
|
+
|
25
|
+
def self.find_by_trello_id(trello_id)
|
26
|
+
without_mongo_raising_errors do
|
27
|
+
find_by(trello_id: trello_id)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.update_or_create_with(trello_card)
|
32
|
+
tracked_card = find_or_create_by(trello_id: trello_card.id)
|
33
|
+
trello_card.attributes.delete(:id)
|
34
|
+
tracked_card_attributes = trello_card.attributes.merge(done: trello_card.in_done_column?)
|
35
|
+
updated_successfully = tracked_card.update_attributes(tracked_card_attributes)
|
36
|
+
return tracked_card if updated_successfully
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.all_tracked_cards(sorting_options = {})
|
40
|
+
cards = all.reject(&:no_tracking?)
|
41
|
+
cards.sort_by!(&sorting_options[:sort_by].to_sym) if sorting_options[:sort_by]
|
42
|
+
cards.reverse! if sorting_options[:order] == :desc
|
43
|
+
return cards
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.build_from(trello_card)
|
47
|
+
trello_card_id = trello_card.id
|
48
|
+
trello_card.attributes.delete(:id)
|
49
|
+
new(trello_card.attributes.merge(trello_id: trello_card_id))
|
50
|
+
end
|
51
|
+
|
52
|
+
def self.efforts_between(search_options)
|
53
|
+
condition = {}
|
54
|
+
{'$gte' => :from_date, '$lte' => :to_date}.each do |selection, option_key|
|
55
|
+
condition[selection] = search_options[option_key] if search_options[option_key]
|
56
|
+
end
|
57
|
+
|
58
|
+
where("efforts.date" => condition)
|
59
|
+
end
|
60
|
+
|
61
|
+
def status
|
62
|
+
if done?
|
63
|
+
:done
|
64
|
+
elsif efforts.empty?
|
65
|
+
:todo
|
66
|
+
else
|
67
|
+
:in_progress
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def add(tracking)
|
72
|
+
tracking.add_to(self)
|
73
|
+
end
|
74
|
+
|
75
|
+
def add!(tracking)
|
76
|
+
add(tracking) && save!
|
77
|
+
end
|
78
|
+
|
79
|
+
def contains_effort?(effort)
|
80
|
+
efforts.unscoped.any? { |e| e.tracking_notification_id == effort.tracking_notification_id }
|
81
|
+
end
|
82
|
+
|
83
|
+
def contains_estimate?(estimate)
|
84
|
+
estimates.any? { |e| e.tracking_notification_id == estimate.tracking_notification_id }
|
85
|
+
end
|
86
|
+
|
87
|
+
def no_tracking?
|
88
|
+
first_activity_date.nil?
|
89
|
+
end
|
90
|
+
|
91
|
+
def first_activity_date
|
92
|
+
[working_start_date, first_estimate_date].compact.min
|
93
|
+
end
|
94
|
+
|
95
|
+
def working_start_date
|
96
|
+
efforts.sort_by(&:date).first.date if efforts.present?
|
97
|
+
end
|
98
|
+
|
99
|
+
def first_estimate_date
|
100
|
+
estimates.sort_by(&:date).first.date if estimates.present?
|
101
|
+
end
|
102
|
+
|
103
|
+
def last_estimate_date
|
104
|
+
estimates.sort_by(&:date).last.date if estimates.present?
|
105
|
+
end
|
106
|
+
|
107
|
+
def total_effort
|
108
|
+
efforts.map(&:amount).inject(0, &:+)
|
109
|
+
end
|
110
|
+
|
111
|
+
def members
|
112
|
+
efforts.map(&:members).flatten.uniq
|
113
|
+
end
|
114
|
+
|
115
|
+
def last_estimate_error
|
116
|
+
estimate_errors.last
|
117
|
+
end
|
118
|
+
|
119
|
+
def estimate_errors
|
120
|
+
return [] if estimates.empty? || efforts.empty?
|
121
|
+
|
122
|
+
estimate_errors = []
|
123
|
+
estimates.each do |each|
|
124
|
+
estimate_errors << (100 * ((total_effort - each.amount) / each.amount * 1.0)).round(2)
|
125
|
+
end
|
126
|
+
|
127
|
+
estimate_errors
|
128
|
+
end
|
129
|
+
|
130
|
+
def trello_notifications
|
131
|
+
# TODO select all efforts, even the muted ones?
|
132
|
+
notification_ids = efforts.map(&:tracking_notification_id) | estimates.map(&:tracking_notification_id)
|
133
|
+
notification_ids.map { |id| Trello::Notification.find(id) rescue nil }.compact.sort_by(&:date)
|
134
|
+
end
|
135
|
+
|
136
|
+
def to_s
|
137
|
+
"[#{name}]. Total effort: #{total_effort}h. Estimates #{estimates.map(&:to_s)}. Efforts: #{efforts.map(&:to_s)}"
|
138
|
+
end
|
139
|
+
|
140
|
+
def ==(other)
|
141
|
+
return true if other.equal?(self)
|
142
|
+
return false unless other.kind_of?(self.class)
|
143
|
+
trello_id == other.trello_id
|
144
|
+
end
|
145
|
+
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
data/lib/tracco/tracking/base.rb
CHANGED
@@ -1,82 +1,85 @@
|
|
1
|
-
module
|
2
|
-
module
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
def date
|
24
|
-
Chronic.parse(date_as_string).to_date
|
25
|
-
end
|
1
|
+
module Tracco
|
2
|
+
module Tracking
|
3
|
+
module Base
|
4
|
+
extend Forwardable
|
5
|
+
include TrelloConfiguration
|
6
|
+
|
7
|
+
TIME_CONVERTERS = {
|
8
|
+
'h' => lambda { |estimate| estimate },
|
9
|
+
'd' => lambda { |estimate| estimate * 8 },
|
10
|
+
'g' => lambda { |estimate| estimate * 8 },
|
11
|
+
'p' => lambda { |estimate| estimate / 2 }
|
12
|
+
}
|
13
|
+
|
14
|
+
DURATION_REGEXP = '(\d+\.?\d*[phdg])'
|
15
|
+
DATE_REGEXP = /(\d{2})\.(\d{2})\.(\d{4})/
|
16
|
+
|
17
|
+
# delegate to the trello notification the member_creator method aliased as 'notifier'
|
18
|
+
def_delegator :@tracking_notification, :member_creator, :notifier
|
19
|
+
|
20
|
+
def initialize(tracking_notification)
|
21
|
+
@tracking_notification = tracking_notification
|
22
|
+
end
|
26
23
|
|
27
|
-
|
28
|
-
|
29
|
-
|
24
|
+
def date
|
25
|
+
Chronic.parse(date_as_string).to_date
|
26
|
+
end
|
30
27
|
|
31
|
-
|
28
|
+
def to_s
|
29
|
+
"[#{date}] From #{notifier.username.color(:green)}\t on card '#{trello_card.name.color(:yellow)}': #{raw_text}"
|
30
|
+
end
|
32
31
|
|
33
|
-
|
34
|
-
@tracking_notification
|
35
|
-
end
|
32
|
+
private
|
36
33
|
|
37
|
-
|
38
|
-
|
39
|
-
|
34
|
+
def tracking_notification
|
35
|
+
@tracking_notification
|
36
|
+
end
|
40
37
|
|
41
|
-
|
42
|
-
|
43
|
-
|
38
|
+
def trello_card
|
39
|
+
@trello_card ||= tracking_notification.card
|
40
|
+
end
|
44
41
|
|
45
|
-
|
46
|
-
|
47
|
-
|
42
|
+
def notification_date
|
43
|
+
Chronic.parse(tracking_notification.date).to_date
|
44
|
+
end
|
48
45
|
|
49
|
-
|
50
|
-
|
51
|
-
|
46
|
+
def raw_tracking
|
47
|
+
raw_text.gsub("@#{tracker_username}", "")
|
48
|
+
end
|
52
49
|
|
53
|
-
|
54
|
-
|
50
|
+
def raw_text
|
51
|
+
tracking_notification.data['text']
|
52
|
+
end
|
55
53
|
|
56
|
-
|
57
|
-
|
58
|
-
converter.call(Float(duration_as_string))
|
59
|
-
end
|
54
|
+
def convert_to_hours(duration_as_string)
|
55
|
+
return if duration_as_string.nil?
|
60
56
|
|
61
|
-
|
62
|
-
|
63
|
-
|
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
|
57
|
+
time_scale = duration_as_string.slice!(-1)
|
58
|
+
converter = TIME_CONVERTERS[time_scale]
|
59
|
+
converter.call(Float(duration_as_string))
|
70
60
|
end
|
71
|
-
end
|
72
61
|
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
62
|
+
def date_as_string
|
63
|
+
case raw_tracking
|
64
|
+
when DATE_REGEXP
|
65
|
+
day, month, year = raw_tracking.scan(DATE_REGEXP).flatten
|
66
|
+
"#{year}-#{month}-#{day}"
|
67
|
+
when /yesterday\s+\+#{DURATION_REGEXP}/, /\+#{DURATION_REGEXP}\s+yesterday/
|
68
|
+
(notification_date - 1).to_s
|
69
|
+
else
|
70
|
+
tracking_notification.date
|
71
|
+
end
|
77
72
|
end
|
78
73
|
|
79
|
-
|
74
|
+
def extract_match_from_raw_tracking(regexp)
|
75
|
+
extracted = nil
|
76
|
+
raw_tracking.scan(regexp) do |match|
|
77
|
+
extracted = match.first
|
78
|
+
end
|
79
|
+
|
80
|
+
extracted
|
81
|
+
end
|
80
82
|
end
|
81
83
|
end
|
84
|
+
|
82
85
|
end
|
@@ -1,10 +1,13 @@
|
|
1
|
-
module
|
2
|
-
|
3
|
-
|
1
|
+
module Tracco
|
2
|
+
module Tracking
|
3
|
+
class CardDoneTracking
|
4
|
+
include Base
|
4
5
|
|
5
|
-
|
6
|
-
|
7
|
-
|
6
|
+
def add_to(card)
|
7
|
+
card.done = true
|
8
|
+
end
|
8
9
|
|
10
|
+
end
|
9
11
|
end
|
12
|
+
|
10
13
|
end
|