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

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/>. 

15 

16import logging 

17from copy import deepcopy 

18 

19from gitlabracadabra.objects.object import GitLabracadabraObject 

20 

21logger = logging.getLogger(__name__) 

22 

23 

24class BoardsMixin(GitLabracadabraObject): 

25 """Object with boards.""" 

26 

27 """_process_boards() 

28 

29 Process the boards param. 

30 """ 

31 

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 

35 

36 unknown_boards = self._content.get("unknown_boards", "warn") 

37 unknown_board_lists = self._content.get("unknown_board_lists", "delete") 

38 

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 ) 

50 

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 

62 

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) 

175 

176 """_handle_board_lists() 

177 

178 Handle board lists. 

179 """ 

180 

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}) 

228 

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 

236 

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) 

245 

246 """_decrement_intermediate_lists() 

247 

248 Decrement intermediate lists. 

249 """ 

250 

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 

255 

256 """_increment_intermediate_lists() 

257 

258 Increment intermediate lists. 

259 """ 

260 

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