From 6c63f53889c5ea782bd5c7c2d62d3f50a1456681 Mon Sep 17 00:00:00 2001 From: Innokentii Konstantinov Date: Fri, 14 Jun 2024 15:46:14 +0800 Subject: [PATCH] Handle slack uninstall event from chatops-proxy (#4510) --- engine/apps/chatops_proxy/events/handlers.py | 38 ++++++++++++++-- .../apps/chatops_proxy/events/root_handler.py | 4 +- engine/apps/chatops_proxy/events/types.py | 6 +++ .../apps/chatops_proxy/tests/test_events.py | 43 +++++++++++++++---- 4 files changed, 78 insertions(+), 13 deletions(-) diff --git a/engine/apps/chatops_proxy/events/handlers.py b/engine/apps/chatops_proxy/events/handlers.py index 967be6ee..11897e99 100644 --- a/engine/apps/chatops_proxy/events/handlers.py +++ b/engine/apps/chatops_proxy/events/handlers.py @@ -3,10 +3,16 @@ import typing from abc import ABC, abstractmethod from apps.chatops_proxy.client import PROVIDER_TYPE_SLACK -from apps.slack.installation import SlackInstallationExc, install_slack_integration +from apps.slack.installation import SlackInstallationExc, install_slack_integration, uninstall_slack_integration from apps.user_management.models import Organization -from .types import INTEGRATION_INSTALLED_EVENT_TYPE, Event, IntegrationInstalledData +from .types import ( + INTEGRATION_INSTALLED_EVENT_TYPE, + INTEGRATION_UNINSTALLED_EVENT_TYPE, + Event, + IntegrationInstalledData, + IntegrationUninstalledData, +) logger = logging.getLogger(__name__) @@ -23,7 +29,7 @@ class Handler(ABC): pass -class SlackInstallationHandler(Handler): +class SlackInstallHandler(Handler): @classmethod def match(cls, event: Event) -> bool: return ( @@ -48,3 +54,29 @@ class SlackInstallationHandler(Handler): f'msg="SlackInstallationHandler: Failed to install Slack integration: %s" org_id={organization.id} stack_id={stack_id}', e, ) + + +class SlackUninstallHandler(Handler): + @classmethod + def match(cls, event: Event) -> bool: + return ( + event.get("event_type") == INTEGRATION_UNINSTALLED_EVENT_TYPE + and event.get("data", {}).get("provider_type") == PROVIDER_TYPE_SLACK + ) + + @classmethod + def handle(cls, data: dict) -> None: + data = typing.cast(IntegrationUninstalledData, data) + + stack_id = data.get("stack_id") + user_id = data.get("grafana_user_id") + + organization = Organization.objects.get(stack_id=stack_id) + user = organization.users.get(user_id=user_id) + try: + uninstall_slack_integration(organization, user) + except SlackInstallationExc as e: + logger.exception( + f'msg="SlackInstallationHandler: Failed to uninstall Slack integration: %s" org_id={organization.id} stack_id={stack_id}', + e, + ) diff --git a/engine/apps/chatops_proxy/events/root_handler.py b/engine/apps/chatops_proxy/events/root_handler.py index d9a8c6f3..4e8af9c9 100644 --- a/engine/apps/chatops_proxy/events/root_handler.py +++ b/engine/apps/chatops_proxy/events/root_handler.py @@ -1,7 +1,7 @@ import logging import typing -from .handlers import Handler, SlackInstallationHandler +from .handlers import Handler, SlackInstallHandler, SlackUninstallHandler from .types import Event logger = logging.getLogger(__name__) @@ -12,7 +12,7 @@ class ChatopsEventsHandler: ChatopsEventsHandler is a root handler which receives event from Chatops-Proxy and chooses the handler to process it. """ - HANDLERS: typing.List[typing.Type[Handler]] = [SlackInstallationHandler] + HANDLERS: typing.List[typing.Type[Handler]] = [SlackInstallHandler, SlackUninstallHandler] def handle(self, event_data: Event) -> bool: """ diff --git a/engine/apps/chatops_proxy/events/types.py b/engine/apps/chatops_proxy/events/types.py index 22ce2310..93843970 100644 --- a/engine/apps/chatops_proxy/events/types.py +++ b/engine/apps/chatops_proxy/events/types.py @@ -15,3 +15,9 @@ class IntegrationInstalledData(typing.TypedDict): stack_id: int grafana_user_id: int payload: dict + + +class IntegrationUninstalledData(typing.TypedDict): + provider_type: str + stack_id: int + grafana_user_id: int diff --git a/engine/apps/chatops_proxy/tests/test_events.py b/engine/apps/chatops_proxy/tests/test_events.py index 01891bdb..79bb2700 100644 --- a/engine/apps/chatops_proxy/tests/test_events.py +++ b/engine/apps/chatops_proxy/tests/test_events.py @@ -4,10 +4,10 @@ import pytest from django.test import override_settings from apps.chatops_proxy.events import ChatopsEventsHandler -from apps.chatops_proxy.events.handlers import SlackInstallationHandler +from apps.chatops_proxy.events.handlers import SlackInstallHandler, SlackUninstallHandler from common.constants.slack_auth import SLACK_OAUTH_ACCESS_RESPONSE -installation_event = { +install_event = { "event_type": "integration_installed", "data": { "provider_type": "slack", @@ -17,6 +17,15 @@ installation_event = { }, } +uninstall_event = { + "event_type": "integration_uninstalled", + "data": { + "provider_type": "slack", + "stack_id": "stack_id", + "grafana_user_id": "grafana_user_id", + }, +} + unknown_event = { "event_type": "unknown_event", "data": { @@ -37,7 +46,8 @@ invalid_schema_event = { @pytest.mark.parametrize( "payload,is_handled", [ - (installation_event, True), + (install_event, True), + (uninstall_event, True), (unknown_event, False), (invalid_schema_event, False), ], @@ -54,13 +64,30 @@ def test_root_event_handler(mock_exec, payload, is_handled): def test_slack_installation_handler(mock_install_slack_integration, make_organization_and_user): organization, user = make_organization_and_user() - installation_event["data"].update({"stack_id": organization.stack_id, "grafana_user_id": user.user_id}) + install_event["data"].update({"stack_id": organization.stack_id, "grafana_user_id": user.user_id}) - h = SlackInstallationHandler() + h = SlackInstallHandler() assert h.match(unknown_event) is False assert h.match(invalid_schema_event) is False - assert h.match(installation_event) is True - h.handle(installation_event["data"]) - assert mock_install_slack_integration.call_args.args == (organization, user, installation_event["data"]["payload"]) + assert h.match(install_event) is True + h.handle(install_event["data"]) + assert mock_install_slack_integration.call_args.args == (organization, user, install_event["data"]["payload"]) + + +@patch("apps.chatops_proxy.events.handlers.uninstall_slack_integration", return_value=None) +@pytest.mark.django_db +def test_slack_uninstall_handler(mock_uninstall_slack_integration, make_organization_and_user): + organization, user = make_organization_and_user() + + uninstall_event["data"].update({"stack_id": organization.stack_id, "grafana_user_id": user.user_id}) + + h = SlackUninstallHandler() + + assert h.match(unknown_event) is False + assert h.match(invalid_schema_event) is False + + assert h.match(uninstall_event) is True + h.handle(uninstall_event["data"]) + assert mock_uninstall_slack_integration.call_args.args == (organization, user)