Skip to content

Commit

Permalink
Whatsapp failed msg logging (#1607)
Browse files Browse the repository at this point in the history
* Added code for logging whatsapp failed messages and added test cases accordingly.

* Added test case for coverage related to whatsapp failure logging.

* Added test case for coverage related to whatsapp failure logging.

* Added test case for coverage related to whatsapp failure logging.
  • Loading branch information
maheshsattala authored Nov 27, 2024
1 parent 9a425e0 commit 48554f8
Show file tree
Hide file tree
Showing 4 changed files with 229 additions and 1 deletion.
1 change: 1 addition & 0 deletions kairon/chat/handlers/channels/clients/whatsapp/cloud.py
Original file line number Diff line number Diff line change
Expand Up @@ -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):
Expand Down
20 changes: 19 additions & 1 deletion kairon/chat/handlers/channels/whatsapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -238,7 +239,24 @@ 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")
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
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, 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)}")
else:
self.send(recipient_id, {"preview_url": True, "body": str(json_message)})

Expand Down
35 changes: 35 additions & 0 deletions kairon/shared/chat/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,41 @@ 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 = 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}")

ChannelLogs(
type=channel_type,
status="failed",
data=resp,
failure_reason=failure_reason,
message_id=message_id,
user=user,
bot=bot,
recipient=recipient,
json_message=json_message,
metadata=metadata
).save()

@staticmethod
def get_instagram_static_comment(bot: str) -> str:
channel = ChatDataProcessor.get_channel_config(bot=bot, connector_type="instagram", mask_characters=False)
Expand Down
174 changes: 174 additions & 0 deletions tests/unit_test/chat/chat_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -198,6 +198,180 @@ 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"
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="wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA",
)
.get()
.to_mongo()
.to_dict()
)
print(log)
assert log['type'] == 'whatsapp'
assert log['data'] == resp
assert log['status'] == 'failed'
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'] == '91865712345'
assert log['json_message'] == {
'data': [
{
'type': 'button',
'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
'id': 1,
'children': [
{
'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
}
]
}
],
'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"
user = "91865712345"
channel_type = "whatsapp"
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="wamid.HBgMOTE5NTE1OTkxNjg1FQIAEhggNjdDMUMxODhENEMyQUM1QzVBREQzN0YxQjQyNzA4MzAA",
)
.get()
.to_mongo()
.to_dict()
)
assert log['type'] == 'whatsapp'
assert log['data'] == resp
assert log['status'] == 'failed'
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'] == '91865712345'
assert log['json_message'] == {
'data': [
{
'type': 'button',
'value': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee',
'id': 1,
'children': [
{
'text': '/byeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee'
}
]
}
],
'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"))
assert channels.__len__() == 3
Expand Down

0 comments on commit 48554f8

Please sign in to comment.