tracco 0.0.14 → 0.0.15
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/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
|