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.
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
@@ -0,0 +1,467 @@
1
+ require 'spec_helper'
2
+
3
+ module Tracco
4
+ describe TrackedCard do
5
+
6
+ before(:each) do
7
+ Date.stub(:today).and_return(Date.parse("2012-11-05"))
8
+ end
9
+
10
+ subject(:card) { build(:tracked_card) }
11
+
12
+ before(:each) do
13
+ # adding a muted effort to check that won't be counted
14
+ card.efforts << build(:effort, amount: 1000, muted: true)
15
+ end
16
+
17
+ %w{piero tommaso michele}.each do |username|
18
+ let(username.to_sym) { Member.new(username: username) }
19
+ end
20
+
21
+ describe "validations" do
22
+ it "is not valid when a name is not given" do
23
+ TrackedCard.new(trello_id: "123456789", short_id: 1234).should_not be_valid
24
+ end
25
+
26
+ it "is not valid when a short_id is not given" do
27
+ TrackedCard.new(name: "any", trello_id: "123456789").should_not be_valid
28
+ end
29
+
30
+ it "is not valid when a trello_id is not given" do
31
+ TrackedCard.new(name: "any", short_id: 1234).should_not be_valid
32
+ end
33
+
34
+ it "is valid when has a name, a short id and a trello id" do
35
+ TrackedCard.new(name: "any", trello_id: "123456789", short_id: 1234).should be_valid
36
+ end
37
+ end
38
+
39
+ describe ".find_by_trello_id" do
40
+ it "finds a card given its Trello id" do
41
+ card = create(:tracked_card, trello_id: "1")
42
+ another_card = create(:tracked_card, trello_id: "2")
43
+
44
+ TrackedCard.find_by_trello_id("1").should == card
45
+ TrackedCard.find_by_trello_id("3").should == nil
46
+ end
47
+ end
48
+
49
+ describe ".with_effort_spent_by" do
50
+ it "finds all the cards worked by a given member" do
51
+ card = create(:tracked_card, efforts: [build(:effort, members: [piero, tommaso])])
52
+ another_card = create(:tracked_card, efforts: [build(:effort, members: [piero, michele])])
53
+
54
+ TrackedCard.with_effort_spent_by("piero").should =~ [card, another_card]
55
+ TrackedCard.with_effort_spent_by("tommaso").should == [card]
56
+ TrackedCard.with_effort_spent_by("michele").should == [another_card]
57
+ end
58
+ end
59
+
60
+ describe ".efforts_between" do
61
+ it "finds all the cards worked in a given date range" do
62
+ a_card = create(:tracked_card, efforts: [build(:effort, date: Date.parse("2013-01-02"))])
63
+ another_card = create(:tracked_card, efforts: [build(:effort, date: Date.parse("2013-11-03"))])
64
+ a_very_old_card = create(:tracked_card, efforts: [build(:effort, date: Date.parse("2009-11-03"))])
65
+
66
+ TrackedCard.efforts_between(from_date: Date.parse("2012-01-01")).should =~ [a_card, another_card]
67
+ TrackedCard.efforts_between(from_date: Date.parse("2013-01-01")).should =~ [a_card, another_card]
68
+ TrackedCard.efforts_between(from_date: Date.parse("2013-01-22")).should == [another_card]
69
+ TrackedCard.efforts_between(from_date: Date.parse("2014-01-22")).should == []
70
+
71
+ TrackedCard.efforts_between(from_date: Date.parse("2012-01-01"), to_date: Date.parse("2012-11-01")).should == []
72
+ TrackedCard.efforts_between(from_date: Date.parse("2012-01-01"), to_date: Date.parse("2013-02-02")).should == [a_card]
73
+ TrackedCard.efforts_between(from_date: Date.parse("2012-01-01"), to_date: Date.parse("2014-02-02")).should =~ [a_card, another_card]
74
+ end
75
+ end
76
+
77
+ describe ".all_tracked_cards" do
78
+ let!(:card) { create(:tracked_card, name: "AAA", estimates: [build(:estimate)], efforts: [build(:effort)]) }
79
+ let!(:another_card) { create(:tracked_card, name: "ZZZ", estimates: [build(:estimate)], efforts: [build(:effort)]) }
80
+ let!(:card_without_tracking) { create(:tracked_card) }
81
+
82
+ it "finds all tracked cards with a valid tracking" do
83
+ TrackedCard.all_tracked_cards.should =~ [card, another_card]
84
+ end
85
+
86
+ it "optionally sorts the cards using a given sorting method" do
87
+ card.update_attributes(name: "AAA")
88
+ another_card.update_attributes(name: "ZZZ")
89
+
90
+ TrackedCard.all_tracked_cards(:sort_by => :name).should == [card, another_card]
91
+ end
92
+
93
+ it "applies an optional sorting order" do
94
+ card.update_attributes(name: "AAA")
95
+ another_card.update_attributes(name: "ZZZ")
96
+
97
+ card_without_tracking = create(:tracked_card)
98
+
99
+ TrackedCard.all_tracked_cards(:sort_by => :name, :order => :desc).should == [another_card, card]
100
+ end
101
+
102
+ it "uses the ascending order as default sorting order option" do
103
+ card.update_attributes(name: "AAA", short_id: 44)
104
+ another_card.update_attributes(name: "ZZZ", short_id: 11)
105
+ card_without_tracking.update_attributes(short_id: 3456)
106
+
107
+ TrackedCard.all_tracked_cards(:sort_by => :short_id).should == [another_card, card]
108
+ end
109
+
110
+ end
111
+
112
+ describe ".update_or_create_with" do
113
+ let(:trello_card) { Trello::Card.new("id" => "ABC123", "name" => "a name", "idShort" => 1, "desc" => "any description") }
114
+
115
+ before(:each) do
116
+ Trello::Card.any_instance.stub(:in_done_column?)
117
+ end
118
+
119
+ it "creates a tracked card on a given trello card" do
120
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
121
+
122
+ tracked_card.name.should == "a name"
123
+ tracked_card.trello_id == "ABC123"
124
+ tracked_card.short_id == 1
125
+ end
126
+
127
+ it "updates an existing tracked card on a given trello card" do
128
+ existing_card = create(:tracked_card, name: "an old name", trello_id: trello_card.id)
129
+
130
+ updated_card = TrackedCard.update_or_create_with(trello_card)
131
+
132
+ updated_card.should == existing_card
133
+ updated_card.name.should == "a name"
134
+ end
135
+
136
+ it "is nil when the trello card is not valid" do
137
+ invalid_trello_card = Trello::Card.new("id" => nil, "name" => nil)
138
+
139
+ TrackedCard.update_or_create_with(invalid_trello_card).should be_nil
140
+ TrackedCard.all.should be_empty
141
+ end
142
+
143
+ it "tracks the card as done when the original trello card is moved in a DONE column" do
144
+ trello_card.stub(:in_done_column?).and_return(true)
145
+
146
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
147
+
148
+ tracked_card.should be_done
149
+ end
150
+
151
+ it "tracks the card as NOT done when the original trello card is moved in a column different from DONE" do
152
+ trello_card.stub(:in_done_column?).and_return(false)
153
+
154
+ tracked_card = TrackedCard.update_or_create_with(trello_card)
155
+
156
+ tracked_card.should_not be_done
157
+ end
158
+
159
+ end
160
+
161
+ describe ".build_from" do
162
+ it "builds a TrackedCard from a Trello Card" do
163
+ tracked_card = TrackedCard.build_from(Trello::Card.new("name" => "a name", "desc" => "any description"))
164
+
165
+ tracked_card.name.should == "a name"
166
+ tracked_card.description.should == "any description"
167
+ end
168
+
169
+ it "takes the Trello Card id and set it as trello_id" do
170
+ tracked_card = TrackedCard.build_from(Trello::Card.new("id" => "abc123", "name" => "a name", "desc" => "any description"))
171
+
172
+ tracked_card.id.should_not == "abc123"
173
+ tracked_card.trello_id.should == "abc123"
174
+ end
175
+
176
+ end
177
+
178
+ describe "card with muted effort" do
179
+ it "fetches only non-muted efforts" do
180
+ card = create(:tracked_card, efforts: [build(:effort, muted: false)])
181
+ card_with_muted_effort = create(:tracked_card, efforts: [build(:effort, muted: true)])
182
+
183
+ TrackedCard.should have(2).cards
184
+
185
+ card.efforts.should have(1).effort
186
+
187
+ card_with_muted_effort.efforts.should be_empty
188
+ card_with_muted_effort.efforts.unscoped.should have(1).effort
189
+ end
190
+
191
+ it "skips muted effort when computing the total effort on the card" do
192
+ card.efforts << build(:effort, amount: 3, muted: true)
193
+ card.efforts << build(:effort, amount: 5, muted: false)
194
+
195
+ card.total_effort.should == 5
196
+ end
197
+ end
198
+
199
+ it "has no estimates and efforts initially" do
200
+ card.estimates.should be_empty
201
+ card.efforts.should be_empty
202
+ end
203
+
204
+ it "is possible to add estimates" do
205
+ card.estimates << build(:estimate) << build(:estimate)
206
+ card.estimates.should have(2).estimates
207
+ end
208
+
209
+ it "is possible to add efforts" do
210
+ card.efforts << build(:effort) << build(:effort)
211
+ card.efforts.should have(2).efforts
212
+ end
213
+
214
+ describe "#trello_notifications" do
215
+ let(:first_notification) { stub("notification1", date: Date.yesterday) }
216
+ let(:second_notification) { stub("notification1", date: Date.today) }
217
+
218
+ it "fetches all the card notifications from trello" do
219
+ card.estimates << Estimate.new(tracking_notification_id: "xyz987", amount: 5, date: Date.yesterday)
220
+ card.efforts << Effort.new(tracking_notification_id: "abc123", amount: 3, date: Date.today, members: [piero])
221
+
222
+ Trello::Notification.should_receive(:find).with("xyz987").and_return(second_notification)
223
+ Trello::Notification.should_receive(:find).with("abc123").and_return(first_notification)
224
+
225
+ card.trello_notifications.should == [first_notification, second_notification]
226
+ end
227
+
228
+ it "skips the notifications not found" do
229
+ card.estimates << build(:estimate, tracking_notification_id: "unexisting_id")
230
+ card.efforts << build(:effort, tracking_notification_id: "first_notification_id")
231
+
232
+ Trello::Notification.should_receive(:find).with("unexisting_id").and_raise(Trello::Error)
233
+ Trello::Notification.should_receive(:find).with("first_notification_id").and_return(first_notification)
234
+
235
+ card.trello_notifications.should == [first_notification]
236
+ end
237
+
238
+ end
239
+
240
+ describe "equality" do
241
+ it "is equal to another TrelloCard when the trello id is the same" do
242
+ card = TrackedCard.new(name: "a name", trello_id: "123456789")
243
+ same_card = TrackedCard.new(name: "a name", trello_id: "123456789")
244
+ another_card = TrackedCard.new(name: "a name", trello_id: "987654321")
245
+
246
+ card.should == same_card
247
+ card.should_not == another_card
248
+ end
249
+ end
250
+
251
+ describe "#add" do
252
+ let(:card) { build(:tracked_card) }
253
+ let(:estimate_tracking) { Tracking::EstimateTracking.new(create_notification(data: { 'text' => "@trackinguser [1h]" })) }
254
+
255
+ it "adds an estimate from a tracking estimate notification" do
256
+ card.add(estimate_tracking)
257
+ card.estimates.should have(1).estimate
258
+ end
259
+
260
+ it "adds an estimate only once" do
261
+ card.add(estimate_tracking)
262
+ card.add(estimate_tracking)
263
+
264
+ card.estimates.should have(1).estimate
265
+ end
266
+
267
+ it "is done when has a DONE notification" do
268
+ card.should_not be_done
269
+
270
+ card.add(Tracking::CardDoneTracking.new(stub(:notification)))
271
+ card.should be_done
272
+ end
273
+
274
+ end
275
+
276
+ describe "#add!" do
277
+ let(:card) { build(:tracked_card) }
278
+
279
+ it "saves the tracked card after adding the tracking" do
280
+ any_tracking = Tracking::EstimateTracking.new(create_notification(data: { 'text' => "@trackinguser [1h]" }))
281
+
282
+ card.add!(any_tracking)
283
+ card.reload.should_not be_nil
284
+ end
285
+ end
286
+
287
+ describe "#total_effort" do
288
+ it "is zero when there's no effort" do
289
+ card.total_effort.should == 0
290
+ end
291
+
292
+ it "computes the total effort on the card" do
293
+ card.efforts << build(:effort, amount: 3)
294
+ card.efforts << build(:effort, amount: 5)
295
+
296
+ card.total_effort.should == 3+5
297
+ end
298
+ end
299
+
300
+ describe "#last_estimate_error" do
301
+
302
+ it "is nil when the card has no estimate" do
303
+ card.efforts << build(:effort, amount: 5)
304
+
305
+ card.last_estimate_error.should be_nil
306
+ end
307
+
308
+ it "is nil when the card has no effort" do
309
+ card.efforts << build(:estimate)
310
+
311
+ card.last_estimate_error.should be_nil
312
+ end
313
+
314
+ it "is zero when actual effort is equal to estimate" do
315
+ card.estimates << build(:estimate, amount: 5)
316
+ card.efforts << build(:effort, amount: 5)
317
+
318
+ card.last_estimate_error.should == 0.0
319
+ end
320
+
321
+ it "is 100 when the actual effort is twice the given estimate" do
322
+ card.estimates << build(:estimate, amount: 5)
323
+ card.efforts << build(:effort, amount: 10)
324
+
325
+ card.last_estimate_error.should == 100.0
326
+ end
327
+
328
+ it "is -50 when the actual effort is half of the given estimate" do
329
+ card.estimates << build(:estimate, amount: 10)
330
+ card.efforts << build(:effort, amount: 5)
331
+
332
+ card.last_estimate_error.should == -50.0
333
+ end
334
+
335
+ it "is rounded with two decimal digits" do
336
+ card.estimates << build(:estimate, amount: 3)
337
+ card.efforts << build(:effort, amount: 5)
338
+
339
+ card.last_estimate_error.should == 66.67
340
+ end
341
+
342
+ describe "#estimate_errors" do
343
+
344
+ it "collects all the estimate errors against the actual effort" do
345
+ card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
346
+ card.efforts << Effort.new(amount: 10, date: Date.yesterday, members: [tommaso])
347
+
348
+ card.estimates << Estimate.new(amount: 10, date: Date.today)
349
+ card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
350
+
351
+ card.estimate_errors.should == [200.0, 50.0]
352
+ end
353
+ end
354
+
355
+ end
356
+
357
+ describe "#members" do
358
+ it "lists all the members which spent some effort on the card" do
359
+ card.efforts << build(:effort, members: [piero, tommaso])
360
+ card.efforts << build(:effort, members: [tommaso])
361
+ card.efforts << build(:effort, members: [tommaso, michele])
362
+
363
+ card.members.should == [piero, tommaso, michele]
364
+ end
365
+ end
366
+
367
+ describe "#working_start_date" do
368
+
369
+ it "is the date of the first effort spent on the card" do
370
+ card.efforts << build(:effort, date: Date.today)
371
+ card.efforts << build(:effort, date: Date.yesterday)
372
+ card.efforts << build(:effort, date: Date.tomorrow)
373
+
374
+ card.working_start_date.should == Date.yesterday
375
+ end
376
+ end
377
+
378
+ describe "#first_activity_date" do
379
+ it "is the date of the first effort or estimate given on the card" do
380
+ card.estimates << build(:estimate, date: Date.yesterday)
381
+ card.estimates << build(:estimate, date: Date.yesterday.prev_day)
382
+ card.estimates << build(:estimate, date: Date.tomorrow)
383
+
384
+ card.efforts << build(:effort, date: Date.yesterday)
385
+ card.efforts << build(:effort, date: Date.today)
386
+
387
+ card.first_activity_date.should == Date.yesterday.prev_day
388
+ end
389
+ end
390
+
391
+ describe "#first_estimate_date" do
392
+ it "is the date of the first estimate given on the card" do
393
+ card.estimates << build(:estimate, date: Date.tomorrow)
394
+ card.estimates << build(:estimate, date: Date.yesterday.prev_day)
395
+ card.estimates << build(:estimate, date: Date.today)
396
+
397
+ card.first_estimate_date.should == Date.yesterday.prev_day
398
+ end
399
+ end
400
+
401
+ describe "#last_estimate_date" do
402
+ it "is the date of the last estimate given on the card" do
403
+ card.estimates << build(:estimate, date: Date.yesterday)
404
+ card.estimates << build(:estimate, date: Date.tomorrow)
405
+ card.estimates << build(:estimate, date: Date.today)
406
+
407
+ card.last_estimate_date.should == Date.tomorrow
408
+ end
409
+ end
410
+
411
+ describe "#no_tracking?" do
412
+ it "is false when there's no effort or estimate tracked on the card" do
413
+ card.no_tracking?.should be_true
414
+
415
+ card.estimates << build(:estimate, date: Date.yesterday)
416
+ card.no_tracking?.should be_false
417
+ end
418
+ end
419
+
420
+ describe "#contains_effort?" do
421
+ it "counts regular efforts" do
422
+ effort = build(:effort, amount: 1, muted: false)
423
+ card.efforts << effort
424
+
425
+ card.contains_effort?(effort).should be_true
426
+ end
427
+
428
+ it "counts even muted efforts" do
429
+ muted_effort = build(:effort, amount: 1, muted: true)
430
+ card.efforts << muted_effort
431
+
432
+ card.contains_effort?(muted_effort).should be_true
433
+ end
434
+
435
+ end
436
+
437
+ describe "#to_s" do
438
+ it "describes the card as a string" do
439
+ card = TrackedCard.new(name: "A Story Name")
440
+ card.estimates << Estimate.new(amount: 5, date: Date.today)
441
+ card.efforts << Effort.new(amount: 3, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
442
+ card.efforts << Effort.new(amount: 6, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
443
+
444
+ card.to_s.should == %Q{[A Story Name]. Total effort: 9.0h. Estimates ["[2012-11-05] estimated 5.0 hours"]. Efforts: ["[2012-11-05] spent 3.0 hours by @piero, @tommaso", "[2012-11-05] spent 6.0 hours by @piero, @tommaso"]}
445
+ end
446
+ end
447
+
448
+ describe "#status" do
449
+ it "is done when is done" do
450
+ done_card = TrackedCard.new(done: true)
451
+ done_card.status.should == :done
452
+
453
+ done_card.efforts << build(:effort)
454
+ done_card.status.should == :done
455
+ end
456
+
457
+ it "is todo when no effort has been spent on the card" do
458
+ card = TrackedCard.new
459
+ card.status.should == :todo
460
+
461
+ card.efforts << build(:effort)
462
+ card.status.should == :in_progress
463
+ end
464
+ end
465
+
466
+ end
467
+ end
@@ -1,3 +1,9 @@
1
+ TIME_MEASUREMENTS = {
2
+ hours: 'h',
3
+ days: 'd',
4
+ giorni: 'g',
5
+ pomodori: 'p'
6
+ }
1
7
 
2
8
  def unrecognized_notification
3
9
  create_notification(data: { 'text' => '@trackinguser hi there!' })
@@ -16,7 +22,7 @@ def create_effort(time_measurement)
16
22
  end
17
23
 
18
24
  def with(notification)
19
- tracking = Tracking::Factory.build_from(notification)
25
+ tracking = Tracco::Tracking::Factory.build_from(notification)
20
26
  yield(tracking)
21
27
  end
22
28
 
@@ -1,18 +1,20 @@
1
1
  require 'spec_helper'
2
2
 
3
- module Tracking
4
- describe CardDoneTracking do
3
+ module Tracco
4
+ module Tracking
5
+ describe CardDoneTracking do
5
6
 
6
- describe "#add_to" do
7
- it "marks the card as done" do
8
- card = TrackedCard.new
7
+ describe "#add_to" do
8
+ it "marks the card as done" do
9
+ card = TrackedCard.new
9
10
 
10
- done_tracking = Tracking::Factory.build_from(notification_with_message("DONE"))
11
- done_tracking.add_to(card)
11
+ done_tracking = Tracking::Factory.build_from(notification_with_message("DONE"))
12
+ done_tracking.add_to(card)
12
13
 
13
- card.done?.should be_true
14
+ card.done?.should be_true
15
+ end
14
16
  end
15
- end
16
17
 
18
+ end
17
19
  end
18
20
  end