Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ feat(aci): Invoke Rule Registry from Notification Action #84524

Merged
merged 9 commits into from
Feb 10, 2025

Conversation

iamrajjoshi
Copy link
Member

This PR introduces a new ABC which we will inherit and create new handlers to invoke the legacy issue alert registry.

Basically after the notification action determines that it needs to invoke the issue alert registry (rule registry), it will call BaseIssueAlertHandler.invoke_legacy_registry which will shape the data into a rule instance and then safe_execute the callback returned by the RuleFuture.

the method mentioned above is based on

def activate_downstream_actions(
rule: Rule,
event: GroupEvent,
notification_uuid: str | None = None,
rule_fire_history: RuleFireHistory | None = None,
) -> MutableMapping[
str, tuple[Callable[[GroupEvent, Sequence[RuleFuture]], None], list[RuleFuture]]
]:
grouped_futures: MutableMapping[
str, tuple[Callable[[GroupEvent, Sequence[RuleFuture]], None], list[RuleFuture]]
] = {}
for action in rule.data.get("actions", ()):
action_inst = instantiate_action(rule, action, rule_fire_history)
if not action_inst:
continue
results = safe_execute(
action_inst.after,
event=event,
notification_uuid=notification_uuid,
)
if results is None:
logger.warning("Action %s did not return any futures", action["id"])
continue
for future in results:
key = future.key if future.key is not None else future.callback
rule_future = RuleFuture(rule=rule, kwargs=future.kwargs)
if key not in grouped_futures:
grouped_futures[key] = (future.callback, [rule_future])
else:
grouped_futures[key][1].append(rule_future)
return grouped_futures

spec

closes https://getsentry.atlassian.net/browse/ACI-150

@iamrajjoshi iamrajjoshi self-assigned this Feb 4, 2025
@iamrajjoshi iamrajjoshi requested a review from a team as a code owner February 4, 2025 17:37
@github-actions github-actions bot added the Scope: Backend Automatically applied to PRs that change backend components label Feb 4, 2025
"sentry.workflow_engine.handlers.action.notification.issue_alert.instantiate_action"
)
@mock.patch("uuid.uuid4")
def test_invoke_legacy_registry(self, mock_uuid, mock_instantiate_action, mock_safe_execute):
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based off of

def activate_downstream_actions(
rule: Rule,
event: GroupEvent,
notification_uuid: str | None = None,
rule_fire_history: RuleFireHistory | None = None,
) -> MutableMapping[
str, tuple[Callable[[GroupEvent, Sequence[RuleFuture]], None], list[RuleFuture]]
]:
grouped_futures: MutableMapping[
str, tuple[Callable[[GroupEvent, Sequence[RuleFuture]], None], list[RuleFuture]]
] = {}
for action in rule.data.get("actions", ()):
action_inst = instantiate_action(rule, action, rule_fire_history)
if not action_inst:
continue
results = safe_execute(
action_inst.after,
event=event,
notification_uuid=notification_uuid,
)
if results is None:
logger.warning("Action %s did not return any futures", action["id"])
continue
for future in results:
key = future.key if future.key is not None else future.callback
rule_future = RuleFuture(rule=rule, kwargs=future.kwargs)
if key not in grouped_futures:
grouped_futures[key] = (future.callback, [rule_future])
else:
grouped_futures[key][1].append(rule_future)
return grouped_futures

Copy link

codecov bot commented Feb 4, 2025

❌ 1 Tests Failed:

Tests completed Failed Passed Skipped
23509 1 23508 289
View the top 1 failed test(s) by shortest run time
tests.sentry.workflow_engine.handlers.action.notification.test_issue_alert.TestBaseIssueAlertHandler::test_create_rule_instance_from_action
Stack Traces | 3.23s run time
#x1B[1m#x1B[.../action/notification/test_issue_alert.py#x1B[0m:46: in test_create_rule_instance_from_action
    assert rule.id == self.detector.id
#x1B[1m#x1B[31mE   AssertionError: assert 17 == 9#x1B[0m
#x1B[1m#x1B[31mE    +  where 17 = <Rule at 0x7f9c204401b0: id=17, project_id=4555528490188802, label='Smashing Bass'>.id#x1B[0m
#x1B[1m#x1B[31mE    +  and   9 = <Detector at 0x7f9c386e3950: id=9>.id#x1B[0m
#x1B[1m#x1B[31mE    +    where <Detector at 0x7f9c386e3950: id=9> = <action.notification.test_issue_alert.TestBaseIssueAlertHandler testMethod=test_create_rule_instance_from_action>.detector#x1B[0m

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

detector: Detector,
) -> None:
"""
This method will create a rule instance from the Action model, and then invoke the legacy registry.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

which part of this is doing the invocation? it looks like it's just executing the futures like we do currently

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i added a lot more docstrings just now. the tldr is we consolidate the post_process process_rules, activate_downstream_actions into this method which will handle building a rule instance from an Action, getting the action handler class, getting the rule futures and executing them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not seeing the use of any registry here, maybe that's what colleen was asking about?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah yes, good point. the rule registry is quite literally invoked in the activate_downstream_actions with the instantiate_action method call it makes. do you think there is a better way for me to be explicit about this?

@iamrajjoshi iamrajjoshi force-pushed the raj/aci/invoke-rule-registry branch from 6394eb6 to 157ca3c Compare February 5, 2025 00:22
Comment on lines +118 to +121
futures = cls.get_rule_futures(job, rule, notification_uuid)

# Execute the futures
cls.execute_futures(job, futures)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do these need to be separate? is it for having separate spans?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes but also- i want to decouple the logic here, i have a feeling when we move to notif platform, we will slowly remove each of these calls.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also makes it easier to test each of these steps with mock data, so I've been encouraging this kind of decoupling. It can also allow us to add metrics surrounding each call to see what the rough failure rate is for each step.

detector: Detector,
) -> None:
"""
This method will create a rule instance from the Action model, and then invoke the legacy registry.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i'm not seeing the use of any registry here, maybe that's what colleen was asking about?

"""

rule = Rule(
id=detector.id,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can you remind me why this needs to be detector.id? is it possible that this collides with an existing Rule id?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

actually i need this to be action.id so i can fetch the correct action in our slack threads implementation & so i build links correctly. this won't collide with anything since this will be a rule instance not saved in the DB.

mock_safe_execute.assert_called_once_with(mock_callback, self.job["event"], mock_futures)


class TestDiscordIssueAlertHandler(BaseWorkflowTest):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we test that the correct after() method is called?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we shouldn't need to. activate_downstream_actions is well tested and as long as we are certain that the rule action id we are sending it (for example, "sentry.integrations.discord.notify_action.DiscordNotifyServiceAction" for Discord), we can be certain it will call the correct after method

@@ -2,4 +2,4 @@
"NotificationActionHandler",
]

from .notification import NotificationActionHandler
from .notification.notification import NotificationActionHandler
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I find this file path a little confusing, since notification/notification doesn't tell me a lot about what's in this module.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updating to handler

Comment on lines +118 to +121
futures = cls.get_rule_futures(job, rule, notification_uuid)

# Execute the futures
cls.execute_futures(job, futures)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also makes it easier to test each of these steps with mock data, so I've been encouraging this kind of decoupling. It can also allow us to add metrics surrounding each call to see what the rough failure rate is for each step.

Comment on lines 61 to 65
logger.exception(
"No issue alert handler found for action type: %s",
action.type,
extra={"action_id": action.id},
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this swallow the exception? Does the caller assume these calls never fail?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good point, let me raise exceptions, we can swallow if needed later

@iamrajjoshi iamrajjoshi enabled auto-merge (squash) February 10, 2025 21:56
@iamrajjoshi iamrajjoshi merged commit da60e50 into master Feb 10, 2025
49 checks passed
@iamrajjoshi iamrajjoshi deleted the raj/aci/invoke-rule-registry branch February 10, 2025 22:21
iamrajjoshi added a commit that referenced this pull request Feb 11, 2025
…all integrations (#84829)

builds on #84524

adds legacy rule registry invokers/handlers for Messaging and Oncall
Integrations
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Scope: Backend Automatically applied to PRs that change backend components
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants