Skip to content

Commit 0a181ee

Browse files
authored
Fix #247 #464: add maybe_update_issue_priority and maybe_update_issue_severity steps (#889)
* Mention 'sync_keywords_labels' in docs * Generalize code of resolution field * Add support for priority and severity * Generalize more Sq * Remove default parameters values for severity and priority * Adjust severity field according to mozilla-hub * Add missing tests * Refactor generic method * Add default priority mapping
1 parent f19fcdc commit 0a181ee

File tree

6 files changed

+262
-75
lines changed

6 files changed

+262
-75
lines changed

docs/actions.md

+3
Original file line numberDiff line numberDiff line change
@@ -149,9 +149,12 @@ linked Jira issue status to "Closed". If the bug changes to a status not listed
149149
- `update_issue`
150150
- `add_jira_comments_for_changes`
151151
- `maybe_assign_jira_user`
152+
- `maybe_update_issue_priority`
152153
- `maybe_update_issue_resolution`
154+
- `maybe_update_issue_severity`
153155
- `maybe_update_issue_status`
154156
- `create_comment`
157+
- `sync_keywords_labels`
155158
- `sync_whiteboard_labels`
156159
- `maybe_update_components`: looks at the component that's set on the bug (if any) and any components added to the project configuration with the `jira_components` parameter (see above). If those components are available on the Jira side as well, they're added to the Jira issue
157160

jbi/jira/service.py

+44-47
Original file line numberDiff line numberDiff line change
@@ -241,15 +241,41 @@ def assign_jira_user(self, context: ActionContext, email: str):
241241
# There doesn't appear to be an easy way to verify that
242242
# this user can be assigned to this issue, so just try
243243
# and do it.
244-
return self.client.update_issue_field(
245-
key=issue_key,
246-
fields={"assignee": {"accountId": jira_user_id}},
244+
return self.update_issue_field(
245+
context, "assignee", jira_user_id, wrap_value="accountId"
247246
)
248247
except (requests_exceptions.HTTPError, IOError) as exc:
249248
raise ValueError(
250249
f"Could not assign {jira_user_id} to issue {issue_key}"
251250
) from exc
252251

252+
def update_issue_field(
253+
self,
254+
context: ActionContext,
255+
field: str,
256+
value: Any,
257+
wrap_value: Optional[str] = None,
258+
):
259+
bug = context.bug
260+
issue_key = context.jira.issue
261+
logger.info(
262+
f"Updating {field} of Jira issue %s to %s for Bug %s",
263+
issue_key,
264+
value,
265+
bug.id,
266+
extra=context.model_dump(),
267+
)
268+
fields: dict[str, Any] = {field: {wrap_value: value} if wrap_value else value}
269+
response = self.client.update_issue_field(key=issue_key, fields=fields)
270+
logger.info(
271+
f"Updated {field} of Jira issue %s to %s for Bug %s",
272+
issue_key,
273+
value,
274+
bug.id,
275+
extra={"response": response, **context.model_dump()},
276+
)
277+
return response
278+
253279
def update_issue_status(self, context: ActionContext, jira_status: str):
254280
"""Update the status of the Jira issue"""
255281
issue_key = context.jira.issue
@@ -267,51 +293,25 @@ def update_issue_status(self, context: ActionContext, jira_status: str):
267293

268294
def update_issue_summary(self, context: ActionContext):
269295
"""Update's an issue's summary with the description of an incoming bug"""
270-
271-
bug = context.bug
272-
issue_key = context.jira.issue
273-
logger.info(
274-
"Update summary of Jira issue %s for Bug %s",
275-
issue_key,
276-
bug.id,
277-
extra=context.model_dump(),
278-
)
279296
truncated_summary = markdown_to_jira(
280-
bug.summary or "", max_length=JIRA_DESCRIPTION_CHAR_LIMIT
297+
context.bug.summary or "", max_length=JIRA_DESCRIPTION_CHAR_LIMIT
298+
)
299+
return self.update_issue_field(
300+
context, field="summary", value=truncated_summary
281301
)
282-
fields: dict[str, str] = {
283-
"summary": truncated_summary,
284-
}
285-
jira_response = self.client.update_issue_field(key=issue_key, fields=fields)
286-
return jira_response
287302

288303
def update_issue_resolution(self, context: ActionContext, jira_resolution: str):
289304
"""Update the resolution of the Jira issue."""
290-
issue_key = context.jira.issue
291-
assert issue_key # Until we have more fine-grained typing of contexts
292-
293-
logger.info(
294-
"Updating resolution of Jira issue %s to %s",
295-
issue_key,
296-
jira_resolution,
297-
extra=context.model_dump(),
305+
return self.update_issue_field(
306+
context,
307+
field="resolution",
308+
value=jira_resolution,
309+
wrap_value="name",
298310
)
299-
response = self.client.update_issue_field(
300-
key=issue_key,
301-
fields={"resolution": {"name": jira_resolution}},
302-
)
303-
logger.info(
304-
"Updated resolution of Jira issue %s to %s",
305-
issue_key,
306-
jira_resolution,
307-
extra={"response": response, **context.model_dump()},
308-
)
309-
return response
310311

311312
def update_issue_components(
312313
self,
313-
issue_key: str,
314-
project: str,
314+
context: ActionContext,
315315
components: Iterable[str],
316316
) -> tuple[Optional[dict], set]:
317317
"""Attempt to add components to the specified issue
@@ -328,7 +328,9 @@ def update_issue_components(
328328
missing_components = set(components)
329329
jira_components = []
330330

331-
all_project_components = self.client.get_project_components(project)
331+
all_project_components = self.client.get_project_components(
332+
context.jira.project
333+
)
332334
for comp in all_project_components:
333335
if comp["name"] in missing_components:
334336
jira_components.append({"id": comp["id"]})
@@ -337,13 +339,8 @@ def update_issue_components(
337339
if not jira_components:
338340
return None, missing_components
339341

340-
logger.info(
341-
"attempting to add components '%s' to issue '%s'",
342-
",".join(components),
343-
issue_key,
344-
)
345-
resp = self.client.update_issue_field(
346-
key=issue_key, fields={"components": jira_components}
342+
resp = self.update_issue_field(
343+
context, field="components", value=jira_components
347344
)
348345
return resp, missing_components
349346

jbi/models.py

+11
Original file line numberDiff line numberDiff line change
@@ -74,9 +74,20 @@ class ActionParams(BaseModel, frozen=True):
7474
jira_project_key: str
7575
steps: ActionSteps = ActionSteps()
7676
jira_components: JiraComponents = JiraComponents()
77+
jira_severity_field: str = "customfield_10716"
78+
jira_priority_field: str = "priority"
79+
jira_resolution_field: str = "resolution"
7780
labels_brackets: Literal["yes", "no", "both"] = "no"
7881
status_map: dict[str, str] = {}
82+
priority_map: dict[str, str] = {
83+
"P1": "P1",
84+
"P2": "P2",
85+
"P3": "P3",
86+
"P4": "Low",
87+
"P5": "Lowest",
88+
}
7989
resolution_map: dict[str, str] = {}
90+
severity_map: dict[str, str] = {}
8091
issue_type_map: dict[str, str] = {"task": "Task", "defect": "Bug"}
8192

8293

jbi/steps.py

+59-25
Original file line numberDiff line numberDiff line change
@@ -200,39 +200,74 @@ def maybe_assign_jira_user(
200200
return (StepStatus.NOOP, context)
201201

202202

203-
def maybe_update_issue_resolution(
204-
context: ActionContext, *, parameters: ActionParams, jira_service: JiraService
203+
def _maybe_update_issue_mapped_field(
204+
source_field: str,
205+
context: ActionContext,
206+
parameters: ActionParams,
207+
jira_service: JiraService,
208+
wrap_value: Optional[str] = None,
205209
) -> StepResult:
206-
"""
207-
Update the Jira issue status
208-
https://support.atlassian.com/jira-cloud-administration/docs/what-are-issue-statuses-priorities-and-resolutions/
209-
"""
210-
211-
bz_resolution = context.bug.resolution or ""
212-
jira_resolution = parameters.resolution_map.get(bz_resolution)
213-
214-
if jira_resolution is None:
210+
source_value = getattr(context.bug, source_field, None) or ""
211+
target_field = getattr(parameters, f"jira_{source_field}_field")
212+
target_value = getattr(parameters, f"{source_field}_map").get(source_value)
213+
if target_value is None:
215214
logger.info(
216-
"Bug resolution %r was not in the resolution map.",
217-
bz_resolution,
215+
f"Bug {source_field} %r was not in the {source_field} map.",
216+
source_value,
218217
extra=context.update(
219218
operation=Operation.IGNORE,
220219
).model_dump(),
221220
)
222221
return (StepStatus.INCOMPLETE, context)
223222

224-
if context.operation == Operation.CREATE:
225-
resp = jira_service.update_issue_resolution(context, jira_resolution)
226-
context.append_responses(resp)
227-
return (StepStatus.SUCCESS, context)
223+
if (
224+
context.operation == Operation.UPDATE
225+
and source_field not in context.event.changed_fields()
226+
):
227+
return (StepStatus.NOOP, context)
228228

229-
if context.operation == Operation.UPDATE:
230-
if "resolution" in context.event.changed_fields():
231-
resp = jira_service.update_issue_resolution(context, jira_resolution)
232-
context.append_responses(resp)
233-
return (StepStatus.SUCCESS, context)
229+
resp = jira_service.update_issue_field(
230+
context,
231+
target_field,
232+
target_value,
233+
wrap_value,
234+
)
235+
context.append_responses(resp)
236+
return (StepStatus.SUCCESS, context)
234237

235-
return (StepStatus.NOOP, context)
238+
239+
def maybe_update_issue_priority(
240+
context: ActionContext, *, parameters: ActionParams, jira_service: JiraService
241+
) -> StepResult:
242+
"""
243+
Update the Jira issue priority
244+
"""
245+
return _maybe_update_issue_mapped_field(
246+
"priority", context, parameters, jira_service, wrap_value="name"
247+
)
248+
249+
250+
def maybe_update_issue_resolution(
251+
context: ActionContext, *, parameters: ActionParams, jira_service: JiraService
252+
) -> StepResult:
253+
"""
254+
Update the Jira issue status
255+
https://support.atlassian.com/jira-cloud-administration/docs/what-are-issue-statuses-priorities-and-resolutions/
256+
"""
257+
return _maybe_update_issue_mapped_field(
258+
"resolution", context, parameters, jira_service, wrap_value="name"
259+
)
260+
261+
262+
def maybe_update_issue_severity(
263+
context: ActionContext, *, parameters: ActionParams, jira_service: JiraService
264+
) -> StepResult:
265+
"""
266+
Update the Jira issue severity
267+
"""
268+
return _maybe_update_issue_mapped_field(
269+
"severity", context, parameters, jira_service, wrap_value="value"
270+
)
236271

237272

238273
def maybe_update_issue_status(
@@ -301,8 +336,7 @@ def maybe_update_components(
301336

302337
try:
303338
resp, missing_components = jira_service.update_issue_components(
304-
issue_key=context.jira.issue,
305-
project=parameters.jira_project_key,
339+
context=context,
306340
components=candidate_components,
307341
)
308342
except requests_exceptions.HTTPError as exc:

tests/unit/jira/test_service.py

+7-3
Original file line numberDiff line numberDiff line change
@@ -203,8 +203,10 @@ def test_update_issue_resolution(
203203
jira_service.update_issue_resolution(context=context, jira_resolution="DONEZO")
204204

205205
before, after = capturelogs.messages
206-
assert before == "Updating resolution of Jira issue JBI-234 to DONEZO"
207-
assert after == "Updated resolution of Jira issue JBI-234 to DONEZO"
206+
assert (
207+
before == "Updating resolution of Jira issue JBI-234 to DONEZO for Bug 654321"
208+
)
209+
assert after == "Updated resolution of Jira issue JBI-234 to DONEZO for Bug 654321"
208210

209211

210212
def test_update_issue_resolution_raises(
@@ -230,7 +232,9 @@ def test_update_issue_resolution_raises(
230232
)
231233

232234
[message] = capturelogs.messages
233-
assert message == "Updating resolution of Jira issue JBI-234 to DONEZO"
235+
assert (
236+
message == "Updating resolution of Jira issue JBI-234 to DONEZO for Bug 654321"
237+
)
234238

235239

236240
def test_create_jira_issue(

0 commit comments

Comments
 (0)