[SDK] Add bot-applicationinsights package (#1075)

* Create main structure

* Add pom file

* Add main classes

* Add TelemetryInitializerMiddleware in core folder

* Add package-info files

* Add MyBotTelemetryClient class in test folder

* Add unit tests

* Fix issue in WaterfallDialog class

Co-authored-by: Federico Bernal <64086728+FedericoBernal@users.noreply.github.com>
Co-authored-by: Martin Battaglino <martinbatta32@gmail.com>
This commit is contained in:
Franco Alvarez 2021-03-23 18:07:08 -03:00 коммит произвёл GitHub
Родитель 5c00a74e28
Коммит c01ecdd56d
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
11 изменённых файлов: 1299 добавлений и 2 удалений

Просмотреть файл

@ -60,9 +60,22 @@
<version>2.4.1</version>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-dialogs</artifactId>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.microsoft.bot</groupId>
<artifactId>bot-builder</artifactId>
<version>${project.version}</version>
<type>test-jar</type>
<scope>test</scope>
</dependency>
</dependencies>

Просмотреть файл

@ -0,0 +1,217 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import com.microsoft.applicationinsights.internal.schemav2.AvailabilityData;
import com.microsoft.applicationinsights.internal.util.LocalStringsUtils;
import com.microsoft.applicationinsights.internal.util.Sanitizer;
import com.microsoft.applicationinsights.telemetry.BaseSampleSourceTelemetry;
import com.microsoft.applicationinsights.telemetry.Duration;
import java.util.Date;
import java.util.concurrent.ConcurrentMap;
/**
* We took this class from https://github.com/microsoft/ApplicationInsights-Java/issues/1099
* as this is not already migrated in ApplicationInsights-Java library.
*/
public final class AvailabilityTelemetry extends BaseSampleSourceTelemetry<AvailabilityData> {
private Double samplingPercentage;
private final AvailabilityData data;
public static final String ENVELOPE_NAME = "Availability";
public static final String BASE_TYPE = "AvailabilityData";
/**
* Initializes a new instance of the AvailabilityTelemetry class.
*/
public AvailabilityTelemetry() {
this.data = new AvailabilityData();
initialize(this.data.getProperties());
setId(LocalStringsUtils.generateRandomIntegerId());
// Setting mandatory fields.
setTimestamp(new Date());
setSuccess(true);
}
/**
* Initializes a new instance of the AvailabilityTelemetry class with the given name,
* time stamp, duration, HTTP response code and success property values.
* @param name A user-friendly name for the request.
* @param duration The time of the request.
* @param runLocation The duration, in milliseconds, of the request processing.
* @param message The HTTP response code.
* @param success 'true' if the request was a success, 'false' otherwise.
* @param measurements The measurements.
* @param properties The corresponding properties.
*/
public AvailabilityTelemetry(String name, Duration duration, String runLocation, String message,
boolean success, ConcurrentMap<String, Double> measurements,
ConcurrentMap<String, String> properties) {
this.data = new AvailabilityData();
this.data.setProperties(properties);
this.data.setMeasurements(measurements);
this.data.setMessage(message);
initialize(this.data.getProperties());
setId(LocalStringsUtils.generateRandomIntegerId());
setTimestamp(new Date());
setName(name);
setRunLocation(runLocation);
setDuration(duration);
setSuccess(success);
}
/**
* Gets the ver value from the data object.
* @return The ver value.
*/
@Override
public int getVer() {
return getData().getVer();
}
/**
* Gets a map of application-defined request metrics.
* @return The map of metrics
*/
public ConcurrentMap<String, Double> getMetrics() {
return data.getMeasurements();
}
/**
* Sets the StartTime. Uses the default behavior and sets the property on the 'data' start time.
* @param timestamp The timestamp as Date.
*/
@Override
public void setTimestamp(Date timestamp) {
if (timestamp == null) {
timestamp = new Date();
}
super.setTimestamp(timestamp);
}
/**
* Gets or human-readable name of the requested page.
* @return A human-readable name.
*/
public String getName() {
return data.getName();
}
/**
* Sets or human-readable name of the requested page.
* @param name A human-readable name.
*/
public void setName(String name) {
data.setName(name);
}
/**
* Gets or human-readable name of the run location.
* @return A human-readable name.
*/
public String getRunLocation() {
return data.getRunLocation();
}
/**
* Sets or human-readable name of the run location.
* @param runLocation A human-readable name
*/
public void setRunLocation(String runLocation) {
data.setRunLocation(runLocation);
}
/**
* Gets the unique identifier of the request.
* @return Unique identifier.
*/
public String getId() {
return data.getId();
}
/**
* Sets the unique identifier of the request.
* @param id Unique identifier.
*/
public void setId(String id) {
data.setId(id);
}
/**
* Gets a value indicating whether application handled the request successfully.
* @return Success indication.
*/
public boolean isSuccess() {
return data.getSuccess();
}
/**
* Sets a value indicating whether application handled the request successfully.
* @param success Success indication.
*/
public void setSuccess(boolean success) {
data.setSuccess(success);
}
/**
* Gets the amount of time it took the application to handle the request.
* @return Amount of time in milliseconds.
*/
public Duration getDuration() {
return data.getDuration();
}
/**
* Sets the amount of time it took the application to handle the request.
* @param duration Amount of time in captured in a {@link com.microsoft.applicationinsights.telemetry.Duration}.
*/
public void setDuration(Duration duration) {
data.setDuration(duration);
}
@Override
public Double getSamplingPercentage() {
return samplingPercentage;
}
@Override
public void setSamplingPercentage(Double samplingPercentage) {
this.samplingPercentage = samplingPercentage;
}
@Override
@Deprecated
protected void additionalSanitize() {
data.setName(Sanitizer.sanitizeName(data.getName()));
data.setId(Sanitizer.sanitizeName(data.getId()));
Sanitizer.sanitizeMeasurements(getMetrics());
}
@Override
protected AvailabilityData getData() {
return data;
}
@Override
public String getEnvelopName() {
return ENVELOPE_NAME;
}
@Override
public String getBaseTypeName() {
return BASE_TYPE;
}
}

Просмотреть файл

@ -0,0 +1,247 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import com.microsoft.applicationinsights.TelemetryClient;
import com.microsoft.applicationinsights.telemetry.EventTelemetry;
import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry;
import com.microsoft.applicationinsights.telemetry.PageViewTelemetry;
import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry;
import com.microsoft.applicationinsights.telemetry.SeverityLevel;
import com.microsoft.applicationinsights.telemetry.TraceTelemetry;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.Severity;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* A logging client for bot telemetry.
*/
public class BotTelemetryClientImpl implements BotTelemetryClient {
private final TelemetryClient telemetryClient;
/**
* Initializes a new instance of the {@link BotTelemetryClient}.
*
* @param withTelemetryClient The telemetry client to forward bot events to.
*/
public BotTelemetryClientImpl(TelemetryClient withTelemetryClient) {
if (withTelemetryClient == null) {
throw new IllegalArgumentException("withTelemetry should be provided");
}
this.telemetryClient = withTelemetryClient;
}
/**
* Send information about availability of an application.
*
* @param name Availability test name.
* @param timeStamp The time when the availability was captured.
* @param duration The time taken for the availability test to run.
* @param runLocation Name of the location the availability test was run from.
* @param success True if the availability test ran successfully.
* @param message Error message on availability test run failure.
* @param properties Named string values you can use to classify and search for this availability telemetry.
* @param metrics Additional values associated with this availability telemetry.
*/
@SuppressWarnings("checkstyle:ParameterNumber")
@Override
public void trackAvailability(String name,
OffsetDateTime timeStamp,
Duration duration,
String runLocation,
boolean success,
String message,
Map<String, String> properties,
Map<String, Double> metrics) {
com.microsoft.applicationinsights.telemetry.Duration durationTelemetry =
new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos());
ConcurrentMap<String, String> concurrentProperties = new ConcurrentHashMap<>(properties);
ConcurrentMap<String, Double> concurrentMetrics = new ConcurrentHashMap<>(metrics);
AvailabilityTelemetry telemetry = new AvailabilityTelemetry(
name,
durationTelemetry,
runLocation,
message,
success,
concurrentMetrics,
concurrentProperties);
if (properties != null) {
for (Map.Entry<String, String> pair: properties.entrySet()) {
telemetry.getProperties().put(pair.getKey(), pair.getValue());
}
}
if (metrics != null) {
for (Map.Entry<String, Double> pair: metrics.entrySet()) {
telemetry.getMetrics().put(pair.getKey(), pair.getValue());
}
}
/**
* This should be telemetryClient.trackAvailability(telemetry).
* However, it is not present in TelemetryClient class
*/
telemetryClient.track(telemetry);
}
/**
* Send information about an external dependency (outgoing call) in the application.
*
* @param dependencyTypeName Name of the command initiated with this dependency call. Low cardinality value.
* Examples are SQL, Azure table, and HTTP.
* @param target External dependency target.
* @param dependencyName Name of the command initiated with this dependency call. Low cardinality value.
* Examples are stored procedure name and URL path template.
* @param data Command initiated by this dependency call. Examples are SQL statement and HTTP
* URL's with all query parameters.
* @param startTime The time when the dependency was called.
* @param duration The time taken by the external dependency to handle the call.
* @param resultCode Result code of dependency call execution.
* @param success True if the dependency call was handled successfully.
*/
@SuppressWarnings("checkstyle:ParameterNumber")
@Override
public void trackDependency(String dependencyTypeName,
String target,
String dependencyName,
String data,
OffsetDateTime startTime,
Duration duration,
String resultCode,
boolean success) {
com.microsoft.applicationinsights.telemetry.Duration durationTelemetry =
new com.microsoft.applicationinsights.telemetry.Duration(duration.toNanos());
RemoteDependencyTelemetry telemetry =
new RemoteDependencyTelemetry(dependencyName, data, durationTelemetry, success);
telemetry.setType(dependencyTypeName);
telemetry.setTarget(target);
telemetry.setTimestamp(new Date(startTime.toInstant().toEpochMilli()));
telemetry.setResultCode(resultCode);
telemetryClient.trackDependency(telemetry);
}
/**
* Logs custom events with extensible named fields.
*
* @param eventName A name for the event.
* @param properties Named string values you can use to search and classify events.
* @param metrics Measurements associated with this event.
*/
@Override
public void trackEvent(String eventName, Map<String, String> properties, Map<String, Double> metrics) {
EventTelemetry telemetry = new EventTelemetry(eventName);
if (properties != null) {
for (Map.Entry<String, String> pair: properties.entrySet()) {
telemetry.getProperties().put(pair.getKey(), pair.getValue());
}
}
if (metrics != null) {
for (Map.Entry<String, Double> pair: metrics.entrySet()) {
telemetry.getMetrics().put(pair.getKey(), pair.getValue());
}
}
telemetryClient.trackEvent(telemetry);
}
/**
* Logs a system exception.
*
* @param exception The exception to log.
* @param properties Named string values you can use to classify and search for this exception.
* @param metrics Additional values associated with this exception
*/
@Override
public void trackException(Exception exception, Map<String, String> properties, Map<String, Double> metrics) {
ExceptionTelemetry telemetry = new ExceptionTelemetry(exception);
if (properties != null) {
for (Map.Entry<String, String> pair: properties.entrySet()) {
telemetry.getProperties().put(pair.getKey(), pair.getValue());
}
}
if (metrics != null) {
for (Map.Entry<String, Double> pair: metrics.entrySet()) {
telemetry.getMetrics().put(pair.getKey(), pair.getValue());
}
}
telemetryClient.trackException(telemetry);
}
/**
* Send a trace message.
*
* @param message Message to display.
* @param severityLevel Trace severity level {@link Severity}.
* @param properties Named string values you can use to search and classify events.
*/
@Override
public void trackTrace(String message, Severity severityLevel, Map<String, String> properties) {
TraceTelemetry telemetry = new TraceTelemetry(message);
telemetry.setSeverityLevel(SeverityLevel.values()[severityLevel.ordinal()]);
if (properties != null) {
for (Map.Entry<String, String> pair: properties.entrySet()) {
telemetry.getProperties().put(pair.getKey(), pair.getValue());
}
}
telemetryClient.trackTrace(telemetry);
}
/**
* We implemented this method calling the tracePageView method from {@link BotTelemetryClientImpl} as the
* IBotPageViewTelemetryClient has not been implemented.
* {@inheritDoc}
*/
@Override
public void trackDialogView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) {
trackPageView(dialogName, properties, metrics);
}
/**
* Logs a dialog entry / as an Application Insights page view.
*
* @param dialogName The name of the dialog to log the entry / start for.
* @param properties Named string values you can use to search and classify events.
* @param metrics Measurements associated with this event.
*/
public void trackPageView(String dialogName, Map<String, String> properties, Map<String, Double> metrics) {
PageViewTelemetry telemetry = new PageViewTelemetry(dialogName);
if (properties != null) {
for (Map.Entry<String, String> pair: properties.entrySet()) {
telemetry.getProperties().put(pair.getKey(), pair.getValue());
}
}
if (metrics != null) {
for (Map.Entry<String, Double> pair: metrics.entrySet()) {
telemetry.getMetrics().put(pair.getKey(), pair.getValue());
}
}
telemetryClient.trackPageView(telemetry);
}
/**
* Flushes the in-memory buffer and any metrics being pre-aggregated.
*/
@Override
public void flush() {
telemetryClient.flush();
}
}

Просмотреть файл

@ -0,0 +1,74 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
package com.microsoft.bot.applicationinsights.core;
import com.microsoft.applicationinsights.core.dependencies.http.client.protocol.HttpClientContext;
import com.microsoft.applicationinsights.core.dependencies.http.protocol.HttpContext;
import com.microsoft.bot.builder.BotAssert;
import com.microsoft.bot.builder.Middleware;
import com.microsoft.bot.builder.NextDelegate;
import com.microsoft.bot.builder.TelemetryLoggerMiddleware;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.schema.Activity;
import java.util.concurrent.CompletableFuture;
/**
* Middleware for storing incoming activity on the HttpContext.
*/
public class TelemetryInitializerMiddleware implements Middleware {
private HttpContext httpContext;
private final String botActivityKey = "BotBuilderActivity";
private final TelemetryLoggerMiddleware telemetryLoggerMiddleware;
private final Boolean logActivityTelemetry;
/**
* Initializes a new instance of the {@link TelemetryInitializerMiddleware}.
* @param withTelemetryLoggerMiddleware The TelemetryLoggerMiddleware to use.
* @param withLogActivityTelemetry Boolean determining if you want to log telemetry activity
*/
public TelemetryInitializerMiddleware(TelemetryLoggerMiddleware withTelemetryLoggerMiddleware,
Boolean withLogActivityTelemetry) {
telemetryLoggerMiddleware = withTelemetryLoggerMiddleware;
if (withLogActivityTelemetry == null) {
withLogActivityTelemetry = true;
}
logActivityTelemetry = withLogActivityTelemetry;
}
/**
* Stores the incoming activity as JSON in the items collection on the HttpContext.
* @param context The incoming TurnContext
* @param next Delegate to run next on
* @return Returns a CompletableFuture with Void value
*/
public CompletableFuture<Void> onTurn(TurnContext context, NextDelegate next) {
BotAssert.contextNotNull(context);
if (context.getActivity() != null) {
Activity activity = context.getActivity();
if (this.httpContext == null) {
this.httpContext = HttpClientContext.create();
}
Object item = httpContext.getAttribute(botActivityKey);
if (item != null) {
httpContext.removeAttribute(botActivityKey);
}
httpContext.setAttribute(botActivityKey, activity);
}
if (logActivityTelemetry) {
return telemetryLoggerMiddleware.onTurn(context, next);
} else {
return next.next();
}
}
}

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
/**
* This package contains the classes for bot-applicationinsights.
*/
package com.microsoft.bot.applicationinsights.core;

Просмотреть файл

@ -0,0 +1,8 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License. See License.txt in the project root for
// license information.
/**
* This package contains the classes for bot-applicationinsights.
*/
package com.microsoft.bot.applicationinsights;

Просмотреть файл

@ -0,0 +1,183 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import com.microsoft.applicationinsights.TelemetryClient;
import com.microsoft.applicationinsights.TelemetryConfiguration;
import com.microsoft.applicationinsights.channel.TelemetryChannel;
import com.microsoft.applicationinsights.telemetry.EventTelemetry;
import com.microsoft.applicationinsights.telemetry.RemoteDependencyTelemetry;
import com.microsoft.applicationinsights.telemetry.PageViewTelemetry;
import com.microsoft.applicationinsights.telemetry.ExceptionTelemetry;
import com.microsoft.applicationinsights.telemetry.TraceTelemetry;
import com.microsoft.applicationinsights.telemetry.SeverityLevel;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.Severity;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.mockito.Mockito;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.Map;
public class BotTelemetryClientTests {
private BotTelemetryClient botTelemetryClient;
private TelemetryChannel mockTelemetryChannel;
@Before
public void initialize() {
mockTelemetryChannel = Mockito.mock(TelemetryChannel.class);
TelemetryConfiguration telemetryConfiguration = new TelemetryConfiguration();
telemetryConfiguration.setInstrumentationKey("UNITTEST-INSTRUMENTATION-KEY");
telemetryConfiguration.setChannel(mockTelemetryChannel);
TelemetryClient telemetryClient = new TelemetryClient(telemetryConfiguration);
botTelemetryClient = new BotTelemetryClientImpl(telemetryClient);
}
@Test
public void nullTelemetryClientThrows() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
new BotTelemetryClientImpl(null);
});
}
@Test
public void nonNullTelemetryClientSucceeds() {
TelemetryClient telemetryClient = new TelemetryClient();
BotTelemetryClient botTelemetryClient = new BotTelemetryClientImpl(telemetryClient);
}
@Test
public void overrideTest() {
TelemetryClient telemetryClient = new TelemetryClient();
MyBotTelemetryClient botTelemetryClient = new MyBotTelemetryClient(telemetryClient);
}
@Test
public void trackAvailabilityTest() {
Map<String, String> properties = new HashMap<>();
Map<String, Double> metrics = new HashMap<>();
properties.put("hello", "value");
metrics.put("metric", 0.6);
botTelemetryClient.trackAvailability(
"test",
OffsetDateTime.now(),
Duration.ofNanos(1000),
"run location",
true,
"message",
properties,
metrics);
Mockito.verify(mockTelemetryChannel, invocations -> {
AvailabilityTelemetry availabilityTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals("test", availabilityTelemetry.getName());
Assert.assertEquals("message", availabilityTelemetry.getData().getMessage());
Assert.assertEquals("value", availabilityTelemetry.getProperties().get("hello"));
Assert.assertEquals(0, Double.compare(0.6, availabilityTelemetry.getMetrics().get("metric")));
}).send(Mockito.any(AvailabilityTelemetry.class));
}
@Test
public void trackEventTest() {
Map<String, String> properties = new HashMap<>();
properties.put("hello", "value");
Map<String, Double> metrics = new HashMap<>();
metrics.put("metric", 0.6);
botTelemetryClient.trackEvent("test", properties, metrics);
Mockito.verify(mockTelemetryChannel, invocations -> {
EventTelemetry eventTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals("test", eventTelemetry.getName());
Assert.assertEquals("value", eventTelemetry.getProperties().get("hello"));
Assert.assertEquals(0, Double.compare(0.6, eventTelemetry.getMetrics().get("metric")));
}).send(Mockito.any(AvailabilityTelemetry.class));
}
@Test
public void trackDependencyTest() {
botTelemetryClient.trackDependency(
"test",
"target",
"dependencyname",
"data",
OffsetDateTime.now(),
Duration.ofNanos(1000),
"result", false);
Mockito.verify(mockTelemetryChannel, invocations -> {
RemoteDependencyTelemetry remoteDependencyTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals("test", remoteDependencyTelemetry.getType());
Assert.assertEquals("target", remoteDependencyTelemetry.getTarget());
Assert.assertEquals("dependencyname", remoteDependencyTelemetry.getName());
Assert.assertEquals("result", remoteDependencyTelemetry.getResultCode());
Assert.assertFalse(remoteDependencyTelemetry.getSuccess());
}).send(Mockito.any(AvailabilityTelemetry.class));
}
@Test
public void trackExceptionTest() {
Exception expectedException = new Exception("test-exception");
Map<String, String> properties = new HashMap<>();
properties.put("foo", "bar");
Map<String, Double> metrics = new HashMap<>();
metrics.put("metric", 0.6);
botTelemetryClient.trackException(expectedException, properties, metrics);
Mockito.verify(mockTelemetryChannel, invocations -> {
ExceptionTelemetry exceptionTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals(expectedException, exceptionTelemetry.getException());
Assert.assertEquals("bar", exceptionTelemetry.getProperties().get("foo"));
Assert.assertEquals(0, Double.compare(0.6, exceptionTelemetry.getMetrics().get("metric")));
}).send(Mockito.any(ExceptionTelemetry.class));
}
@Test
public void trackTraceTest() {
Map<String, String> properties = new HashMap<>();
properties.put("foo", "bar");
botTelemetryClient.trackTrace("hello", Severity.CRITICAL, properties);
Mockito.verify(mockTelemetryChannel, invocations -> {
TraceTelemetry traceTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals("hello", traceTelemetry.getMessage());
Assert.assertEquals(SeverityLevel.Critical, traceTelemetry.getSeverityLevel());
Assert.assertEquals("bar", traceTelemetry.getProperties().get("foo"));
}).send(Mockito.any(TraceTelemetry.class));
}
@Test
public void trackPageViewTest() {
Map<String, String> properties = new HashMap<>();
properties.put("hello", "value");
Map<String, Double> metrics = new HashMap<>();
metrics.put("metric", 0.6);
botTelemetryClient.trackDialogView("test", properties, metrics);
Mockito.verify(mockTelemetryChannel, invocations -> {
PageViewTelemetry pageViewTelemetry = invocations.getAllInvocations().get(0).getArgument(0);
Assert.assertEquals("test", pageViewTelemetry.getName());
Assert.assertEquals("value", pageViewTelemetry.getProperties().get("hello"));
Assert.assertEquals(0, Double.compare(0.6, pageViewTelemetry.getMetrics().get("metric")));
}).send(Mockito.any(PageViewTelemetry.class));
}
}

Просмотреть файл

@ -0,0 +1,87 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import com.microsoft.applicationinsights.TelemetryClient;
import com.microsoft.bot.builder.Severity;
import java.time.Duration;
import java.time.OffsetDateTime;
import java.util.Map;
public class MyBotTelemetryClient extends BotTelemetryClientImpl {
public MyBotTelemetryClient(TelemetryClient telemetryClient) {
super(telemetryClient);
}
@Override
public void trackDependency(
String dependencyTypeName,
String target,
String dependencyName,
String data,
OffsetDateTime startTime,
Duration duration,
String resultCode,
boolean success)
{
super.trackDependency(dependencyName, target, dependencyName, data, startTime, duration, resultCode, success);
}
@Override
public void trackAvailability(
String name,
OffsetDateTime timeStamp,
Duration duration,
String runLocation,
boolean success,
String message,
Map<String, String> properties,
Map<String, Double> metrics)
{
super.trackAvailability(name, timeStamp, duration, runLocation, success, message, properties, metrics);
}
@Override
public void trackEvent(
String eventName,
Map<String, String> properties,
Map<String, Double> metrics)
{
super.trackEvent(eventName, properties, metrics);
}
@Override
public void trackException(
Exception exception,
Map<String, String> properties,
Map<String, Double> metrics)
{
super.trackException(exception, properties, metrics);
}
@Override
public void trackTrace(
String message,
Severity severityLevel,
Map<String, String> properties)
{
super.trackTrace(message, severityLevel, properties);
}
@Override
public void trackPageView(
String name,
Map<String, String> properties,
Map<String, Double> metrics)
{
super.trackPageView(name, properties, metrics);
}
@Override
public void flush()
{
super.flush();
}
}

Просмотреть файл

@ -0,0 +1,132 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mockito;
import com.microsoft.bot.applicationinsights.core.TelemetryInitializerMiddleware;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.TelemetryLoggerMiddleware;
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.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class TelemetryInitializerTests {
@Captor
ArgumentCaptor<String> eventNameCaptor;
@Captor
ArgumentCaptor<Map<String, String>> propertiesCaptor;
@Test
public void telemetryInitializerMiddlewareLogActivitiesEnabled() {
// Arrange
BotTelemetryClient mockTelemetryClient = Mockito.mock(BotTelemetryClient.class);
TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(mockTelemetryClient, false);
TestAdapter testAdapter = new TestAdapter()
.use(new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, true));
// Act
// Default case logging Send/Receive Activities
new TestFlow(testAdapter, turnContext -> {
Activity typingActivity = new Activity(ActivityTypes.TYPING);
typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo());
turnContext.sendActivity(typingActivity).join();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// Empty error
}
turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join();
return CompletableFuture.completedFuture(null);
})
.send("foo")
.assertReply(activity -> {
Assert.assertTrue(activity.isType(ActivityTypes.TYPING));
})
.assertReply("echo:foo")
.send("bar")
.assertReply(activity -> {
Assert.assertTrue(activity.isType(ActivityTypes.TYPING));
})
.assertReply("echo:bar")
.startTest().join();
// Verify
verify(mockTelemetryClient, times(6)).trackEvent(
eventNameCaptor.capture(),
propertiesCaptor.capture()
);
List<String> eventNames = eventNameCaptor.getAllValues();
Assert.assertEquals(6, eventNames.size());
}
@Test
public void telemetryInitializerMiddlewareNotLogActivitiesDisabled() {
// Arrange
BotTelemetryClient mockTelemetryClient = Mockito.mock(BotTelemetryClient.class);
TelemetryLoggerMiddleware telemetryLoggerMiddleware = new TelemetryLoggerMiddleware(mockTelemetryClient, false);
TestAdapter testAdapter = new TestAdapter()
.use(new TelemetryInitializerMiddleware(telemetryLoggerMiddleware, false));
// Act
// Default case logging Send/Receive Activities
new TestFlow(testAdapter, (turnContext) -> {
Activity typingActivity = new Activity(ActivityTypes.TYPING);
typingActivity.setRelatesTo(turnContext.getActivity().getRelatesTo());
turnContext.sendActivity(typingActivity).join();
try {
TimeUnit.MILLISECONDS.sleep(500);
} catch (InterruptedException e) {
// Empty error
}
turnContext.sendActivity(String.format("echo:%s", turnContext.getActivity().getText())).join();
return CompletableFuture.completedFuture(null);
})
.send("foo")
.assertReply(activity -> {
Assert.assertTrue(activity.isType(ActivityTypes.TYPING));
})
.assertReply("echo:foo")
.send("bar")
.assertReply(activity -> {
Assert.assertTrue(activity.isType(ActivityTypes.TYPING));
})
.assertReply("echo:bar")
.startTest().join();
// Verify
verify(mockTelemetryClient, times(0)).trackEvent(
eventNameCaptor.capture(),
propertiesCaptor.capture()
);
List<String> eventNames = eventNameCaptor.getAllValues();
Assert.assertEquals(0, eventNames.size());
}
}

Просмотреть файл

@ -0,0 +1,328 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.microsoft.bot.applicationinsights;
import com.microsoft.bot.builder.AutoSaveStateMiddleware;
import com.microsoft.bot.builder.BotTelemetryClient;
import com.microsoft.bot.builder.StatePropertyAccessor;
import com.microsoft.bot.builder.TurnContext;
import com.microsoft.bot.builder.adapters.TestAdapter;
import com.microsoft.bot.builder.ConversationState;
import com.microsoft.bot.builder.MemoryStorage;
import com.microsoft.bot.builder.adapters.TestFlow;
import com.microsoft.bot.dialogs.Dialog;
import com.microsoft.bot.dialogs.DialogContext;
import com.microsoft.bot.dialogs.DialogInstance;
import com.microsoft.bot.dialogs.DialogReason;
import com.microsoft.bot.dialogs.DialogSet;
import com.microsoft.bot.dialogs.DialogState;
import com.microsoft.bot.dialogs.WaterfallDialog;
import com.microsoft.bot.dialogs.WaterfallStep;
import org.junit.Assert;
import org.junit.Test;
import org.mockito.Mockito;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
public class TelemetryWaterfallTests {
@Test
public void waterfall() {
ConversationState convoState = new ConversationState(new MemoryStorage());
TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("Waterfall", "User1", "Bot"))
.use(new AutoSaveStateMiddleware(convoState));
BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class);
StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState");
DialogSet dialogs = new DialogSet(dialogState);
dialogs.add(new WaterfallDialog("test", newWaterfall()));
dialogs.setTelemetryClient(telemetryClient);
new TestFlow(adapter, turnContext -> {
DialogContext dc = dialogs.createContext(turnContext).join();
dc.continueDialog().join();
if (!turnContext.getResponded()) {
dc.beginDialog("test", null).join();
}
return CompletableFuture.completedFuture(null);
})
.send("hello")
.assertReply("step1")
.send("hello")
.assertReply("step2")
.send("hello")
.assertReply("step3")
.startTest()
.join();
// C#'s trackEvent method of BotTelemetryClient has nullable parameters,
// therefore it always calls the same method.
// On the other hand, Java's BotTelemetryClient overloads the trackEvent method,
// so instead of calling the same method, it calls a method with less parameters.
// In this particular test, WaterfallDialog's beginDialog calls the method with only two parameters
Mockito.verify(telemetryClient, Mockito.times(4)).trackEvent(
Mockito.anyString(),
Mockito.anyMap()
);
System.out.printf("Complete");
}
@Test
public void waterfallWithCallback() {
ConversationState convoState = new ConversationState(new MemoryStorage());
TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("WaterfallWithCallback", "User1", "Bot"))
.use(new AutoSaveStateMiddleware(convoState));
BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class);
StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState");
DialogSet dialogs = new DialogSet(dialogState);
WaterfallDialog waterfallDialog = new WaterfallDialog("test", newWaterfall());
dialogs.add(waterfallDialog);
dialogs.setTelemetryClient(telemetryClient);
new TestFlow(adapter, turnContext -> {
DialogContext dc = dialogs.createContext(turnContext).join();
dc.continueDialog().join();
if (!turnContext.getResponded()) {
dc.beginDialog("test", null).join();
}
return CompletableFuture.completedFuture(null);
})
.send("hello")
.assertReply("step1")
.send("hello")
.assertReply("step2")
.send("hello")
.assertReply("step3")
.startTest()
.join();
// C#'s trackEvent method of BotTelemetryClient has nullable parameters,
// therefore it always calls the same method.
// On the other hand, Java's BotTelemetryClient overloads the trackEvent method,
// so instead of calling the same method, it calls a method with less parameters.
// In this particular test, WaterfallDialog's beginDialog calls the method with only two parameters
Mockito.verify(telemetryClient, Mockito.times(4)).trackEvent(
Mockito.anyString(),
Mockito.anyMap()
);
}
@Test
public void waterfallWithActionsNull() {
Assert.assertThrows(IllegalArgumentException.class, () -> {
BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class);
WaterfallDialog waterfall = new WaterfallDialog("test", null);
waterfall.setTelemetryClient(telemetryClient);
waterfall.addStep(null);
});
}
@Test
public void ensureEndDialogCalled() {
ConversationState convoState = new ConversationState(new MemoryStorage());
TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("EnsureEndDialogCalled", "User1", "Bot"))
.use(new AutoSaveStateMiddleware(convoState));
StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState");
DialogSet dialogs = new DialogSet(dialogState);
HashMap<String, Map<String, String>> saved_properties = new HashMap<>();
final int[] counter = {0};
// Set up the client to save all logged property names and associated properties (in "saved_properties").
BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class);
Mockito.doAnswer(invocation -> {
String eventName = invocation.getArgument(0);
Map<String, String> properties = invocation.getArgument(1);
StringBuilder sb = new StringBuilder(eventName).append("_").append(counter[0]++);
saved_properties.put(sb.toString(), properties);
return null;
}).when(telemetryClient).trackEvent(Mockito.anyString(), Mockito.anyMap());
MyWaterfallDialog waterfallDialog = new MyWaterfallDialog("test", newWaterfall());
dialogs.add(waterfallDialog);
dialogs.setTelemetryClient(telemetryClient);
new TestFlow(adapter, turnContext -> {
DialogContext dc = dialogs.createContext(turnContext).join();
dc.continueDialog().join();
if (!turnContext.getResponded()) {
dc.beginDialog("test", null).join();
}
return CompletableFuture.completedFuture(null);
})
.send("hello")
.assertReply("step1")
.send("hello")
.assertReply("step2")
.send("hello")
.assertReply("step3")
.send("hello")
.assertReply("step1")
.startTest()
.join();
Mockito.verify(telemetryClient, Mockito.times(7)).trackEvent(
Mockito.anyString(),
Mockito.anyMap()
);
// Verify:
// Event name is "WaterfallComplete"
// Event occurs on the 4th event logged
// Event contains DialogId
// Event DialogId is set correctly.
Assert.assertTrue(saved_properties.get("WaterfallComplete_4").containsKey("DialogId"));
Assert.assertEquals("test", saved_properties.get("WaterfallComplete_4").get("DialogId"));
Assert.assertTrue(saved_properties.get("WaterfallComplete_4").containsKey("InstanceId"));
Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("InstanceId"));
// Verify naming on lambda's is "StepXofY"
Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("StepName"));
Assert.assertEquals("Step1of3", saved_properties.get("WaterfallStep_1").get("StepName"));
Assert.assertTrue(saved_properties.get("WaterfallStep_1").containsKey("InstanceId"));
Assert.assertTrue(waterfallDialog.getEndDialogCalled());
}
@Test
public void ensureCancelDialogCalled() {
ConversationState convoState = new ConversationState(new MemoryStorage());
TestAdapter adapter = new TestAdapter(TestAdapter.createConversationReference("EnsureCancelDialogCalled", "User1", "Bot"))
.use(new AutoSaveStateMiddleware(convoState));
StatePropertyAccessor<DialogState> dialogState = convoState.createProperty("dialogState");
DialogSet dialogs = new DialogSet(dialogState);
HashMap<String, Map<String, String>> saved_properties = new HashMap<>();
final int[] counter = {0};
// Set up the client to save all logged property names and associated properties (in "saved_properties").
BotTelemetryClient telemetryClient = Mockito.mock(BotTelemetryClient.class);
Mockito.doAnswer(invocation -> {
String eventName = invocation.getArgument(0);
Map<String, String> properties = invocation.getArgument(1);
StringBuilder sb = new StringBuilder(eventName).append("_").append(counter[0]++);
saved_properties.put(sb.toString(), properties);
return null;
}).when(telemetryClient).trackEvent(Mockito.anyString(), Mockito.anyMap());
List<WaterfallStep> steps = new ArrayList<>();
steps.add(step -> {
step.getContext().sendActivity("step1").join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
steps.add(step -> {
step.getContext().sendActivity("step2").join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
steps.add(step -> {
step.cancelAllDialogs().join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
MyWaterfallDialog waterfallDialog = new MyWaterfallDialog("test", steps);
dialogs.add(waterfallDialog);
dialogs.setTelemetryClient(telemetryClient);
new TestFlow(adapter, turnContext -> {
DialogContext dc = dialogs.createContext(turnContext).join();
dc.continueDialog().join();
if (!turnContext.getResponded()) {
dc.beginDialog("test", null).join();
}
return CompletableFuture.completedFuture(null);
})
.send("hello")
.assertReply("step1")
.send("hello")
.assertReply("step2")
.send("hello")
.assertReply("step1")
.startTest()
.join();
Mockito.verify(telemetryClient, Mockito.times(7)).trackEvent(
Mockito.anyString(),
Mockito.anyMap()
);
// Verify:
// Event name is "WaterfallCancel"
// Event occurs on the 4th event logged
// Event contains DialogId
// Event DialogId is set correctly.
Assert.assertTrue(saved_properties.get("WaterfallStart_0").containsKey("DialogId"));
Assert.assertTrue(saved_properties.get("WaterfallStart_0").containsKey("InstanceId"));
Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("DialogId"));
Assert.assertEquals("test", saved_properties.get("WaterfallCancel_4").get("DialogId"));
Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("StepName"));
Assert.assertTrue(saved_properties.get("WaterfallCancel_4").containsKey("InstanceId"));
// Event contains "StepName"
// Event naming on lambda's is "StepXofY"
Assert.assertEquals("Step3of3", saved_properties.get("WaterfallCancel_4").get("StepName"));
Assert.assertTrue(waterfallDialog.getCancelDialogCalled());
Assert.assertFalse(waterfallDialog.getEndDialogCalled());
}
private static List<WaterfallStep> newWaterfall() {
List<WaterfallStep> waterfall = new ArrayList<>();
waterfall.add(step -> {
step.getContext().sendActivity("step1").join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
waterfall.add(step -> {
step.getContext().sendActivity("step2").join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
waterfall.add(step -> {
step.getContext().sendActivity("step3").join();
return CompletableFuture.completedFuture(Dialog.END_OF_TURN);
});
return waterfall;
}
private class MyWaterfallDialog extends WaterfallDialog {
private Boolean endDialogCalled = false;
private Boolean cancelDialogCalled = false;
public MyWaterfallDialog(String id, List<WaterfallStep> actions) {
super(id, actions);
}
@Override
public CompletableFuture<Void> endDialog(TurnContext turnContext, DialogInstance instance, DialogReason reason) {
if (reason == DialogReason.END_CALLED) {
endDialogCalled = true;
} else if (reason == DialogReason.CANCEL_CALLED) {
cancelDialogCalled = true;
}
return super.endDialog(turnContext, instance, reason);
}
public Boolean getEndDialogCalled() {
return endDialogCalled;
}
public Boolean getCancelDialogCalled() {
return cancelDialogCalled;
}
}
}

Просмотреть файл

@ -267,8 +267,8 @@ public class WaterfallDialog extends Dialog {
String stepName = steps.get(index).getClass().getSimpleName();
// Default stepname for lambdas
if (StringUtils.isAllBlank(stepName) || stepName.contains("<")) {
stepName = String.format("Step%0of%1", index + 1, steps.size());
if (StringUtils.isAllBlank(stepName) || stepName.contains("$Lambda$")) {
stepName = String.format("Step%dof%d", index + 1, steps.size());
}
return stepName;