trello_effort_tracker 0.0.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +25 -0
- data/.rspec +3 -0
- data/.rvmrc.template +2 -0
- data/.travis.yml +21 -0
- data/Gemfile +22 -0
- data/Gemfile.lock +101 -0
- data/LICENSE.txt +15 -0
- data/README.md +159 -0
- data/Rakefile +72 -0
- data/config/config.template.yml +7 -0
- data/config/mongoid.template.yml +24 -0
- data/lib/patches/trello/member.rb +20 -0
- data/lib/startup_trello.rb +2 -0
- data/lib/trello_effort_tracker/effort.rb +27 -0
- data/lib/trello_effort_tracker/estimate.rb +25 -0
- data/lib/trello_effort_tracker/google_docs_exporter.rb +67 -0
- data/lib/trello_effort_tracker/member.rb +46 -0
- data/lib/trello_effort_tracker/mongoid_helper.rb +13 -0
- data/lib/trello_effort_tracker/tracked_card.rb +103 -0
- data/lib/trello_effort_tracker/tracking.rb +130 -0
- data/lib/trello_effort_tracker/trello_authorize.rb +32 -0
- data/lib/trello_effort_tracker/trello_configuration.rb +33 -0
- data/lib/trello_effort_tracker/trello_tracker.rb +48 -0
- data/lib/trello_effort_tracker/version.rb +3 -0
- data/lib/trello_effort_tracker.rb +23 -0
- data/script/ci/before_script.sh +1 -0
- data/script/ci/run_build.sh +2 -0
- data/script/crontab.template +8 -0
- data/script/mate.sh +1 -0
- data/spec/effort_spec.rb +52 -0
- data/spec/estimate_spec.rb +38 -0
- data/spec/integration/trello_authorization_spec.rb +12 -0
- data/spec/member_spec.rb +45 -0
- data/spec/spec_helper.rb +21 -0
- data/spec/tracked_card_spec.rb +267 -0
- data/spec/tracking_spec.rb +236 -0
- data/spec/trello_authorize_spec.rb +71 -0
- data/spec/trello_configuration_spec.rb +43 -0
- data/trello_effort_tracker.gemspec +19 -0
- metadata +86 -0
@@ -0,0 +1,267 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe TrackedCard do
|
4
|
+
|
5
|
+
before(:each) do
|
6
|
+
Date.stub(:today).and_return(Date.parse("2012-11-05"))
|
7
|
+
TrackedCard.delete_all
|
8
|
+
end
|
9
|
+
|
10
|
+
after(:each) do
|
11
|
+
TrackedCard.delete_all
|
12
|
+
end
|
13
|
+
|
14
|
+
subject(:card) { TrackedCard.new(name: "any", short_id: 1234, trello_id: "123123") }
|
15
|
+
|
16
|
+
%w{piero tommaso michele}.each do |username|
|
17
|
+
let(username.to_sym) { Member.new(username: username) }
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "validations" do
|
21
|
+
it "is not valid when a name is not given" do
|
22
|
+
TrackedCard.new(trello_id: "123456789", short_id: 1234).should_not be_valid
|
23
|
+
end
|
24
|
+
|
25
|
+
it "is not valid when a short_id is not given" do
|
26
|
+
TrackedCard.new(name: "any", trello_id: "123456789").should_not be_valid
|
27
|
+
end
|
28
|
+
|
29
|
+
it "is not valid when a trello_id is not given" do
|
30
|
+
TrackedCard.new(name: "any", short_id: 1234).should_not be_valid
|
31
|
+
end
|
32
|
+
|
33
|
+
it "is valid when has a name, a short id and a trello id" do
|
34
|
+
TrackedCard.new(name: "any", trello_id: "123456789", short_id: 1234).should be_valid
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe ".find_by_trello_id" do
|
39
|
+
it "find a card given its Trello id" do
|
40
|
+
card = TrackedCard.create(name: "any card", short_id: 1234, trello_id: "1")
|
41
|
+
another_card = TrackedCard.create(name: "another card", short_id: 3456, trello_id: "2")
|
42
|
+
|
43
|
+
TrackedCard.find_by_trello_id("1").should == card
|
44
|
+
TrackedCard.find_by_trello_id("3").should == nil
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
describe ".update_or_create_with" do
|
49
|
+
let(:trello_card) { Trello::Card.new("id" => "ABC123", "name" => "a name", "idShort" => 1, "desc" => "any description") }
|
50
|
+
|
51
|
+
it "creates a tracked card on a given trello card" do
|
52
|
+
tracked_card = TrackedCard.update_or_create_with(trello_card)
|
53
|
+
|
54
|
+
tracked_card.name.should == "a name"
|
55
|
+
tracked_card.trello_id == "ABC123"
|
56
|
+
tracked_card.short_id == 1
|
57
|
+
end
|
58
|
+
|
59
|
+
it "updates an existing tracked card on a given trello card" do
|
60
|
+
existing_card = TrackedCard.create(name: "an old name", short_id: 1234, trello_id: trello_card.id)
|
61
|
+
|
62
|
+
updated_card = TrackedCard.update_or_create_with(trello_card)
|
63
|
+
|
64
|
+
updated_card.should == existing_card
|
65
|
+
updated_card.name.should == "a name"
|
66
|
+
end
|
67
|
+
|
68
|
+
it "is nil when the trello card is not valid" do
|
69
|
+
invalid_trello_card = Trello::Card.new("id" => nil, "name" => nil)
|
70
|
+
|
71
|
+
TrackedCard.update_or_create_with(invalid_trello_card).should be_nil
|
72
|
+
TrackedCard.all.should be_empty
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
77
|
+
describe ".build_from" do
|
78
|
+
it "builds a TrackedCard from a Trello Card" do
|
79
|
+
tracked_card = TrackedCard.build_from(Trello::Card.new("name" => "a name", "desc" => "any description"))
|
80
|
+
|
81
|
+
tracked_card.name.should == "a name"
|
82
|
+
tracked_card.description.should == "any description"
|
83
|
+
end
|
84
|
+
|
85
|
+
it "takes the Trello Card id and set it as trello_id" do
|
86
|
+
tracked_card = TrackedCard.build_from(Trello::Card.new("id" => "abc123", "name" => "a name", "desc" => "any description"))
|
87
|
+
|
88
|
+
tracked_card.id.should_not == "abc123"
|
89
|
+
tracked_card.trello_id.should == "abc123"
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
93
|
+
|
94
|
+
it "has no estimates and efforts initially" do
|
95
|
+
card.estimates.should be_empty
|
96
|
+
card.efforts.should be_empty
|
97
|
+
end
|
98
|
+
|
99
|
+
it "is possible to add estimates" do
|
100
|
+
card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
|
101
|
+
card.estimates << Estimate.new(amount: 12, date: Date.today)
|
102
|
+
|
103
|
+
card.estimates.should have(2).estimates
|
104
|
+
end
|
105
|
+
|
106
|
+
it "is possible to add efforts" do
|
107
|
+
card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
|
108
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
109
|
+
|
110
|
+
card.efforts.should have(2).efforts
|
111
|
+
end
|
112
|
+
|
113
|
+
describe "equality" do
|
114
|
+
it "is equal to another TrelloCard when the trello id is the same" do
|
115
|
+
card = TrackedCard.new(name: "a name", trello_id: "123456789")
|
116
|
+
same_card = TrackedCard.new(name: "a name", trello_id: "123456789")
|
117
|
+
another_card = TrackedCard.new(name: "a name", trello_id: "987654321")
|
118
|
+
|
119
|
+
card.should == same_card
|
120
|
+
card.should_not == another_card
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "#total_effort" do
|
125
|
+
it "is zero when there's no effort" do
|
126
|
+
card.total_effort.should == 0
|
127
|
+
end
|
128
|
+
|
129
|
+
it "computes the total effort on the card" do
|
130
|
+
card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
|
131
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
132
|
+
|
133
|
+
card.total_effort.should == 3+5
|
134
|
+
end
|
135
|
+
end
|
136
|
+
|
137
|
+
describe "#last_estimate_error" do
|
138
|
+
it "is nil when the card has no estimate" do
|
139
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
140
|
+
|
141
|
+
card.last_estimate_error.should be_nil
|
142
|
+
end
|
143
|
+
|
144
|
+
it "is nil when the card has no effort" do
|
145
|
+
card.efforts << Estimate.new(amount: 5, date: Date.today)
|
146
|
+
|
147
|
+
card.last_estimate_error.should be_nil
|
148
|
+
end
|
149
|
+
|
150
|
+
it "is zero when actual effort is equal to estimate" do
|
151
|
+
card.estimates << Estimate.new(amount: 5, date: Date.today)
|
152
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
153
|
+
|
154
|
+
card.last_estimate_error.should == 0.0
|
155
|
+
end
|
156
|
+
|
157
|
+
it "is 100 when the actual effort is twice the given estimate" do
|
158
|
+
card.estimates << Estimate.new(amount: 5, date: Date.today)
|
159
|
+
card.efforts << Effort.new(amount: 10, date: Date.today, members: [tommaso])
|
160
|
+
|
161
|
+
card.last_estimate_error.should == 100.0
|
162
|
+
end
|
163
|
+
|
164
|
+
it "is -50 when the actual effort is half of the given estimate" do
|
165
|
+
card.estimates << Estimate.new(amount: 10, date: Date.today)
|
166
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
167
|
+
|
168
|
+
card.last_estimate_error.should == -50.0
|
169
|
+
end
|
170
|
+
|
171
|
+
it "is rounded with two decimal digits" do
|
172
|
+
card.estimates << Estimate.new(amount: 3, date: Date.today)
|
173
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
174
|
+
|
175
|
+
card.last_estimate_error.should == 66.67
|
176
|
+
end
|
177
|
+
|
178
|
+
describe "#estimate_errors" do
|
179
|
+
|
180
|
+
it "collects all the estimate errors against the actual effort" do
|
181
|
+
card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
|
182
|
+
card.efforts << Effort.new(amount: 10, date: Date.yesterday, members: [tommaso])
|
183
|
+
|
184
|
+
card.estimates << Estimate.new(amount: 10, date: Date.today)
|
185
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
186
|
+
|
187
|
+
card.estimate_errors.should == [200.0, 50.0]
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
describe "#members" do
|
194
|
+
it "lists all the members which spent some effort on the card" do
|
195
|
+
card.efforts << Effort.new(amount: 3, date: Date.today, members: [piero, tommaso])
|
196
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
197
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso, michele])
|
198
|
+
|
199
|
+
card.members.should == [piero, tommaso, michele]
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
describe "#working_start_date" do
|
204
|
+
|
205
|
+
it "is the date of the first effort spent on the card" do
|
206
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
207
|
+
card.efforts << Effort.new(amount: 3, date: Date.yesterday, members: [tommaso])
|
208
|
+
card.efforts << Effort.new(amount: 5, date: Date.tomorrow, members: [tommaso])
|
209
|
+
|
210
|
+
card.working_start_date.should == Date.yesterday
|
211
|
+
end
|
212
|
+
end
|
213
|
+
|
214
|
+
describe "#first_activity_date" do
|
215
|
+
it "is the date of the first effort or estimate given on the card" do
|
216
|
+
card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
|
217
|
+
card.estimates << Estimate.new(amount: 12, date: Date.yesterday.prev_day)
|
218
|
+
card.estimates << Estimate.new(amount: 12, date: Date.tomorrow)
|
219
|
+
|
220
|
+
card.efforts << Effort.new(amount: 3, date: Date.yesterday, members: [tommaso])
|
221
|
+
card.efforts << Effort.new(amount: 5, date: Date.today, members: [tommaso])
|
222
|
+
|
223
|
+
card.first_activity_date.should == Date.yesterday.prev_day
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
describe "#first_estimate_date" do
|
228
|
+
it "is the date of the first estimate given on the card" do
|
229
|
+
card.estimates << Estimate.new(amount: 5, date: Date.tomorrow)
|
230
|
+
card.estimates << Estimate.new(amount: 12, date: Date.yesterday.prev_day)
|
231
|
+
card.estimates << Estimate.new(amount: 12, date: Date.today)
|
232
|
+
|
233
|
+
card.first_estimate_date.should == Date.yesterday.prev_day
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
describe "#last_estimate_date" do
|
238
|
+
it "is the date of the last estimate given on the card" do
|
239
|
+
card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
|
240
|
+
card.estimates << Estimate.new(amount: 12, date: Date.tomorrow)
|
241
|
+
card.estimates << Estimate.new(amount: 12, date: Date.today)
|
242
|
+
|
243
|
+
card.last_estimate_date.should == Date.tomorrow
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
describe "#no_tracking?" do
|
248
|
+
it "is false when there's no effort or estimate tracked on the card" do
|
249
|
+
card.no_tracking?.should be_true
|
250
|
+
|
251
|
+
card.estimates << Estimate.new(amount: 5, date: Date.yesterday)
|
252
|
+
card.no_tracking?.should be_false
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
describe "#to_s" do
|
257
|
+
it "describes the card as a string" do
|
258
|
+
card = TrackedCard.new(name: "A Story Name")
|
259
|
+
card.estimates << Estimate.new(amount: 5, date: Date.today)
|
260
|
+
card.efforts << Effort.new(amount: 3, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
|
261
|
+
card.efforts << Effort.new(amount: 6, date: Date.today, members: [Member.new(username: "piero"), Member.new(username: "tommaso")])
|
262
|
+
|
263
|
+
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"]}
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
end
|
@@ -0,0 +1,236 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Tracking do
|
4
|
+
|
5
|
+
TIME_MEASUREMENTS = {
|
6
|
+
hours: 'h',
|
7
|
+
days: 'd',
|
8
|
+
giorni: 'g',
|
9
|
+
pomodori: 'p'
|
10
|
+
}
|
11
|
+
|
12
|
+
let(:unrecognized_notification) { create_notification(data: { 'text' => '@trackinguser hi there!' }) }
|
13
|
+
|
14
|
+
describe "#unknown_format?" do
|
15
|
+
it "tells when a notification cannot be interpreted as a tracking info" do
|
16
|
+
Tracking.new(unrecognized_notification).unknown_format?.should be_true
|
17
|
+
|
18
|
+
with_message("@trackinguser +30m") { |tracking| tracking.unknown_format?.should be_true }
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
describe "#estimate?" do
|
24
|
+
it "is false when the notification does not contain an estimate" do
|
25
|
+
Tracking.new(unrecognized_notification).estimate?.should be_false
|
26
|
+
end
|
27
|
+
|
28
|
+
TIME_MEASUREMENTS.each_key do |time_measurement|
|
29
|
+
it "is true when the notification contains an estimate in #{time_measurement}" do
|
30
|
+
tracking_estimate = Tracking.new(create_estimate(time_measurement))
|
31
|
+
|
32
|
+
tracking_estimate.estimate?.should be_true
|
33
|
+
tracking_estimate.effort?.should be_false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
describe "#estimate" do
|
40
|
+
|
41
|
+
it "is nil when the notification does not contain an estimate" do
|
42
|
+
Tracking.new(unrecognized_notification).estimate.should be_nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it "is the hour-based estimate when the notification contains an estimate in hours" do
|
46
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser [2h]" }, date: "2012-10-28T21:06:14.801Z")
|
47
|
+
|
48
|
+
Tracking.new(raw_data).estimate.should == Estimate.new(amount: 2.0, date: Date.parse('2012-10-28'))
|
49
|
+
end
|
50
|
+
|
51
|
+
it "converts the estimate in hours when the notification contains an estimate in days" do
|
52
|
+
Tracking.new(create_notification(data: { 'text' => "@trackinguser [1.5d]" })).estimate.amount.should == 8+4
|
53
|
+
Tracking.new(create_notification(data: { 'text' => "@trackinguser [1.5g]" })).estimate.amount.should == 8+4
|
54
|
+
end
|
55
|
+
|
56
|
+
it "converts the estimate in hours when the notification contains an estimate in pomodori" do
|
57
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser [10p]" })
|
58
|
+
Tracking.new(raw_data).estimate.amount.should == 5
|
59
|
+
end
|
60
|
+
|
61
|
+
it "fetch the estimate from a complex estimate message" do
|
62
|
+
raw_data = create_notification(data: { 'text' => "@maxmazza Dobbiamo ancora lavorarci.\n@trackinguser ristimo ancora [3h] per il fix" })
|
63
|
+
Tracking.new(raw_data).estimate.amount.should == 3.0
|
64
|
+
end
|
65
|
+
|
66
|
+
it "tracks the effort with the date given in the notification text, not the actual notification date" do
|
67
|
+
raw_data = create_notification( data: { 'text' => "@trackinguser 22.11.2012 [6p]" }, date: "2012-09-19T12:46:13.713Z")
|
68
|
+
|
69
|
+
tracking = Tracking.new(raw_data)
|
70
|
+
|
71
|
+
tracking.estimate.date.should == Date.parse('2012-11-22')
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
76
|
+
describe "#effort?" do
|
77
|
+
it "is false when the notification does not contain an estimate" do
|
78
|
+
Tracking.new(unrecognized_notification).effort?.should be_false
|
79
|
+
end
|
80
|
+
|
81
|
+
TIME_MEASUREMENTS.each_key do |time_measurement|
|
82
|
+
it "is true when the notification contains an estimate in #{time_measurement}" do
|
83
|
+
tracking_effort = Tracking.new(create_effort(time_measurement))
|
84
|
+
|
85
|
+
tracking_effort.effort?.should be_true
|
86
|
+
tracking_effort.estimate?.should be_false
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
describe "#effort" do
|
92
|
+
|
93
|
+
%w{pietrodibello michelepangrazzi alessandrodescovi michelevincenzi}.each do |username|
|
94
|
+
let(username.to_sym) { Member.new(username: username) }
|
95
|
+
end
|
96
|
+
|
97
|
+
before(:each) do
|
98
|
+
Trello::Member.stub(:find).and_return(Member.new(username: "any"))
|
99
|
+
end
|
100
|
+
|
101
|
+
it "is nil when the notification does not contain an estimate" do
|
102
|
+
with(unrecognized_notification) { |tracking| tracking.effort.should be_nil }
|
103
|
+
end
|
104
|
+
|
105
|
+
it "does not parse effort in minutes (e.g. +30m)" do
|
106
|
+
with_message("@trackinguser +30m") { |tracking| tracking.effort.should be_nil }
|
107
|
+
end
|
108
|
+
|
109
|
+
it "is the hour-based effort when the notification contains an effort in hours" do
|
110
|
+
Trello::Member.should_receive(:find).with("michelepangrazzi").and_return(michelepangrazzi)
|
111
|
+
|
112
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser +2h" },
|
113
|
+
date: "2012-10-28T21:06:14.801Z",
|
114
|
+
member_creator: stub(username: "michelepangrazzi"))
|
115
|
+
|
116
|
+
Tracking.new(raw_data).effort.should == Effort.new(amount: 2.0, date: Date.parse('2012-10-28'), members: [michelepangrazzi])
|
117
|
+
end
|
118
|
+
|
119
|
+
it "converts the effort in hours when the notification contains an effort in days" do
|
120
|
+
with_message("@trackinguser +1.5d") { |t| t.effort.amount.should == 8+4 }
|
121
|
+
with_message("@trackinguser +1.5g") { |t| t.effort.amount.should == 8+4 }
|
122
|
+
end
|
123
|
+
|
124
|
+
it "converts the effort in hours when the notification contains an effort in pomodori" do
|
125
|
+
with_message("@trackinguser +10p") { |t| t.effort.amount.should == 5}
|
126
|
+
end
|
127
|
+
|
128
|
+
it "fetch the effort from a complex effort message" do
|
129
|
+
with_message "@trackinguser ho speso +2h e spero che stavolta possiamo rilasciarla" do |tracking|
|
130
|
+
tracking.effort.amount.should == 2.0
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
it "fetch the effort even when beween square brackets" do
|
135
|
+
with_message "@trackinguser [+0.5h]" do |tracking|
|
136
|
+
tracking.effort.amount.should == 0.5
|
137
|
+
end
|
138
|
+
end
|
139
|
+
|
140
|
+
it "computes the effort considering all the mentioned team mates in the message" do
|
141
|
+
with_message "@trackinguser +2h assieme a @michelepangrazzi e @alessandrodescovi" do |tracking|
|
142
|
+
tracking.effort.amount.should == 2.0 * 3
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
it "tracks all the team mates which spent that effort on the card" do
|
147
|
+
%w{pietrodibello michelepangrazzi alessandrodescovi}.each do |username|
|
148
|
+
Trello::Member.should_receive(:find).with(username).and_return(self.send(username))
|
149
|
+
end
|
150
|
+
|
151
|
+
notification = create_notification(data: { 'text' => "@trackinguser +2h assieme a @michelepangrazzi e @alessandrodescovi" },
|
152
|
+
member_creator: stub(username: "pietrodibello"))
|
153
|
+
with notification do |tracking|
|
154
|
+
tracking.effort.members.should == [michelepangrazzi, alessandrodescovi, pietrodibello]
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
it "tracks the effort only on the team members listed between round brackets" do
|
159
|
+
%w{michelevincenzi alessandrodescovi}.each do |username|
|
160
|
+
Trello::Member.should_receive(:find).with(username).and_return(self.send(username))
|
161
|
+
end
|
162
|
+
|
163
|
+
notification = create_notification(data: { 'text' => "@trackinguser +3p (@alessandrodescovi @michelevincenzi)" },
|
164
|
+
member_creator: stub(username: "pietrodibello"))
|
165
|
+
|
166
|
+
with notification do |tracking|
|
167
|
+
tracking.effort.members.should == [alessandrodescovi, michelevincenzi]
|
168
|
+
tracking.effort.amount.should == 1.5 * 2
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
it "does not have an effort when is an estimate" do
|
173
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser stimata [6h]" })
|
174
|
+
|
175
|
+
tracking = Tracking.new(raw_data)
|
176
|
+
|
177
|
+
tracking.effort.should be_nil
|
178
|
+
end
|
179
|
+
|
180
|
+
it "tracks the effort with the date given in the notification text, not the actual notification date" do
|
181
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser 22.11.2012 +6p" }, date: "2012-09-19T12:46:13.713Z")
|
182
|
+
|
183
|
+
tracking = Tracking.new(raw_data)
|
184
|
+
|
185
|
+
tracking.effort.date.should == Date.parse('2012-11-22')
|
186
|
+
end
|
187
|
+
|
188
|
+
it "tracks the effort to yesterday when the keyword 'yesterday' is present before the effort amount" do
|
189
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser yesterday +6p" }, date: "2012-09-19T12:46:13.713Z")
|
190
|
+
|
191
|
+
tracking = Tracking.new(raw_data)
|
192
|
+
|
193
|
+
tracking.effort.date.should == Date.parse('2012-09-18')
|
194
|
+
end
|
195
|
+
|
196
|
+
it "tracks the effort to yesterday when the keyword 'yesterday' is present before the effort amount" do
|
197
|
+
raw_data = create_notification(data: { 'text' => "@trackinguser +6p yesterday" }, date: "2012-09-19T12:46:13.713Z")
|
198
|
+
|
199
|
+
tracking = Tracking.new(raw_data)
|
200
|
+
|
201
|
+
tracking.effort.date.should == Date.parse('2012-09-18')
|
202
|
+
end
|
203
|
+
|
204
|
+
end
|
205
|
+
|
206
|
+
private
|
207
|
+
|
208
|
+
def notification_with_message(message)
|
209
|
+
create_notification(data: { 'text' => message })
|
210
|
+
end
|
211
|
+
|
212
|
+
def create_notification(custom_params)
|
213
|
+
params = { data: { 'text' => "@trackinguser +2h" }, date: "2012-10-28T21:06:14.801Z", member_creator: stub(username: "pietrodibello") }
|
214
|
+
params.merge!(custom_params)
|
215
|
+
|
216
|
+
stub(data: params[:data], date: params[:date], member_creator: params[:member_creator]).as_null_object
|
217
|
+
end
|
218
|
+
|
219
|
+
def create_estimate(time_measurement)
|
220
|
+
create_notification(data: { 'text' => "@trackinguser [1.5#{TIME_MEASUREMENTS[time_measurement]}]" })
|
221
|
+
end
|
222
|
+
|
223
|
+
def create_effort(time_measurement)
|
224
|
+
create_notification(data: { 'text' => "@trackinguser +4.5#{TIME_MEASUREMENTS[time_measurement]}]" })
|
225
|
+
end
|
226
|
+
|
227
|
+
def with(notification)
|
228
|
+
tracking = Tracking.new(notification)
|
229
|
+
yield(tracking)
|
230
|
+
end
|
231
|
+
|
232
|
+
def with_message(notification_message, &block)
|
233
|
+
with(notification_with_message(notification_message), &block)
|
234
|
+
end
|
235
|
+
|
236
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'trello'
|
3
|
+
|
4
|
+
describe TrelloAuthorize do
|
5
|
+
include Trello::Authorization
|
6
|
+
include TrelloAuthorize
|
7
|
+
|
8
|
+
before(:each) do
|
9
|
+
keep_original_auth_envs
|
10
|
+
end
|
11
|
+
|
12
|
+
after(:each) do
|
13
|
+
reset_original_auth_envs
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "#authorize_on_trello" do
|
17
|
+
|
18
|
+
it "creates oath credentials using auth params given as params when present as arguments, ignoring env vars or config YML vars" do
|
19
|
+
ENV["developer_public_key"] = ENV["access_token_key"] = ENV["developer_secret"] = "anything"
|
20
|
+
YAML.should_receive(:load_file).never
|
21
|
+
|
22
|
+
authorize_on_trello("developer_public_key" => "custom_dpk", "access_token_key" => "custom_atk", "developer_secret" => "custom_ds")
|
23
|
+
|
24
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.key.should == "custom_dpk"
|
25
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.secret.should == "custom_ds"
|
26
|
+
Trello::Authorization::OAuthPolicy.token.key.should == "custom_atk"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "creates oath credentials using auth params taken from env variables when auth params are not given as arguments" do
|
30
|
+
ENV["developer_public_key"] = "my_dpk"
|
31
|
+
ENV["access_token_key"] = "my_atk"
|
32
|
+
ENV["developer_secret"] = "my_ds"
|
33
|
+
|
34
|
+
YAML.should_receive(:load_file).never
|
35
|
+
|
36
|
+
authorize_on_trello("any" => "thing")
|
37
|
+
|
38
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.key.should == "my_dpk"
|
39
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.secret.should == "my_ds"
|
40
|
+
Trello::Authorization::OAuthPolicy.token.key.should == "my_atk"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "creates oath credentials using auth params from config file when auth env variables are not set" do
|
44
|
+
ENV["developer_public_key"] = ENV["access_token_key"] = ENV["developer_secret"] = nil
|
45
|
+
|
46
|
+
config_hash = {"trello" => { "developer_public_key" => "any_dpk", "access_token_key" => "any_atk", "developer_secret" => "any_ds"}}
|
47
|
+
YAML.should_receive(:load_file).with("config/config.yml").and_return(config_hash)
|
48
|
+
|
49
|
+
authorize_on_trello("any" => "thing")
|
50
|
+
|
51
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.key.should == "any_dpk"
|
52
|
+
Trello::Authorization::OAuthPolicy.consumer_credential.secret.should == "any_ds"
|
53
|
+
Trello::Authorization::OAuthPolicy.token.key.should == "any_atk"
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def keep_original_auth_envs
|
61
|
+
@original_developer_public_key = ENV["developer_public_key"]
|
62
|
+
@original_access_token_key = ENV["access_token_key"]
|
63
|
+
@original_developer_secret = ENV["developer_secret"]
|
64
|
+
end
|
65
|
+
|
66
|
+
def reset_original_auth_envs
|
67
|
+
ENV["developer_public_key"] = @original_developer_public_key
|
68
|
+
ENV["access_token_key"] = @original_access_token_key
|
69
|
+
ENV["developer_secret"] = @original_developer_secre
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'trello'
|
3
|
+
|
4
|
+
describe TrelloConfiguration do
|
5
|
+
include TrelloConfiguration
|
6
|
+
|
7
|
+
describe "#authorization_params_from_config_file" do
|
8
|
+
it "loads the default trello auth params from config yml" do
|
9
|
+
config_hash = {"trello" => { "developer_public_key" => "any_dpk", "access_token_key" => "any_atk", "developer_secret" => "any_ds"} }
|
10
|
+
YAML.should_receive(:load_file).with("config/config.yml").and_return(config_hash)
|
11
|
+
|
12
|
+
authorization_params_from_config_file["developer_public_key"].should == "any_dpk"
|
13
|
+
authorization_params_from_config_file["access_token_key"].should == "any_atk"
|
14
|
+
authorization_params_from_config_file["developer_secret"].should == "any_ds"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
describe "#tracker_username" do
|
19
|
+
before(:each) do
|
20
|
+
@original = ENV["tracker_username"]
|
21
|
+
end
|
22
|
+
|
23
|
+
after(:each) do
|
24
|
+
ENV["tracker_username"] = @original
|
25
|
+
end
|
26
|
+
|
27
|
+
it "searches for the trello tracker username first from an env var" do
|
28
|
+
ENV["tracker_username"] = "my_tracker"
|
29
|
+
YAML.should_receive(:load_file).never
|
30
|
+
|
31
|
+
tracker_username.should == "my_tracker"
|
32
|
+
end
|
33
|
+
it "searches for the trello tracker username in the config yml file if the env var is not set" do
|
34
|
+
ENV["tracker_username"] = nil
|
35
|
+
config_hash = {"tracker_username" => "my_trello_tracker" }
|
36
|
+
YAML.should_receive(:load_file).with("config/config.yml").and_return(config_hash)
|
37
|
+
|
38
|
+
tracker_username.should == "my_trello_tracker"
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/trello_effort_tracker/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.name = "trello_effort_tracker"
|
6
|
+
gem.version = TrelloEffortTracker::VERSION
|
7
|
+
gem.description = "A tool to extract estimates and efforts from Trello"
|
8
|
+
gem.summary = "You notify all the estimates and efforts of your Trello cards. This tool will extract and store these estimates and actual efforts to let you extract useful key metrics (e.g. estimate errors)"
|
9
|
+
gem.authors = ['Pietro Di Bello']
|
10
|
+
gem.email = 'pierodibello@gmail.com'
|
11
|
+
gem.homepage = 'http://xplayer.wordpress.com'
|
12
|
+
gem.date = Time.now.strftime "%Y-%m-%d"
|
13
|
+
gem.require_paths = ["lib"]
|
14
|
+
gem.files = `git ls-files`.split("\n")
|
15
|
+
gem.test_files = `git ls-files -- {spec}/*`.split("\n")
|
16
|
+
gem.extra_rdoc_files = ["README.md"]
|
17
|
+
gem.rubygems_version = %q{1.8.24}
|
18
|
+
gem.required_rubygems_version = ">= 1.3.6"
|
19
|
+
end
|