From d5d41510a69fda70d00e220bec68e35a86702afd Mon Sep 17 00:00:00 2001 From: Guillaume Binet Date: Sat, 12 Aug 2017 07:12:58 -0700 Subject: [PATCH] Makes hinting on flows useable (#1062) It needs to be attached to a specific node so it will be set at initialization phase after the fact with a 'hints' parameter. --- errbot/flow.py | 18 +++++++++--------- tests/flow_e2e_test.py | 13 ++++++++++++- tests/flow_plugin/flowtest_flows.py | 23 +++++++++++++++++++++-- 3 files changed, 42 insertions(+), 12 deletions(-) diff --git a/errbot/flow.py b/errbot/flow.py index 72e8967cc..cb5eee630 100644 --- a/errbot/flow.py +++ b/errbot/flow.py @@ -25,13 +25,15 @@ class FlowNode(object): The predicate is a function that takes one parameter, the context of the conversation. """ - def __init__(self, command: str=None): + def __init__(self, command: str=None, hints: bool=True): """ Creates a FlowNone, takes the command to which the Node is linked to. :param command: the command this Node is linked to. Can only be None if this Node is a Root. + :param hints: hints the users for the next steps in chat. """ self.command = command self.children = [] # (predicate, node) + self.hints = hints def connect(self, node_or_command: Union['FlowNode', str], predicate: Predicate=lambda _: False): """ @@ -42,6 +44,7 @@ def connect(self, node_or_command: Union['FlowNode', str], predicate: Predicate= (this node or command will be the follow up of this one) :param predicate: function with one parameter, the context, to determine of the flow executor can continue automatically this flow with no user intervention. + :param hints: hints the user on the next step possible. :return: the newly created node if you passed a command or the node you gave it to be easily chainable. """ node_to_connect_to = node_or_command if isinstance(node_or_command, FlowNode) else FlowNode(node_or_command) @@ -72,11 +75,13 @@ def __init__(self, name: str, description: str): :param name: The name of the conversation/flow. :param description: A human description of what this flow does. + :param hints: Hints for the next steps when triggered. """ super().__init__() self.name = name self.description = description self.auto_triggers = set() + self.room_flow = False def connect(self, node_or_command: Union['FlowNode', str], @@ -122,21 +127,17 @@ class Flow(object): This is a live Flow. It keeps context of the conversation (requestor and context). Context is just a python dictionary representing the state of the conversation. """ - def __init__(self, root: FlowRoot, requestor: Identifier, initial_context: Mapping[str, Any], - next_step_hinting: int=True): + def __init__(self, root: FlowRoot, requestor: Identifier, initial_context: Mapping[str, Any]): """ :param root: the root of this flow. :param requestor: the user requesting this flow. :param initial_context: any data we already have that could help executing this flow automatically. - :param next_step_hinting: toggle display of the "you are now in the flow xxx, you may continue with yyy, zzz" - messages after each flow step """ self._root = root self._current_step = self._root self.ctx = dict(initial_context) self.requestor = requestor - self.next_step_hinting = next_step_hinting def next_autosteps(self) -> List[FlowNode]: """ @@ -390,7 +391,7 @@ def execute(self, flow: Flow): self.in_flight.remove(flow) break - if not autosteps: + if not autosteps and flow.current_step.hints: possible_next_steps = ["You are in the flow **%s**, you can continue with:\n\n" % flow.name] for step in steps: cmd = step.command @@ -405,8 +406,7 @@ def execute(self, flow: Flow): if syntax_args: syntax += syntax_args possible_next_steps.append("- %s" % syntax) - if flow.next_step_hinting: - self._bot.send(flow.requestor, "\n".join(possible_next_steps)) + self._bot.send(flow.requestor, "\n".join(possible_next_steps)) break log.debug("Steps triggered automatically %s", ', '.join(str(node) for node in autosteps)) diff --git a/tests/flow_e2e_test.py b/tests/flow_e2e_test.py index 665865e44..77fde8e6b 100644 --- a/tests/flow_e2e_test.py +++ b/tests/flow_e2e_test.py @@ -7,15 +7,17 @@ def test_list_flows(testbot): - assert len(testbot.bot.flow_executor.flow_roots) == 3 + assert len(testbot.bot.flow_executor.flow_roots) == 4 testbot.bot.push_message('!flows list') result = testbot.pop_message() assert 'documentation of W1' in result assert 'documentation of W2' in result assert 'documentation of W3' in result + assert 'documentation of W4' in result assert 'w1' in result assert 'w2' in result assert 'w3' in result + assert 'w4' in result def test_no_autotrigger(testbot): @@ -62,6 +64,15 @@ def test_manual_flow(testbot): assert '!c' in flow_message +def test_manual_flow_with_or_without_hinting(testbot): + assert 'Flow w4 started' in testbot.exec_command('!flows start w4') + assert 'a' in testbot.exec_command('!a') + assert 'b' in testbot.exec_command('!b') + flow_message = testbot.pop_message() + assert 'You are in the flow w4, you can continue with' in flow_message + assert '!c' in flow_message + + def test_no_flyby_trigger_flow(testbot): testbot.bot.push_message('!flows start w1') # One message or the other can arrive first. diff --git a/tests/flow_plugin/flowtest_flows.py b/tests/flow_plugin/flowtest_flows.py index f8c289f69..b58af89e8 100644 --- a/tests/flow_plugin/flowtest_flows.py +++ b/tests/flow_plugin/flowtest_flows.py @@ -14,9 +14,14 @@ def w1(self, flow: FlowRoot): c_node = a_node.connect('c') # crosses the autotrigger of w2 d_node = c_node.connect('d') + assert a_node.hints + assert b_node.hints + assert c_node.hints + assert d_node.hints + @botflow def w2(self, flow: FlowRoot): - "documentation of W2" + """documentation of W2""" c_node = flow.connect('c', auto_trigger=True) b_node = c_node.connect('b') e_node = flow.connect('e', auto_trigger=True) # 2 autotriggers for the same workflow @@ -24,6 +29,20 @@ def w2(self, flow: FlowRoot): @botflow def w3(self, flow: FlowRoot): - "documentation of W3" + """documentation of W3""" c_node = flow.connect('a', room_flow=True) b_node = c_node.connect('b') + + @botflow + def w4(self, flow: FlowRoot): + """documentation of W4""" + a_node = flow.connect('a') + b_node = a_node.connect('b') + c_node = b_node.connect('c') + c_node.connect('d') + + # set some hinting. + flow.hints = False + a_node.hints = False + b_node.hints = True + c_node.hint = False