Skip to content

Commit d11a98a

Browse files
authored
Do not mutate the request object (fixes #227) (#230)
* Remove request from log contexts, keep bug and event * Move map_as_comments our of request object * Move map_as_comment our of request object * Use bug and event in actions, instead of request
1 parent d08a509 commit d11a98a

12 files changed

+164
-148
lines changed

jbi/actions/README.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ Let's create a `new_action`!
1212

1313
```python
1414
from jbi import ActionResult, Operation
15+
from jbi.models import BugzillaBug, BugzillaWebhookEvent
1516

1617
JIRA_REQUIRED_PERMISSIONS = {"CREATE_ISSUES"}
1718

1819
def init(jira_project_key, optional_param=42):
1920

20-
def execute(payload) -> ActionResult:
21+
def execute(bug: BugzillaBug, event: BugzillaWebhookEvent) -> ActionResult:
2122
print(f"{optional_param}, going to {jira_project_key}!")
2223
return True, {"result": 42}
2324

@@ -26,10 +27,10 @@ Let's create a `new_action`!
2627

2728
1. In the above example the `jira_project_key` parameter is required
2829
1. `optional_param`, which has a default value, is not required to run this action
29-
1. `init()` returns a `__call__`able object that the system calls with the Bugzilla request payload
30+
1. `init()` returns a `__call__`able object that the system calls with the Bugzilla bug and WebHook event objects
3031
1. The returned `ActionResult` features a boolean to indicate whether something was performed or not, along with a `Dict` (used as a response to the WebHook endpoint).
3132

32-
1. Use the `payload` to perform the desired processing!
33+
1. Use the `bug` and `event` information to perform the desired processing!
3334
1. List the required Jira permissions to be set on projects that will use this action in the `JIRA_REQUIRED_PERMISSIONS` constant. The list of built-in permissions is [available on Atlanssian API docs](https://developer.atlassian.com/cloud/jira/platform/rest/v3/api-group-permission-schemes/#built-in-permissions).
3435
1. Use the available service calls from `jbi/services` (or make new ones)
3536
1. Update the `README.md` to document your action

jbi/actions/default.py

Lines changed: 53 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,7 @@
1212
from jbi import ActionResult, Operation
1313
from jbi.environment import get_settings
1414
from jbi.errors import ActionError
15-
from jbi.models import (
16-
ActionLogContext,
17-
BugzillaBug,
18-
BugzillaWebhookRequest,
19-
JiraContext,
20-
)
15+
from jbi.models import ActionLogContext, BugzillaBug, BugzillaWebhookEvent, JiraContext
2116
from jbi.services import bugzilla, jira
2217

2318
settings = get_settings()
@@ -54,32 +49,36 @@ def __init__(self, jira_project_key, **kwargs):
5449
self.jira_client = jira.get_client()
5550

5651
def __call__( # pylint: disable=inconsistent-return-statements
57-
self, payload: BugzillaWebhookRequest
52+
self,
53+
bug: BugzillaBug,
54+
event: BugzillaWebhookEvent,
5855
) -> ActionResult:
5956
"""Called from BZ webhook when default action is used. All default-action webhook-events are processed here."""
60-
target = payload.event.target # type: ignore
57+
target = event.target # type: ignore
6158
if target == "comment":
62-
return self.comment_create_or_noop(payload=payload) # type: ignore
59+
return self.comment_create_or_noop(bug=bug, event=event) # type: ignore
6360
if target == "bug":
64-
return self.bug_create_or_update(payload=payload)
61+
return self.bug_create_or_update(bug=bug, event=event)
6562
logger.debug(
6663
"Ignore event target %r",
6764
target,
6865
extra=ActionLogContext(
69-
request=payload,
66+
bug=bug,
67+
event=event,
7068
operation=Operation.IGNORE,
7169
).dict(),
7270
)
7371
return False, {}
7472

75-
def comment_create_or_noop(self, payload: BugzillaWebhookRequest) -> ActionResult:
73+
def comment_create_or_noop(
74+
self, bug: BugzillaBug, event: BugzillaWebhookEvent
75+
) -> ActionResult:
7676
"""Confirm issue is already linked, then apply comments; otherwise noop"""
77-
bug_obj = payload.bug
78-
linked_issue_key = bug_obj.extract_from_see_also()
77+
linked_issue_key = bug.extract_from_see_also()
7978

8079
log_context = ActionLogContext(
81-
request=payload,
82-
bug=bug_obj,
80+
event=event,
81+
bug=bug,
8382
operation=Operation.COMMENT,
8483
jira=JiraContext(
8584
issue=linked_issue_key,
@@ -89,19 +88,19 @@ def comment_create_or_noop(self, payload: BugzillaWebhookRequest) -> ActionResul
8988
if not linked_issue_key:
9089
logger.debug(
9190
"No Jira issue linked to Bug %s",
92-
bug_obj.id,
91+
bug.id,
9392
extra=log_context.dict(),
9493
)
9594
return False, {}
9695

97-
if bug_obj.comment is None:
96+
if bug.comment is None:
9897
logger.debug(
9998
"No matching comment found in payload",
10099
extra=log_context.dict(),
101100
)
102101
return False, {}
103102

104-
formatted_comment = payload.map_as_jira_comment()
103+
formatted_comment = bug.map_event_as_comment(event)
105104
jira_response = self.jira_client.issue_add_comment(
106105
issue_key=linked_issue_key,
107106
comment=formatted_comment,
@@ -113,45 +112,45 @@ def comment_create_or_noop(self, payload: BugzillaWebhookRequest) -> ActionResul
113112
)
114113
return True, {"jira_response": jira_response}
115114

116-
def jira_fields(self, bug_obj: BugzillaBug):
115+
def jira_fields(self, bug: BugzillaBug):
117116
"""Extract bug info as jira issue dictionary"""
118117
fields: dict[str, Any] = {
119-
"summary": bug_obj.summary,
118+
"summary": bug.summary,
120119
}
121120

122121
if self.sync_whiteboard_labels:
123-
fields["labels"] = bug_obj.get_jira_labels()
122+
fields["labels"] = bug.get_jira_labels()
124123

125124
return fields
126125

127126
def jira_comments_for_update(
128127
self,
129-
payload: BugzillaWebhookRequest,
128+
bug: BugzillaBug,
129+
event: BugzillaWebhookEvent,
130130
):
131131
"""Returns the comments to post to Jira for a changed bug"""
132-
return payload.map_as_comments()
132+
return bug.map_changes_as_comments(event)
133133

134134
def update_issue(
135135
self,
136-
payload: BugzillaWebhookRequest,
137-
bug_obj: BugzillaBug,
136+
bug: BugzillaBug,
137+
event: BugzillaWebhookEvent,
138138
linked_issue_key: str,
139139
is_new: bool,
140140
):
141141
"""Allows sub-classes to modify the Jira issue in response to a bug event"""
142142

143143
def bug_create_or_update(
144-
self, payload: BugzillaWebhookRequest
144+
self, bug: BugzillaBug, event: BugzillaWebhookEvent
145145
) -> ActionResult: # pylint: disable=too-many-locals
146146
"""Create and link jira issue with bug, or update; rollback if multiple events fire"""
147-
bug_obj = payload.bug
148-
linked_issue_key = bug_obj.extract_from_see_also() # type: ignore
147+
linked_issue_key = bug.extract_from_see_also() # type: ignore
149148
if not linked_issue_key:
150-
return self.create_and_link_issue(payload, bug_obj)
149+
return self.create_and_link_issue(bug=bug, event=event)
151150

152151
log_context = ActionLogContext(
153-
request=payload,
154-
bug=bug_obj,
152+
event=event,
153+
bug=bug,
155154
operation=Operation.LINK,
156155
jira=JiraContext(
157156
issue=linked_issue_key,
@@ -162,14 +161,14 @@ def bug_create_or_update(
162161
logger.debug(
163162
"Update fields of Jira issue %s for Bug %s",
164163
linked_issue_key,
165-
bug_obj.id,
164+
bug.id,
166165
extra=log_context.dict(),
167166
)
168167
jira_response_update = self.jira_client.update_issue_field(
169-
key=linked_issue_key, fields=self.jira_fields(bug_obj)
168+
key=linked_issue_key, fields=self.jira_fields(bug)
170169
)
171170

172-
comments = self.jira_comments_for_update(payload)
171+
comments = self.jira_comments_for_update(bug=bug, event=event)
173172
jira_response_comments = []
174173
for i, comment in enumerate(comments):
175174
logger.debug(
@@ -184,33 +183,35 @@ def bug_create_or_update(
184183
)
185184
)
186185

187-
self.update_issue(payload, bug_obj, linked_issue_key, is_new=False)
186+
self.update_issue(bug, event, linked_issue_key, is_new=False)
188187

189188
return True, {"jira_responses": [jira_response_update, jira_response_comments]}
190189

191190
def create_and_link_issue( # pylint: disable=too-many-locals
192-
self, payload, bug_obj
191+
self,
192+
bug,
193+
event,
193194
) -> ActionResult:
194195
"""create jira issue and establish link between bug and issue; rollback/delete if required"""
195196
log_context = ActionLogContext(
196-
request=payload,
197-
bug=bug_obj,
197+
event=event,
198+
bug=bug,
198199
operation=Operation.CREATE,
199200
jira=JiraContext(
200201
project=self.jira_project_key,
201202
),
202203
)
203204
logger.debug(
204205
"Create new Jira issue for Bug %s",
205-
bug_obj.id,
206+
bug.id,
206207
extra=log_context.dict(),
207208
)
208-
comment_list = self.bugzilla_client.get_comments(bug_obj.id)
209+
comment_list = self.bugzilla_client.get_comments(bug.id)
209210
description = comment_list[0].text[:JIRA_DESCRIPTION_CHAR_LIMIT]
210211

211212
fields = {
212-
**self.jira_fields(bug_obj), # type: ignore
213-
"issuetype": {"name": bug_obj.issue_type()},
213+
**self.jira_fields(bug), # type: ignore
214+
"issuetype": {"name": bug.issue_type()},
214215
"description": description,
215216
"project": {"key": self.jira_project_key},
216217
}
@@ -236,9 +237,9 @@ def create_and_link_issue( # pylint: disable=too-many-locals
236237

237238
# In the time taken to create the Jira issue the bug may have been updated so
238239
# re-retrieve it to ensure we have the latest data.
239-
bug_obj = self.bugzilla_client.get_bug(bug_obj.id)
240+
bug = self.bugzilla_client.get_bug(bug.id)
240241

241-
jira_key_in_bugzilla = bug_obj.extract_from_see_also()
242+
jira_key_in_bugzilla = bug.extract_from_see_also()
242243
_duplicate_creation_event = (
243244
jira_key_in_bugzilla is not None
244245
and jira_key_in_response != jira_key_in_bugzilla
@@ -247,7 +248,7 @@ def create_and_link_issue( # pylint: disable=too-many-locals
247248
logger.warning(
248249
"Delete duplicated Jira issue %s from Bug %s",
249250
jira_key_in_response,
250-
bug_obj.id,
251+
bug.id,
251252
extra=log_context.update(operation=Operation.DELETE).dict(),
252253
)
253254
jira_response_delete = self.jira_client.delete_issue(
@@ -259,14 +260,14 @@ def create_and_link_issue( # pylint: disable=too-many-locals
259260
logger.debug(
260261
"Link %r on Bug %s",
261262
jira_url,
262-
bug_obj.id,
263+
bug.id,
263264
extra=log_context.update(operation=Operation.LINK).dict(),
264265
)
265266
bugzilla_response = self.bugzilla_client.update_bug(
266-
bug_obj.id, see_also_add=jira_url
267+
bug.id, see_also_add=jira_url
267268
)
268269

269-
bugzilla_url = f"{settings.bugzilla_base_url}/show_bug.cgi?id={bug_obj.id}"
270+
bugzilla_url = f"{settings.bugzilla_base_url}/show_bug.cgi?id={bug.id}"
270271
logger.debug(
271272
"Link %r on Jira issue %s",
272273
bugzilla_url,
@@ -282,7 +283,9 @@ def create_and_link_issue( # pylint: disable=too-many-locals
282283
icon_title=icon_url,
283284
)
284285

285-
self.update_issue(payload, bug_obj, jira_key_in_response, is_new=True)
286+
self.update_issue(
287+
bug=bug, event=event, linked_issue_key=jira_key_in_response, is_new=True
288+
)
286289

287290
return True, {
288291
"bugzilla_response": bugzilla_response,

jbi/actions/default_with_assignee_and_status.py

Lines changed: 14 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,7 @@
1414
JIRA_REQUIRED_PERMISSIONS as DEFAULT_JIRA_REQUIRED_PERMISSIONS,
1515
)
1616
from jbi.actions.default import DefaultExecutor
17-
from jbi.models import (
18-
ActionLogContext,
19-
BugzillaBug,
20-
BugzillaWebhookRequest,
21-
JiraContext,
22-
)
17+
from jbi.models import ActionLogContext, BugzillaBug, BugzillaWebhookEvent, JiraContext
2318

2419
logger = logging.getLogger(__name__)
2520

@@ -45,25 +40,26 @@ def __init__(self, status_map, resolution_map, **kwargs):
4540

4641
def jira_comments_for_update(
4742
self,
48-
payload: BugzillaWebhookRequest,
43+
bug: BugzillaBug,
44+
event: BugzillaWebhookEvent,
4945
):
5046
"""Returns the comments to post to Jira for a changed bug"""
51-
return payload.map_as_comments(
52-
status_log_enabled=False, assignee_log_enabled=False
47+
return bug.map_changes_as_comments(
48+
event, status_log_enabled=False, assignee_log_enabled=False
5349
)
5450

5551
def update_issue(
5652
self,
57-
payload: BugzillaWebhookRequest,
58-
bug_obj: BugzillaBug,
53+
bug: BugzillaBug,
54+
event: BugzillaWebhookEvent,
5955
linked_issue_key: str,
6056
is_new: bool,
6157
):
62-
changed_fields = payload.event.changed_fields() or []
58+
changed_fields = event.changed_fields() or []
6359

6460
log_context = ActionLogContext(
65-
request=payload,
66-
bug=bug_obj,
61+
event=event,
62+
bug=bug,
6763
operation=Operation.UPDATE,
6864
jira=JiraContext(
6965
project=self.jira_project_key,
@@ -85,17 +81,15 @@ def clear_assignee():
8581
# If this is a new issue or if the bug's assignee has changed then
8682
# update the assignee.
8783
if is_new or "assigned_to" in changed_fields:
88-
if bug_obj.assigned_to == "[email protected]":
84+
if bug.assigned_to == "[email protected]":
8985
clear_assignee()
9086
else:
9187
logger.debug(
9288
"Attempting to update assignee",
9389
extra=log_context.dict(),
9490
)
9591
# Look up this user in Jira
96-
users = self.jira_client.user_find_by_user_string(
97-
query=bug_obj.assigned_to
98-
)
92+
users = self.jira_client.user_find_by_user_string(query=bug.assigned_to)
9993
if len(users) == 1:
10094
try:
10195
# There doesn't appear to be an easy way to verify that
@@ -125,7 +119,7 @@ def clear_assignee():
125119
# changed then update the issue status.
126120
if is_new or "status" in changed_fields or "resolution" in changed_fields:
127121
# If action has configured mappings for the issue resolution field, update it.
128-
bz_resolution = bug_obj.resolution
122+
bz_resolution = bug.resolution
129123
jira_resolution = self.resolution_map.get(bz_resolution)
130124
if jira_resolution:
131125
logger.debug(
@@ -150,7 +144,7 @@ def clear_assignee():
150144
)
151145

152146
# We use resolution if one exists or status otherwise.
153-
bz_status = bz_resolution or bug_obj.status
147+
bz_status = bz_resolution or bug.status
154148
jira_status = self.status_map.get(bz_status)
155149
if jira_status:
156150
logger.debug(

0 commit comments

Comments
 (0)