Coverage for src/gitlabracadabra/mixins/boards.py: 74%
123 statements
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 17:02 +0100
« prev ^ index » next coverage.py v7.6.12, created at 2025-03-10 17:02 +0100
1# Copyright (C) 2019-2025 Mathieu Parent <math.parent@gmail.com>
2#
3# This program is free software: you can redistribute it and/or modify
4# it under the terms of the GNU Lesser General Public License as published by
5# the Free Software Foundation, either version 3 of the License, or
6# (at your option) any later version.
7#
8# This program is distributed in the hope that it will be useful,
9# but WITHOUT ANY WARRANTY; without even the implied warranty of
10# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11# GNU Lesser General Public License for more details.
12#
13# You should have received a copy of the GNU Lesser General Public License
14# along with this program. If not, see <http://www.gnu.org/licenses/>.
16import logging
17from copy import deepcopy
19from gitlabracadabra.objects.object import GitLabracadabraObject
21logger = logging.getLogger(__name__)
24class BoardsMixin(GitLabracadabraObject):
25 """Object with boards."""
27 """_process_boards()
29 Process the boards param.
30 """
32 def _process_boards(self, param_name, param_value, *, dry_run=False, skip_save=False):
33 assert param_name == "boards" # noqa: S101
34 assert not skip_save # noqa: S101
36 unknown_boards = self._content.get("unknown_boards", "warn")
37 unknown_board_lists = self._content.get("unknown_board_lists", "delete")
39 current_boards = dict(
40 [[current_board.name, current_board] for current_board in self._obj.boards.list(all=True)]
41 )
42 target_boards = dict([[target_board["name"], deepcopy(target_board)] for target_board in param_value])
43 target_boards_by_old_name = dict(
44 [
45 [target_board["old_name"], deepcopy(target_board)]
46 for target_board in param_value
47 if "old_name" in target_board
48 ]
49 )
51 # We do not reuse labels from LabelsMixin, to have fresh data
52 try:
53 self._current_labels = dict(
54 [[current_label.name, current_label] for current_label in self._obj.labels.list(all=True)]
55 )
56 except AttributeError:
57 # https://github.com/python-gitlab/python-gitlab/pull/847
58 logger.error(
59 "[%s] Unable to manage labels: %s", self._name, "group labels requires python-gitlab >= 1.11.0"
60 )
61 return
63 # We first check for already existing boards
64 for current_board_name, current_board in sorted(current_boards.items()):
65 if current_board_name in target_boards:
66 target_board = target_boards.pop(current_board_name)
67 elif current_board_name in target_boards_by_old_name:
68 target_board = target_boards_by_old_name[current_board_name]
69 target_boards.pop(target_board["name"])
70 if dry_run: 70 ↛ 71line 70 didn't jump to line 71 because the condition on line 70 was never true
71 logger.info(
72 "[%s] NOT Changing board %s %s: %s -> %s (dry-run)",
73 self._name,
74 current_board_name,
75 "name",
76 current_board_name,
77 target_board["name"],
78 )
79 else:
80 logger.info(
81 "[%s] Changing board %s %s: %s -> %s",
82 self._name,
83 current_board_name,
84 "name",
85 current_board_name,
86 target_board["name"],
87 )
88 current_board.name = target_board["name"]
89 try:
90 current_board.save()
91 except KeyError:
92 # v1.10.0 - feat: add support for board update
93 # https://github.com/python-gitlab/python-gitlab/pull/804
94 logger.error(
95 "[%s] Unable to update board: %s",
96 self._name,
97 "board update requires python-gitlab >= 1.10.0",
98 )
99 return
100 else:
101 target_board = None
102 if target_board:
103 for target_board_param_name, target_board_param_value in target_board.items():
104 if target_board_param_name == "lists":
105 unknown_lists = target_board.get("unknown_lists", unknown_board_lists)
106 self._handle_board_lists(
107 current_board, target_board_param_value, unknown_lists, dry_run=dry_run
108 )
109 continue
110 if target_board_param_name in ["old_name", "unknown_lists"]:
111 continue
112 try:
113 current_board_param_value = getattr(current_board, target_board_param_name)
114 except AttributeError:
115 logger.info(
116 "[%s] NOT Changing boards %s %s: %s -> %s " "(current value is not available)",
117 self._name,
118 current_board_name,
119 target_board_param_name,
120 None,
121 target_board_param_value,
122 )
123 continue
124 if current_board_param_value != target_board_param_value:
125 if dry_run: 125 ↛ 126line 125 didn't jump to line 126 because the condition on line 125 was never true
126 logger.info(
127 "[%s] NOT Changing board %s %s: %s -> %s (dry-run)",
128 self._name,
129 current_board_name,
130 target_board_param_name,
131 current_board_param_value,
132 target_board_param_value,
133 )
134 else:
135 logger.info(
136 "[%s] Changing board %s %s: %s -> %s",
137 self._name,
138 current_board_name,
139 target_board_param_name,
140 current_board_param_value,
141 target_board_param_value,
142 )
143 setattr(current_board, target_board_param_name, target_board_param_value)
144 try:
145 current_board.save()
146 except KeyError:
147 # v1.10.0 - feat: add support for board update
148 # https://github.com/python-gitlab/python-gitlab/pull/804
149 logger.error(
150 "[%s] Unable to update board: %s",
151 self._name,
152 "board update requires python-gitlab >= 1.10.0",
153 )
154 return
155 elif unknown_boards in ["delete", "remove"]: 155 ↛ 161line 155 didn't jump to line 161 because the condition on line 155 was always true
156 if dry_run: 156 ↛ 157line 156 didn't jump to line 157 because the condition on line 156 was never true
157 logger.info("[%s] NOT Removing board %s (dry-run)", self._name, current_board_name)
158 else:
159 logger.info("[%s] Removing board %s", self._name, current_board_name)
160 current_board.delete()
161 elif unknown_boards not in ["ignore", "skip"]:
162 logger.warning("[%s] NOT Removing board: %s", self._name, current_board_name)
163 # Remaining boards
164 for target_board_name, target_board in sorted(target_boards.items()):
165 target_board_lists = target_board.pop("lists", [])
166 unknown_lists = target_board.pop("unknown_lists", unknown_board_lists)
167 if dry_run: 167 ↛ 168line 167 didn't jump to line 168 because the condition on line 167 was never true
168 logger.info(
169 "[%s] NOT Adding board %s: %s -> %s (dry-run)", self._name, target_board_name, None, target_board
170 )
171 else:
172 logger.info("[%s] Adding board %s: %s -> %s", self._name, target_board_name, None, target_board)
173 board = self._obj.boards.create(target_board)
174 self._handle_board_lists(board, target_board_lists, unknown_lists, dry_run=dry_run)
176 """_handle_board_lists()
178 Handle board lists.
179 """
181 def _handle_board_lists(self, board, target_lists, unknown_lists, *, dry_run=False):
182 if len(target_lists) == 0 and unknown_lists in ["ignore", "skip"]: 182 ↛ 183line 182 didn't jump to line 183 because the condition on line 182 was never true
183 return
184 board = self._obj.boards.get(board.id)
185 current_lists = dict([[current_list.label["name"], current_list] for current_list in board.lists.list()])
186 # We first add any missing list
187 for target_list_position, target_list in enumerate(target_lists):
188 if target_list["label"] in current_lists:
189 current_list = current_lists[target_list["label"]]
190 if target_list_position != current_list.position:
191 old_list_position = current_list.position
192 # Boards::Lists::MoveService
193 # https://gitlab.com/gitlab-org/gitlab/-/blob/master/app/services/boards/lists/move_service.rb#L29
194 if old_list_position < target_list_position: 194 ↛ 195line 194 didn't jump to line 195 because the condition on line 194 was never true
195 self._decrement_intermediate_lists(current_lists, old_list_position, target_list_position)
196 else:
197 self._increment_intermediate_lists(current_lists, old_list_position, target_list_position)
198 current_list.position = target_list_position
199 if dry_run: 199 ↛ 200line 199 didn't jump to line 200 because the condition on line 199 was never true
200 logger.info(
201 "[%s] NOT Changing board list position %s: %s -> %s (dry-run)",
202 self._name,
203 target_list,
204 current_list.position,
205 target_list_position,
206 )
207 else:
208 logger.info(
209 "[%s] Changing board list position %s: %s -> %s (dry-run)",
210 self._name,
211 target_list,
212 current_list.position,
213 target_list_position,
214 )
215 current_list.save()
216 else:
217 if target_list["label"] not in self._current_labels:
218 logger.warning(
219 "[%s] Label not found for board %s: %s", self._name, board.name, target_list["label"]
220 )
221 continue
222 label_id = self._current_labels[target_list["label"]].id
223 if dry_run: 223 ↛ 224line 223 didn't jump to line 224 because the condition on line 223 was never true
224 logger.info("[%s] NOT Adding board list %s (dry-run)", self._name, target_list)
225 else:
226 logger.info("[%s] Adding board list %s", self._name, target_list)
227 board.lists.create({"label_id": label_id})
229 if unknown_lists in ["ignore", "skip"]: 229 ↛ 230line 229 didn't jump to line 230 because the condition on line 229 was never true
230 return
231 # Delete any remaining lists
232 for current_list_label, current_list in sorted(current_lists.items()):
233 current_list_dict = {"label": current_list_label}
234 if current_list_dict in target_lists:
235 continue
237 if unknown_lists in ["delete", "remove"]: 237 ↛ 243line 237 didn't jump to line 243 because the condition on line 237 was always true
238 if dry_run: 238 ↛ 239line 238 didn't jump to line 239 because the condition on line 238 was never true
239 logger.info("[%s] NOT Removing board list %s (dry-run)", self._name, current_list_dict)
240 else:
241 logger.info("[%s] Removing board list %s", self._name, current_list_dict)
242 current_list.delete()
243 elif unknown_lists not in ["ignore", "skip"]:
244 logger.warning("[%s] NOT Removing board list: %s", self._name, current_list_dict)
246 """_decrement_intermediate_lists()
248 Decrement intermediate lists.
249 """
251 def _decrement_intermediate_lists(self, lists, old_position, new_position):
252 for list_object in lists.values():
253 if list_object.position > old_position and list_object.position <= new_position:
254 list_object.position = list_object.position - 1
256 """_increment_intermediate_lists()
258 Increment intermediate lists.
259 """
261 def _increment_intermediate_lists(self, lists, old_position, new_position):
262 for list_object in lists.values():
263 if list_object.position >= new_position and list_object.position < old_position:
264 list_object.position = list_object.position + 1