Merge lp:~dpb/lp2kanban/filter-bugs-correct-movement into lp:lp2kanban
- filter-bugs-correct-movement
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 142 |
Proposed branch: | lp:~dpb/lp2kanban/filter-bugs-correct-movement |
Merge into: | lp:lp2kanban |
Diff against target: |
604 lines (+247/-52) 8 files modified
Makefile (+4/-2) README (+6/-0) jenkins.sh (+3/-2) setup.py (+1/-0) src/lp2kanban/bugs2cards.py (+91/-19) src/lp2kanban/kanban.py (+31/-23) src/lp2kanban/tests/common.py (+6/-2) src/lp2kanban/tests/test_bugs2cards.py (+105/-4) |
To merge this branch: | bzr merge lp:~dpb/lp2kanban/filter-bugs-correct-movement |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
🤖 Landscape Builder | test results | Needs Fixing | |
Eric Snow (community) | Approve | ||
Landscape | Pending | ||
Review via email: mp+304273@code.launchpad.net |
Commit message
- Remove archive from default board fetch, this speeds up operations significantly. I don't have any way exposed externally from the config to turn this back on, but we don't use this, so I'm skipping for now.
- Remove cards inside taskboard from board fetch. This speeds up operations significantly, and we currently have no use case for automation moving inside a taskboard, nor does the config support doing these kinds of operations for us.
- Add clean make target
- Update README to talk about using a virtualenv for testing
- Add bug filter and card filter for testing individual moves on the board
- Add logging (follow on branch will convert all prints to logging calls
- log interesting paths for easier post-mortem log analysis (Moves and syncs)
- Explicitly skip attempting to move for no-op cases (before we relied on lkk to noop our request for us)
Description of the change
- Remove archive from default board fetch, this speeds up operations significantly. I don't have any way exposed externally from the config to turn this back on, but we don't use this, so I'm skipping for now.
- Remove cards inside taskboard from board fetch. This speeds up operations significantly, and we currently have no use case for automation moving inside a taskboard, nor does the config support doing these kinds of operations for us.
- Add clean make target
- Update README to talk about using a virtualenv for testing
- Add bug filter and card filter for testing individual moves on the board
- Add logging (follow on branch will convert all prints to logging calls
- log interesting paths for easier post-mortem log analysis (Moves and syncs)
- Explicitly skip attempting to move for no-op cases (before we relied on lkk to noop our request for us)
After doing this, I cleaned up lp:~landscape/landscape/lp2kanban-configs, and added better descriptions to that file.
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: http_proxy=
Result: Success
Revno: 163
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/
Adam Collard (adam-collard) : | # |
Eric Snow (ericsnowcurrently) : | # |
David Britton (dpb) wrote : | # |
Fixed Adam's comments
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: http_proxy=
Result: Fail
Revno: 164
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/
David Britton (dpb) wrote : | # |
Addressed all comments.
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: http_proxy=
Result: Fail
Revno: 165
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/
Eric Snow (ericsnowcurrently) wrote : | # |
Other than one small fix, +1. Thanks for taking the time on this!
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: http_proxy=
Result: Fail
Revno: 166
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) : | # |
🤖 Landscape Builder (landscape-builder) wrote : | # |
Command: http_proxy=
Result: Fail
Revno: 167
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/
🤖 Landscape Builder (landscape-builder) wrote : | # |
The attempt to merge lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement into lp:lp2kanban failed. Below is the output from the failed tests.
test -d venv || virtualenv venv; \
. venv/bin/activate; \
make check
New python executable in venv/bin/python
Installing setuptools, pip...done.
make[1]: Entering directory `/var/tmp/
mkdir download-cache
python bootstrap.py -v 1.7.1 -f eggs -c buildout.cfg
make[1]: Leaving directory `/var/tmp/
Traceback (most recent call last):
File "bootstrap.py", line 80, in <module>
exec(urlopen('https:/
File "/usr/lib/
return _opener.open(url, data, timeout)
File "/usr/lib/
response = self._open(req, data)
File "/usr/lib/
'_open', req)
File "/usr/lib/
result = func(*args)
File "/usr/lib/
return self.do_
File "/usr/lib/
raise URLError(err)
urllib2.URLError: <urlopen error Tunnel connection failed: 403 Forbidden>
make[1]: *** [bin/buildout] Error 1
make: *** [ci-test] Error 2
🤖 Landscape Builder (landscape-builder) wrote : | # |
No approved revision specified.
Preview Diff
1 | === modified file 'Makefile' |
2 | --- Makefile 2016-08-29 20:18:23 +0000 |
3 | +++ Makefile 2016-09-01 21:37:25 +0000 |
4 | @@ -17,12 +17,14 @@ |
5 | # buildout. |
6 | BUILDOUT_BIN = $(PY) bin/tags bin/test |
7 | |
8 | -default: check |
9 | +default: bin/test |
10 | |
11 | |
12 | download-cache: |
13 | mkdir download-cache |
14 | |
15 | +clean: |
16 | + @rm -rf bin |
17 | |
18 | bin/buildout: download-cache |
19 | $(PYTHON) bootstrap.py -v 1.7.1 -f eggs -c buildout.cfg |
20 | @@ -59,4 +61,4 @@ |
21 | . venv/bin/activate; \ |
22 | $(MAKE) check |
23 | |
24 | -.PHONY: check default configs needs-xdg-utils ci-test |
25 | +.PHONY: check default configs needs-xdg-utils clean ci-test |
26 | |
27 | === modified file 'README' |
28 | --- README 2012-12-10 15:21:44 +0000 |
29 | +++ README 2016-09-01 21:37:25 +0000 |
30 | @@ -38,5 +38,11 @@ |
31 | Testing |
32 | ------- |
33 | |
34 | +All tests should work on 16.04+, please run in a vitrualenv though: |
35 | + |
36 | + virtualenv ve |
37 | + source ve/bin/activate |
38 | + make check |
39 | + |
40 | 'lp2kanban test' board configuration connects to LP staging by default |
41 | to allow easier testing. |
42 | |
43 | === modified file 'jenkins.sh' |
44 | --- jenkins.sh 2015-12-16 16:56:38 +0000 |
45 | +++ jenkins.sh 2016-09-01 21:37:25 +0000 |
46 | @@ -1,6 +1,5 @@ |
47 | #!/bin/bash -xe |
48 | |
49 | -ini="$1.ini" |
50 | date "+START: %c" |
51 | |
52 | #export https_proxy=http://squid.external:3128 |
53 | @@ -28,5 +27,7 @@ |
54 | # - https://stackoverflow.com/questions/1473577 |
55 | # - https://stackoverflow.com/questions/492483 |
56 | # - https://stackoverflow.com/questions/9932406 |
57 | -PYTHONIOENCODING="utf-8" bin/py src/lp2kanban/bugs2cards.py -c configs/${1}.ini |
58 | +config=$1 |
59 | +shift |
60 | +PYTHONIOENCODING="utf-8" bin/py src/lp2kanban/bugs2cards.py -c "configs/${config}.ini" "${@}" |
61 | date "+END: %c" |
62 | |
63 | === modified file 'setup.py' |
64 | --- setup.py 2016-04-13 16:06:23 +0000 |
65 | +++ setup.py 2016-09-01 21:37:25 +0000 |
66 | @@ -18,6 +18,7 @@ |
67 | 'requests', |
68 | 'setuptools', |
69 | 'simplejson', |
70 | + 'testfixtures', |
71 | 'testtools', |
72 | ], |
73 | ) |
74 | |
75 | === modified file 'src/lp2kanban/bugs2cards.py' |
76 | --- src/lp2kanban/bugs2cards.py 2016-08-26 17:11:41 +0000 |
77 | +++ src/lp2kanban/bugs2cards.py 2016-09-01 21:37:25 +0000 |
78 | @@ -3,6 +3,7 @@ |
79 | from ConfigParser import ConfigParser |
80 | from argparse import ArgumentParser |
81 | from launchpadlib.launchpad import Launchpad |
82 | +import logging |
83 | from lp2kanban.cards2workitems import ( |
84 | convert_cards_to_work_items, |
85 | get_blueprint_linked_cards, |
86 | @@ -45,7 +46,13 @@ |
87 | help="List available kanban boards.") |
88 | parser.add_argument( |
89 | '-d', '--debug', action='store_const', const=True, default=False, |
90 | - help="Turn on debugging for HTTP traffic") |
91 | + help="Turn on logging debug, debugging for HTTP traffic") |
92 | + parser.add_argument( |
93 | + '-B', '--bug', required=False, type=int, default=None, |
94 | + help="Only operate on this bug.") |
95 | + parser.add_argument( |
96 | + '-C', '--card', required=False, type=int, default=None, |
97 | + help="Only operate on this card id, can obtain by looking at URL.") |
98 | return parser |
99 | |
100 | |
101 | @@ -207,7 +214,8 @@ |
102 | return lp_bug |
103 | |
104 | |
105 | -def move_cards_to_feature_taskboard(feature_card, feature_bug, source_lanes): |
106 | +def move_cards_to_feature_taskboard( |
107 | + feature_card, feature_bug, source_lanes, filter_card=None): |
108 | """Move all cards related to subtasks of the feature_card. |
109 | |
110 | This will only move cards which are in the landing_lanes or deploy_lanes. |
111 | @@ -220,6 +228,8 @@ |
112 | """ |
113 | linked_cards = get_cards_for_feature(feature_card, feature_bug=feature_bug) |
114 | for card in linked_cards: |
115 | + if filter_card and filter_card != card.id: |
116 | + return |
117 | if card.type.name == "Feature": # We don't want to move feature cards |
118 | continue |
119 | card_path = card.lane.path |
120 | @@ -349,11 +359,14 @@ |
121 | """ |
122 | if conf.get('sync_cards', 'off') == 'off': |
123 | return False |
124 | + logging.debug("Should I sync? - {}".format(card.title)) |
125 | no_sync_lanes = [ |
126 | lane_path.strip() |
127 | for lane_path in conf.get("no_sync_lanes", "").split(",")] |
128 | for no_sync_lane_path in no_sync_lanes: |
129 | if no_sync_lane_path and card.lane.path.endswith(no_sync_lane_path): |
130 | + logging.debug("Not syncing, {} in no_sync_lanes".format( |
131 | + card.lane.path)) |
132 | return False |
133 | if conf.get('autosync', None) in ['on', 'active-projects']: |
134 | has_id = (card.external_card_id is not None and |
135 | @@ -666,6 +679,7 @@ |
136 | |
137 | def create_cards(board, bconf, lp_users, lp_projects): |
138 | """Create new cards on all projects.""" |
139 | + logging.info("Creating new cards") |
140 | new_bug_tag = bconf.get("bug_to_card_tag") |
141 | if new_bug_tag is not None: |
142 | for lp_project in lp_projects: |
143 | @@ -674,10 +688,13 @@ |
144 | bconf.get("bug_to_card_lane"), lp_users.feature_lanes) |
145 | |
146 | |
147 | -def sync_cards_linked_branches(board, bconf, lp, lp_users): |
148 | +def sync_cards_linked_branches( |
149 | + board, bconf, lp, lp_users, filter_card=None): |
150 | """Sync any cards that have linked external branches.""" |
151 | - print " Syncing branch cards:" |
152 | + logging.info("Syncing branch cards") |
153 | for card in board.getCardsWithExternalLinks(only_branches=True): |
154 | + if filter_card and filter_card != card.id: |
155 | + continue |
156 | if card.external_card_id: |
157 | # Cards with external_id are processed by getCardsWithExternalIds |
158 | continue |
159 | @@ -714,9 +731,16 @@ |
160 | print " * %s (in %s)" % (card.title, card_path) |
161 | |
162 | |
163 | -def sync_cards_external_ids(board, bconf, lp, lp_users, lp_projects): |
164 | +def sync_cards_external_ids( |
165 | + board, bconf, lp, lp_users, lp_projects, filter_bug=None, |
166 | + filter_card=None): |
167 | """Sync any cards that have linked external ids (bugs in launchpad).""" |
168 | + logging.info("Syncing cards with external ids") |
169 | for card in board.getCardsWithExternalIds(): |
170 | + if filter_bug and card.external_card_id != filter_bug: |
171 | + continue |
172 | + if filter_card and filter_card != card.id: |
173 | + continue |
174 | if should_sync_card(card, bconf): |
175 | lp_bug = get_lp_bug(card, lp) |
176 | if lp_bug is None: |
177 | @@ -763,8 +787,10 @@ |
178 | print " * %s: %s -- Not synced" % ( |
179 | lp_bug.id, card.title) |
180 | |
181 | -def move_cards_to_taskboards(board, bconf, lp, lp_users): |
182 | +def move_cards_to_taskboards( |
183 | + board, bconf, lp, lp_users, filter_card=None): |
184 | """Move branch cards to feature cards if they are landed.""" |
185 | + logging.info("Move cards to taskboards") |
186 | if "${group}" in bconf["landing_lanes"]: |
187 | # Replace the optional template ${group} with configured board groups. |
188 | # This creates a list of landing_lanes, one per group on the board. |
189 | @@ -779,11 +805,12 @@ |
190 | feature_card.lane.path in valid_taskboard_lanes): |
191 | lp_bug = get_lp_bug(feature_card, lp) |
192 | move_cards_to_feature_taskboard( |
193 | - feature_card, lp_bug, valid_taskboard_lanes) |
194 | + feature_card, lp_bug, valid_taskboard_lanes, filter_card) |
195 | |
196 | |
197 | def convert_cards_to_work_items(board, bconf, lp, lp_users, lp_projects): |
198 | """Create work items out of cards if desired.""" |
199 | + logging.info("Convert cards to work items") |
200 | if bconf.get("convert_cards_to_work_items", False) == "on": |
201 | # Sync cards to blueprint work items, if so configured. |
202 | print " Checking for cards to sync with work items..." |
203 | @@ -808,25 +835,46 @@ |
204 | |
205 | If a card is located in a "no_move" lane, the card won't get moved. |
206 | """ |
207 | + logging.debug("Evaluating for move: {}".format(card.title)) |
208 | target_lane_path = bconf[ConfigOptionByStatus[bug_status]] |
209 | - if assignees: |
210 | - group = lp_users.lp_to_group.get(assignees[0].name) |
211 | - if group: |
212 | - target_lane_path = target_lane_path.replace("${group}", group) |
213 | + if "${group}" in target_lane_path: |
214 | + if assignees: |
215 | + group = lp_users.lp_to_group.get(assignees[0].name) |
216 | + if group: |
217 | + target_lane_path = target_lane_path.replace("${group}", group) |
218 | + else: |
219 | + logging.debug( |
220 | + "Not moving, assignee: {} not in group: {}".format( |
221 | + assignees[0].name, group)) |
222 | + return |
223 | + else: |
224 | + logging.debug("Not moving, not assigned") |
225 | + return |
226 | + |
227 | omnidirectional_card_moves = ( |
228 | bconf.get("omnidirectional_card_moves", "on") == "on") |
229 | no_move_lanes = [ |
230 | lane_path.strip() |
231 | - for lane_path in bconf.get("no_move_lanes", "").split(",")] |
232 | + for lane_path in bconf.get("no_move_lanes", "").split(",") |
233 | + if lane_path] |
234 | for no_move_lane_path in no_move_lanes: |
235 | if no_move_lane_path and card.lane.path.endswith(no_move_lane_path): |
236 | + logging.debug("Not moving, {} in no_move_lanes".format( |
237 | + card.lane.path)) |
238 | return |
239 | + |
240 | if omnidirectional_card_moves: |
241 | target_lane = find_card_target_lane_omnidirectional( |
242 | card.lane, target_lane_path) |
243 | else: |
244 | target_lane = find_card_target_lane_next( |
245 | card.lane, target_lane_path) |
246 | + if card.lane.path == target_lane_path: |
247 | + logging.debug("Not moving, already in {}".format(card.lane.path)) |
248 | + return |
249 | + |
250 | + logging.debug( |
251 | + " * Moving: {} -> {}".format(card.lane.path, target_lane_path)) |
252 | card.move(target_lane) |
253 | |
254 | |
255 | @@ -853,13 +901,43 @@ |
256 | continue |
257 | |
258 | |
259 | +def process_board(board, bconf, filter_bug, filter_card): |
260 | + """Sync a given board. |
261 | + |
262 | + Decomposition of main, and handles interpreting |
263 | + filter arguments. |
264 | + """ |
265 | + lp, lp_projects, lp_users = get_lp_data(board, bconf) |
266 | + if not filter_bug and not filter_card: |
267 | + create_cards(board, bconf, lp_users, lp_projects) |
268 | + if not filter_bug: |
269 | + sync_cards_linked_branches( |
270 | + board, bconf, lp, lp_users, filter_card=filter_card) |
271 | + sync_cards_external_ids( |
272 | + board, bconf, lp, lp_users, lp_projects, filter_bug=filter_bug, |
273 | + filter_card=filter_card) |
274 | + if not filter_bug: |
275 | + move_cards_to_taskboards( |
276 | + board, bconf, lp, lp_users, filter_card=filter_card) |
277 | + if not filter_bug and not filter_card: |
278 | + convert_cards_to_work_items(board, bconf, lp, lp_users, lp_projects) |
279 | + save_board(board) |
280 | + |
281 | + |
282 | def main(argv): |
283 | """Update the Yellow team's kanban board with data from Launchpad.""" |
284 | args = get_arg_parser().parse_args() |
285 | + if args.bug: |
286 | + args.bug = str(args.bug) |
287 | |
288 | if args.debug: |
289 | + logging.basicConfig( |
290 | + level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s') |
291 | import httplib |
292 | httplib.HTTPConnection.debuglevel = 1 |
293 | + else: |
294 | + logging.basicConfig( |
295 | + level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') |
296 | |
297 | config = ConfigParser() |
298 | config.read(args.config) |
299 | @@ -891,13 +969,7 @@ |
300 | print "Could not retrieve board. Skipping." |
301 | continue |
302 | bconf = boards_config[board_name] |
303 | - lp, lp_projects, lp_users = get_lp_data(board, bconf) |
304 | - create_cards(board, bconf, lp_users, lp_projects) |
305 | - sync_cards_linked_branches(board, bconf, lp, lp_users) |
306 | - sync_cards_external_ids(board, bconf, lp, lp_users, lp_projects) |
307 | - move_cards_to_taskboards(board, bconf, lp, lp_users) |
308 | - convert_cards_to_work_items(board, bconf, lp, lp_users) |
309 | - save_board(board) |
310 | + process_board(board, bconf, args.bug, args.card) |
311 | except (KeyError, AssertionError) as e: |
312 | print "Exception caught. Skipping board." |
313 | print_exc() |
314 | |
315 | === modified file 'src/lp2kanban/kanban.py' |
316 | --- src/lp2kanban/kanban.py 2016-04-14 20:50:10 +0000 |
317 | +++ src/lp2kanban/kanban.py 2016-09-01 21:37:25 +0000 |
318 | @@ -3,6 +3,7 @@ |
319 | |
320 | import requests |
321 | import json |
322 | +import logging |
323 | import operator |
324 | from pprint import pprint |
325 | import re |
326 | @@ -180,9 +181,8 @@ |
327 | return name.upper() |
328 | |
329 | def __setattr__(self, attr, value): |
330 | - if ((not hasattr(self, attr) or |
331 | - getattr(self, attr, None) != value) and |
332 | - attr in self._watched_attrs): |
333 | + if (getattr(self, attr, object()) != value and |
334 | + attr in self._watched_attrs): |
335 | self.direct_setattr('is_dirty', True) |
336 | self.dirty_attrs.add(attr) |
337 | self.direct_setattr(attr, value) |
338 | @@ -531,30 +531,38 @@ |
339 | def getCardsWithDescriptionAnnotations(self): |
340 | return self._cards_with_description_annotations |
341 | |
342 | - def fetchDetails(self): |
343 | + def fetchDetails(self, include_archive=False, include_taskboards=False): |
344 | + """Fetch all details from the board into our datastructure. |
345 | + |
346 | + include_taskboards - add all cards on taskboards into my card list |
347 | + include_archive - add all cards from archive into my card list. |
348 | + """ |
349 | self.details = self.connector.get( |
350 | self.base_uri + str(self.id)).ReplyData[0] |
351 | |
352 | self._populateUsers(self.details['BoardUsers']) |
353 | self._populateCardTypes(self.details['CardTypes']) |
354 | - self._archive = self.connector.get( |
355 | - "/Kanban/Api/Board/" + str(self.id) + "/Archive").ReplyData[0] |
356 | - archive_lanes = [lane_dict['Lane'] for lane_dict in self._archive] |
357 | - archive_lanes.extend( |
358 | - [lane_dict['Lane'] for |
359 | - lane_dict in self._archive[0]['ChildLanes']]) |
360 | - self._backlog = self.connector.get( |
361 | + lanes = self.details['Lanes'] |
362 | + if include_archive: |
363 | + self._archive = self.connector.get( |
364 | + "/Kanban/Api/Board/" + str(self.id) + "/Archive").ReplyData[0] |
365 | + archive_lanes = [lane_dict['Lane'] for lane_dict in self._archive] |
366 | + archive_lanes.extend( |
367 | + [lane_dict['Lane'] for |
368 | + lane_dict in self._archive[0]['ChildLanes']]) |
369 | + lanes += archive_lanes |
370 | + lanes += self.connector.get( |
371 | "/Kanban/Api/Board/" + str(self.id) + "/Backlog").ReplyData[0] |
372 | - self._populateLanes( |
373 | - self.details['Lanes'] + archive_lanes + self._backlog) |
374 | - for card in self.cards: |
375 | - if card.current_task_board_id: # We are a task board |
376 | - taskboard_data = self.connector.get( |
377 | - "/Kanban/Api/v1/board/%s/card/%s/taskboard" % (self.id, card.id)) |
378 | - card.taskboard = LeankitTaskBoard( |
379 | - taskboard_data["ReplyData"][0], self, card) |
380 | - card.taskboard.fetchDetails() |
381 | - self.cards.extend(card.taskboard.cards) |
382 | + self._populateLanes(lanes) |
383 | + if include_taskboards: |
384 | + for card in self.cards: |
385 | + if card.current_task_board_id: # We are a task board |
386 | + taskboard_data = self.connector.get( |
387 | + "/Kanban/Api/v1/board/%s/card/%s/taskboard" % (self.id, card.id)) |
388 | + card.taskboard = LeankitTaskBoard( |
389 | + taskboard_data["ReplyData"][0], self, card) |
390 | + card.taskboard.fetchDetails() |
391 | + self.cards.extend(card.taskboard.cards) |
392 | self._classifyCards() |
393 | self._populateFeatureTagPaths() |
394 | |
395 | @@ -769,10 +777,10 @@ |
396 | board = self._findBoardInCache(board_id, title) |
397 | return board |
398 | |
399 | - def getBoard(self, board_id=None, title=None): |
400 | + def getBoard(self, board_id=None, title=None, include_archive=False): |
401 | board = self._findBoard(board_id, title) |
402 | if board is not None: |
403 | - board.fetchDetails() |
404 | + board.fetchDetails(include_archive=include_archive) |
405 | return board |
406 | |
407 | |
408 | |
409 | === modified file 'src/lp2kanban/tests/common.py' |
410 | --- src/lp2kanban/tests/common.py 2016-05-02 23:07:28 +0000 |
411 | +++ src/lp2kanban/tests/common.py 2016-09-01 21:37:25 +0000 |
412 | @@ -1,10 +1,13 @@ |
413 | # Copyright 2012 Canonical Ltd. |
414 | """Common fixtures for lp2kanban tests.""" |
415 | |
416 | +import uuid |
417 | + |
418 | +from lp2kanban.kanban import Record |
419 | + |
420 | + |
421 | __metaclass__ = type |
422 | |
423 | -from lp2kanban.kanban import Record |
424 | - |
425 | |
426 | class FauxCardType: |
427 | |
428 | @@ -123,6 +126,7 @@ |
429 | self.external_system_url = external_system_url |
430 | self.moved_to = None |
431 | self.saved = False |
432 | + self.id = uuid.uuid1() |
433 | |
434 | def save(self): |
435 | """A no-op for compatibility.""" |
436 | |
437 | === modified file 'src/lp2kanban/tests/test_bugs2cards.py' |
438 | --- src/lp2kanban/tests/test_bugs2cards.py 2016-08-25 22:37:31 +0000 |
439 | +++ src/lp2kanban/tests/test_bugs2cards.py 2016-09-01 21:37:25 +0000 |
440 | @@ -13,6 +13,8 @@ |
441 | import datetime |
442 | import unittest |
443 | |
444 | +from testfixtures import log_capture |
445 | + |
446 | from lp2kanban.bugs2cards import ( |
447 | CardStatus, |
448 | create_cards_in_project, |
449 | @@ -50,7 +52,6 @@ |
450 | ) |
451 | |
452 | |
453 | - |
454 | def parse_config_text(text): |
455 | config = ConfigParser() |
456 | config.readfp(StringIO(dedent(text))) |
457 | @@ -1489,7 +1490,18 @@ |
458 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) |
459 | self.assertEqual("coding", card.moved_to.path) |
460 | |
461 | - def test_group_no_assignee(self): |
462 | + @log_capture() |
463 | + def test_noop(self, testlog): |
464 | + """Skip move attempt if already in right path.""" |
465 | + card = self.qa.addCard(FauxCard()) |
466 | + move_card(card, CardStatus.QA, self.bconf, [], self.lp_users) |
467 | + self.assertEqual(None, card.moved_to) |
468 | + testlog.check( |
469 | + ('root', 'DEBUG', 'Evaluating for move: '), |
470 | + ('root', 'DEBUG', 'Not moving, already in qa')) |
471 | + |
472 | + @log_capture() |
473 | + def test_group_no_assignee(self, testlog): |
474 | # If a target lane requires a group, and the card doesn't have |
475 | # an assignee, the card isn't moved. |
476 | self.bconf["coding_lanes"] = "${group}::coding" |
477 | @@ -1497,6 +1509,9 @@ |
478 | card = self.backlog.addCard(FauxCard()) |
479 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) |
480 | self.assertEqual(None, card.moved_to) |
481 | + testlog.check( |
482 | + ('root', 'DEBUG', 'Evaluating for move: '), |
483 | + ('root', 'DEBUG', 'Not moving, not assigned')) |
484 | |
485 | def test_group_assignee_in_group(self): |
486 | # If a target lane requires a group, and the card has an |
487 | @@ -1511,7 +1526,8 @@ |
488 | self.lp_users) |
489 | self.assertEqual("test group::coding", card.moved_to.path) |
490 | |
491 | - def test_group_assignee_not_in_group(self): |
492 | + @log_capture() |
493 | + def test_group_assignee_not_in_group(self, testlog): |
494 | # If a target lane requires a group, and the card has an |
495 | # assignee that is not part of a group, the card isn't moved. |
496 | self.bconf["coding_lanes"] = "${group}::coding" |
497 | @@ -1522,6 +1538,11 @@ |
498 | card, CardStatus.CODING, self.bconf, [FauxPerson("bar")], |
499 | self.lp_users) |
500 | self.assertEqual(None, card.moved_to) |
501 | + testlog.check( |
502 | + ('root', 'DEBUG', 'Evaluating for move: '), |
503 | + ('root', 'DEBUG', 'Not moving, assignee: bar not in group: None')) |
504 | + |
505 | + |
506 | |
507 | def test_no_move_lanes(self): |
508 | # Cards in a "no_move" lane doesn't get moved by move_card(). |
509 | @@ -1709,6 +1730,86 @@ |
510 | self.assertEqual(self.feature_card, landed_card1.moved_to) |
511 | self.assertEqual(self.feature_card, landed_card2.moved_to) |
512 | |
513 | + def test_sync_cards_external_ids_filter_card(self): |
514 | + """filter_card skips and selects correctly.""" |
515 | + # Configure bugs2cards to sync only active-projects myproject1 and myproject2 |
516 | + self.bconf.update({ |
517 | + "autosync": "active-projects", |
518 | + "projects": "myproject", |
519 | + "sync_cards": "on", "move_cards": "on", |
520 | + "new_lanes": "new"}) |
521 | + # Setup bug1 and bug2 in separate projects which both need to move coding -> new |
522 | + project1 = FauxTarget(resource_type_link="project", project="myproject") |
523 | + lp_users = FauxLaunchpadUsersForBoard(lp_to_kanban={"person1": "Person One"}) |
524 | + lp_projects = [project1] |
525 | + bug_task1 = FauxBugTask( |
526 | + bug_id=123, target=project1, title='Test bug in myproject', |
527 | + description='Test bug description.') |
528 | + bug_task2 = FauxBugTask( |
529 | + bug_id=456, target=project1, title='Test bug in otherproject', |
530 | + description='Test bug description.') |
531 | + bug_card1 = FauxCard( |
532 | + title='BugCard123', external_card_id=u'123') |
533 | + bug_card2 = FauxCard( |
534 | + title='BugCard456', external_card_id=u'456') |
535 | + board = FauxBoard( |
536 | + cards=[bug_card1, bug_card2], bug_cards=[bug_card1, bug_card2]) |
537 | + new_lane = board.addLane('new') |
538 | + coding_lane = board.addLane('coding') |
539 | + bug_card1.lane = coding_lane |
540 | + bug_card2.lane = coding_lane |
541 | + bug_type = FauxCardType(id=3, name='Bug', is_default=False) |
542 | + bug_card1.type = bug_type |
543 | + bug_card2.type = bug_type |
544 | + self.board.cards.extend([bug_card1, bug_card2]) |
545 | + lp = FauxLP({"projects": {"myproject": project1}, |
546 | + "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) |
547 | + sync_cards_external_ids( |
548 | + board, self.bconf, lp, lp_users, lp_projects, filter_card=bug_card2.id) |
549 | + coding_lane = FauxLane(board=board, path='coding') |
550 | + self.assertEqual(new_lane, bug_card2.moved_to) |
551 | + self.assertIsNone(bug_card1.moved_to) |
552 | + |
553 | + def test_sync_cards_skips_bugs_not_in_filter_bug(self): |
554 | + """filter_bug skips and selects bugs in sync_cards_external_ids.""" |
555 | + # Configure bugs2cards to sync only active-projects myproject1 and myproject2 |
556 | + self.bconf.update({ |
557 | + "autosync": "active-projects", |
558 | + "projects": "myproject", |
559 | + "sync_cards": "on", "move_cards": "on", |
560 | + "new_lanes": "new"}) |
561 | + # Setup bug1 and bug2 in separate projects which both need to move coding -> new |
562 | + project1 = FauxTarget(resource_type_link="project", project="myproject") |
563 | + lp_users = FauxLaunchpadUsersForBoard(lp_to_kanban={"person1": "Person One"}) |
564 | + lp_projects = [project1] |
565 | + bug_task1 = FauxBugTask( |
566 | + bug_id=123, target=project1, title='Test bug in myproject', |
567 | + description='Test bug description.') |
568 | + bug_task2 = FauxBugTask( |
569 | + bug_id=456, target=project1, title='Test bug in otherproject', |
570 | + description='Test bug description.') |
571 | + bug_card1 = FauxCard( |
572 | + title='BugCard123', external_card_id=u'123') |
573 | + bug_card2 = FauxCard( |
574 | + title='BugCard456', external_card_id=u'456') |
575 | + board = FauxBoard( |
576 | + cards=[bug_card1, bug_card2], bug_cards=[bug_card1, bug_card2]) |
577 | + new_lane = board.addLane('new') |
578 | + coding_lane = board.addLane('coding') |
579 | + bug_card1.lane = coding_lane |
580 | + bug_card2.lane = coding_lane |
581 | + bug_type = FauxCardType(id=3, name='Bug', is_default=False) |
582 | + bug_card1.type = bug_type |
583 | + bug_card2.type = bug_type |
584 | + self.board.cards.extend([bug_card1, bug_card2]) |
585 | + lp = FauxLP({"projects": {"myproject": project1}, |
586 | + "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) |
587 | + sync_cards_external_ids( |
588 | + board, self.bconf, lp, lp_users, lp_projects, filter_bug=u"456") |
589 | + coding_lane = FauxLane(board=board, path='coding') |
590 | + self.assertEqual(new_lane, bug_card2.moved_to) |
591 | + self.assertIsNone(bug_card1.moved_to) |
592 | + |
593 | def test_sync_cards_skips_bugs_of_external_projects_when_active_projects_set(self): |
594 | """Bugs of external projects are skipped when autosync set to active-projects.""" |
595 | # Configure bugs2cards to sync only active-projects myproject1 and myproject2 |
596 | @@ -1744,7 +1845,7 @@ |
597 | self.board.cards.extend([bug_card1, bug_card2]) |
598 | # Include project1 and project2 in our active 'projects' fake LP data |
599 | lp = FauxLP({"projects": {"myproject": project1, "myproject2": project2}, |
600 | - "bugs": {123: bug_task1.bug, "456": bug_task2.bug}}) |
601 | + "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) |
602 | sync_cards_external_ids(board, self.bconf, lp, lp_users, lp_projects) |
603 | coding_lane = FauxLane(board=board, path='coding') |
604 | self.assertEqual(new_lane, bug_card1.moved_to) |
Command: make check /ci.lscape. net/job/ latch-test/ 9627/
Result: Fail
Revno: 162
Branch: lp:~davidpbritton/lp2kanban/filter-bugs-correct-movement
Jenkins: https:/