Skip to content

Commit 2ef1c92

Browse files
committed
Execute multiple actions if configured
This additionally adds a test that verifies actions are properly detected and executed.
1 parent 82f31b0 commit 2ef1c92

File tree

2 files changed

+92
-27
lines changed

2 files changed

+92
-27
lines changed

jbi/runner.py

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -206,12 +206,17 @@ def execute_action(
206206
request: bugzilla_models.WebhookRequest,
207207
actions: Actions,
208208
):
209-
"""Execute the configured action for the specified `request`.
209+
"""Execute the configured actions for the specified `request`.
210+
211+
If multiple actions are configured for a given request, all of them
212+
are executed.
210213
211214
This will raise an `IgnoreInvalidRequestError` error if the request
212215
does not contain bug data or does not match any action.
213216
214-
The value returned by the action call is returned.
217+
A dictionary containing the values returned by the actions calls
218+
is returned. The action tag is used to index the responses in the
219+
dictionary.
215220
"""
216221
bug, event = request.bug, request.event
217222
runner_context = RunnerContext(
@@ -224,7 +229,7 @@ def execute_action(
224229
raise IgnoreInvalidRequestError("private bugs are not supported")
225230

226231
try:
227-
action = lookup_actions(bug, actions)[0]
232+
relevant_actions = lookup_actions(bug, actions)
228233
except ActionNotFoundError as err:
229234
raise IgnoreInvalidRequestError(
230235
f"no bug whiteboard matching action tags: {err}"
@@ -243,8 +248,42 @@ def execute_action(
243248

244249
runner_context = runner_context.update(bug=bug)
245250

246-
runner_context = runner_context.update(actions=[action])
251+
runner_context = runner_context.update(actions=relevant_actions)
252+
253+
return do_execute_actions(runner_context, bug, relevant_actions)
254+
except IgnoreInvalidRequestError as exception:
255+
logger.info(
256+
"Ignore incoming request: %s",
257+
exception,
258+
extra=runner_context.update(operation=Operation.IGNORE).model_dump(),
259+
)
260+
statsd.incr("jbi.bugzilla.ignored.count")
261+
raise
262+
263+
264+
@statsd.timer("jbi.action.execution.timer")
265+
def do_execute_actions(
266+
runner_context: RunnerContext,
267+
bug: bugzilla_models.Bug,
268+
actions: Actions,
269+
):
270+
"""Execute the provided actions on the bug, within the provided context.
271+
272+
This will raise an `IgnoreInvalidRequestError` error if the request
273+
does not contain bug data or does not match any action.
274+
275+
A dictionary containing the values returned by the actions calls
276+
is returned. The action tag is used to index the responses in the
277+
dictionary.
278+
"""
279+
runner_context = runner_context.update(bug=bug)
280+
281+
runner_context = runner_context.update(actions=actions)
247282

283+
event = runner_context.event
284+
285+
details = {}
286+
for action in actions:
248287
linked_issue_key: Optional[str] = bug.extract_from_see_also(
249288
project_key=action.jira_project_key
250289
)
@@ -309,7 +348,8 @@ def execute_action(
309348
extra=runner_context.update(operation=Operation.EXECUTE).model_dump(),
310349
)
311350
executor = Executor(parameters=action.parameters)
312-
handled, details = executor(context=action_context)
351+
handled, action_details = executor(context=action_context)
352+
details[action.whiteboard_tag] = action_details
313353
statsd.incr(f"jbi.operation.{action_context.operation.lower()}.count")
314354
logger.info(
315355
"Action %r executed successfully for Bug %s",
@@ -320,12 +360,4 @@ def execute_action(
320360
).model_dump(),
321361
)
322362
statsd.incr("jbi.bugzilla.processed.count")
323-
return details
324-
except IgnoreInvalidRequestError as exception:
325-
logger.info(
326-
"Ignore incoming request: %s",
327-
exception,
328-
extra=runner_context.update(operation=Operation.IGNORE).model_dump(),
329-
)
330-
statsd.incr("jbi.bugzilla.ignored.count")
331-
raise
363+
return details

tests/unit/test_runner.py

Lines changed: 46 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@
44
import pytest
55
import requests
66
import responses
7-
import tests.fixtures.factories as factories
87

8+
import tests.fixtures.factories as factories
99
from jbi import Operation
1010
from jbi.bugzilla.client import BugNotAccessibleError
1111
from jbi.environment import get_settings
@@ -634,18 +634,20 @@ def test_lookup_action_found(whiteboard, actions, bug_factory):
634634
],
635635
)
636636
def test_multiple_lookup_actions_found(whiteboard, expected_tags, bug_factory):
637-
actions = factories.ActionsFactory(root=[
638-
factories.ActionFactory(
639-
whiteboard_tag="devtest",
640-
bugzilla_user_id="tbd",
641-
description="test config",
642-
),
643-
factories.ActionFactory(
644-
whiteboard_tag="other",
645-
bugzilla_user_id="tbd",
646-
description="test config",
647-
),
648-
])
637+
actions = factories.ActionsFactory(
638+
root=[
639+
factories.ActionFactory(
640+
whiteboard_tag="devtest",
641+
bugzilla_user_id="tbd",
642+
description="test config",
643+
),
644+
factories.ActionFactory(
645+
whiteboard_tag="other",
646+
bugzilla_user_id="tbd",
647+
description="test config",
648+
),
649+
]
650+
)
649651
bug = bug_factory(id=1234, whiteboard=whiteboard)
650652
acts = lookup_actions(bug, actions)
651653
assert len(acts) == len(expected_tags)
@@ -681,3 +683,34 @@ def test_lookup_action_not_found(whiteboard, actions, bug_factory):
681683
with pytest.raises(ActionNotFoundError) as exc_info:
682684
lookup_actions(bug, actions)[0]
683685
assert str(exc_info.value) == "devtest"
686+
687+
688+
def test_request_triggers_multiple_actions(
689+
webhook_request_factory,
690+
mocked_bugzilla,
691+
):
692+
actions = factories.ActionsFactory(
693+
root=[
694+
factories.ActionFactory(
695+
whiteboard_tag="devtest",
696+
bugzilla_user_id="tbd",
697+
description="test config",
698+
),
699+
factories.ActionFactory(
700+
whiteboard_tag="other",
701+
bugzilla_user_id="tbd",
702+
description="test config",
703+
),
704+
]
705+
)
706+
707+
webhook = webhook_request_factory(bug__whiteboard="[devtest][other]")
708+
mocked_bugzilla.get_bug.return_value = webhook.bug
709+
710+
details = execute_action(request=webhook, actions=actions)
711+
712+
# Details has the following shape:
713+
# {'devtest': {'responses': [..]}, 'other': {'responses': [...]}}
714+
assert len(actions) == len(details)
715+
assert "devtest" in details
716+
assert "other" in details

0 commit comments

Comments
 (0)