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
@@ -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