From 9e2d5fa9100fed05c32208555804fdad48f1feee Mon Sep 17 00:00:00 2001 From: Mahesh Date: Thu, 21 Nov 2024 13:01:09 +0530 Subject: [PATCH 1/4] Added code for logging whatsapp failed messages and added test cases accordingly. --- kairon/chat/handlers/channels/whatsapp.py | 6 +- kairon/shared/chat/processor.py | 32 +++++++++++ tests/unit_test/chat/chat_test.py | 67 +++++++++++++++++++++++ 3 files changed, 104 insertions(+), 1 deletion(-) diff --git a/kairon/chat/handlers/channels/whatsapp.py b/kairon/chat/handlers/channels/whatsapp.py index 4c72d6726..67f4416fe 100644 --- a/kairon/chat/handlers/channels/whatsapp.py +++ b/kairon/chat/handlers/channels/whatsapp.py @@ -238,7 +238,11 @@ async def send_custom_json( from kairon.chat.converters.channels.response_factory import ConverterFactory converter_instance = ConverterFactory.getConcreteInstance(messagetype, ChannelTypes.WHATSAPP.value) response = await converter_instance.messageConverter(message) - self.whatsapp_client.send(response, recipient_id, messaging_type) + resp = self.whatsapp_client.send(response, recipient_id, messaging_type) + if resp.get("error"): + bot = kwargs.get("assistant_id") + ChatDataProcessor.save_whatsapp_failed_messages(resp, bot, recipient_id, ChannelTypes.WHATSAPP.value, + json_message=json_message) else: self.send(recipient_id, {"preview_url": True, "body": str(json_message)}) diff --git a/kairon/shared/chat/processor.py b/kairon/shared/chat/processor.py index f23f6a361..112a2b133 100644 --- a/kairon/shared/chat/processor.py +++ b/kairon/shared/chat/processor.py @@ -167,6 +167,38 @@ def save_whatsapp_audit_log(status_data: Dict, bot: Text, user: Text, recipient: campaign_id=campaign_id ).save() + @staticmethod + def save_whatsapp_failed_messages(resp: Dict, bot: Text, recipient: Text, channel_type: Text, **kwargs): + """ + Logs failed WhatsApp message responses for debugging and tracking purposes. + + Args: + resp (Dict): The response dictionary containing error details. + bot (Text): The bot identifier. + recipient (Text): The recipient identifier. + channel_type (Text): The type of channel (e.g., WhatsApp). + + Returns: + None + """ + error = resp.get("error", {}) + message_id = f"failed-{channel_type}-{recipient}-{bot}" + user = "unknown_user" + failure_reason = error.get("error_data", {}).get("details") + logger.debug(f"WhatsApp message failed to send: {error}") + + ChannelLogs( + type=channel_type, + status="failed", + data=resp, + failure_reason=failure_reason, + message_id=message_id, + user=user, + bot=bot, + recipient=recipient, + json_message=kwargs.get('json_message') + ).save() + @staticmethod def get_instagram_static_comment(bot: str) -> str: channel = ChatDataProcessor.get_channel_config(bot=bot, connector_type="instagram", mask_characters=False) diff --git a/tests/unit_test/chat/chat_test.py b/tests/unit_test/chat/chat_test.py index 2e94b39d8..96b52790d 100644 --- a/tests/unit_test/chat/chat_test.py +++ b/tests/unit_test/chat/chat_test.py @@ -198,6 +198,73 @@ def __mock_get_bot(*args, **kwargs): "client_secret": "a23456789sfdghhtyutryuivcbn", "is_primary": False}}, "test", "test" ) + def test_save_whatsapp_failed_messages(self): + from kairon.shared.chat.data_objects import ChannelLogs + json_message = { + 'data': [ + { + 'type': 'button', + 'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + 'id': 1, + 'children': [ + { + 'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + } + ] + } + ], + 'type': 'button' + } + + resp = { + 'error': { + 'message': '(#131009) Parameter value is not valid', 'type': 'OAuthException', + 'code': 131009, + 'error_data': { + 'messaging_product': 'whatsapp', + 'details': 'Button title length invalid. Min length: 1, Max length: 20' + }, + 'fbtrace_id': 'ALfhCiNWtld8enTUJyg0FFo' + } + } + bot = "5e564fbcdcf0d5fad89e3abd" + recipient = "919876543210" + channel_type = "whatsapp" + ChatDataProcessor.save_whatsapp_failed_messages(resp, bot, recipient, channel_type, json_message=json_message) + log = ( + ChannelLogs.objects( + bot=bot, + message_id="failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abd", + ) + .get() + .to_mongo() + .to_dict() + ) + print(log) + assert log['type'] == 'whatsapp' + assert log['data'] == resp + assert log['status'] == 'failed' + assert log['message_id'] == 'failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abd' + assert log['failure_reason'] == 'Button title length invalid. Min length: 1, Max length: 20' + assert log['recipient'] == '919876543210' + assert log['type'] == 'whatsapp' + assert log['user'] == 'unknown_user' + assert log['json_message'] == { + 'data': [ + { + 'type': 'button', + 'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + 'id': 1, + 'children': [ + { + 'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + } + ] + } + ], + 'type': 'button' + } + def test_list_channels(self): channels = list(ChatDataProcessor.list_channel_config("test")) assert channels.__len__() == 3 From cc58de0f5d3f392738d5bd1e01667bf1695f78d5 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Tue, 26 Nov 2024 11:50:32 +0530 Subject: [PATCH 2/4] Added test case for coverage related to whatsapp failure logging. --- tests/unit_test/chat/chat_test.py | 73 +++++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) diff --git a/tests/unit_test/chat/chat_test.py b/tests/unit_test/chat/chat_test.py index 96b52790d..7a1551e6b 100644 --- a/tests/unit_test/chat/chat_test.py +++ b/tests/unit_test/chat/chat_test.py @@ -265,6 +265,79 @@ def test_save_whatsapp_failed_messages(self): 'type': 'button' } + @pytest.mark.asyncio + @mock.patch("kairon.chat.handlers.channels.clients.whatsapp.dialog360.BSP360Dialog.send", autospec=True) + async def test_save_whatsapp_failed_messages_through_send_custom_json(self, mock_bsp): + from kairon.shared.chat.data_objects import ChannelLogs + from kairon.chat.handlers.channels.whatsapp import WhatsappBot + from kairon.chat.handlers.channels.clients.whatsapp.dialog360 import BSP360Dialog + + json_message = { + 'data': [ + { + 'type': 'button', + 'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + 'id': 1, + 'children': [ + { + 'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + } + ] + } + ], + 'type': 'button' + } + + resp = { + 'error': { + 'message': '(#131009) Parameter value is not valid', 'type': 'OAuthException', + 'code': 131009, + 'error_data': { + 'messaging_product': 'whatsapp', + 'details': 'Button title length invalid. Min length: 1, Max length: 20' + }, + 'fbtrace_id': 'ALfhCiNWtld8enTUJyg0FFo' + } + } + mock_bsp.return_value = resp + bot = "5e564fbcdcf0d5fad89e3abcd" + recipient = "919876543210" + channel_type = "whatsapp" + client = BSP360Dialog("asajjdkjskdkdjfk") + await WhatsappBot(client).send_custom_json(recipient, json_message, assistant_id=bot) + log = ( + ChannelLogs.objects( + bot=bot, + message_id="failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abcd", + ) + .get() + .to_mongo() + .to_dict() + ) + assert log['type'] == 'whatsapp' + assert log['data'] == resp + assert log['status'] == 'failed' + assert log['message_id'] == 'failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abcd' + assert log['failure_reason'] == 'Button title length invalid. Min length: 1, Max length: 20' + assert log['recipient'] == '919876543210' + assert log['type'] == 'whatsapp' + assert log['user'] == 'unknown_user' + assert log['json_message'] == { + 'data': [ + { + 'type': 'button', + 'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee', + 'id': 1, + 'children': [ + { + 'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee' + } + ] + } + ], + 'type': 'button' + } + def test_list_channels(self): channels = list(ChatDataProcessor.list_channel_config("test")) assert channels.__len__() == 3 From dc867ca49234e37999e9bcf3926b18299803c9e9 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Tue, 26 Nov 2024 12:11:40 +0530 Subject: [PATCH 3/4] Added test case for coverage related to whatsapp failure logging. --- kairon/chat/handlers/channels/whatsapp.py | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/kairon/chat/handlers/channels/whatsapp.py b/kairon/chat/handlers/channels/whatsapp.py index 67f4416fe..7093cca8d 100644 --- a/kairon/chat/handlers/channels/whatsapp.py +++ b/kairon/chat/handlers/channels/whatsapp.py @@ -239,10 +239,20 @@ async def send_custom_json( converter_instance = ConverterFactory.getConcreteInstance(messagetype, ChannelTypes.WHATSAPP.value) response = await converter_instance.messageConverter(message) resp = self.whatsapp_client.send(response, recipient_id, messaging_type) + if resp.get("error"): bot = kwargs.get("assistant_id") - ChatDataProcessor.save_whatsapp_failed_messages(resp, bot, recipient_id, ChannelTypes.WHATSAPP.value, - json_message=json_message) + if not bot: + logger.error("Missing assistant_id in kwargs for failed message logging") + return + logger.error(f"WhatsApp message failed: {resp.get('error')}") + try: + ChatDataProcessor.save_whatsapp_failed_messages( + resp, bot, recipient_id, ChannelTypes.WHATSAPP.value, + json_message=json_message + ) + except Exception as e: + logger.error(f"Failed to log WhatsApp error: {str(e)}") else: self.send(recipient_id, {"preview_url": True, "body": str(json_message)}) From 7974cc51b8a0f28234b11c7faa51911685bdae53 Mon Sep 17 00:00:00 2001 From: Mahesh Date: Tue, 26 Nov 2024 18:36:44 +0530 Subject: [PATCH 4/4] Added test case for coverage related to whatsapp failure logging. --- .../channels/clients/whatsapp/cloud.py | 1 + kairon/chat/handlers/channels/whatsapp.py | 6 ++- kairon/shared/chat/processor.py | 9 ++-- tests/unit_test/chat/chat_test.py | 50 ++++++++++++++++--- 4 files changed, 54 insertions(+), 12 deletions(-) diff --git a/kairon/chat/handlers/channels/clients/whatsapp/cloud.py b/kairon/chat/handlers/channels/clients/whatsapp/cloud.py index 672d2f5b3..a1d2c9639 100644 --- a/kairon/chat/handlers/channels/clients/whatsapp/cloud.py +++ b/kairon/chat/handlers/channels/clients/whatsapp/cloud.py @@ -46,6 +46,7 @@ def __init__(self, access_token, **kwargs): self.api_version = kwargs.get('api_version', DEFAULT_API_VERSION) self.app = 'https://graph.facebook.com/v{api_version}'.format(api_version=self.api_version) self.app_secret = kwargs.get('app_secret') + self.metadata = kwargs.get("metadata") @property def client_type(self): diff --git a/kairon/chat/handlers/channels/whatsapp.py b/kairon/chat/handlers/channels/whatsapp.py index 7093cca8d..cf8cac718 100644 --- a/kairon/chat/handlers/channels/whatsapp.py +++ b/kairon/chat/handlers/channels/whatsapp.py @@ -158,6 +158,7 @@ async def _handle_user_message( ) -> None: """Pass on the text to the dialogue engine for processing.""" out_channel = WhatsappBot(self.client) + self.client.metadata = metadata await out_channel.mark_as_read(metadata["id"]) user_msg = UserMessage( text, out_channel, sender_id, input_channel=self.name(), metadata=metadata @@ -242,6 +243,8 @@ async def send_custom_json( if resp.get("error"): bot = kwargs.get("assistant_id") + message_id = self.whatsapp_client.metadata.get("id") + user = self.whatsapp_client.metadata.get("display_phone_number") if not bot: logger.error("Missing assistant_id in kwargs for failed message logging") return @@ -249,7 +252,8 @@ async def send_custom_json( try: ChatDataProcessor.save_whatsapp_failed_messages( resp, bot, recipient_id, ChannelTypes.WHATSAPP.value, - json_message=json_message + json_message=json_message, message_id=message_id, user=user, + metadata=self.whatsapp_client.metadata ) except Exception as e: logger.error(f"Failed to log WhatsApp error: {str(e)}") diff --git a/kairon/shared/chat/processor.py b/kairon/shared/chat/processor.py index 112a2b133..43d136e50 100644 --- a/kairon/shared/chat/processor.py +++ b/kairon/shared/chat/processor.py @@ -182,8 +182,10 @@ def save_whatsapp_failed_messages(resp: Dict, bot: Text, recipient: Text, channe None """ error = resp.get("error", {}) - message_id = f"failed-{channel_type}-{recipient}-{bot}" - user = "unknown_user" + message_id = kwargs.get("message_id") + user = kwargs.get("user") + json_message = kwargs.get('json_message') + metadata = kwargs.get("metadata") failure_reason = error.get("error_data", {}).get("details") logger.debug(f"WhatsApp message failed to send: {error}") @@ -196,7 +198,8 @@ def save_whatsapp_failed_messages(resp: Dict, bot: Text, recipient: Text, channe user=user, bot=bot, recipient=recipient, - json_message=kwargs.get('json_message') + json_message=json_message, + metadata=metadata ).save() @staticmethod diff --git a/tests/unit_test/chat/chat_test.py b/tests/unit_test/chat/chat_test.py index 7a1551e6b..36f5b1000 100644 --- a/tests/unit_test/chat/chat_test.py +++ b/tests/unit_test/chat/chat_test.py @@ -230,11 +230,14 @@ def test_save_whatsapp_failed_messages(self): bot = "5e564fbcdcf0d5fad89e3abd" recipient = "919876543210" channel_type = "whatsapp" - ChatDataProcessor.save_whatsapp_failed_messages(resp, bot, recipient, channel_type, json_message=json_message) + message_id = "wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA" + user = "91865712345" + ChatDataProcessor.save_whatsapp_failed_messages(resp, bot, recipient, channel_type, json_message=json_message, + message_id=message_id, user=user) log = ( ChannelLogs.objects( bot=bot, - message_id="failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abd", + message_id="wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA", ) .get() .to_mongo() @@ -244,11 +247,11 @@ def test_save_whatsapp_failed_messages(self): assert log['type'] == 'whatsapp' assert log['data'] == resp assert log['status'] == 'failed' - assert log['message_id'] == 'failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abd' + assert log['message_id'] == 'wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA' assert log['failure_reason'] == 'Button title length invalid. Min length: 1, Max length: 20' assert log['recipient'] == '919876543210' assert log['type'] == 'whatsapp' - assert log['user'] == 'unknown_user' + assert log['user'] == '91865712345' assert log['json_message'] == { 'data': [ { @@ -302,13 +305,29 @@ async def test_save_whatsapp_failed_messages_through_send_custom_json(self, mock mock_bsp.return_value = resp bot = "5e564fbcdcf0d5fad89e3abcd" recipient = "919876543210" + user = "91865712345" channel_type = "whatsapp" - client = BSP360Dialog("asajjdkjskdkdjfk") + metadata = { + 'from': '919876543210', + 'id': 'wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA', + 'timestamp': '1732622052', + 'text': {'body': '/btn'}, + 'type': 'text', + 'is_integration_user': True, + 'bot': bot, + 'account': 8, + 'channel_type': 'whatsapp', + 'bsp_type': '360dialog', + 'tabname': 'default', + 'display_phone_number': '91865712345', + 'phone_number_id': '142427035629239' + } + client = BSP360Dialog("asajjdkjskdkdjfk", metadata=metadata) await WhatsappBot(client).send_custom_json(recipient, json_message, assistant_id=bot) log = ( ChannelLogs.objects( bot=bot, - message_id="failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abcd", + message_id="wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA", ) .get() .to_mongo() @@ -317,11 +336,11 @@ async def test_save_whatsapp_failed_messages_through_send_custom_json(self, mock assert log['type'] == 'whatsapp' assert log['data'] == resp assert log['status'] == 'failed' - assert log['message_id'] == 'failed-whatsapp-919876543210-5e564fbcdcf0d5fad89e3abcd' + assert log['message_id'] == 'wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA' assert log['failure_reason'] == 'Button title length invalid. Min length: 1, Max length: 20' assert log['recipient'] == '919876543210' assert log['type'] == 'whatsapp' - assert log['user'] == 'unknown_user' + assert log['user'] == '91865712345' assert log['json_message'] == { 'data': [ { @@ -337,6 +356,21 @@ async def test_save_whatsapp_failed_messages_through_send_custom_json(self, mock ], 'type': 'button' } + assert log['metadata'] == { + 'from': recipient, + 'id': 'wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA', + 'timestamp': '1732622052', + 'text': {'body': '/btn'}, + 'type': 'text', + 'is_integration_user': True, + 'bot': bot, + 'account': 8, + 'channel_type': 'whatsapp', + 'bsp_type': '360dialog', + 'tabname': 'default', + 'display_phone_number': user, + 'phone_number_id': '142427035629239' + } def test_list_channels(self): channels = list(ChatDataProcessor.list_channel_config("test"))