From e9eb6e15fecf97135aba2c7550e613f977058a27 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Fri, 27 Sep 2019 15:52:51 -0700 Subject: [PATCH 1/3] Concat Attachments if exist in Prompt --- .../botbuilder/core/adapters/test_adapter.py | 2 +- .../dialogs/choices/choice_factory.py | 26 +++-- .../botbuilder/dialogs/prompts/prompt.py | 8 +- .../tests/test_choice_prompt.py | 96 ++++++++++++++++++- 4 files changed, 114 insertions(+), 18 deletions(-) diff --git a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py index 18da635b8..656050bab 100644 --- a/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py +++ b/libraries/botbuilder-core/botbuilder/core/adapters/test_adapter.py @@ -352,7 +352,7 @@ async def assert_reply( :param timeout: :return: """ - + # TODO: refactor method so expected can take a Callable[[Activity], None] def default_inspector(reply, description=None): if isinstance(expected, Activity): validate_activity(reply, expected) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py index 6acc1cbec..e3dda2fd9 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py @@ -1,7 +1,7 @@ # Copyright (c) Microsoft Corporation. All rights reserved. # Licensed under the MIT License. -from typing import List +from typing import List, Union from botbuilder.core import CardFactory, MessageFactory from botbuilder.schema import ActionTypes, Activity, CardAction, HeroCard, InputHints @@ -17,7 +17,7 @@ class ChoiceFactory: @staticmethod def for_channel( channel_id: str, - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -36,8 +36,7 @@ def for_channel( if channel_id is None: channel_id = "" - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) # Find maximum title length max_title_length = 0 @@ -74,7 +73,7 @@ def for_channel( @staticmethod def inline( - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -89,8 +88,7 @@ def inline( speak: (Optional) SSML. Text to be spoken by your bot on a speech-enabled channel. options: (Optional) The formatting options to use to tweak rendering of list. """ - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) if options is None: options = ChoiceFactoryOptions() @@ -134,7 +132,7 @@ def inline( @staticmethod def list_style( - choices: List[Choice], + choices: List[Union[str, Choice]], text: str = None, speak: str = None, options: ChoiceFactoryOptions = None, @@ -153,8 +151,7 @@ def list_style( options: (Optional) The formatting options to use to tweak rendering of list. """ - if choices is None: - choices = [] + choices = ChoiceFactory._to_choices(choices) if options is None: options = ChoiceFactoryOptions() @@ -206,7 +203,7 @@ def suggested_action( @staticmethod def hero_card( - choices: List[Choice], text: str = None, speak: str = None + choices: List[Union[Choice, str]], text: str = None, speak: str = None ) -> Activity: """ Creates a message activity that includes a lsit of coices that have been added as `HeroCard`'s @@ -221,18 +218,19 @@ def hero_card( ) @staticmethod - def _to_choices(choices: List[str]) -> List[Choice]: + def _to_choices(choices: List[Union[str, Choice]]) -> List[Choice]: """ Takes a list of strings and returns them as [`Choice`]. """ if choices is None: return [] - return [Choice(value=choice.value) for choice in choices] + return [Choice(value=choice) if isinstance(choice, str) else choice for choice in choices] @staticmethod - def _extract_actions(choices: List[Choice]) -> List[CardAction]: + def _extract_actions(choices: List[Union[str, Choice]]) -> List[CardAction]: if choices is None: choices = [] + choices = ChoiceFactory._to_choices(choices) card_actions: List[CardAction] = [] for choice in choices: if choice.action is not None: diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py index 1e907ae1d..147c11c30 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py @@ -217,8 +217,12 @@ def default() -> Activity: ): prompt.suggested_actions = msg.suggested_actions - if msg.attachments is not None and msg.attachments: - prompt.attachments = msg.attachments + if msg.attachments: + if prompt.attachments: + prompt.attachments.extend(msg.attachments) + else: + prompt.attachments = msg.attachments + return prompt diff --git a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py index 902e9adee..c1cf228b9 100644 --- a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py +++ b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py @@ -6,7 +6,7 @@ import aiounittest from recognizers_text import Culture -from botbuilder.core import ConversationState, MemoryStorage, TurnContext +from botbuilder.core import CardFactory, ConversationState, MemoryStorage, TurnContext from botbuilder.core.adapters import TestAdapter from botbuilder.dialogs import DialogSet, DialogTurnResult, DialogTurnStatus from botbuilder.dialogs.choices import Choice, ListStyle @@ -588,3 +588,97 @@ async def exec_test(turn_context: TurnContext): ) step3 = await step2.send("1") await step3.assert_reply("red") + + async def test_should_display_choices_on_hero_card(self): + size_choices = ["large", "medium", "small"] + async def exec_test(turn_context: TurnContext): + dialog_context = await dialogs.create_context(turn_context) + + results: DialogTurnResult = await dialog_context.continue_dialog() + + if results.status == DialogTurnStatus.Empty: + options = PromptOptions( + prompt=Activity( + type=ActivityTypes.message, text="Please choose a size." + ), + choices=size_choices, + ) + await dialog_context.prompt("prompt", options) + elif results.status == DialogTurnStatus.Complete: + selected_choice = results.result + await turn_context.send_activity(selected_choice.value) + + await convo_state.save_changes(turn_context) + + def assert_expected_activity(activity: Activity, description): # pylint: disable=unused-argument + assert len(activity.attachments) == 1 + assert activity.attachments[0].content_type == CardFactory.content_types.hero_card + assert activity.attachments[0].content.text == "Please choose a size." + + adapter = TestAdapter(exec_test) + + convo_state = ConversationState(MemoryStorage()) + dialog_state = convo_state.create_property("dialogState") + dialogs = DialogSet(dialog_state) + + choice_prompt = ChoicePrompt("prompt") + + # Change the ListStyle of the prompt to ListStyle.none. + choice_prompt.style = ListStyle.hero_card + + dialogs.add(choice_prompt) + + step1 = await adapter.send("Hello") + step2 = await step1.assert_reply(assert_expected_activity) + step3 = await step2.send("1") + await step3.assert_reply(size_choices[0]) + + async def test_should_display_choices_on_hero_card_with_additional_attachment(self): + size_choices = ["large", "medium", "small"] + card = CardFactory.adaptive_card( + { + "type": "AdaptiveCard", + "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", + "version": "1.2", + "body": [] + } + ) + card_activity = Activity(attachments=[card]) + + async def exec_test(turn_context: TurnContext): + dialog_context = await dialogs.create_context(turn_context) + + results: DialogTurnResult = await dialog_context.continue_dialog() + + if results.status == DialogTurnStatus.Empty: + options = PromptOptions( + prompt=card_activity, + choices=size_choices, + ) + await dialog_context.prompt("prompt", options) + elif results.status == DialogTurnStatus.Complete: + selected_choice = results.result + await turn_context.send_activity(selected_choice.value) + + await convo_state.save_changes(turn_context) + + def assert_expected_activity(activity: Activity, description): # pylint: disable=unused-argument + assert len(activity.attachments) == 2 + assert activity.attachments[0].content_type == CardFactory.content_types.adaptive_card + assert activity.attachments[1].content_type == CardFactory.content_types.hero_card + + adapter = TestAdapter(exec_test) + + convo_state = ConversationState(MemoryStorage()) + dialog_state = convo_state.create_property("dialogState") + dialogs = DialogSet(dialog_state) + + choice_prompt = ChoicePrompt("prompt") + + # Change the ListStyle of the prompt to ListStyle.none. + choice_prompt.style = ListStyle.hero_card + + dialogs.add(choice_prompt) + + step1 = await adapter.send("Hello") + await step1.assert_reply(assert_expected_activity) From 6bf554889c77247abae8b69940140e72141cbaa5 Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Fri, 27 Sep 2019 15:54:18 -0700 Subject: [PATCH 2/3] Concat Attachments if exist in Prompt -- pylint fixes --- .../botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py | 1 - 1 file changed, 1 deletion(-) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py index 147c11c30..0ab60ba17 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/prompts/prompt.py @@ -222,7 +222,6 @@ def default() -> Activity: prompt.attachments.extend(msg.attachments) else: prompt.attachments = msg.attachments - return prompt From 9a66765dae8c03bf1244f591e56e3aaf673e2c7d Mon Sep 17 00:00:00 2001 From: Axel Suarez Date: Fri, 27 Sep 2019 17:20:22 -0700 Subject: [PATCH 3/3] Updating pylint rules to comply to latest version --black compliant --- .../dialogs/choices/choice_factory.py | 5 ++- .../tests/test_choice_prompt.py | 31 +++++++++++++------ 2 files changed, 25 insertions(+), 11 deletions(-) diff --git a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py index e3dda2fd9..a9d17f16f 100644 --- a/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py +++ b/libraries/botbuilder-dialogs/botbuilder/dialogs/choices/choice_factory.py @@ -224,7 +224,10 @@ def _to_choices(choices: List[Union[str, Choice]]) -> List[Choice]: """ if choices is None: return [] - return [Choice(value=choice) if isinstance(choice, str) else choice for choice in choices] + return [ + Choice(value=choice) if isinstance(choice, str) else choice + for choice in choices + ] @staticmethod def _extract_actions(choices: List[Union[str, Choice]]) -> List[CardAction]: diff --git a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py index c1cf228b9..8b4499e1d 100644 --- a/libraries/botbuilder-dialogs/tests/test_choice_prompt.py +++ b/libraries/botbuilder-dialogs/tests/test_choice_prompt.py @@ -591,6 +591,7 @@ async def exec_test(turn_context: TurnContext): async def test_should_display_choices_on_hero_card(self): size_choices = ["large", "medium", "small"] + async def exec_test(turn_context: TurnContext): dialog_context = await dialogs.create_context(turn_context) @@ -610,9 +611,14 @@ async def exec_test(turn_context: TurnContext): await convo_state.save_changes(turn_context) - def assert_expected_activity(activity: Activity, description): # pylint: disable=unused-argument + def assert_expected_activity( + activity: Activity, description + ): # pylint: disable=unused-argument assert len(activity.attachments) == 1 - assert activity.attachments[0].content_type == CardFactory.content_types.hero_card + assert ( + activity.attachments[0].content_type + == CardFactory.content_types.hero_card + ) assert activity.attachments[0].content.text == "Please choose a size." adapter = TestAdapter(exec_test) @@ -640,7 +646,7 @@ async def test_should_display_choices_on_hero_card_with_additional_attachment(se "type": "AdaptiveCard", "$schema": "http://adaptivecards.io/schemas/adaptive-card.json", "version": "1.2", - "body": [] + "body": [], } ) card_activity = Activity(attachments=[card]) @@ -651,10 +657,7 @@ async def exec_test(turn_context: TurnContext): results: DialogTurnResult = await dialog_context.continue_dialog() if results.status == DialogTurnStatus.Empty: - options = PromptOptions( - prompt=card_activity, - choices=size_choices, - ) + options = PromptOptions(prompt=card_activity, choices=size_choices) await dialog_context.prompt("prompt", options) elif results.status == DialogTurnStatus.Complete: selected_choice = results.result @@ -662,10 +665,18 @@ async def exec_test(turn_context: TurnContext): await convo_state.save_changes(turn_context) - def assert_expected_activity(activity: Activity, description): # pylint: disable=unused-argument + def assert_expected_activity( + activity: Activity, description + ): # pylint: disable=unused-argument assert len(activity.attachments) == 2 - assert activity.attachments[0].content_type == CardFactory.content_types.adaptive_card - assert activity.attachments[1].content_type == CardFactory.content_types.hero_card + assert ( + activity.attachments[0].content_type + == CardFactory.content_types.adaptive_card + ) + assert ( + activity.attachments[1].content_type + == CardFactory.content_types.hero_card + ) adapter = TestAdapter(exec_test)