From 6eca7641bc76ea4c8063ce2af55d30b693104e61 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Wed, 20 Nov 2019 20:51:39 -0800 Subject: [PATCH 01/18] fix to allow the bot to respond when mentioned or directly messaged --- rasa/core/channels/slack.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index f6dd221c18db..a365c010c3f4 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -319,14 +319,18 @@ async def webhook(request: Request): return response.json(output.get("challenge")) elif self._is_user_message(output): - return await self.process_message( - request, - on_new_message, - text=self._sanitize_user_message( - output["event"]["text"], output["authed_users"] - ), - sender_id=output.get("event").get("user"), - ) + if self.is_direct_message(output) or self.is_app_mention(output): + self.set_output_channel(output["event"]["channel"]) + return await self.process_message( + request, + on_new_message, + text=self._sanitize_user_message( + output["event"]["text"], output["authed_users"] + ), + sender_id=output.get("event").get("user"), + ) + else: + return response.text("") return response.text("Bot message delivered") @@ -334,3 +338,18 @@ async def webhook(request: Request): def get_output_channel(self) -> OutputChannel: return SlackBot(self.slack_token, self.slack_channel) + + def set_output_channel(self, channel) -> None: + self.slack_channel = channel + + def is_direct_message(self, user_message) -> bool: + try: + return user_message["event"]["channel_type"] == "im" + except KeyError: + return False + + def is_app_mention(self, user_message) -> bool: + try: + return user_message["event"]["type"] == "app_mention" + except KeyError: + return False From f5aa081d29ebae5ce8d3e343cc3b3c55fe64345b Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Wed, 20 Nov 2019 21:03:28 -0800 Subject: [PATCH 02/18] allow bot to communicate without mention on a configured channel --- rasa/core/channels/slack.py | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index a365c010c3f4..d364549a1031 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -109,6 +109,20 @@ async def send_custom_json( return super(SlackBot, self).api_call("chat.postMessage", **json_message) +def is_app_mention(user_message) -> bool: + try: + return user_message["event"]["type"] == "app_mention" + except KeyError: + return False + + +def is_direct_message(user_message) -> bool: + try: + return user_message["event"]["channel_type"] == "im" + except KeyError: + return False + + class SlackInput(InputChannel): """Slack input channel implementation. Based on the HTTPInputChannel.""" @@ -319,8 +333,13 @@ async def webhook(request: Request): return response.json(output.get("challenge")) elif self._is_user_message(output): - if self.is_direct_message(output) or self.is_app_mention(output): - self.set_output_channel(output["event"]["channel"]) + channel = output["event"]["channel"] + if ( + is_direct_message(output) + or is_app_mention(output) + or channel == self.slack_channel + ): + self.set_output_channel(channel) return await self.process_message( request, on_new_message, @@ -341,15 +360,3 @@ def get_output_channel(self) -> OutputChannel: def set_output_channel(self, channel) -> None: self.slack_channel = channel - - def is_direct_message(self, user_message) -> bool: - try: - return user_message["event"]["channel_type"] == "im" - except KeyError: - return False - - def is_app_mention(self, user_message) -> bool: - try: - return user_message["event"]["type"] == "app_mention" - except KeyError: - return False From 345044721c1171fa1459e9e07bc393c6e3c0fb43 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 17 Dec 2019 11:29:19 -0800 Subject: [PATCH 03/18] add output channel to metadata --- rasa/core/channels/slack.py | 38 ++++++++++++++++++------------------- 1 file changed, 19 insertions(+), 19 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 0a1a0251cd96..31d66af4b4f6 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -111,21 +111,6 @@ async def send_custom_json( return super().api_call("chat.postMessage", **json_message) -def is_app_mention(user_message: Dict) -> bool: - print(type(user_message)) - try: - return user_message["event"]["type"] == "app_mention" - except KeyError: - return False - - -def is_direct_message(user_message: Dict) -> bool: - try: - return user_message["event"]["channel_type"] == "im" - except KeyError: - return False - - class SlackInput(InputChannel): """Slack input channel implementation. Based on the HTTPInputChannel.""" @@ -185,6 +170,20 @@ def __init__( self.retry_reason_header = slack_retry_reason_header self.retry_num_header = slack_retry_number_header + @staticmethod + def is_app_mention(slack_event: Dict) -> bool: + try: + return slack_event["event"]["type"] == "app_mention" + except KeyError: + return False + + @staticmethod + def is_direct_message(slack_event: Dict) -> bool: + try: + return slack_event["event"]["channel_type"] == "im" + except KeyError: + return False + @staticmethod def _is_user_message(slack_event: Dict) -> bool: return ( @@ -197,6 +196,7 @@ def _is_user_message(slack_event: Dict) -> bool: and not slack_event.get("event", {}).get("bot_id") ) + @staticmethod def _sanitize_user_message(text, uids_to_remove) -> Text: """Remove superfluous/wrong/problematic tokens from a message. @@ -310,10 +310,9 @@ async def process_message( return response.text(None, status=201, headers={"X-Slack-No-Retry": 1}) try: - out_channel = self.get_output_channel() user_msg = UserMessage( text, - out_channel, + metadata["out_channel"], sender_id, input_channel=self.name(), metadata=metadata, @@ -369,7 +368,7 @@ async def webhook(request: Request) -> HTTPResponse: or is_app_mention(output) or channel == self.slack_channel ): - self.set_output_channel(channel) + metadata["out_channel"] = channel return await self.process_message( request, on_new_message, @@ -377,12 +376,13 @@ async def webhook(request: Request) -> HTTPResponse: output["event"]["text"], output["authed_users"] ), sender_id=output.get("event").get("user"), - metadata=metadata + metadata=metadata, ) else: return response.text("") return response.text("Bot message delivered") + return slack_webhook def get_output_channel(self) -> OutputChannel: From 559b72779879cefc1c777f0fcf5480fab8bc09e2 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 17 Dec 2019 11:55:35 -0800 Subject: [PATCH 04/18] make message parsing methods private --- rasa/core/channels/slack.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 31d66af4b4f6..fd8ac07a1e6f 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -171,14 +171,14 @@ def __init__( self.retry_num_header = slack_retry_number_header @staticmethod - def is_app_mention(slack_event: Dict) -> bool: + def _is_app_mention(slack_event: Dict) -> bool: try: return slack_event["event"]["type"] == "app_mention" except KeyError: return False @staticmethod - def is_direct_message(slack_event: Dict) -> bool: + def _is_direct_message(slack_event: Dict) -> bool: try: return slack_event["event"]["channel_type"] == "im" except KeyError: @@ -364,8 +364,8 @@ async def webhook(request: Request) -> HTTPResponse: metadata = self.get_metadata(request) channel = output["event"]["channel"] if ( - is_direct_message(output) - or is_app_mention(output) + self._is_direct_message(output) + or self._is_app_mention(output) or channel == self.slack_channel ): metadata["out_channel"] = channel From 995cacee100a80d0636631f1abf53f85ac870c61 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 17 Dec 2019 12:59:01 -0800 Subject: [PATCH 05/18] update to create a new channel for each user interaction --- rasa/core/channels/slack.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index fd8ac07a1e6f..4e291768fa74 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -196,7 +196,6 @@ def _is_user_message(slack_event: Dict) -> bool: and not slack_event.get("event", {}).get("bot_id") ) - @staticmethod def _sanitize_user_message(text, uids_to_remove) -> Text: """Remove superfluous/wrong/problematic tokens from a message. @@ -312,7 +311,7 @@ async def process_message( try: user_msg = UserMessage( text, - metadata["out_channel"], + self.get_output_channel(metadata.get("out_channel")), sender_id, input_channel=self.name(), metadata=metadata, @@ -325,6 +324,18 @@ async def process_message( return response.text("") + def get_metadata(self, request: Request): + slack_event = request.json + event = slack_event.get("event") + + metadata = {} + metadata["out_channel"] = event.get("channel") + metadata["text"] = event.get("text") + metadata["sender"] = event.get("user") + metadata["users"] = slack_event.get("authed_users") + + return metadata + def blueprint( self, on_new_message: Callable[[UserMessage], Awaitable[Any]] ) -> Blueprint: @@ -362,20 +373,18 @@ async def webhook(request: Request) -> HTTPResponse: elif self._is_user_message(output): metadata = self.get_metadata(request) - channel = output["event"]["channel"] if ( self._is_direct_message(output) or self._is_app_mention(output) - or channel == self.slack_channel + or metadata["out_channel"] == self.slack_channel ): - metadata["out_channel"] = channel return await self.process_message( request, on_new_message, text=self._sanitize_user_message( - output["event"]["text"], output["authed_users"] + metadata["text"], metadata["users"] ), - sender_id=output.get("event").get("user"), + sender_id=metadata["sender"], metadata=metadata, ) else: @@ -385,8 +394,11 @@ async def webhook(request: Request) -> HTTPResponse: return slack_webhook - def get_output_channel(self) -> OutputChannel: - return SlackBot(self.slack_token, self.slack_channel) + def get_output_channel(self, channel: Optional[Text] = None) -> OutputChannel: + if channel is not None: + return SlackBot(self.slack_token, channel) + else: + return SlackBot(self.slack_token, self.slack_channel) def set_output_channel(self, channel) -> None: self.slack_channel = channel From 097dc978c691194fb6478a2a79a1e899e6028765 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 16 Jan 2020 09:35:57 -0800 Subject: [PATCH 06/18] fix typing error --- rasa/core/channels/slack.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 4e291768fa74..a9803a3714d9 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -308,10 +308,15 @@ async def process_message( return response.text(None, status=201, headers={"X-Slack-No-Retry": 1}) + if metadata is not None: + output_channel = metadata.get("out_channel") + else: + output_channel = None + try: user_msg = UserMessage( text, - self.get_output_channel(metadata.get("out_channel")), + self.get_output_channel(output_channel), sender_id, input_channel=self.name(), metadata=metadata, From d5907f9dc5810d841ee05daacd6103d494f6167b Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 23 Jan 2020 09:22:19 -0800 Subject: [PATCH 07/18] Apply suggestions from code review Co-Authored-By: Tobias Wochinger --- rasa/core/channels/slack.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index a9803a3714d9..29ebdfd81ac4 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -329,11 +329,11 @@ async def process_message( return response.text("") - def get_metadata(self, request: Request): + def get_metadata(self, request: Request) -> Dict[Text, Any]: slack_event = request.json event = slack_event.get("event") - metadata = {} + return {"out_channel": event.get("channel"), "text": ..., ... } metadata["out_channel"] = event.get("channel") metadata["text"] = event.get("text") metadata["sender"] = event.get("user") @@ -405,5 +405,5 @@ def get_output_channel(self, channel: Optional[Text] = None) -> OutputChannel: else: return SlackBot(self.slack_token, self.slack_channel) - def set_output_channel(self, channel) -> None: + def set_output_channel(self, channel: Text) -> None: self.slack_channel = channel From aecdd77b06cf93e5b186f86f30ee4a490843decc Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 23 Jan 2020 11:16:54 -0800 Subject: [PATCH 08/18] add test case and comment for extracting metadata from the slack event API --- rasa/core/channels/slack.py | 32 +++++++++++++++++++-------- tests/core/test_channels.py | 44 +++++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+), 9 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 29ebdfd81ac4..b51123a0a416 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -330,16 +330,26 @@ async def process_message( return response.text("") def get_metadata(self, request: Request) -> Dict[Text, Any]: - slack_event = request.json - event = slack_event.get("event") + """Extracts the metadata from a slack API event (https://api.slack.com/types/event). - return {"out_channel": event.get("channel"), "text": ..., ... } - metadata["out_channel"] = event.get("channel") - metadata["text"] = event.get("text") - metadata["sender"] = event.get("user") - metadata["users"] = slack_event.get("authed_users") + Args: + request: a `Request` object that contains a slack API event in the body. - return metadata + Returns: + A `dict` containing the output channel for the response, the text from the user, the sender's ID, and + users that have installed the bot. + """ + slack_event = request.json + event = slack_event.get("event") + print(event) + print("*****") + print(event.get("channel")) + return { + "out_channel": event.get("channel"), + "text": event.get("text"), + "sender": event.get("user"), + "users": slack_event.get("authed_users"), + } def blueprint( self, on_new_message: Callable[[UserMessage], Awaitable[Any]] @@ -393,7 +403,11 @@ async def webhook(request: Request) -> HTTPResponse: metadata=metadata, ) else: - return response.text("") + return response.text( + "Received message on unsupported channel: {}".format( + metadata["out_channel"] + ) + ) return response.text("Bot message delivered") diff --git a/tests/core/test_channels.py b/tests/core/test_channels.py index eeaa7ca4ea2e..ba244853d376 100644 --- a/tests/core/test_channels.py +++ b/tests/core/test_channels.py @@ -461,6 +461,50 @@ def test_botframework_attachments(): assert ch.add_attachments_to_metadata(payload, metadata) == updated_metadata +def test_slack_metadata(): + from rasa.core.channels.slack import SlackInput + from sanic.request import Request + + user = "user1" + channel = "channel1" + direct_message_event = { + "authed_users": ["test"], + "event": { + "client_msg_id": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "type": "message", + "text": "hello world", + "user": user, + "ts": "1579802617.000800", + "team": "XXXXXXXXX", + "blocks": [ + { + "type": "rich_text", + "block_id": "XXXXX", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "hi"}], + } + ], + } + ], + "channel": channel, + "event_ts": "1579802617.000800", + "channel_type": "im", + }, + } + + input_channel = SlackInput( + slack_token="YOUR_SLACK_TOKEN", slack_channel="YOUR_SLACK_CHANNEL" + ) + + r = Request(None, None, None, None, None, app="test") + r.parsed_json = direct_message_event + metadata = input_channel.get_metadata(request=r) + assert metadata["sender"] == user + assert metadata["out_channel"] == channel + + def test_slack_message_sanitization(): from rasa.core.channels.slack import SlackInput From f11754fa910277cd3649398d84d5401660f71be5 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 23 Jan 2020 11:17:47 -0800 Subject: [PATCH 09/18] remove print statements --- rasa/core/channels/slack.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index b51123a0a416..2a1092064344 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -341,9 +341,6 @@ def get_metadata(self, request: Request) -> Dict[Text, Any]: """ slack_event = request.json event = slack_event.get("event") - print(event) - print("*****") - print(event.get("channel")) return { "out_channel": event.get("channel"), "text": event.get("text"), From 3df6fac152eb16937b60c2fde244f2523be7e022 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 30 Jan 2020 09:26:22 -0800 Subject: [PATCH 10/18] Apply suggestions from code review Co-Authored-By: Tobias Wochinger --- rasa/core/channels/slack.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 2a1092064344..d1ff657976e2 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -333,14 +333,14 @@ def get_metadata(self, request: Request) -> Dict[Text, Any]: """Extracts the metadata from a slack API event (https://api.slack.com/types/event). Args: - request: a `Request` object that contains a slack API event in the body. + request: A `Request` object that contains a slack API event in the body. Returns: A `dict` containing the output channel for the response, the text from the user, the sender's ID, and users that have installed the bot. """ slack_event = request.json - event = slack_event.get("event") + event = slack_event.get("event", {}) return { "out_channel": event.get("channel"), "text": event.get("text"), @@ -411,7 +411,8 @@ async def webhook(request: Request) -> HTTPResponse: return slack_webhook def get_output_channel(self, channel: Optional[Text] = None) -> OutputChannel: - if channel is not None: + channel = channel or self.slack_channel + return SlackBot(self.slack_token, channel) return SlackBot(self.slack_token, channel) else: return SlackBot(self.slack_token, self.slack_channel) From b622f8d2521c933c1df31c4024026db4040c1833 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 4 Feb 2020 11:49:17 -0800 Subject: [PATCH 11/18] add additional test for missing data and authed users and clarity fixes --- rasa/core/channels/slack.py | 57 ++++++++++++++++++------------------- tests/core/test_channels.py | 47 +++++++++++++++++++++++++++++- 2 files changed, 74 insertions(+), 30 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index d1ff657976e2..c04b86cc3f5a 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -336,8 +336,8 @@ def get_metadata(self, request: Request) -> Dict[Text, Any]: request: A `Request` object that contains a slack API event in the body. Returns: - A `dict` containing the output channel for the response, the text from the user, the sender's ID, and - users that have installed the bot. + Metadata extracted from the sent event payload. This includes the output channel for the response, the text + from the user and users that have installed the bot. """ slack_event = request.json event = slack_event.get("event", {}) @@ -380,42 +380,41 @@ async def webhook(request: Request) -> HTTPResponse: elif request.json: output = request.json + metadata = self.get_metadata(request) if "challenge" in output: return response.json(output.get("challenge")) - elif self._is_user_message(output): - metadata = self.get_metadata(request) - if ( - self._is_direct_message(output) - or self._is_app_mention(output) - or metadata["out_channel"] == self.slack_channel - ): - return await self.process_message( - request, - on_new_message, - text=self._sanitize_user_message( - metadata["text"], metadata["users"] - ), - sender_id=metadata["sender"], - metadata=metadata, - ) - else: - return response.text( - "Received message on unsupported channel: {}".format( - metadata["out_channel"] - ) - ) - - return response.text("Bot message delivered") + elif self._is_user_message(output) and self._is_supported_channel( + output, metadata + ): + return await self.process_message( + request, + on_new_message, + text=self._sanitize_user_message( + metadata["text"], metadata["users"] + ), + sender_id=metadata["sender"], + metadata=metadata, + ) + else: + logger.warning( + f"Received message on unsupported channel: {metadata['out_channel']}" + ) + + return response.text("Bot message delivered.") return slack_webhook + def _is_supported_channel(self, slack_event: Dict, metadata: Dict) -> bool: + return ( + self._is_direct_message(slack_event) + or self._is_app_mention(slack_event) + or metadata["out_channel"] == self.slack_channel + ) + def get_output_channel(self, channel: Optional[Text] = None) -> OutputChannel: channel = channel or self.slack_channel return SlackBot(self.slack_token, channel) - return SlackBot(self.slack_token, channel) - else: - return SlackBot(self.slack_token, self.slack_channel) def set_output_channel(self, channel: Text) -> None: self.slack_channel = channel diff --git a/tests/core/test_channels.py b/tests/core/test_channels.py index 4a69a7bd56f4..fe0eb999f5d1 100644 --- a/tests/core/test_channels.py +++ b/tests/core/test_channels.py @@ -468,8 +468,9 @@ def test_slack_metadata(): user = "user1" channel = "channel1" + authed_users = ["XXXXXXX", "YYYYYYY", "ZZZZZZZ"] direct_message_event = { - "authed_users": ["test"], + "authed_users": authed_users, "event": { "client_msg_id": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", "type": "message", @@ -504,6 +505,50 @@ def test_slack_metadata(): metadata = input_channel.get_metadata(request=r) assert metadata["sender"] == user assert metadata["out_channel"] == channel + assert metadata["users"] == authed_users + + +def test_slack_metadata_missing_keys(): + from rasa.core.channels.slack import SlackInput + from sanic.request import Request + + channel = "channel1" + direct_message_event = { + "event": { + "client_msg_id": "XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX", + "type": "message", + "text": "hello world", + "ts": "1579802617.000800", + "team": "XXXXXXXXX", + "blocks": [ + { + "type": "rich_text", + "block_id": "XXXXX", + "elements": [ + { + "type": "rich_text_section", + "elements": [{"type": "text", "text": "hi"}], + } + ], + } + ], + "channel": channel, + "event_ts": "1579802617.000800", + "channel_type": "im", + }, + } + + input_channel = SlackInput( + slack_token="YOUR_SLACK_TOKEN", slack_channel="YOUR_SLACK_CHANNEL" + ) + + r = Request(None, None, None, None, None, app="test") + r.parsed_json = direct_message_event + metadata = input_channel.get_metadata(request=r) + assert metadata["sender"] is None + assert metadata["users"] is None + assert metadata["out_channel"] == channel + def test_slack_message_sanitization(): From 1f6d3474f7e258a1639da34ffafd04f9946006d1 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 4 Feb 2020 11:57:59 -0800 Subject: [PATCH 12/18] update changelog --- CHANGELOG.rst | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 2ecb851d5845..17db15543690 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -54,6 +54,11 @@ Features ``intent_tokenization_flag``: Flag to check whether to split intents (default ``False``). ``intent_split_symbol``: Symbol on which intent should be split (default ``_``) +- `#4811 `_: Support invoking a ``SlackBot`` by direct messaging or @ mentions. + + ``SlackInput`` channel will now process additional metadata from the Slack API to support direct messaging and + mentioning of the bot by users in addition to any configured channels. + Improvements ------------ - `#1988 `_: Remove the need of specifying utter actions in the ``actions`` section explicitly if these actions are already From 3ffd78dc7b4c4a19a399558c6eebe6b291f69152 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 4 Feb 2020 12:09:01 -0800 Subject: [PATCH 13/18] updated docs to the new behavior of slack connector. --- docs/user-guide/connectors/slack.rst | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/user-guide/connectors/slack.rst b/docs/user-guide/connectors/slack.rst index 9bd828cb4d68..ee79e73e177e 100644 --- a/docs/user-guide/connectors/slack.rst +++ b/docs/user-guide/connectors/slack.rst @@ -53,9 +53,9 @@ e.g. using: You need to supply a ``credentials.yml`` with the following content: -- The ``slack_channel`` is the target your bot posts to. - This can be a channel or an individual person. You can leave out - the argument to post DMs to the bot. +- The ``slack_channel`` is a channel that the bot will listen to for messages + in addition to direct messages and app mentions, i.e. "@app_name". + This can be a channel or an individual person. - Use the entry for ``Bot User OAuth Access Token`` in the "OAuth & Permissions" tab as your ``slack_token``. It should start @@ -75,4 +75,4 @@ The endpoint for receiving slack messages is ``http://localhost:5005/webhooks/slack/webhook``, replacing the host and port with the appropriate values. This is the URL you should add in the "OAuth & Permissions" section as well as -the "Event Subscriptions". +the "Event Subscriptions". \ No newline at end of file From 0a6fa5609000983eb4f68522bc00e0d59793f710 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 4 Feb 2020 14:59:16 -0800 Subject: [PATCH 14/18] remove extra blank space --- tests/core/test_channels.py | 1 - 1 file changed, 1 deletion(-) diff --git a/tests/core/test_channels.py b/tests/core/test_channels.py index 487080b2042d..0e4ebf8a50e5 100644 --- a/tests/core/test_channels.py +++ b/tests/core/test_channels.py @@ -550,7 +550,6 @@ def test_slack_metadata_missing_keys(): assert metadata["out_channel"] == channel - def test_slack_message_sanitization(): from rasa.core.channels.slack import SlackInput From bedab0545b8ca769052f45d013465e099bae7f73 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Tue, 11 Feb 2020 07:08:26 -0800 Subject: [PATCH 15/18] add changelog, update documentation, fix mock request --- CHANGELOG.rst | 94 +--------------------------- changelog/4811.improvement.rst | 1 + docs/user-guide/connectors/slack.rst | 6 +- tests/core/test_channels.py | 10 +-- 4 files changed, 12 insertions(+), 99 deletions(-) create mode 100644 changelog/4811.improvement.rst diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 6a4fa187f5ed..fd845c9d7449 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,8 +1,8 @@ -:desc: Rasa Open Source Changelog +:desc: Rasa OSS Changelog -Rasa Open Source Change Log -=========================== +Rasa OSS Change Log +=================== All notable changes to this project will be documented in this file. This project adheres to `Semantic Versioning`_ starting with version 1.0. @@ -17,94 +17,6 @@ This project adheres to `Semantic Versioning`_ starting with version 1.0. .. towncrier release notes start -[1.7.0] - 2020-01-29 -^^^^^^^^^^^^^^^^^^^^ - -Deprecations and Removals -------------------------- -- `#4964 `_: The endpoint ``/conversations//execute`` is now deprecated. Instead, users should use - the ``/conversations//trigger_intent`` endpoint and thus trigger intents instead of actions. -- `#4978 `_: Remove option ``use_cls_token`` from tokenizers and option ``return_sequence`` from featurizers. - - By default all tokenizer add a special token (``__CLS__``) to the end of the list of tokens. - This token will be used to capture the features of the whole utterance. - - The featurizers will return a matrix of size (number-of-tokens x feature-dimension) by default. - This allows to train sequence models. - However, the feature vector of the ``__CLS__`` token can be used to train non-sequence models. - The corresponding classifier can decide what kind of features to use. - -Features --------- -- `#400 `_: Rename ``templates`` key in domain to ``responses``. - - ``templates`` key will still work for backwards compatibility but will raise a future warning. -- `#4902 `_: Added a new configuration parameter, ``ranking_length`` to the ``EmbeddingPolicy``, ``EmbeddingIntentClassifier``, - and ``ResponseSelector`` classes. -- `#4964 `_: External events and reminders now trigger intents (and entities) instead of actions. - - Add new endpoint ``/conversations//trigger_intent``, which lets the user specify an intent and a - list of entities that is injected into the conversation in place of a user message. The bot then predicts and - executes a response action. -- `#4978 `_: Add ``ConveRTTokenizer``. - - The tokenizer should be used whenever the ``ConveRTFeaturizer`` is used. - - Every tokenizer now supports the following configuration options: - ``intent_tokenization_flag``: Flag to check whether to split intents (default ``False``). - ``intent_split_symbol``: Symbol on which intent should be split (default ``_``) - -- `#4811 `_: Support invoking a ``SlackBot`` by direct messaging or @ mentions. - - ``SlackInput`` channel will now process additional metadata from the Slack API to support direct messaging and - mentioning of the bot by users in addition to any configured channels. - -Improvements ------------- -- `#1988 `_: Remove the need of specifying utter actions in the ``actions`` section explicitly if these actions are already - listed in the ``templates`` section. -- `#4877 `_: Entity examples that have been extracted using an external extractor are excluded - from Markdown dumping in ``MarkdownWriter.dumps()``. The excluded external extractors - are ``DucklingHTTPExtractor`` and ``SpacyEntityExtractor``. -- `#4902 `_: The ``EmbeddingPolicy``, ``EmbeddingIntentClassifier``, and ``ResponseSelector`` now by default normalize confidence - levels over the top 10 results. See :ref:`migration-to-rasa-1.7` for more details. -- `#4964 `_: ``ReminderCancelled`` can now cancel multiple reminders if no name is given. It still cancels a single - reminder if the reminder's name is specified. - -Bugfixes --------- -- `#4774 `_: Requests to ``/model/train`` do not longer block other requests to the Rasa server. -- `#4896 `_: Fixed default behavior of ``rasa test core --evaluate-model-directory`` when called without ``--model``. Previously, the latest model file was used as ``--model``. Now the default model directory is used instead. - - New behavior of ``rasa test core --evaluate-model-directory`` when given an existing file as argument for ``--model``: Previously, this led to an error. Now a warning is displayed and the directory containing the given file is used as ``--model``. -- `#5040 `_: Updated the dependency ``networkx`` from 2.3.0 to 2.4.0. The old version created incompatibilities when using pip. - - There is an imcompatibility between Rasa dependecy requests 2.22.0 and the own depedency from Rasa for networkx raising errors upon pip install. There is also a bug corrected in ``requirements.txt`` which used ``~=`` instead of ``==``. All of these are fixed using networkx 2.4.0. -- `#5057 `_: Fixed compatibility issue with Microsoft Bot Framework Emulator if ``service_url`` lacked a trailing ``/``. -- `#5092 `_: DynamoDB tracker store decimal values will now be rounded on save. Previously values exceeding 38 digits caused an unhandled error. - -Miscellaneous internal changes ------------------------------- -- #4458, #4664, #4780, #5029 - - -[1.6.2] - 2020-01-28 -^^^^^^^^^^^^^^^^^^^^ - -Improvements ------------- -- `#4994 `_: Switching back to a TensorFlow release which only includes CPU support to reduce the - size of the dependencies. If you want to use the TensorFlow package with GPU support, - please run ``pip install tensorflow-gpu==1.15.0``. - -Bugfixes --------- -- `#5111 `_: Fixes ``Exception 'Loop' object has no attribute '_ready'`` error when running - ``rasa init``. -- `#5126 `_: Updated the end-to-end ValueError you recieve when you have a invalid story format to point - to the updated doc link. - - [1.6.1] - 2020-01-07 ^^^^^^^^^^^^^^^^^^^^ diff --git a/changelog/4811.improvement.rst b/changelog/4811.improvement.rst new file mode 100644 index 000000000000..c64bb7bc0ee6 --- /dev/null +++ b/changelog/4811.improvement.rst @@ -0,0 +1 @@ +Support invoking a ``SlackBot`` by direct messaging or @ mentions. \ No newline at end of file diff --git a/docs/user-guide/connectors/slack.rst b/docs/user-guide/connectors/slack.rst index ee79e73e177e..fe2ccf4ff001 100644 --- a/docs/user-guide/connectors/slack.rst +++ b/docs/user-guide/connectors/slack.rst @@ -53,9 +53,9 @@ e.g. using: You need to supply a ``credentials.yml`` with the following content: -- The ``slack_channel`` is a channel that the bot will listen to for messages - in addition to direct messages and app mentions, i.e. "@app_name". - This can be a channel or an individual person. +- The ``slack_channel`` can be a channel or an individual person that the bot should listen to for communications, in + addition to the default behavior of listening for direct messages and app mentions, i.e. "@app_name". + - Use the entry for ``Bot User OAuth Access Token`` in the "OAuth & Permissions" tab as your ``slack_token``. It should start diff --git a/tests/core/test_channels.py b/tests/core/test_channels.py index 0e4ebf8a50e5..792f2fdaf545 100644 --- a/tests/core/test_channels.py +++ b/tests/core/test_channels.py @@ -2,7 +2,7 @@ import logging import urllib.parse from typing import Dict -from unittest.mock import patch, MagicMock +from unittest.mock import patch, MagicMock, Mock import pytest import responses @@ -500,8 +500,8 @@ def test_slack_metadata(): slack_token="YOUR_SLACK_TOKEN", slack_channel="YOUR_SLACK_CHANNEL" ) - r = Request(None, None, None, None, None, app="test") - r.parsed_json = direct_message_event + r = Mock() + r.json = direct_message_event metadata = input_channel.get_metadata(request=r) assert metadata["sender"] == user assert metadata["out_channel"] == channel @@ -542,8 +542,8 @@ def test_slack_metadata_missing_keys(): slack_token="YOUR_SLACK_TOKEN", slack_channel="YOUR_SLACK_CHANNEL" ) - r = Request(None, None, None, None, None, app="test") - r.parsed_json = direct_message_event + r = Mock() + r.json = direct_message_event metadata = input_channel.get_metadata(request=r) assert metadata["sender"] is None assert metadata["users"] is None From cf2740247471a05b3c0f047f89d1d73fb85215ff Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 20 Feb 2020 14:05:55 -0800 Subject: [PATCH 16/18] fixed typing issue and separated metadata and core message data --- rasa/core/channels/slack.py | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/rasa/core/channels/slack.py b/rasa/core/channels/slack.py index 45df5734ac8a..ac97f94b7e87 100644 --- a/rasa/core/channels/slack.py +++ b/rasa/core/channels/slack.py @@ -307,7 +307,10 @@ async def process_message( return response.text(None, status=201, headers={"X-Slack-No-Retry": 1}) - output_channel = metadata.get("out_channel") + if metadata is not None: + output_channel = metadata.get("out_channel") + else: + output_channel = None try: user_msg = UserMessage( @@ -332,15 +335,14 @@ def get_metadata(self, request: Request) -> Dict[Text, Any]: request: A `Request` object that contains a slack API event in the body. Returns: - Metadata extracted from the sent event payload. This includes the output channel for the response, the text - from the user and users that have installed the bot. + Metadata extracted from the sent event payload. This includes the output channel for the response, + and users that have installed the bot. """ slack_event = request.json event = slack_event.get("event", {}) + return { "out_channel": event.get("channel"), - "text": event.get("text"), - "sender": event.get("user"), "users": slack_event.get("authed_users"), } @@ -376,7 +378,11 @@ async def webhook(request: Request) -> HTTPResponse: elif request.json: output = request.json + event = output.get("event", {}) + user_message = event.get("text", "") + sender_id = event.get("user", "") metadata = self.get_metadata(request) + if "challenge" in output: return response.json(output.get("challenge")) @@ -387,9 +393,9 @@ async def webhook(request: Request) -> HTTPResponse: request, on_new_message, text=self._sanitize_user_message( - metadata["text"], metadata["users"] + user_message, metadata["users"] ), - sender_id=metadata["sender"], + sender_id=sender_id, metadata=metadata, ) else: From 167c055872f44b47627bea1932e2df5cddf501f1 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Thu, 20 Feb 2020 16:11:22 -0800 Subject: [PATCH 17/18] fix unit test after the removal of sender from metadata object --- tests/core/test_channels.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/core/test_channels.py b/tests/core/test_channels.py index 51d9b9c493e4..5d56a49f1381 100644 --- a/tests/core/test_channels.py +++ b/tests/core/test_channels.py @@ -500,7 +500,6 @@ def test_slack_metadata(): r = Mock() r.json = direct_message_event metadata = input_channel.get_metadata(request=r) - assert metadata["sender"] == user assert metadata["out_channel"] == channel assert metadata["users"] == authed_users @@ -542,7 +541,6 @@ def test_slack_metadata_missing_keys(): r = Mock() r.json = direct_message_event metadata = input_channel.get_metadata(request=r) - assert metadata["sender"] is None assert metadata["users"] is None assert metadata["out_channel"] == channel From 8f96d032154583d37eab2dfc79d18e4a558232e7 Mon Sep 17 00:00:00 2001 From: Will Kearns Date: Mon, 24 Feb 2020 07:01:57 -0800 Subject: [PATCH 18/18] Update CHANGELOG.rst Co-Authored-By: Tobias Wochinger --- CHANGELOG.rst | 1 - 1 file changed, 1 deletion(-) diff --git a/CHANGELOG.rst b/CHANGELOG.rst index bc1b56ffdbf0..3c501123026d 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -133,7 +133,6 @@ Bugfixes to the updated doc link. ->>>>>>> 5a3b9d254164a0f17892bb85747de2a73a9a0c63 [1.6.1] - 2020-01-07 ^^^^^^^^^^^^^^^^^^^^