11
11
from __future__ import annotations
12
12
13
13
import logging
14
+ from enum import Enum , auto
14
15
from typing import TYPE_CHECKING , Optional
15
16
16
17
from requests import exceptions as requests_exceptions
17
18
18
19
from jbi import Operation
19
- from jbi .errors import IncompleteStepError
20
+
21
+
22
+ class StepStatus (Enum ):
23
+ """
24
+ Options for the result of executing a step function:
25
+ SUCCESS: The step succeeded at doing meaningful work
26
+ INCOMPLETE: The step did not execute successfully, but it's an error we anticipated
27
+ NOOP: The step executed successfully, but didn't have any meaningful work to do
28
+ """
29
+
30
+ SUCCESS = auto ()
31
+ INCOMPLETE = auto ()
32
+ NOOP = auto ()
33
+
20
34
21
35
# https://docs.python.org/3.11/library/typing.html#typing.TYPE_CHECKING
22
36
if TYPE_CHECKING :
23
37
from jbi .models import ActionContext , ActionParams
24
38
from jbi .services .bugzilla import BugzillaService
25
39
from jbi .services .jira import JiraService
26
40
41
+ StepResult = tuple [StepStatus , ActionContext ]
42
+
27
43
logger = logging .getLogger (__name__ )
28
44
29
45
30
- def create_comment (context : ActionContext , * , jira_service : JiraService ):
46
+ def create_comment (context : ActionContext , * , jira_service : JiraService ) -> StepResult :
31
47
"""Create a Jira comment using `context.bug.comment`"""
32
48
bug = context .bug
33
49
@@ -36,11 +52,11 @@ def create_comment(context: ActionContext, *, jira_service: JiraService):
36
52
"No matching comment found in payload" ,
37
53
extra = context .model_dump (),
38
54
)
39
- return context
55
+ return ( StepStatus . NOOP , context )
40
56
41
57
jira_response = jira_service .add_jira_comment (context )
42
58
context = context .append_responses (jira_response )
43
- return context
59
+ return ( StepStatus . SUCCESS , context )
44
60
45
61
46
62
def create_issue (
@@ -49,7 +65,7 @@ def create_issue(
49
65
parameters : ActionParams ,
50
66
jira_service : JiraService ,
51
67
bugzilla_service : BugzillaService ,
52
- ):
68
+ ) -> StepResult :
53
69
"""Create the Jira issue with the first comment as the description."""
54
70
bug = context .bug
55
71
issue_type = parameters .issue_type_map .get (bug .type or "" , "Task" )
@@ -65,29 +81,33 @@ def create_issue(
65
81
jira = context .jira .update (issue = issue_key ),
66
82
)
67
83
context = context .append_responses (jira_create_response )
68
- return context
84
+ return ( StepStatus . SUCCESS , context )
69
85
70
86
71
- def add_link_to_jira (context : ActionContext , * , bugzilla_service : BugzillaService ):
87
+ def add_link_to_jira (
88
+ context : ActionContext , * , bugzilla_service : BugzillaService
89
+ ) -> StepResult :
72
90
"""Add the URL to the Jira issue in the `see_also` field on the Bugzilla ticket"""
73
91
bugzilla_response = bugzilla_service .add_link_to_jira (context )
74
92
context = context .append_responses (bugzilla_response )
75
- return context
93
+ return ( StepStatus . SUCCESS , context )
76
94
77
95
78
- def add_link_to_bugzilla (context : ActionContext , * , jira_service : JiraService ):
96
+ def add_link_to_bugzilla (
97
+ context : ActionContext , * , jira_service : JiraService
98
+ ) -> StepResult :
79
99
"""Add the URL of the Bugzilla ticket to the links of the Jira issue"""
80
100
jira_response = jira_service .add_link_to_bugzilla (context )
81
101
context = context .append_responses (jira_response )
82
- return context
102
+ return ( StepStatus . SUCCESS , context )
83
103
84
104
85
105
def maybe_delete_duplicate (
86
106
context : ActionContext ,
87
107
* ,
88
108
bugzilla_service : BugzillaService ,
89
109
jira_service : JiraService ,
90
- ):
110
+ ) -> StepResult :
91
111
"""
92
112
In the time taken to create the Jira issue the bug may have been updated so
93
113
re-retrieve it to ensure we have the latest data, and delete any duplicate
@@ -99,29 +119,36 @@ def maybe_delete_duplicate(
99
119
)
100
120
if jira_response_delete :
101
121
context = context .append_responses (jira_response_delete )
102
- return context
122
+ return (StepStatus .SUCCESS , context )
123
+ return (StepStatus .NOOP , context )
103
124
104
125
105
- def update_issue_summary (context : ActionContext , * , jira_service : JiraService ):
126
+ def update_issue_summary (
127
+ context : ActionContext , * , jira_service : JiraService
128
+ ) -> StepResult :
106
129
"""Update the Jira issue's summary if the linked bug is modified."""
107
130
108
131
if "summary" not in context .event .changed_fields ():
109
- return context
132
+ return ( StepStatus . NOOP , context )
110
133
111
134
jira_response_update = jira_service .update_issue_summary (context )
112
135
context = context .append_responses (jira_response_update )
113
- return context
136
+ return ( StepStatus . SUCCESS , context )
114
137
115
138
116
- def add_jira_comments_for_changes (context : ActionContext , * , jira_service : JiraService ):
139
+ def add_jira_comments_for_changes (
140
+ context : ActionContext , * , jira_service : JiraService
141
+ ) -> StepResult :
117
142
"""Add a Jira comment for each field (assignee, status, resolution) change on
118
143
the Bugzilla ticket."""
119
144
comments_responses = jira_service .add_jira_comments_for_changes (context )
120
145
context .append_responses (comments_responses )
121
- return context
146
+ return ( StepStatus . SUCCESS , context )
122
147
123
148
124
- def maybe_assign_jira_user (context : ActionContext , * , jira_service : JiraService ):
149
+ def maybe_assign_jira_user (
150
+ context : ActionContext , * , jira_service : JiraService
151
+ ) -> StepResult :
125
152
"""Assign the user on the Jira issue, based on the Bugzilla assignee email.
126
153
127
154
It will attempt to assign the Jira issue the same person as the bug is assigned to. This relies on
@@ -134,19 +161,19 @@ def maybe_assign_jira_user(context: ActionContext, *, jira_service: JiraService)
134
161
135
162
if context .operation == Operation .CREATE :
136
163
if not bug .is_assigned ():
137
- return context
164
+ return ( StepStatus . NOOP , context )
138
165
139
166
try :
140
167
resp = jira_service .assign_jira_user (context , bug .assigned_to ) # type: ignore
141
168
context .append_responses (resp )
142
- return context
169
+ return ( StepStatus . SUCCESS , context )
143
170
except ValueError as exc :
144
171
logger .debug (str (exc ), extra = context .model_dump ())
145
- raise IncompleteStepError ( context ) from exc
172
+ return ( StepStatus . INCOMPLETE , context )
146
173
147
174
if context .operation == Operation .UPDATE :
148
175
if "assigned_to" not in event .changed_fields ():
149
- return context
176
+ return ( StepStatus . SUCCESS , context )
150
177
151
178
if not bug .is_assigned ():
152
179
resp = jira_service .clear_assignee (context )
@@ -158,15 +185,14 @@ def maybe_assign_jira_user(context: ActionContext, *, jira_service: JiraService)
158
185
# If that failed then just fall back to clearing the assignee.
159
186
resp = jira_service .clear_assignee (context )
160
187
context .append_responses (resp )
161
- return context
188
+ return ( StepStatus . SUCCESS , context )
162
189
163
- # This happens when exceptions are raised an ignored.
164
- return context
190
+ return (StepStatus .NOOP , context )
165
191
166
192
167
193
def maybe_update_issue_resolution (
168
194
context : ActionContext , * , parameters : ActionParams , jira_service : JiraService
169
- ):
195
+ ) -> StepResult :
170
196
"""
171
197
Update the Jira issue status
172
198
https://support.atlassian.com/jira-cloud-administration/docs/what-are-issue-statuses-priorities-and-resolutions/
@@ -183,25 +209,25 @@ def maybe_update_issue_resolution(
183
209
operation = Operation .IGNORE ,
184
210
).model_dump (),
185
211
)
186
- raise IncompleteStepError ( context )
212
+ return ( StepStatus . INCOMPLETE , context )
187
213
188
214
if context .operation == Operation .CREATE :
189
215
resp = jira_service .update_issue_resolution (context , jira_resolution )
190
216
context .append_responses (resp )
191
- return context
217
+ return ( StepStatus . SUCCESS , context )
192
218
193
219
if context .operation == Operation .UPDATE :
194
220
if "resolution" in context .event .changed_fields ():
195
221
resp = jira_service .update_issue_resolution (context , jira_resolution )
196
222
context .append_responses (resp )
197
- return context
223
+ return ( StepStatus . SUCCESS , context )
198
224
199
- return context
225
+ return ( StepStatus . NOOP , context )
200
226
201
227
202
228
def maybe_update_issue_status (
203
229
context : ActionContext , * , parameters : ActionParams , jira_service : JiraService
204
- ):
230
+ ) -> StepResult :
205
231
"""
206
232
Update the Jira issue resolution
207
233
https://support.atlassian.com/jira-cloud-administration/docs/what-are-issue-statuses-priorities-and-resolutions/
@@ -217,27 +243,27 @@ def maybe_update_issue_status(
217
243
operation = Operation .IGNORE ,
218
244
).model_dump (),
219
245
)
220
- raise IncompleteStepError ( context )
246
+ return ( StepStatus . INCOMPLETE , context )
221
247
222
248
if context .operation == Operation .CREATE :
223
249
resp = jira_service .update_issue_status (context , jira_status )
224
250
context .append_responses (resp )
225
- return context
251
+ return ( StepStatus . SUCCESS , context )
226
252
227
253
if context .operation == Operation .UPDATE :
228
254
changed_fields = context .event .changed_fields ()
229
255
230
256
if "status" in changed_fields or "resolution" in changed_fields :
231
257
resp = jira_service .update_issue_status (context , jira_status )
232
258
context .append_responses (resp )
233
- return context
259
+ return ( StepStatus . SUCCESS , context )
234
260
235
- return context
261
+ return ( StepStatus . NOOP , context )
236
262
237
263
238
264
def maybe_update_components (
239
265
context : ActionContext , * , parameters : ActionParams , jira_service : JiraService
240
- ):
266
+ ) -> StepResult :
241
267
"""
242
268
Update the Jira issue components
243
269
"""
@@ -254,7 +280,7 @@ def maybe_update_components(
254
280
255
281
if not candidate_components :
256
282
# no components to update
257
- return context
283
+ return ( StepStatus . NOOP , context )
258
284
259
285
# Although we previously introspected the project components, we
260
286
# still have to catch any potential 400 error response here, because
@@ -280,18 +306,18 @@ def maybe_update_components(
280
306
extra = context .model_dump (),
281
307
)
282
308
context .append_responses (exc .response )
283
- raise IncompleteStepError ( context ) from exc
309
+ return ( StepStatus . INCOMPLETE , context )
284
310
285
311
if missing_components :
286
312
logger .warning (
287
313
"Could not find components '%s' in project" ,
288
314
"," .join (sorted (missing_components )),
289
315
extra = context .model_dump (),
290
316
)
291
- raise IncompleteStepError ( context )
317
+ return ( StepStatus . INCOMPLETE , context )
292
318
293
319
context .append_responses (resp )
294
- return context
320
+ return ( StepStatus . SUCCESS , context )
295
321
296
322
297
323
def _whiteboard_as_labels (labels_brackets : str , whiteboard : Optional [str ]) -> list [str ]:
@@ -328,7 +354,7 @@ def _build_labels_update(
328
354
329
355
def sync_whiteboard_labels (
330
356
context : ActionContext , * , parameters : ActionParams , jira_service : JiraService
331
- ):
357
+ ) -> StepResult :
332
358
"""
333
359
Set whiteboard tags as labels on the Jira issue.
334
360
"""
@@ -342,8 +368,7 @@ def sync_whiteboard_labels(
342
368
labels_brackets = parameters .labels_brackets ,
343
369
)
344
370
else :
345
- # Whiteboard field not changed, ignore.
346
- return context
371
+ return (StepStatus .NOOP , context )
347
372
else :
348
373
# On creation, just add them all.
349
374
additions , removals = _build_labels_update (
@@ -368,7 +393,7 @@ def sync_whiteboard_labels(
368
393
extra = context .model_dump (),
369
394
)
370
395
context .append_responses (exc .response )
371
- raise IncompleteStepError ( context ) from exc
396
+ return ( StepStatus . INCOMPLETE , context )
372
397
373
398
context .append_responses (resp )
374
- return context
399
+ return ( StepStatus . SUCCESS , context )
0 commit comments