Fix next handling in ComponentFlow
- This fixes a bug where returning null from a next()
didn't stop a flow.
- Backport #510
- Fixes #513
(cherry picked from commit 7c4700b7b5)
This commit is contained in:
@@ -421,11 +421,19 @@ public interface ComponentFlow {
|
||||
context = node.data.getOperation().apply(context);
|
||||
if (node.data.next != null) {
|
||||
Optional<String> n = node.data.next.apply(context);
|
||||
if (n.isPresent()) {
|
||||
if (n == null) {
|
||||
// actual function returned null optional which is case
|
||||
// when no next function was set. this is different
|
||||
// than when null is returned for next.
|
||||
node = node.next;
|
||||
}
|
||||
else if (n.isPresent()) {
|
||||
// user returned value
|
||||
node = oiol.get(n.get());
|
||||
}
|
||||
else {
|
||||
node = node.next;
|
||||
// user returned null
|
||||
node = null;
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -474,7 +482,7 @@ public interface ComponentFlow {
|
||||
Function<StringInputContext, String> f1 = input.getNext();
|
||||
Function<ComponentContext<?>, Optional<String>> f2 = context -> f1 != null
|
||||
? Optional.ofNullable(f1.apply(selector.getThisContext(context)))
|
||||
: Optional.empty();
|
||||
: null;
|
||||
return OrderedInputOperation.of(input.getId(), input.getOrder(), operation, f2);
|
||||
});
|
||||
}
|
||||
@@ -512,7 +520,7 @@ public interface ComponentFlow {
|
||||
Function<PathInputContext, String> f1 = input.getNext();
|
||||
Function<ComponentContext<?>, Optional<String>> f2 = context -> f1 != null
|
||||
? Optional.ofNullable(f1.apply(selector.getThisContext(context)))
|
||||
: Optional.empty();
|
||||
: null;
|
||||
return OrderedInputOperation.of(input.getId(), input.getOrder(), operation, f2);
|
||||
});
|
||||
}
|
||||
@@ -550,7 +558,7 @@ public interface ComponentFlow {
|
||||
Function<ConfirmationInputContext, String> f1 = input.getNext();
|
||||
Function<ComponentContext<?>, Optional<String>> f2 = context -> f1 != null
|
||||
? Optional.ofNullable(f1.apply(selector.getThisContext(context)))
|
||||
: Optional.empty();
|
||||
: null;
|
||||
return OrderedInputOperation.of(input.getId(), input.getOrder(), operation, f2);
|
||||
});
|
||||
}
|
||||
@@ -609,7 +617,7 @@ public interface ComponentFlow {
|
||||
Function<SingleItemSelectorContext<String, SelectorItem<String>>, String> f1 = input.getNext();
|
||||
Function<ComponentContext<?>, Optional<String>> f2 = context -> f1 != null
|
||||
? Optional.ofNullable(f1.apply(selector.getThisContext(context)))
|
||||
: Optional.empty();
|
||||
: null;
|
||||
return OrderedInputOperation.of(input.getId(), input.getOrder(), operation, f2);
|
||||
});
|
||||
}
|
||||
@@ -654,7 +662,7 @@ public interface ComponentFlow {
|
||||
Function<MultiItemSelectorContext<String, SelectorItem<String>>, String> f1 = input.getNext();
|
||||
Function<ComponentContext<?>, Optional<String>> f2 = context -> f1 != null
|
||||
? Optional.ofNullable(f1.apply(selector.getThisContext(context)))
|
||||
: Optional.empty();
|
||||
: null;
|
||||
return OrderedInputOperation.of(input.getId(), input.getOrder(), operation, f2);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -105,7 +105,8 @@ public interface ConfirmationInputSpec extends BaseInputSpec<ConfirmationInputSp
|
||||
ConfirmationInputSpec storeResult(boolean store);
|
||||
|
||||
/**
|
||||
* Define a function which may return id of a next component to go.
|
||||
* Define a function which may return id of a next component to go. Returning a
|
||||
* {@code null} or non existent id indicates that flow should stop.
|
||||
*
|
||||
* @param next next component function
|
||||
* @return a builder
|
||||
|
||||
@@ -124,7 +124,8 @@ public interface MultiItemSelectorSpec extends BaseInputSpec<MultiItemSelectorSp
|
||||
MultiItemSelectorSpec storeResult(boolean store);
|
||||
|
||||
/**
|
||||
* Define a function which may return id of a next component to go.
|
||||
* Define a function which may return id of a next component to go. Returning a
|
||||
* {@code null} or non existent id indicates that flow should stop.
|
||||
*
|
||||
* @param next next component function
|
||||
* @return a builder
|
||||
|
||||
@@ -107,7 +107,8 @@ public interface PathInputSpec extends BaseInputSpec<PathInputSpec> {
|
||||
PathInputSpec storeResult(boolean store);
|
||||
|
||||
/**
|
||||
* Define a function which may return id of a next component to go.
|
||||
* Define a function which may return id of a next component to go. Returning a
|
||||
* {@code null} or non existent id indicates that flow should stop.
|
||||
*
|
||||
* @param next next component function
|
||||
* @return a builder
|
||||
|
||||
@@ -142,7 +142,8 @@ public interface SingleItemSelectorSpec extends BaseInputSpec<SingleItemSelector
|
||||
SingleItemSelectorSpec storeResult(boolean store);
|
||||
|
||||
/**
|
||||
* Define a function which may return id of a next component to go.
|
||||
* Define a function which may return id of a next component to go. Returning a
|
||||
* {@code null} or non existent id indicates that flow should stop.
|
||||
*
|
||||
* @param next next component function
|
||||
* @return a builder
|
||||
|
||||
@@ -114,7 +114,8 @@ public interface StringInputSpec extends BaseInputSpec<StringInputSpec> {
|
||||
StringInputSpec storeResult(boolean store);
|
||||
|
||||
/**
|
||||
* Define a function which may return id of a next component to go.
|
||||
* Define a function which may return id of a next component to go. Returning a
|
||||
* {@code null} or non existent id indicates that flow should stop.
|
||||
*
|
||||
* @param next next component function
|
||||
* @return a builder
|
||||
|
||||
@@ -161,7 +161,7 @@ public class ComponentFlowTests extends AbstractShellTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChoosesDynamically() throws InterruptedException {
|
||||
public void testChoosesDynamicallyShouldJumpOverAndStop() throws InterruptedException {
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
@@ -202,12 +202,106 @@ public class ComponentFlowTests extends AbstractShellTests {
|
||||
ComponentFlowResult inputWizardResult = result.get();
|
||||
assertThat(inputWizardResult).isNotNull();
|
||||
String id1 = inputWizardResult.getContext().get("id1");
|
||||
// TODO: should be able to check if variable exists
|
||||
// String id2 = inputWizardResult.getContext().get("id2");
|
||||
String id3 = inputWizardResult.getContext().get("id3");
|
||||
assertThat(id1).isEqualTo("id3");
|
||||
// assertThat(id2).isNull();
|
||||
assertThat(id3).isEqualTo("value3");
|
||||
assertThat(inputWizardResult.getContext().containsKey("id2")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChoosesDynamicallyShouldNotContinueToNext() throws InterruptedException {
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
.withStringInput("id1")
|
||||
.name("name")
|
||||
.next(ctx -> ctx.get("id1"))
|
||||
.and()
|
||||
.withStringInput("id2")
|
||||
.name("name")
|
||||
.resultValue("value2")
|
||||
.resultMode(ResultMode.ACCEPT)
|
||||
.next(ctx -> null)
|
||||
.and()
|
||||
.withStringInput("id3")
|
||||
.name("name")
|
||||
.resultValue("value3")
|
||||
.resultMode(ResultMode.ACCEPT)
|
||||
.next(ctx -> null)
|
||||
.and()
|
||||
.build();
|
||||
|
||||
ExecutorService service = Executors.newFixedThreadPool(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<ComponentFlowResult> result = new AtomicReference<>();
|
||||
service.execute(() -> {
|
||||
result.set(wizard.run());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
// id1
|
||||
TestBuffer testBuffer = new TestBuffer().append("id2").cr();
|
||||
write(testBuffer.getBytes());
|
||||
// id2
|
||||
testBuffer = new TestBuffer().cr();
|
||||
write(testBuffer.getBytes());
|
||||
latch.await(4, TimeUnit.SECONDS);
|
||||
ComponentFlowResult inputWizardResult = result.get();
|
||||
assertThat(inputWizardResult).isNotNull();
|
||||
String id1 = inputWizardResult.getContext().get("id1");
|
||||
String id2 = inputWizardResult.getContext().get("id2");
|
||||
assertThat(id1).isEqualTo("id2");
|
||||
assertThat(id2).isEqualTo("value2");
|
||||
assertThat(inputWizardResult.getContext().containsKey("id3")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChoosesNonExistingComponent() throws InterruptedException {
|
||||
ComponentFlow wizard = ComponentFlow.builder()
|
||||
.terminal(getTerminal())
|
||||
.resourceLoader(getResourceLoader())
|
||||
.templateExecutor(getTemplateExecutor())
|
||||
.withStringInput("id1")
|
||||
.name("name")
|
||||
.next(ctx -> ctx.get("id1"))
|
||||
.and()
|
||||
.withStringInput("id2")
|
||||
.name("name")
|
||||
.resultValue("value2")
|
||||
.resultMode(ResultMode.ACCEPT)
|
||||
.next(ctx -> null)
|
||||
.and()
|
||||
.withStringInput("id3")
|
||||
.name("name")
|
||||
.resultValue("value3")
|
||||
.resultMode(ResultMode.ACCEPT)
|
||||
.next(ctx -> null)
|
||||
.and()
|
||||
.build();
|
||||
|
||||
ExecutorService service = Executors.newFixedThreadPool(1);
|
||||
CountDownLatch latch = new CountDownLatch(1);
|
||||
AtomicReference<ComponentFlowResult> result = new AtomicReference<>();
|
||||
service.execute(() -> {
|
||||
result.set(wizard.run());
|
||||
latch.countDown();
|
||||
});
|
||||
|
||||
// id1
|
||||
TestBuffer testBuffer = new TestBuffer().append("fake").cr();
|
||||
write(testBuffer.getBytes());
|
||||
|
||||
// don't execute id2 or id3
|
||||
testBuffer = new TestBuffer().cr();
|
||||
write(testBuffer.getBytes());
|
||||
latch.await(4, TimeUnit.SECONDS);
|
||||
ComponentFlowResult inputWizardResult = result.get();
|
||||
assertThat(inputWizardResult).isNotNull();
|
||||
String id1 = inputWizardResult.getContext().get("id1");
|
||||
assertThat(id1).isEqualTo("fake");
|
||||
assertThat(inputWizardResult.getContext().containsKey("id2")).isFalse();
|
||||
assertThat(inputWizardResult.getContext().containsKey("id3")).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,9 +21,6 @@
|
||||
[12.734384, "o", "\u001b[?1l\u001b>\u001b[?12;25h\u001b[K"]
|
||||
[12.740754, "o", "\u001b[32;1m?\u001b[0m \u001b[97;1mField1\u001b[0m \u001b[34mdefaultField1Value\u001b[0m\r\n"]
|
||||
[12.74362, "o", "\u001b[?1h\u001b=\u001b[?25l"]
|
||||
[12.750944, "o", "\u001b[32;1m?\u001b[0m \u001b[97;1mField2\u001b[0m \u001b[34m[Default defaultField2Value]\u001b[0m\r"]
|
||||
[13.542429, "o", "\u001b[?1l\u001b>\u001b[?12;25h\u001b[K"]
|
||||
[13.545672, "o", "\u001b[32;1m?\u001b[0m \u001b[97;1mField2\u001b[0m \u001b[34mdefaultField2Value\u001b[0m\r\n"]
|
||||
[13.547149, "o", "\u001b[?1h\u001b=\u001b[?2004h\u001b[33mmy-shell:>\u001b[0m"]
|
||||
[14.091367, "o", "\u001b[1mflow conditional\u001b[0m"]
|
||||
[14.419251, "o", "\r\r\n\u001b[?1l\u001b>\u001b[?1000l\u001b[?2004l"]
|
||||
|
||||
File diff suppressed because one or more lines are too long
|
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 14 KiB |
@@ -19,6 +19,10 @@ include::{snippets}/FlowComponentSnippets.java[tag=snippet1]
|
||||
|
||||
image::images/component-flow-showcase-1.svg[text input]
|
||||
|
||||
Normal execution order of a components is same as defined with a builder. It's
|
||||
possible to conditionally choose where to jump in a flow by using a `next`
|
||||
function and returning target _component id_. If this returned id is aither _null_
|
||||
or doesn't exist flow is essentially stopped right there.
|
||||
|
||||
====
|
||||
[source, java, indent=0]
|
||||
|
||||
Reference in New Issue
Block a user