Added ShowTypingMiddleware and tests
This commit is contained in:
Родитель
338492b949
Коммит
77a7d8fcd7
|
@ -0,0 +1,104 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.microsoft.bot.builder;
|
||||
|
||||
import com.microsoft.bot.connector.ExecutorFactory;
|
||||
import com.microsoft.bot.schema.Activity;
|
||||
import com.microsoft.bot.schema.ActivityTypes;
|
||||
import com.microsoft.bot.schema.ConversationReference;
|
||||
import com.microsoft.bot.schema.ResourceResponse;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
/**
|
||||
* When added, this middleware will send typing activities back to the user when a Message activity
|
||||
* is received to let them know that the bot has received the message and is working on the response.
|
||||
* You can specify a delay in milliseconds before the first typing activity is sent and then a frequency,
|
||||
* also in milliseconds which determines how often another typing activity is sent. Typing activities
|
||||
* will continue to be sent until your bot sends another message back to the user.
|
||||
*/
|
||||
public class ShowTypingMiddleware implements Middleware {
|
||||
/**
|
||||
* Initial delay before sending first typing indicator. Defaults to 500ms.
|
||||
*/
|
||||
private long delay;
|
||||
|
||||
/**
|
||||
* Rate at which additional typing indicators will be sent. Defaults to every 2000ms.
|
||||
*/
|
||||
private long period;
|
||||
|
||||
public ShowTypingMiddleware() {
|
||||
this(500, 2000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes a new instance of the ShowTypingMiddleware class.
|
||||
*
|
||||
* @param withDelay Initial delay before sending first typing indicator.
|
||||
* @param withPeriod Rate at which additional typing indicators will be sent.
|
||||
*/
|
||||
public ShowTypingMiddleware(long withDelay, long withPeriod) {
|
||||
if (withDelay < 0) {
|
||||
throw new IllegalArgumentException("Delay must be greater than or equal to zero");
|
||||
}
|
||||
|
||||
if (withPeriod < 0) {
|
||||
throw new IllegalArgumentException("Repeat period must be greater than zero");
|
||||
}
|
||||
|
||||
delay = withDelay;
|
||||
period = withPeriod;
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an incoming activity.
|
||||
*
|
||||
* @param turnContext The context object for this turn.
|
||||
* @param next The delegate to call to continue the bot middleware pipeline.
|
||||
* @return A task that represents the work queued to execute.
|
||||
*/
|
||||
@Override
|
||||
public CompletableFuture<Void> onTurn(TurnContext turnContext, NextDelegate next) {
|
||||
if (!turnContext.getActivity().isType(ActivityTypes.MESSAGE)) {
|
||||
return next.next();
|
||||
}
|
||||
|
||||
// do not await task - we want this to run in the background and we will cancel it when its done
|
||||
CompletableFuture sendFuture = sendTyping(turnContext, delay, period);
|
||||
return next.next()
|
||||
.thenAccept(result -> sendFuture.cancel(true));
|
||||
}
|
||||
|
||||
private static CompletableFuture<Void> sendTyping(TurnContext turnContext, long delay, long period) {
|
||||
return CompletableFuture.runAsync(() -> {
|
||||
try {
|
||||
Thread.sleep(delay);
|
||||
|
||||
while (!Thread.currentThread().isInterrupted()) {
|
||||
sendTypingActivity(turnContext).join();
|
||||
Thread.sleep(period);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing
|
||||
}
|
||||
}, ExecutorFactory.getExecutor());
|
||||
}
|
||||
|
||||
private static CompletableFuture<ResourceResponse[]> sendTypingActivity(TurnContext turnContext) {
|
||||
// create a TypingActivity, associate it with the conversation and send immediately
|
||||
Activity typingActivity = new Activity(ActivityTypes.TYPING) {{
|
||||
setRelatesTo(turnContext.getActivity().getRelatesTo());
|
||||
}};
|
||||
|
||||
// sending the Activity directly on the Adapter avoids other Middleware and avoids setting the Responded
|
||||
// flag, however, this also requires that the conversation reference details are explicitly added.
|
||||
ConversationReference conversationReference = turnContext.getActivity().getConversationReference();
|
||||
typingActivity.applyConversationReference(conversationReference);
|
||||
|
||||
// make sure to send the Activity directly on the Adapter rather than via the TurnContext
|
||||
return turnContext.getAdapter().sendActivities(turnContext, Collections.singletonList(typingActivity));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
package com.microsoft.bot.builder;
|
||||
|
||||
import com.microsoft.bot.builder.adapters.TestAdapter;
|
||||
import com.microsoft.bot.builder.adapters.TestFlow;
|
||||
import com.microsoft.bot.schema.Activity;
|
||||
import com.microsoft.bot.schema.ActivityTypes;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class ShowTypingMiddlewareTests {
|
||||
@Test
|
||||
public void ShowTyping_TestMiddleware_1_Second_Interval() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(100, 1000));
|
||||
|
||||
new TestFlow(adapter, (turnContext -> {
|
||||
try {
|
||||
Thread.sleep(2500);
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
Assert.assertFalse(turnContext.getResponded());
|
||||
|
||||
turnContext.sendActivity("Message send after delay").join();
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}))
|
||||
.send("foo")
|
||||
.assertReply(this::validateTypingActivity)
|
||||
.assertReply(this::validateTypingActivity)
|
||||
.assertReply(this::validateTypingActivity)
|
||||
.assertReply("Message send after delay")
|
||||
.startTest().join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ShowTyping_TestMiddleware_Context_Completes_Before_Typing_Interval() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(100, 5000));
|
||||
|
||||
new TestFlow(adapter, (turnContext -> {
|
||||
try {
|
||||
Thread.sleep(2000);
|
||||
} catch (InterruptedException e) {
|
||||
// do nothing
|
||||
}
|
||||
|
||||
turnContext.sendActivity("Message send after delay").join();
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}))
|
||||
.send("foo")
|
||||
.assertReply(this::validateTypingActivity)
|
||||
.assertReply("Message send after delay")
|
||||
.startTest().join();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void ShowTyping_TestMiddleware_ImmediateResponse_5SecondInterval() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(2000, 5000));
|
||||
|
||||
new TestFlow(adapter, (turnContext -> {
|
||||
turnContext.sendActivity("Message send after delay").join();
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}))
|
||||
.send("foo")
|
||||
.assertReply("Message send after delay")
|
||||
.startTest().join();
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void ShowTyping_TestMiddleware_NegativeDelay() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(-100, 5000));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void ShowTyping_TestMiddleware_ZeroFrequency() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(-100, 0));
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void ShowTyping_TestMiddleware_NegativePerion() {
|
||||
TestAdapter adapter = new TestAdapter()
|
||||
.use(new ShowTypingMiddleware(500, -500));
|
||||
}
|
||||
|
||||
private void validateTypingActivity(Activity obj) {
|
||||
if (!obj.isType(ActivityTypes.TYPING)) {
|
||||
throw new RuntimeException("Activity was not of type TypingActivity");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
// Copyright (c) Microsoft Corporation. All rights reserved.
|
||||
// Licensed under the MIT License.
|
||||
|
||||
package com.microsoft.bot.builder;
|
||||
|
||||
public class StateSettings {
|
||||
private boolean lastWriterWins = true;
|
||||
|
||||
public boolean getLastWriterWins() {
|
||||
return this.lastWriterWins;
|
||||
}
|
||||
|
||||
public void setLast(boolean lastWriterWins) {
|
||||
this.lastWriterWins = lastWriterWins;
|
||||
}
|
||||
}
|
|
@ -95,7 +95,7 @@ public class TranscriptMiddlewareTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void Transcript_LogUpdateActivities() throws InterruptedException {
|
||||
public void Transcript_LogUpdateActivities() {
|
||||
MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore();
|
||||
TestAdapter adapter = (new TestAdapter()).use(new TranscriptLoggerMiddleware(transcriptStore));
|
||||
final String[] conversationId = {null};
|
||||
|
@ -117,25 +117,23 @@ public class TranscriptMiddlewareTest {
|
|||
return CompletableFuture.completedFuture(null);
|
||||
})
|
||||
.send("foo")
|
||||
.delay(50)
|
||||
.send("update")
|
||||
.delay(50)
|
||||
.assertReply("new response")
|
||||
.startTest().join();
|
||||
|
||||
Thread.sleep(500);
|
||||
PagedResult pagedResult = transcriptStore.getTranscriptActivities("test", conversationId[0]).join();
|
||||
Assert.assertEquals(4, pagedResult.getItems().size());
|
||||
Assert.assertEquals("foo", ((Activity) pagedResult.getItems().get(0)).getText());
|
||||
Assert.assertEquals("response", ((Activity) pagedResult.getItems().get(1)).getText());
|
||||
if (!StringUtils.equals(((Activity)pagedResult.getItems().get(2)).getText(), "new response")) {
|
||||
Assert.fail("fail");
|
||||
}
|
||||
Assert.assertEquals("new response", ((Activity)pagedResult.getItems().get(2)).getText());
|
||||
Assert.assertEquals("update", ((Activity)pagedResult.getItems().get(3)).getText());
|
||||
Assert.assertEquals( ((Activity)pagedResult.getItems().get(1)).getId(), ((Activity) pagedResult.getItems().get(2)).getId());
|
||||
}
|
||||
|
||||
@Test
|
||||
public final void Transcript_LogDeleteActivities() throws InterruptedException {
|
||||
public final void Transcript_LogDeleteActivities() {
|
||||
MemoryTranscriptStore transcriptStore = new MemoryTranscriptStore();
|
||||
TestAdapter adapter = (new TestAdapter()).use(new TranscriptLoggerMiddleware(transcriptStore));
|
||||
final String[] conversationId = {null};
|
||||
|
@ -153,11 +151,11 @@ public class TranscriptMiddlewareTest {
|
|||
return CompletableFuture.completedFuture(null);
|
||||
})
|
||||
.send("foo")
|
||||
.delay(50)
|
||||
.assertReply("response")
|
||||
.send("deleteIt")
|
||||
.startTest().join();
|
||||
|
||||
Thread.sleep(500);
|
||||
PagedResult pagedResult = transcriptStore.getTranscriptActivities("test", conversationId[0]).join();
|
||||
for (Object act : pagedResult.getItems()) {
|
||||
System.out.printf("Here is the object: %s : Type: %s\n", act.getClass().getTypeName(), ((Activity) act).getType());
|
||||
|
@ -175,7 +173,7 @@ public class TranscriptMiddlewareTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void Transcript_TestDateLogUpdateActivities() throws InterruptedException {
|
||||
public void Transcript_TestDateLogUpdateActivities() {
|
||||
OffsetDateTime dateTimeStartOffset1 = OffsetDateTime.now();
|
||||
OffsetDateTime dateTimeStartOffset2 = OffsetDateTime.now(ZoneId.of("UTC"));
|
||||
|
||||
|
@ -200,12 +198,12 @@ public class TranscriptMiddlewareTest {
|
|||
return CompletableFuture.completedFuture(null);
|
||||
})
|
||||
.send("foo")
|
||||
.delay(50)
|
||||
.send("update")
|
||||
.delay(50)
|
||||
.assertReply("new response")
|
||||
.startTest().join();
|
||||
|
||||
Thread.sleep(500);
|
||||
|
||||
PagedResult pagedResult = transcriptStore.getTranscriptActivities("test", conversationId[0], null, dateTimeStartOffset1).join();
|
||||
Assert.assertEquals(4, pagedResult.getItems().size());
|
||||
Assert.assertEquals("foo", ((Activity) pagedResult.getItems().get(0)).getText());
|
||||
|
|
|
@ -57,8 +57,7 @@ public class TestAdapter extends BotAdapter {
|
|||
return this;
|
||||
}
|
||||
|
||||
public CompletableFuture<Void> processActivity(Activity activity,
|
||||
BotCallbackHandler callback) {
|
||||
public CompletableFuture<Void> processActivity(Activity activity, BotCallbackHandler callback) {
|
||||
return CompletableFuture.supplyAsync(() -> {
|
||||
synchronized (conversationReference()) {
|
||||
// ready for next reply
|
||||
|
@ -69,6 +68,7 @@ public class TestAdapter extends BotAdapter {
|
|||
activity.setRecipient(conversationReference().getBot());
|
||||
activity.setConversation(conversationReference().getConversation());
|
||||
activity.setServiceUrl(conversationReference().getServiceUrl());
|
||||
|
||||
Integer next = nextId++;
|
||||
activity.setId(next.toString());
|
||||
}
|
||||
|
@ -104,11 +104,11 @@ public class TestAdapter extends BotAdapter {
|
|||
|
||||
responses.add(new ResourceResponse(activity.getId()));
|
||||
|
||||
System.out.println(String.format("TestAdapter:SendActivities(tid:%s):Count:%s", Thread.currentThread().getId(), activities.size()));
|
||||
System.out.println(String.format("TestAdapter:SendActivities, Count:%s (tid:%s)", activities.size(), Thread.currentThread().getId()));
|
||||
for (Activity act : activities) {
|
||||
System.out.printf(":--------\n: To:%s\n", act.getRecipient().getName());
|
||||
System.out.printf(": From:%s\n", (act.getFrom() == null) ? "No from set" : act.getFrom().getName());
|
||||
System.out.printf(": Text:%s\n:---------", (act.getText() == null) ? "No text set" : act.getText());
|
||||
System.out.printf(" :--------\n : To:%s\n", act.getRecipient().getName());
|
||||
System.out.printf(" : From:%s\n", (act.getFrom() == null) ? "No from set" : act.getFrom().getName());
|
||||
System.out.printf(" : Text:%s\n :---------\n", (act.getText() == null) ? "No text set" : act.getText());
|
||||
}
|
||||
|
||||
// This is simulating DELAY
|
||||
|
|
|
@ -62,7 +62,7 @@ public class TestFlow {
|
|||
return new TestFlow(
|
||||
testTask
|
||||
.thenCompose(result -> {
|
||||
System.out.print(String.format("USER SAYS: %s (Thread Id: %s)\n", userSays, Thread.currentThread().getId()));
|
||||
System.out.print(String.format("USER SAYS: %s (tid: %s)\n", userSays, Thread.currentThread().getId()));
|
||||
return this.adapter.sendTextToBot(userSays, this.callback);
|
||||
}), this);
|
||||
}
|
||||
|
@ -79,7 +79,7 @@ public class TestFlow {
|
|||
|
||||
return new TestFlow(
|
||||
testTask.thenCompose(result -> {
|
||||
System.out.printf("TestFlow(%s): Send with User Activity! %s", Thread.currentThread().getId(), userActivity.getText());
|
||||
System.out.printf("TestFlow: Send with User Activity! %s (tid:%s)", userActivity.getText(), Thread.currentThread().getId());
|
||||
return this.adapter.processActivity(userActivity, this.callback);
|
||||
}
|
||||
), this);
|
||||
|
@ -92,17 +92,18 @@ public class TestFlow {
|
|||
* @return
|
||||
*/
|
||||
public TestFlow delay(int ms) {
|
||||
return new TestFlow(CompletableFuture.supplyAsync(() ->
|
||||
{
|
||||
System.out.printf("TestFlow(%s): Delay(%s ms) called. ", Thread.currentThread().getId(), ms);
|
||||
System.out.flush();
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
return new TestFlow(
|
||||
testTask
|
||||
.thenCompose(result -> {
|
||||
System.out.printf("TestFlow: Delay(%s ms) called. (tid:%s)\n", ms, Thread.currentThread().getId());
|
||||
System.out.flush();
|
||||
try {
|
||||
Thread.sleep(ms);
|
||||
} catch (InterruptedException e) {
|
||||
|
||||
}
|
||||
return null;
|
||||
}, ExecutorFactory.getExecutor()), this);
|
||||
}
|
||||
return CompletableFuture.completedFuture(null);
|
||||
}), this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -164,7 +165,7 @@ public class TestFlow {
|
|||
public TestFlow assertReply(Consumer<Activity> validateActivity, String description, int timeout) {
|
||||
return new TestFlow(testTask
|
||||
.thenApply(result -> {
|
||||
System.out.println(String.format("AssertReply: Starting loop : %s (Thread:%s)", description, Thread.currentThread().getId()));
|
||||
System.out.println(String.format("AssertReply: Starting loop : %s (tid:%s)", description, Thread.currentThread().getId()));
|
||||
System.out.flush();
|
||||
|
||||
int finalTimeout = Integer.MAX_VALUE;
|
||||
|
@ -189,10 +190,12 @@ public class TestFlow {
|
|||
// System.out.flush();
|
||||
|
||||
if (replyActivity != null) {
|
||||
System.out.printf("AssertReply(tid:%s): Received Reply: %s ", Thread.currentThread().getId(), (replyActivity.getText() == null) ? "No Text set" : replyActivity.getText());
|
||||
System.out.printf("AssertReply: Received Reply (tid:%s)", Thread.currentThread().getId());
|
||||
System.out.flush();
|
||||
System.out.printf("=============\n From: %s\n To:%s\n ==========\n", (replyActivity.getFrom() == null) ? "No from set" : replyActivity.getFrom().getName(),
|
||||
(replyActivity.getRecipient() == null) ? "No recipient set" : replyActivity.getRecipient().getName());
|
||||
System.out.printf("\n =============\n From: %s\n To:%s\n Text:%s\n ==========\n",
|
||||
(replyActivity.getFrom() == null) ? "No from set" : replyActivity.getFrom().getName(),
|
||||
(replyActivity.getRecipient() == null) ? "No recipient set" : replyActivity.getRecipient().getName(),
|
||||
(replyActivity.getText() == null) ? "No Text set" : replyActivity.getText());
|
||||
System.out.flush();
|
||||
|
||||
// if we have a reply
|
||||
|
|
Загрузка…
Ссылка в новой задаче