tracco 0.0.14 → 0.0.15

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/CHANGELOG +16 -0
  2. data/Gemfile.lock +1 -1
  3. data/README.md +4 -4
  4. data/lib/patches/trello/card.rb +3 -2
  5. data/lib/patches/trello/member.rb +4 -2
  6. data/lib/tasks/tasks.rake +2 -2
  7. data/lib/tracco/google_docs_exporter.rb +50 -47
  8. data/lib/tracco/models/effort.rb +40 -0
  9. data/lib/tracco/models/estimate.rb +29 -0
  10. data/lib/tracco/models/member.rb +64 -0
  11. data/lib/tracco/models/tracked_card.rb +148 -0
  12. data/lib/tracco/tracking/base.rb +68 -65
  13. data/lib/tracco/tracking/card_done_tracking.rb +9 -6
  14. data/lib/tracco/tracking/effort_tracking.rb +32 -29
  15. data/lib/tracco/tracking/estimate_tracking.rb +16 -13
  16. data/lib/tracco/tracking/factory.rb +18 -16
  17. data/lib/tracco/tracking/invalid_tracking.rb +15 -13
  18. data/lib/tracco/trello_tracker.rb +34 -30
  19. data/lib/tracco/version.rb +2 -2
  20. data/lib/tracco.rb +4 -4
  21. data/spec/factories/effort_factory.rb +2 -2
  22. data/spec/factories/estimate_factory.rb +1 -1
  23. data/spec/factories/tracked_card_factory.rb +1 -1
  24. data/spec/integration/trello_tracker_spec.rb +49 -47
  25. data/spec/models/effort_spec.rb +61 -0
  26. data/spec/models/estimate_spec.rb +40 -0
  27. data/spec/models/member_spec.rb +83 -0
  28. data/spec/models/tracked_card_spec.rb +467 -0
  29. data/spec/support/spec_helper_methods.rb +7 -1
  30. data/spec/tracking/card_done_tracking_spec.rb +11 -9
  31. data/spec/tracking/effort_tracking_spec.rb +77 -75
  32. data/spec/tracking/estimate_tracking_spec.rb +30 -28
  33. data/spec/tracking/factory_spec.rb +39 -0
  34. data/spec/trello_tracker_spec.rb +20 -18
  35. data/tracco.gemspec +1 -1
  36. metadata +12 -12
  37. data/lib/tracco/effort.rb +0 -37
  38. data/lib/tracco/estimate.rb +0 -26
  39. data/lib/tracco/member.rb +0 -61
  40. data/lib/tracco/tracked_card.rb +0 -145
  41. data/spec/effort_spec.rb +0 -59
  42. data/spec/estimate_spec.rb +0 -38
  43. data/spec/member_spec.rb +0 -81
  44. data/spec/tracked_card_spec.rb +0 -465
  45. 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
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- tracco (0.0.14)
4
+ tracco (0.0.15)
5
5
  bson_ext
6
6
  chronic
7
7
  google_drive
data/README.md CHANGED
@@ -4,8 +4,8 @@
4
4
  # Tracco
5
5
 
6
6
  ## What is Tracco?
7
- Tracco is a Trello effort tracker: the purpose of Tracco is to extract and track estimates and actual efforts out of the cards on your Trello boards.
8
- You simply notify all of your estimates and efforts tracked on your Trello cards using a conventional format.
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")
@@ -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
- # Reopening the Trello::Card class to add a method detecting a card moved in a DONE column
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
- # Reopening the Trello::Member class to add a notifications helper method
4
- def notifications_since(starting_date)
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
- class GoogleDocsExporter
5
- include TrelloConfiguration
4
+ module Tracco
5
+ class GoogleDocsExporter
6
+ include TrelloConfiguration
6
7
 
7
- def initialize(spreadsheet_name, worksheet_name)
8
- @spreadsheet_name = spreadsheet_name || "trello effort tracking"
9
- @worksheet_name = worksheet_name || "tracking"
10
- end
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
- def export
13
- Trello.logger.info "Running exporter from db env '#{db_environment}' to google docs '#{@spreadsheet_name.color(:green)}##{@worksheet_name.color(:green)}'..."
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
- spreadsheet = google_docs_session.spreadsheet_by_title(@spreadsheet_name) || google_docs_session.create_spreadsheet(@spreadsheet_name)
16
- worksheet = spreadsheet.worksheet_by_title(@worksheet_name) || spreadsheet.add_worksheet(@worksheet_name)
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
- create_header(worksheet)
19
- index = 2 # skip the header
19
+ create_header(worksheet)
20
+ index = 2 # skip the header
20
21
 
21
- cards = TrackedCard.all_tracked_cards(:method => :first_activity_date, :order => :desc)
22
- cards.each do |card|
23
- print ".".color(:green)
24
- worksheet[index, columns[:user_story_id]] = card.short_id
25
- worksheet[index, columns[:user_story_name]] = card.name
26
- worksheet[index, columns[:start_date]] = card.working_start_date
27
- worksheet[index, columns[:total_effort]] = card.total_effort
28
- worksheet[index, columns[:last_estimate_error]] = card.last_estimate_error
29
- card.estimates.each_with_index do |estimate, i|
30
- worksheet[index, columns[:estimate]+i] = estimate.amount
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
- index += 1
35
+
36
+ saved = worksheet.save
37
+ spreadsheet.human_url if saved
33
38
  end
34
39
 
35
- saved = worksheet.save
36
- spreadsheet.human_url if saved
37
- end
40
+ private
38
41
 
39
- private
42
+ def google_docs_session(email=configuration["google_docs_username"])
43
+ @session ||= login(email)
44
+ end
40
45
 
41
- def google_docs_session(email=configuration["google_docs_username"])
42
- @session ||= login(email)
43
- end
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
- def login(email)
46
- username = ask("Enter your google docs username: ") { |q| q.default = email }
47
- password = ask("Enter your google docs password: ") { |q| q.echo = false }
50
+ GoogleDrive.login(username, password)
51
+ end
48
52
 
49
- GoogleDrive.login(username, password)
50
- end
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
- def columns
53
- @columns ||= {
54
- user_story_id: 1,
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
@@ -1,82 +1,85 @@
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
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
- def to_s
28
- "[#{date}] From #{notifier.username.color(:green)}\t on card '#{trello_card.name.color(:yellow)}': #{raw_text}"
29
- end
24
+ def date
25
+ Chronic.parse(date_as_string).to_date
26
+ end
30
27
 
31
- private
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
- def tracking_notification
34
- @tracking_notification
35
- end
32
+ private
36
33
 
37
- def trello_card
38
- @trello_card ||= tracking_notification.card
39
- end
34
+ def tracking_notification
35
+ @tracking_notification
36
+ end
40
37
 
41
- def notification_date
42
- Chronic.parse(tracking_notification.date).to_date
43
- end
38
+ def trello_card
39
+ @trello_card ||= tracking_notification.card
40
+ end
44
41
 
45
- def raw_tracking
46
- raw_text.gsub("@#{tracker_username}", "")
47
- end
42
+ def notification_date
43
+ Chronic.parse(tracking_notification.date).to_date
44
+ end
48
45
 
49
- def raw_text
50
- tracking_notification.data['text']
51
- end
46
+ def raw_tracking
47
+ raw_text.gsub("@#{tracker_username}", "")
48
+ end
52
49
 
53
- def convert_to_hours(duration_as_string)
54
- return if duration_as_string.nil?
50
+ def raw_text
51
+ tracking_notification.data['text']
52
+ end
55
53
 
56
- time_scale = duration_as_string.slice!(-1)
57
- converter = TIME_CONVERTERS[time_scale]
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
- 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
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
- def extract_match_from_raw_tracking(regexp)
74
- extracted = nil
75
- raw_tracking.scan(regexp) do |match|
76
- extracted = match.first
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
- extracted
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 Tracking
2
- class CardDoneTracking
3
- include Base
1
+ module Tracco
2
+ module Tracking
3
+ class CardDoneTracking
4
+ include Base
4
5
 
5
- def add_to(card)
6
- card.done = true
7
- end
6
+ def add_to(card)
7
+ card.done = true
8
+ end
8
9
 
10
+ end
9
11
  end
12
+
10
13
  end