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 | 17 | # buildout. | 17 | # buildout. |
6 | 18 | BUILDOUT_BIN = $(PY) bin/tags bin/test | 18 | BUILDOUT_BIN = $(PY) bin/tags bin/test |
7 | 19 | 19 | ||
9 | 20 | default: check | 20 | default: bin/test |
10 | 21 | 21 | ||
11 | 22 | 22 | ||
12 | 23 | download-cache: | 23 | download-cache: |
13 | 24 | mkdir download-cache | 24 | mkdir download-cache |
14 | 25 | 25 | ||
15 | 26 | clean: | ||
16 | 27 | @rm -rf bin | ||
17 | 26 | 28 | ||
18 | 27 | bin/buildout: download-cache | 29 | bin/buildout: download-cache |
19 | 28 | $(PYTHON) bootstrap.py -v 1.7.1 -f eggs -c buildout.cfg | 30 | $(PYTHON) bootstrap.py -v 1.7.1 -f eggs -c buildout.cfg |
20 | @@ -59,4 +61,4 @@ | |||
21 | 59 | . venv/bin/activate; \ | 61 | . venv/bin/activate; \ |
22 | 60 | $(MAKE) check | 62 | $(MAKE) check |
23 | 61 | 63 | ||
25 | 62 | .PHONY: check default configs needs-xdg-utils ci-test | 64 | .PHONY: check default configs needs-xdg-utils clean ci-test |
26 | 63 | 65 | ||
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 | 38 | Testing | 38 | Testing |
32 | 39 | ------- | 39 | ------- |
33 | 40 | 40 | ||
34 | 41 | All tests should work on 16.04+, please run in a vitrualenv though: | ||
35 | 42 | |||
36 | 43 | virtualenv ve | ||
37 | 44 | source ve/bin/activate | ||
38 | 45 | make check | ||
39 | 46 | |||
40 | 41 | 'lp2kanban test' board configuration connects to LP staging by default | 47 | 'lp2kanban test' board configuration connects to LP staging by default |
41 | 42 | to allow easier testing. | 48 | to allow easier testing. |
42 | 43 | 49 | ||
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 | 1 | #!/bin/bash -xe | 1 | #!/bin/bash -xe |
48 | 2 | 2 | ||
49 | 3 | ini="$1.ini" | ||
50 | 4 | date "+START: %c" | 3 | date "+START: %c" |
51 | 5 | 4 | ||
52 | 6 | #export https_proxy=http://squid.external:3128 | 5 | #export https_proxy=http://squid.external:3128 |
53 | @@ -28,5 +27,7 @@ | |||
54 | 28 | # - https://stackoverflow.com/questions/1473577 | 27 | # - https://stackoverflow.com/questions/1473577 |
55 | 29 | # - https://stackoverflow.com/questions/492483 | 28 | # - https://stackoverflow.com/questions/492483 |
56 | 30 | # - https://stackoverflow.com/questions/9932406 | 29 | # - https://stackoverflow.com/questions/9932406 |
58 | 31 | PYTHONIOENCODING="utf-8" bin/py src/lp2kanban/bugs2cards.py -c configs/${1}.ini | 30 | config=$1 |
59 | 31 | shift | ||
60 | 32 | PYTHONIOENCODING="utf-8" bin/py src/lp2kanban/bugs2cards.py -c "configs/${config}.ini" "${@}" | ||
61 | 32 | date "+END: %c" | 33 | date "+END: %c" |
62 | 33 | 34 | ||
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 | 18 | 'requests', | 18 | 'requests', |
68 | 19 | 'setuptools', | 19 | 'setuptools', |
69 | 20 | 'simplejson', | 20 | 'simplejson', |
70 | 21 | 'testfixtures', | ||
71 | 21 | 'testtools', | 22 | 'testtools', |
72 | 22 | ], | 23 | ], |
73 | 23 | ) | 24 | ) |
74 | 24 | 25 | ||
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 | 3 | from ConfigParser import ConfigParser | 3 | from ConfigParser import ConfigParser |
80 | 4 | from argparse import ArgumentParser | 4 | from argparse import ArgumentParser |
81 | 5 | from launchpadlib.launchpad import Launchpad | 5 | from launchpadlib.launchpad import Launchpad |
82 | 6 | import logging | ||
83 | 6 | from lp2kanban.cards2workitems import ( | 7 | from lp2kanban.cards2workitems import ( |
84 | 7 | convert_cards_to_work_items, | 8 | convert_cards_to_work_items, |
85 | 8 | get_blueprint_linked_cards, | 9 | get_blueprint_linked_cards, |
86 | @@ -45,7 +46,13 @@ | |||
87 | 45 | help="List available kanban boards.") | 46 | help="List available kanban boards.") |
88 | 46 | parser.add_argument( | 47 | parser.add_argument( |
89 | 47 | '-d', '--debug', action='store_const', const=True, default=False, | 48 | '-d', '--debug', action='store_const', const=True, default=False, |
91 | 48 | help="Turn on debugging for HTTP traffic") | 49 | help="Turn on logging debug, debugging for HTTP traffic") |
92 | 50 | parser.add_argument( | ||
93 | 51 | '-B', '--bug', required=False, type=int, default=None, | ||
94 | 52 | help="Only operate on this bug.") | ||
95 | 53 | parser.add_argument( | ||
96 | 54 | '-C', '--card', required=False, type=int, default=None, | ||
97 | 55 | help="Only operate on this card id, can obtain by looking at URL.") | ||
98 | 49 | return parser | 56 | return parser |
99 | 50 | 57 | ||
100 | 51 | 58 | ||
101 | @@ -207,7 +214,8 @@ | |||
102 | 207 | return lp_bug | 214 | return lp_bug |
103 | 208 | 215 | ||
104 | 209 | 216 | ||
106 | 210 | def move_cards_to_feature_taskboard(feature_card, feature_bug, source_lanes): | 217 | def move_cards_to_feature_taskboard( |
107 | 218 | feature_card, feature_bug, source_lanes, filter_card=None): | ||
108 | 211 | """Move all cards related to subtasks of the feature_card. | 219 | """Move all cards related to subtasks of the feature_card. |
109 | 212 | 220 | ||
110 | 213 | This will only move cards which are in the landing_lanes or deploy_lanes. | 221 | This will only move cards which are in the landing_lanes or deploy_lanes. |
111 | @@ -220,6 +228,8 @@ | |||
112 | 220 | """ | 228 | """ |
113 | 221 | linked_cards = get_cards_for_feature(feature_card, feature_bug=feature_bug) | 229 | linked_cards = get_cards_for_feature(feature_card, feature_bug=feature_bug) |
114 | 222 | for card in linked_cards: | 230 | for card in linked_cards: |
115 | 231 | if filter_card and filter_card != card.id: | ||
116 | 232 | return | ||
117 | 223 | if card.type.name == "Feature": # We don't want to move feature cards | 233 | if card.type.name == "Feature": # We don't want to move feature cards |
118 | 224 | continue | 234 | continue |
119 | 225 | card_path = card.lane.path | 235 | card_path = card.lane.path |
120 | @@ -349,11 +359,14 @@ | |||
121 | 349 | """ | 359 | """ |
122 | 350 | if conf.get('sync_cards', 'off') == 'off': | 360 | if conf.get('sync_cards', 'off') == 'off': |
123 | 351 | return False | 361 | return False |
124 | 362 | logging.debug("Should I sync? - {}".format(card.title)) | ||
125 | 352 | no_sync_lanes = [ | 363 | no_sync_lanes = [ |
126 | 353 | lane_path.strip() | 364 | lane_path.strip() |
127 | 354 | for lane_path in conf.get("no_sync_lanes", "").split(",")] | 365 | for lane_path in conf.get("no_sync_lanes", "").split(",")] |
128 | 355 | for no_sync_lane_path in no_sync_lanes: | 366 | for no_sync_lane_path in no_sync_lanes: |
129 | 356 | if no_sync_lane_path and card.lane.path.endswith(no_sync_lane_path): | 367 | if no_sync_lane_path and card.lane.path.endswith(no_sync_lane_path): |
130 | 368 | logging.debug("Not syncing, {} in no_sync_lanes".format( | ||
131 | 369 | card.lane.path)) | ||
132 | 357 | return False | 370 | return False |
133 | 358 | if conf.get('autosync', None) in ['on', 'active-projects']: | 371 | if conf.get('autosync', None) in ['on', 'active-projects']: |
134 | 359 | has_id = (card.external_card_id is not None and | 372 | has_id = (card.external_card_id is not None and |
135 | @@ -666,6 +679,7 @@ | |||
136 | 666 | 679 | ||
137 | 667 | def create_cards(board, bconf, lp_users, lp_projects): | 680 | def create_cards(board, bconf, lp_users, lp_projects): |
138 | 668 | """Create new cards on all projects.""" | 681 | """Create new cards on all projects.""" |
139 | 682 | logging.info("Creating new cards") | ||
140 | 669 | new_bug_tag = bconf.get("bug_to_card_tag") | 683 | new_bug_tag = bconf.get("bug_to_card_tag") |
141 | 670 | if new_bug_tag is not None: | 684 | if new_bug_tag is not None: |
142 | 671 | for lp_project in lp_projects: | 685 | for lp_project in lp_projects: |
143 | @@ -674,10 +688,13 @@ | |||
144 | 674 | bconf.get("bug_to_card_lane"), lp_users.feature_lanes) | 688 | bconf.get("bug_to_card_lane"), lp_users.feature_lanes) |
145 | 675 | 689 | ||
146 | 676 | 690 | ||
148 | 677 | def sync_cards_linked_branches(board, bconf, lp, lp_users): | 691 | def sync_cards_linked_branches( |
149 | 692 | board, bconf, lp, lp_users, filter_card=None): | ||
150 | 678 | """Sync any cards that have linked external branches.""" | 693 | """Sync any cards that have linked external branches.""" |
152 | 679 | print " Syncing branch cards:" | 694 | logging.info("Syncing branch cards") |
153 | 680 | for card in board.getCardsWithExternalLinks(only_branches=True): | 695 | for card in board.getCardsWithExternalLinks(only_branches=True): |
154 | 696 | if filter_card and filter_card != card.id: | ||
155 | 697 | continue | ||
156 | 681 | if card.external_card_id: | 698 | if card.external_card_id: |
157 | 682 | # Cards with external_id are processed by getCardsWithExternalIds | 699 | # Cards with external_id are processed by getCardsWithExternalIds |
158 | 683 | continue | 700 | continue |
159 | @@ -714,9 +731,16 @@ | |||
160 | 714 | print " * %s (in %s)" % (card.title, card_path) | 731 | print " * %s (in %s)" % (card.title, card_path) |
161 | 715 | 732 | ||
162 | 716 | 733 | ||
164 | 717 | def sync_cards_external_ids(board, bconf, lp, lp_users, lp_projects): | 734 | def sync_cards_external_ids( |
165 | 735 | board, bconf, lp, lp_users, lp_projects, filter_bug=None, | ||
166 | 736 | filter_card=None): | ||
167 | 718 | """Sync any cards that have linked external ids (bugs in launchpad).""" | 737 | """Sync any cards that have linked external ids (bugs in launchpad).""" |
168 | 738 | logging.info("Syncing cards with external ids") | ||
169 | 719 | for card in board.getCardsWithExternalIds(): | 739 | for card in board.getCardsWithExternalIds(): |
170 | 740 | if filter_bug and card.external_card_id != filter_bug: | ||
171 | 741 | continue | ||
172 | 742 | if filter_card and filter_card != card.id: | ||
173 | 743 | continue | ||
174 | 720 | if should_sync_card(card, bconf): | 744 | if should_sync_card(card, bconf): |
175 | 721 | lp_bug = get_lp_bug(card, lp) | 745 | lp_bug = get_lp_bug(card, lp) |
176 | 722 | if lp_bug is None: | 746 | if lp_bug is None: |
177 | @@ -763,8 +787,10 @@ | |||
178 | 763 | print " * %s: %s -- Not synced" % ( | 787 | print " * %s: %s -- Not synced" % ( |
179 | 764 | lp_bug.id, card.title) | 788 | lp_bug.id, card.title) |
180 | 765 | 789 | ||
182 | 766 | def move_cards_to_taskboards(board, bconf, lp, lp_users): | 790 | def move_cards_to_taskboards( |
183 | 791 | board, bconf, lp, lp_users, filter_card=None): | ||
184 | 767 | """Move branch cards to feature cards if they are landed.""" | 792 | """Move branch cards to feature cards if they are landed.""" |
185 | 793 | logging.info("Move cards to taskboards") | ||
186 | 768 | if "${group}" in bconf["landing_lanes"]: | 794 | if "${group}" in bconf["landing_lanes"]: |
187 | 769 | # Replace the optional template ${group} with configured board groups. | 795 | # Replace the optional template ${group} with configured board groups. |
188 | 770 | # This creates a list of landing_lanes, one per group on the board. | 796 | # This creates a list of landing_lanes, one per group on the board. |
189 | @@ -779,11 +805,12 @@ | |||
190 | 779 | feature_card.lane.path in valid_taskboard_lanes): | 805 | feature_card.lane.path in valid_taskboard_lanes): |
191 | 780 | lp_bug = get_lp_bug(feature_card, lp) | 806 | lp_bug = get_lp_bug(feature_card, lp) |
192 | 781 | move_cards_to_feature_taskboard( | 807 | move_cards_to_feature_taskboard( |
194 | 782 | feature_card, lp_bug, valid_taskboard_lanes) | 808 | feature_card, lp_bug, valid_taskboard_lanes, filter_card) |
195 | 783 | 809 | ||
196 | 784 | 810 | ||
197 | 785 | def convert_cards_to_work_items(board, bconf, lp, lp_users, lp_projects): | 811 | def convert_cards_to_work_items(board, bconf, lp, lp_users, lp_projects): |
198 | 786 | """Create work items out of cards if desired.""" | 812 | """Create work items out of cards if desired.""" |
199 | 813 | logging.info("Convert cards to work items") | ||
200 | 787 | if bconf.get("convert_cards_to_work_items", False) == "on": | 814 | if bconf.get("convert_cards_to_work_items", False) == "on": |
201 | 788 | # Sync cards to blueprint work items, if so configured. | 815 | # Sync cards to blueprint work items, if so configured. |
202 | 789 | print " Checking for cards to sync with work items..." | 816 | print " Checking for cards to sync with work items..." |
203 | @@ -808,25 +835,46 @@ | |||
204 | 808 | 835 | ||
205 | 809 | If a card is located in a "no_move" lane, the card won't get moved. | 836 | If a card is located in a "no_move" lane, the card won't get moved. |
206 | 810 | """ | 837 | """ |
207 | 838 | logging.debug("Evaluating for move: {}".format(card.title)) | ||
208 | 811 | target_lane_path = bconf[ConfigOptionByStatus[bug_status]] | 839 | target_lane_path = bconf[ConfigOptionByStatus[bug_status]] |
213 | 812 | if assignees: | 840 | if "${group}" in target_lane_path: |
214 | 813 | group = lp_users.lp_to_group.get(assignees[0].name) | 841 | if assignees: |
215 | 814 | if group: | 842 | group = lp_users.lp_to_group.get(assignees[0].name) |
216 | 815 | target_lane_path = target_lane_path.replace("${group}", group) | 843 | if group: |
217 | 844 | target_lane_path = target_lane_path.replace("${group}", group) | ||
218 | 845 | else: | ||
219 | 846 | logging.debug( | ||
220 | 847 | "Not moving, assignee: {} not in group: {}".format( | ||
221 | 848 | assignees[0].name, group)) | ||
222 | 849 | return | ||
223 | 850 | else: | ||
224 | 851 | logging.debug("Not moving, not assigned") | ||
225 | 852 | return | ||
226 | 853 | |||
227 | 816 | omnidirectional_card_moves = ( | 854 | omnidirectional_card_moves = ( |
228 | 817 | bconf.get("omnidirectional_card_moves", "on") == "on") | 855 | bconf.get("omnidirectional_card_moves", "on") == "on") |
229 | 818 | no_move_lanes = [ | 856 | no_move_lanes = [ |
230 | 819 | lane_path.strip() | 857 | lane_path.strip() |
232 | 820 | for lane_path in bconf.get("no_move_lanes", "").split(",")] | 858 | for lane_path in bconf.get("no_move_lanes", "").split(",") |
233 | 859 | if lane_path] | ||
234 | 821 | for no_move_lane_path in no_move_lanes: | 860 | for no_move_lane_path in no_move_lanes: |
235 | 822 | if no_move_lane_path and card.lane.path.endswith(no_move_lane_path): | 861 | if no_move_lane_path and card.lane.path.endswith(no_move_lane_path): |
236 | 862 | logging.debug("Not moving, {} in no_move_lanes".format( | ||
237 | 863 | card.lane.path)) | ||
238 | 823 | return | 864 | return |
239 | 865 | |||
240 | 824 | if omnidirectional_card_moves: | 866 | if omnidirectional_card_moves: |
241 | 825 | target_lane = find_card_target_lane_omnidirectional( | 867 | target_lane = find_card_target_lane_omnidirectional( |
242 | 826 | card.lane, target_lane_path) | 868 | card.lane, target_lane_path) |
243 | 827 | else: | 869 | else: |
244 | 828 | target_lane = find_card_target_lane_next( | 870 | target_lane = find_card_target_lane_next( |
245 | 829 | card.lane, target_lane_path) | 871 | card.lane, target_lane_path) |
246 | 872 | if card.lane.path == target_lane_path: | ||
247 | 873 | logging.debug("Not moving, already in {}".format(card.lane.path)) | ||
248 | 874 | return | ||
249 | 875 | |||
250 | 876 | logging.debug( | ||
251 | 877 | " * Moving: {} -> {}".format(card.lane.path, target_lane_path)) | ||
252 | 830 | card.move(target_lane) | 878 | card.move(target_lane) |
253 | 831 | 879 | ||
254 | 832 | 880 | ||
255 | @@ -853,13 +901,43 @@ | |||
256 | 853 | continue | 901 | continue |
257 | 854 | 902 | ||
258 | 855 | 903 | ||
259 | 904 | def process_board(board, bconf, filter_bug, filter_card): | ||
260 | 905 | """Sync a given board. | ||
261 | 906 | |||
262 | 907 | Decomposition of main, and handles interpreting | ||
263 | 908 | filter arguments. | ||
264 | 909 | """ | ||
265 | 910 | lp, lp_projects, lp_users = get_lp_data(board, bconf) | ||
266 | 911 | if not filter_bug and not filter_card: | ||
267 | 912 | create_cards(board, bconf, lp_users, lp_projects) | ||
268 | 913 | if not filter_bug: | ||
269 | 914 | sync_cards_linked_branches( | ||
270 | 915 | board, bconf, lp, lp_users, filter_card=filter_card) | ||
271 | 916 | sync_cards_external_ids( | ||
272 | 917 | board, bconf, lp, lp_users, lp_projects, filter_bug=filter_bug, | ||
273 | 918 | filter_card=filter_card) | ||
274 | 919 | if not filter_bug: | ||
275 | 920 | move_cards_to_taskboards( | ||
276 | 921 | board, bconf, lp, lp_users, filter_card=filter_card) | ||
277 | 922 | if not filter_bug and not filter_card: | ||
278 | 923 | convert_cards_to_work_items(board, bconf, lp, lp_users, lp_projects) | ||
279 | 924 | save_board(board) | ||
280 | 925 | |||
281 | 926 | |||
282 | 856 | def main(argv): | 927 | def main(argv): |
283 | 857 | """Update the Yellow team's kanban board with data from Launchpad.""" | 928 | """Update the Yellow team's kanban board with data from Launchpad.""" |
284 | 858 | args = get_arg_parser().parse_args() | 929 | args = get_arg_parser().parse_args() |
285 | 930 | if args.bug: | ||
286 | 931 | args.bug = str(args.bug) | ||
287 | 859 | 932 | ||
288 | 860 | if args.debug: | 933 | if args.debug: |
289 | 934 | logging.basicConfig( | ||
290 | 935 | level=logging.DEBUG, format='%(asctime)s %(levelname)s %(message)s') | ||
291 | 861 | import httplib | 936 | import httplib |
292 | 862 | httplib.HTTPConnection.debuglevel = 1 | 937 | httplib.HTTPConnection.debuglevel = 1 |
293 | 938 | else: | ||
294 | 939 | logging.basicConfig( | ||
295 | 940 | level=logging.INFO, format='%(asctime)s %(levelname)s %(message)s') | ||
296 | 863 | 941 | ||
297 | 864 | config = ConfigParser() | 942 | config = ConfigParser() |
298 | 865 | config.read(args.config) | 943 | config.read(args.config) |
299 | @@ -891,13 +969,7 @@ | |||
300 | 891 | print "Could not retrieve board. Skipping." | 969 | print "Could not retrieve board. Skipping." |
301 | 892 | continue | 970 | continue |
302 | 893 | bconf = boards_config[board_name] | 971 | bconf = boards_config[board_name] |
310 | 894 | lp, lp_projects, lp_users = get_lp_data(board, bconf) | 972 | process_board(board, bconf, args.bug, args.card) |
304 | 895 | create_cards(board, bconf, lp_users, lp_projects) | ||
305 | 896 | sync_cards_linked_branches(board, bconf, lp, lp_users) | ||
306 | 897 | sync_cards_external_ids(board, bconf, lp, lp_users, lp_projects) | ||
307 | 898 | move_cards_to_taskboards(board, bconf, lp, lp_users) | ||
308 | 899 | convert_cards_to_work_items(board, bconf, lp, lp_users) | ||
309 | 900 | save_board(board) | ||
311 | 901 | except (KeyError, AssertionError) as e: | 973 | except (KeyError, AssertionError) as e: |
312 | 902 | print "Exception caught. Skipping board." | 974 | print "Exception caught. Skipping board." |
313 | 903 | print_exc() | 975 | print_exc() |
314 | 904 | 976 | ||
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 | 3 | 3 | ||
320 | 4 | import requests | 4 | import requests |
321 | 5 | import json | 5 | import json |
322 | 6 | import logging | ||
323 | 6 | import operator | 7 | import operator |
324 | 7 | from pprint import pprint | 8 | from pprint import pprint |
325 | 8 | import re | 9 | import re |
326 | @@ -180,9 +181,8 @@ | |||
327 | 180 | return name.upper() | 181 | return name.upper() |
328 | 181 | 182 | ||
329 | 182 | def __setattr__(self, attr, value): | 183 | def __setattr__(self, attr, value): |
333 | 183 | if ((not hasattr(self, attr) or | 184 | if (getattr(self, attr, object()) != value and |
334 | 184 | getattr(self, attr, None) != value) and | 185 | attr in self._watched_attrs): |
332 | 185 | attr in self._watched_attrs): | ||
335 | 186 | self.direct_setattr('is_dirty', True) | 186 | self.direct_setattr('is_dirty', True) |
336 | 187 | self.dirty_attrs.add(attr) | 187 | self.dirty_attrs.add(attr) |
337 | 188 | self.direct_setattr(attr, value) | 188 | self.direct_setattr(attr, value) |
338 | @@ -531,30 +531,38 @@ | |||
339 | 531 | def getCardsWithDescriptionAnnotations(self): | 531 | def getCardsWithDescriptionAnnotations(self): |
340 | 532 | return self._cards_with_description_annotations | 532 | return self._cards_with_description_annotations |
341 | 533 | 533 | ||
343 | 534 | def fetchDetails(self): | 534 | def fetchDetails(self, include_archive=False, include_taskboards=False): |
344 | 535 | """Fetch all details from the board into our datastructure. | ||
345 | 536 | |||
346 | 537 | include_taskboards - add all cards on taskboards into my card list | ||
347 | 538 | include_archive - add all cards from archive into my card list. | ||
348 | 539 | """ | ||
349 | 535 | self.details = self.connector.get( | 540 | self.details = self.connector.get( |
350 | 536 | self.base_uri + str(self.id)).ReplyData[0] | 541 | self.base_uri + str(self.id)).ReplyData[0] |
351 | 537 | 542 | ||
352 | 538 | self._populateUsers(self.details['BoardUsers']) | 543 | self._populateUsers(self.details['BoardUsers']) |
353 | 539 | self._populateCardTypes(self.details['CardTypes']) | 544 | self._populateCardTypes(self.details['CardTypes']) |
361 | 540 | self._archive = self.connector.get( | 545 | lanes = self.details['Lanes'] |
362 | 541 | "/Kanban/Api/Board/" + str(self.id) + "/Archive").ReplyData[0] | 546 | if include_archive: |
363 | 542 | archive_lanes = [lane_dict['Lane'] for lane_dict in self._archive] | 547 | self._archive = self.connector.get( |
364 | 543 | archive_lanes.extend( | 548 | "/Kanban/Api/Board/" + str(self.id) + "/Archive").ReplyData[0] |
365 | 544 | [lane_dict['Lane'] for | 549 | archive_lanes = [lane_dict['Lane'] for lane_dict in self._archive] |
366 | 545 | lane_dict in self._archive[0]['ChildLanes']]) | 550 | archive_lanes.extend( |
367 | 546 | self._backlog = self.connector.get( | 551 | [lane_dict['Lane'] for |
368 | 552 | lane_dict in self._archive[0]['ChildLanes']]) | ||
369 | 553 | lanes += archive_lanes | ||
370 | 554 | lanes += self.connector.get( | ||
371 | 547 | "/Kanban/Api/Board/" + str(self.id) + "/Backlog").ReplyData[0] | 555 | "/Kanban/Api/Board/" + str(self.id) + "/Backlog").ReplyData[0] |
382 | 548 | self._populateLanes( | 556 | self._populateLanes(lanes) |
383 | 549 | self.details['Lanes'] + archive_lanes + self._backlog) | 557 | if include_taskboards: |
384 | 550 | for card in self.cards: | 558 | for card in self.cards: |
385 | 551 | if card.current_task_board_id: # We are a task board | 559 | if card.current_task_board_id: # We are a task board |
386 | 552 | taskboard_data = self.connector.get( | 560 | taskboard_data = self.connector.get( |
387 | 553 | "/Kanban/Api/v1/board/%s/card/%s/taskboard" % (self.id, card.id)) | 561 | "/Kanban/Api/v1/board/%s/card/%s/taskboard" % (self.id, card.id)) |
388 | 554 | card.taskboard = LeankitTaskBoard( | 562 | card.taskboard = LeankitTaskBoard( |
389 | 555 | taskboard_data["ReplyData"][0], self, card) | 563 | taskboard_data["ReplyData"][0], self, card) |
390 | 556 | card.taskboard.fetchDetails() | 564 | card.taskboard.fetchDetails() |
391 | 557 | self.cards.extend(card.taskboard.cards) | 565 | self.cards.extend(card.taskboard.cards) |
392 | 558 | self._classifyCards() | 566 | self._classifyCards() |
393 | 559 | self._populateFeatureTagPaths() | 567 | self._populateFeatureTagPaths() |
394 | 560 | 568 | ||
395 | @@ -769,10 +777,10 @@ | |||
396 | 769 | board = self._findBoardInCache(board_id, title) | 777 | board = self._findBoardInCache(board_id, title) |
397 | 770 | return board | 778 | return board |
398 | 771 | 779 | ||
400 | 772 | def getBoard(self, board_id=None, title=None): | 780 | def getBoard(self, board_id=None, title=None, include_archive=False): |
401 | 773 | board = self._findBoard(board_id, title) | 781 | board = self._findBoard(board_id, title) |
402 | 774 | if board is not None: | 782 | if board is not None: |
404 | 775 | board.fetchDetails() | 783 | board.fetchDetails(include_archive=include_archive) |
405 | 776 | return board | 784 | return board |
406 | 777 | 785 | ||
407 | 778 | 786 | ||
408 | 779 | 787 | ||
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 | 1 | # Copyright 2012 Canonical Ltd. | 1 | # Copyright 2012 Canonical Ltd. |
414 | 2 | """Common fixtures for lp2kanban tests.""" | 2 | """Common fixtures for lp2kanban tests.""" |
415 | 3 | 3 | ||
416 | 4 | import uuid | ||
417 | 5 | |||
418 | 6 | from lp2kanban.kanban import Record | ||
419 | 7 | |||
420 | 8 | |||
421 | 4 | __metaclass__ = type | 9 | __metaclass__ = type |
422 | 5 | 10 | ||
423 | 6 | from lp2kanban.kanban import Record | ||
424 | 7 | |||
425 | 8 | 11 | ||
426 | 9 | class FauxCardType: | 12 | class FauxCardType: |
427 | 10 | 13 | ||
428 | @@ -123,6 +126,7 @@ | |||
429 | 123 | self.external_system_url = external_system_url | 126 | self.external_system_url = external_system_url |
430 | 124 | self.moved_to = None | 127 | self.moved_to = None |
431 | 125 | self.saved = False | 128 | self.saved = False |
432 | 129 | self.id = uuid.uuid1() | ||
433 | 126 | 130 | ||
434 | 127 | def save(self): | 131 | def save(self): |
435 | 128 | """A no-op for compatibility.""" | 132 | """A no-op for compatibility.""" |
436 | 129 | 133 | ||
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 | 13 | import datetime | 13 | import datetime |
442 | 14 | import unittest | 14 | import unittest |
443 | 15 | 15 | ||
444 | 16 | from testfixtures import log_capture | ||
445 | 17 | |||
446 | 16 | from lp2kanban.bugs2cards import ( | 18 | from lp2kanban.bugs2cards import ( |
447 | 17 | CardStatus, | 19 | CardStatus, |
448 | 18 | create_cards_in_project, | 20 | create_cards_in_project, |
449 | @@ -50,7 +52,6 @@ | |||
450 | 50 | ) | 52 | ) |
451 | 51 | 53 | ||
452 | 52 | 54 | ||
453 | 53 | |||
454 | 54 | def parse_config_text(text): | 55 | def parse_config_text(text): |
455 | 55 | config = ConfigParser() | 56 | config = ConfigParser() |
456 | 56 | config.readfp(StringIO(dedent(text))) | 57 | config.readfp(StringIO(dedent(text))) |
457 | @@ -1489,7 +1490,18 @@ | |||
458 | 1489 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) | 1490 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) |
459 | 1490 | self.assertEqual("coding", card.moved_to.path) | 1491 | self.assertEqual("coding", card.moved_to.path) |
460 | 1491 | 1492 | ||
462 | 1492 | def test_group_no_assignee(self): | 1493 | @log_capture() |
463 | 1494 | def test_noop(self, testlog): | ||
464 | 1495 | """Skip move attempt if already in right path.""" | ||
465 | 1496 | card = self.qa.addCard(FauxCard()) | ||
466 | 1497 | move_card(card, CardStatus.QA, self.bconf, [], self.lp_users) | ||
467 | 1498 | self.assertEqual(None, card.moved_to) | ||
468 | 1499 | testlog.check( | ||
469 | 1500 | ('root', 'DEBUG', 'Evaluating for move: '), | ||
470 | 1501 | ('root', 'DEBUG', 'Not moving, already in qa')) | ||
471 | 1502 | |||
472 | 1503 | @log_capture() | ||
473 | 1504 | def test_group_no_assignee(self, testlog): | ||
474 | 1493 | # If a target lane requires a group, and the card doesn't have | 1505 | # If a target lane requires a group, and the card doesn't have |
475 | 1494 | # an assignee, the card isn't moved. | 1506 | # an assignee, the card isn't moved. |
476 | 1495 | self.bconf["coding_lanes"] = "${group}::coding" | 1507 | self.bconf["coding_lanes"] = "${group}::coding" |
477 | @@ -1497,6 +1509,9 @@ | |||
478 | 1497 | card = self.backlog.addCard(FauxCard()) | 1509 | card = self.backlog.addCard(FauxCard()) |
479 | 1498 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) | 1510 | move_card(card, CardStatus.CODING, self.bconf, [], self.lp_users) |
480 | 1499 | self.assertEqual(None, card.moved_to) | 1511 | self.assertEqual(None, card.moved_to) |
481 | 1512 | testlog.check( | ||
482 | 1513 | ('root', 'DEBUG', 'Evaluating for move: '), | ||
483 | 1514 | ('root', 'DEBUG', 'Not moving, not assigned')) | ||
484 | 1500 | 1515 | ||
485 | 1501 | def test_group_assignee_in_group(self): | 1516 | def test_group_assignee_in_group(self): |
486 | 1502 | # If a target lane requires a group, and the card has an | 1517 | # If a target lane requires a group, and the card has an |
487 | @@ -1511,7 +1526,8 @@ | |||
488 | 1511 | self.lp_users) | 1526 | self.lp_users) |
489 | 1512 | self.assertEqual("test group::coding", card.moved_to.path) | 1527 | self.assertEqual("test group::coding", card.moved_to.path) |
490 | 1513 | 1528 | ||
492 | 1514 | def test_group_assignee_not_in_group(self): | 1529 | @log_capture() |
493 | 1530 | def test_group_assignee_not_in_group(self, testlog): | ||
494 | 1515 | # If a target lane requires a group, and the card has an | 1531 | # If a target lane requires a group, and the card has an |
495 | 1516 | # assignee that is not part of a group, the card isn't moved. | 1532 | # assignee that is not part of a group, the card isn't moved. |
496 | 1517 | self.bconf["coding_lanes"] = "${group}::coding" | 1533 | self.bconf["coding_lanes"] = "${group}::coding" |
497 | @@ -1522,6 +1538,11 @@ | |||
498 | 1522 | card, CardStatus.CODING, self.bconf, [FauxPerson("bar")], | 1538 | card, CardStatus.CODING, self.bconf, [FauxPerson("bar")], |
499 | 1523 | self.lp_users) | 1539 | self.lp_users) |
500 | 1524 | self.assertEqual(None, card.moved_to) | 1540 | self.assertEqual(None, card.moved_to) |
501 | 1541 | testlog.check( | ||
502 | 1542 | ('root', 'DEBUG', 'Evaluating for move: '), | ||
503 | 1543 | ('root', 'DEBUG', 'Not moving, assignee: bar not in group: None')) | ||
504 | 1544 | |||
505 | 1545 | |||
506 | 1525 | 1546 | ||
507 | 1526 | def test_no_move_lanes(self): | 1547 | def test_no_move_lanes(self): |
508 | 1527 | # Cards in a "no_move" lane doesn't get moved by move_card(). | 1548 | # Cards in a "no_move" lane doesn't get moved by move_card(). |
509 | @@ -1709,6 +1730,86 @@ | |||
510 | 1709 | self.assertEqual(self.feature_card, landed_card1.moved_to) | 1730 | self.assertEqual(self.feature_card, landed_card1.moved_to) |
511 | 1710 | self.assertEqual(self.feature_card, landed_card2.moved_to) | 1731 | self.assertEqual(self.feature_card, landed_card2.moved_to) |
512 | 1711 | 1732 | ||
513 | 1733 | def test_sync_cards_external_ids_filter_card(self): | ||
514 | 1734 | """filter_card skips and selects correctly.""" | ||
515 | 1735 | # Configure bugs2cards to sync only active-projects myproject1 and myproject2 | ||
516 | 1736 | self.bconf.update({ | ||
517 | 1737 | "autosync": "active-projects", | ||
518 | 1738 | "projects": "myproject", | ||
519 | 1739 | "sync_cards": "on", "move_cards": "on", | ||
520 | 1740 | "new_lanes": "new"}) | ||
521 | 1741 | # Setup bug1 and bug2 in separate projects which both need to move coding -> new | ||
522 | 1742 | project1 = FauxTarget(resource_type_link="project", project="myproject") | ||
523 | 1743 | lp_users = FauxLaunchpadUsersForBoard(lp_to_kanban={"person1": "Person One"}) | ||
524 | 1744 | lp_projects = [project1] | ||
525 | 1745 | bug_task1 = FauxBugTask( | ||
526 | 1746 | bug_id=123, target=project1, title='Test bug in myproject', | ||
527 | 1747 | description='Test bug description.') | ||
528 | 1748 | bug_task2 = FauxBugTask( | ||
529 | 1749 | bug_id=456, target=project1, title='Test bug in otherproject', | ||
530 | 1750 | description='Test bug description.') | ||
531 | 1751 | bug_card1 = FauxCard( | ||
532 | 1752 | title='BugCard123', external_card_id=u'123') | ||
533 | 1753 | bug_card2 = FauxCard( | ||
534 | 1754 | title='BugCard456', external_card_id=u'456') | ||
535 | 1755 | board = FauxBoard( | ||
536 | 1756 | cards=[bug_card1, bug_card2], bug_cards=[bug_card1, bug_card2]) | ||
537 | 1757 | new_lane = board.addLane('new') | ||
538 | 1758 | coding_lane = board.addLane('coding') | ||
539 | 1759 | bug_card1.lane = coding_lane | ||
540 | 1760 | bug_card2.lane = coding_lane | ||
541 | 1761 | bug_type = FauxCardType(id=3, name='Bug', is_default=False) | ||
542 | 1762 | bug_card1.type = bug_type | ||
543 | 1763 | bug_card2.type = bug_type | ||
544 | 1764 | self.board.cards.extend([bug_card1, bug_card2]) | ||
545 | 1765 | lp = FauxLP({"projects": {"myproject": project1}, | ||
546 | 1766 | "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) | ||
547 | 1767 | sync_cards_external_ids( | ||
548 | 1768 | board, self.bconf, lp, lp_users, lp_projects, filter_card=bug_card2.id) | ||
549 | 1769 | coding_lane = FauxLane(board=board, path='coding') | ||
550 | 1770 | self.assertEqual(new_lane, bug_card2.moved_to) | ||
551 | 1771 | self.assertIsNone(bug_card1.moved_to) | ||
552 | 1772 | |||
553 | 1773 | def test_sync_cards_skips_bugs_not_in_filter_bug(self): | ||
554 | 1774 | """filter_bug skips and selects bugs in sync_cards_external_ids.""" | ||
555 | 1775 | # Configure bugs2cards to sync only active-projects myproject1 and myproject2 | ||
556 | 1776 | self.bconf.update({ | ||
557 | 1777 | "autosync": "active-projects", | ||
558 | 1778 | "projects": "myproject", | ||
559 | 1779 | "sync_cards": "on", "move_cards": "on", | ||
560 | 1780 | "new_lanes": "new"}) | ||
561 | 1781 | # Setup bug1 and bug2 in separate projects which both need to move coding -> new | ||
562 | 1782 | project1 = FauxTarget(resource_type_link="project", project="myproject") | ||
563 | 1783 | lp_users = FauxLaunchpadUsersForBoard(lp_to_kanban={"person1": "Person One"}) | ||
564 | 1784 | lp_projects = [project1] | ||
565 | 1785 | bug_task1 = FauxBugTask( | ||
566 | 1786 | bug_id=123, target=project1, title='Test bug in myproject', | ||
567 | 1787 | description='Test bug description.') | ||
568 | 1788 | bug_task2 = FauxBugTask( | ||
569 | 1789 | bug_id=456, target=project1, title='Test bug in otherproject', | ||
570 | 1790 | description='Test bug description.') | ||
571 | 1791 | bug_card1 = FauxCard( | ||
572 | 1792 | title='BugCard123', external_card_id=u'123') | ||
573 | 1793 | bug_card2 = FauxCard( | ||
574 | 1794 | title='BugCard456', external_card_id=u'456') | ||
575 | 1795 | board = FauxBoard( | ||
576 | 1796 | cards=[bug_card1, bug_card2], bug_cards=[bug_card1, bug_card2]) | ||
577 | 1797 | new_lane = board.addLane('new') | ||
578 | 1798 | coding_lane = board.addLane('coding') | ||
579 | 1799 | bug_card1.lane = coding_lane | ||
580 | 1800 | bug_card2.lane = coding_lane | ||
581 | 1801 | bug_type = FauxCardType(id=3, name='Bug', is_default=False) | ||
582 | 1802 | bug_card1.type = bug_type | ||
583 | 1803 | bug_card2.type = bug_type | ||
584 | 1804 | self.board.cards.extend([bug_card1, bug_card2]) | ||
585 | 1805 | lp = FauxLP({"projects": {"myproject": project1}, | ||
586 | 1806 | "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) | ||
587 | 1807 | sync_cards_external_ids( | ||
588 | 1808 | board, self.bconf, lp, lp_users, lp_projects, filter_bug=u"456") | ||
589 | 1809 | coding_lane = FauxLane(board=board, path='coding') | ||
590 | 1810 | self.assertEqual(new_lane, bug_card2.moved_to) | ||
591 | 1811 | self.assertIsNone(bug_card1.moved_to) | ||
592 | 1812 | |||
593 | 1712 | def test_sync_cards_skips_bugs_of_external_projects_when_active_projects_set(self): | 1813 | def test_sync_cards_skips_bugs_of_external_projects_when_active_projects_set(self): |
594 | 1713 | """Bugs of external projects are skipped when autosync set to active-projects.""" | 1814 | """Bugs of external projects are skipped when autosync set to active-projects.""" |
595 | 1714 | # Configure bugs2cards to sync only active-projects myproject1 and myproject2 | 1815 | # Configure bugs2cards to sync only active-projects myproject1 and myproject2 |
596 | @@ -1744,7 +1845,7 @@ | |||
597 | 1744 | self.board.cards.extend([bug_card1, bug_card2]) | 1845 | self.board.cards.extend([bug_card1, bug_card2]) |
598 | 1745 | # Include project1 and project2 in our active 'projects' fake LP data | 1846 | # Include project1 and project2 in our active 'projects' fake LP data |
599 | 1746 | lp = FauxLP({"projects": {"myproject": project1, "myproject2": project2}, | 1847 | lp = FauxLP({"projects": {"myproject": project1, "myproject2": project2}, |
601 | 1747 | "bugs": {123: bug_task1.bug, "456": bug_task2.bug}}) | 1848 | "bugs": {123: bug_task1.bug, 456: bug_task2.bug}}) |
602 | 1748 | sync_cards_external_ids(board, self.bconf, lp, lp_users, lp_projects) | 1849 | sync_cards_external_ids(board, self.bconf, lp, lp_users, lp_projects) |
603 | 1749 | coding_lane = FauxLane(board=board, path='coding') | 1850 | coding_lane = FauxLane(board=board, path='coding') |
604 | 1750 | self.assertEqual(new_lane, bug_card1.moved_to) | 1851 | 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:/