diff --git a/.github/workflows/IJ.yml b/.github/workflows/IJ.yml index 4e86dab6..31aac29c 100644 --- a/.github/workflows/IJ.yml +++ b/.github/workflows/IJ.yml @@ -29,6 +29,12 @@ jobs: jvm: 17 - IJ: IC-2022.3 jvm: 17 + - IJ: IC-2023.1 + jvm: 17 + - IJ: IC-2023.2 + jvm: 17 + - IJ: IC-2023.3 + jvm: 17 steps: - uses: actions/checkout@v2 diff --git a/build.gradle b/build.gradle index 2f0557db..aa87fb05 100644 --- a/build.gradle +++ b/build.gradle @@ -39,7 +39,7 @@ dependencies { testImplementation( "org.junit.jupiter:junit-jupiter:5.9.1", "org.junit.platform:junit-platform-launcher:1.9.1", - "org.assertj:assertj-core:3.22.0", + "org.assertj:assertj-core:3.24.2", "org.mockito:mockito-inline:4.5.1" ) testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine' diff --git a/gradle.properties b/gradle.properties index fa7e4eb1..48fb75fd 100644 --- a/gradle.properties +++ b/gradle.properties @@ -2,8 +2,8 @@ ideaVersion = IC-2020.3 # build number ranges # https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started/build_number_ranges.html sinceIdeaBuild=203 -projectVersion=1.0.1-SNAPSHOT -intellijPluginVersion=1.13.3 +projectVersion=1.1.0-SNAPSHOT +intellijPluginVersion=1.16.1 jetBrainsToken=invalid jetBrainsChannel=stable nexusUser=invalid diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 943f0cbf..d64cd491 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2b22d057..e6aba251 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.6-all.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-all.zip networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 65dcd68d..1aa94a42 100755 --- a/gradlew +++ b/gradlew @@ -83,10 +83,8 @@ done # This is normally unused # shellcheck disable=SC2034 APP_BASE_NAME=${0##*/} -APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. MAX_FD=maximum @@ -133,10 +131,13 @@ location of your Java installation." fi else JAVACMD=java - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. @@ -144,7 +145,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then case $MAX_FD in #( max*) # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 MAX_FD=$( ulimit -H -n ) || warn "Could not query maximum file descriptor limit" esac @@ -152,7 +153,7 @@ if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then '' | soft) :;; #( *) # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. - # shellcheck disable=SC3045 + # shellcheck disable=SC2039,SC3045 ulimit -n "$MAX_FD" || warn "Could not set maximum file descriptor limit to $MAX_FD" esac @@ -197,11 +198,15 @@ if "$cygwin" || "$msys" ; then done fi -# Collect all arguments for the java command; -# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of -# shell script including quotes and variable substitutions, so put them in -# double quotes to make sure that they get re-expanded; and -# * put everything else in single quotes, so that it's not re-expanded. + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. set -- \ "-Dorg.gradle.appname=$APP_BASE_NAME" \ diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/IMessageBroker.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/IMessageBroker.java index 77031532..6d3523fb 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/IMessageBroker.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/IMessageBroker.java @@ -10,9 +10,13 @@ ******************************************************************************/ package com.redhat.devtools.intellij.telemetry.core; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent; +import com.redhat.devtools.intellij.telemetry.core.service.Event; public interface IMessageBroker { - void send(TelemetryEvent event); + void send(Event event); void dispose(); + + interface IMessageBrokerFactory { + IMessageBroker create(boolean isDebug, ClassLoader classLoader); + } } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/ITelemetryService.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/IService.java similarity index 74% rename from src/main/java/com/redhat/devtools/intellij/telemetry/core/ITelemetryService.java rename to src/main/java/com/redhat/devtools/intellij/telemetry/core/IService.java index c5d8961f..fe8cb531 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/ITelemetryService.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/IService.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Red Hat, Inc. + * Copyright (c) 2023 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, @@ -10,8 +10,8 @@ ******************************************************************************/ package com.redhat.devtools.intellij.telemetry.core; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent; +import com.redhat.devtools.intellij.telemetry.core.service.Event; -public interface ITelemetryService { - void send(TelemetryEvent event); +public interface IService { + void send(Event event); } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Environment.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Environment.java index 1a065f8c..2e797e90 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Environment.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Environment.java @@ -117,7 +117,7 @@ private void ensureCountry() { } } - class Buildable { + public class Buildable { public Environment build() { ensureIDE(); ensurePlatform(); diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryEvent.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Event.java similarity index 84% rename from src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryEvent.java rename to src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Event.java index 76dad9f0..7b8ca510 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryEvent.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Event.java @@ -1,5 +1,5 @@ /******************************************************************************* - * Copyright (c) 2021 Red Hat, Inc. + * Copyright (c) 2023 Red Hat, Inc. * Distributed under license by Red Hat, Inc. All rights reserved. * This program is made available under the terms of the * Eclipse Public License v2.0 which accompanies this distribution, @@ -13,7 +13,7 @@ import java.util.HashMap; import java.util.Map; -public class TelemetryEvent { +public class Event { public enum Type { USER, ACTION, STARTUP, SHUTDOWN @@ -23,11 +23,11 @@ public enum Type { private final String name; private final Map properties; - public TelemetryEvent(Type type, String name) { + public Event(Type type, String name) { this(type, name, new HashMap<>()); } - public TelemetryEvent(Type type, String name, Map properties) { + public Event(Type type, String name, Map properties) { this.type = type; this.name = name; this.properties = properties; diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackService.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackService.java new file mode 100644 index 00000000..43d0db3b --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackService.java @@ -0,0 +1,35 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.telemetry.core.service; + +import com.intellij.openapi.diagnostic.Logger; +import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; +import com.redhat.devtools.intellij.telemetry.core.IService; + +public class FeedbackService implements IService { + + private static final Logger LOGGER = Logger.getInstance(FeedbackService.class); + + protected final IMessageBroker broker; + + public FeedbackService(final IMessageBroker broker) { + this.broker = broker; + } + + @Override + public void send(Event event) { + broker.send(event); + } + + public void dispose() { + broker.dispose(); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackServiceFactory.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackServiceFactory.java new file mode 100644 index 00000000..b040e1af --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/FeedbackServiceFactory.java @@ -0,0 +1,25 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.telemetry.core.service; + +import com.intellij.openapi.project.DumbAware; +import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; +import com.redhat.devtools.intellij.telemetry.core.IService; +import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; +import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker; +import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentConfiguration; + +public class FeedbackServiceFactory implements DumbAware { + + public IService create(IMessageBroker broker) { + return new FeedbackService(broker); + } +} diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Message.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Message.java new file mode 100644 index 00000000..4f2cff42 --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/Message.java @@ -0,0 +1,122 @@ +/******************************************************************************* + * Copyright (c) 2023 Red Hat, Inc. + * Distributed under license by Red Hat, Inc. All rights reserved. + * This program is made available under the terms of the + * Eclipse Public License v2.0 which accompanies this distribution, + * and is available at http://www.eclipse.org/legal/epl-v20.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + ******************************************************************************/ +package com.redhat.devtools.intellij.telemetry.core.service; + +import com.intellij.openapi.diagnostic.Logger; +import com.redhat.devtools.intellij.telemetry.core.IService; + +import java.util.HashMap; +import java.util.Map; + +import static com.redhat.devtools.intellij.telemetry.core.util.AnonymizeUtils.anonymize; + +abstract class Message> { + + private static final Logger LOGGER = Logger.getInstance(Message.class); + + static final String PROP_RESULT = "result"; + + public static final String RESULT_SUCCESS = "success"; + + public static final String RESULT_ABORTED = "aborted"; + + static final String PROP_ERROR = "error"; + + private final Event.Type type; + private final Map properties = new HashMap<>(); + private final String name; + private final IService service; + + protected Message(Event.Type type, String name, IService service) { + this.name = name; + this.type = type; + this.service = service; + } + + String getName() { + return name; + } + + Event.Type getType() { + return type; + } + + String getError() { + return getProperty(PROP_ERROR); + } + + public T error(Exception exception) { + if (exception == null) { + return (T) this; + } + return error(exception.getMessage()); + } + + public T error(String message) { + property(PROP_ERROR, anonymize(message)); + return clearResult(); + } + + protected T clearError() { + properties().remove(PROP_ERROR); + return (T) this; + } + + String getResult() { + return getProperty(PROP_RESULT); + } + + public T result(String result) { + property(PROP_RESULT, result); + return clearError(); + } + + public T success() { + return result(RESULT_SUCCESS); + } + + public T aborted() { + return result(RESULT_ABORTED); + } + + protected T clearResult() { + properties().remove(PROP_RESULT); + return (T) this; + } + + public T property(String key, String value) { + if (key == null + || value == null) { + LOGGER.warn("Ignored property with key: " + key + " value: " + value); + } else { + properties.put(key, value); + } + return (T) this; + } + + String getProperty(String key) { + return properties.get(key); + } + + Map properties() { + return properties; + } + + protected boolean hasProperty(String key) { + return properties.containsKey(key); + } + + public Event send() { + Event event = new Event(type, name, new HashMap<>(properties)); + service.send(event); + return event; + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilder.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilder.java index 71896cf8..34048a08 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilder.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilder.java @@ -10,63 +10,78 @@ ******************************************************************************/ package com.redhat.devtools.intellij.telemetry.core.service; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.ACTION; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.SHUTDOWN; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.STARTUP; -import static com.redhat.devtools.intellij.telemetry.core.util.AnonymizeUtils.anonymize; -import static com.redhat.devtools.intellij.telemetry.core.util.TimeUtils.toLocalTime; - -import java.time.Duration; -import java.time.LocalDateTime; -import java.util.HashMap; -import java.util.Map; - import com.intellij.ide.AppLifecycleListener; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.messages.MessageBusConnection; -import com.redhat.devtools.intellij.telemetry.core.ITelemetryService; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type; +import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; +import com.redhat.devtools.intellij.telemetry.core.IService; +import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; +import com.redhat.devtools.intellij.telemetry.core.service.Event.Type; +import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBrokerFactory; +import com.redhat.devtools.intellij.telemetry.core.util.Lazy; import com.redhat.devtools.intellij.telemetry.core.util.TimeUtils; +import java.time.Duration; +import java.time.LocalDateTime; +import java.util.function.Supplier; + +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.ACTION; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.SHUTDOWN; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.STARTUP; +import static com.redhat.devtools.intellij.telemetry.core.util.TimeUtils.toLocalTime; + public class TelemetryMessageBuilder { private static final Logger LOGGER = Logger.getInstance(TelemetryMessageBuilder.class); - private final ServiceFacade service; + private final IService telemetryFacade; + private final IService feedbackFacade; public TelemetryMessageBuilder(ClassLoader classLoader) { - this(new ServiceFacade(classLoader)); + this(new SegmentBrokerFactory().create(TelemetryConfiguration.getInstance().isDebug(), classLoader)); + } + + TelemetryMessageBuilder(IMessageBroker messageBroker) { + this( + new TelemetryServiceFacade(TelemetryConfiguration.getInstance(), messageBroker), + new FeedbackServiceFacade(messageBroker) + ); } - TelemetryMessageBuilder(ServiceFacade serviceFacade) { - this.service = serviceFacade; + TelemetryMessageBuilder(IService telemetryFacade, IService feedbackFacade) { + this.telemetryFacade = telemetryFacade; + this.feedbackFacade = feedbackFacade; } public ActionMessage action(String name) { - return new ActionMessage(name, service); + return new ActionMessage(name, telemetryFacade); + } + + public FeedbackMessage feedback(String name) { + return new FeedbackMessage(name, feedbackFacade); } - static class StartupMessage extends Message { + static class StartupMessage extends TelemetryMessage { - private StartupMessage(ServiceFacade service) { + private StartupMessage(IService service) { super(STARTUP, "startup", service); } } - static class ShutdownMessage extends Message { + static class ShutdownMessage extends TelemetryMessage { private static final String PROP_SESSION_DURATION = "session_duration"; - private ShutdownMessage(ServiceFacade service) { + private ShutdownMessage(IService service) { this(toLocalTime(ApplicationManager.getApplication().getStartTime()), service); } - private ShutdownMessage(LocalDateTime startup, ServiceFacade service) { + private ShutdownMessage(LocalDateTime startup, IService service) { this(startup, LocalDateTime.now(), service); } - ShutdownMessage(LocalDateTime startup, LocalDateTime shutdown, ServiceFacade service) { + ShutdownMessage(LocalDateTime startup, LocalDateTime shutdown, IService service) { super(SHUTDOWN, "shutdown", service); sessionDuration(startup, shutdown); } @@ -84,17 +99,13 @@ String getSessionDuration() { } } - public static class ActionMessage extends Message { + public static class ActionMessage extends TelemetryMessage { static final String PROP_DURATION = "duration"; - static final String PROP_ERROR = "error"; - static final String PROP_RESULT = "result"; - - public static final String RESULT_SUCCESS = "success"; private LocalDateTime started; - private ActionMessage(String name, ServiceFacade service) { + private ActionMessage(String name, IService service) { super(ACTION, name, service); started(); } @@ -126,47 +137,8 @@ String getDuration() { return getProperty(PROP_DURATION); } - public ActionMessage success() { - return result(RESULT_SUCCESS); - } - - public ActionMessage result(String result) { - property(PROP_RESULT, result); - return clearError(); - } - - protected ActionMessage clearResult() { - properties().remove(PROP_RESULT); - return this; - } - - String getResult() { - return getProperty(PROP_RESULT); - } - - public ActionMessage error(Exception exception) { - if (exception == null) { - return this; - } - return error(exception.getMessage()); - } - - public ActionMessage error(String message) { - property(PROP_ERROR, anonymize(message)); - return clearResult(); - } - - protected ActionMessage clearError() { - properties().remove(PROP_ERROR); - return this; - } - - String getError() { - return getProperty(PROP_ERROR); - } - @Override - public TelemetryEvent send() { + public Event send() { ensureFinished(); ensureResultOrError(); return super.send(); @@ -186,76 +158,31 @@ private void ensureResultOrError() { } } - private abstract static class Message> { - - private final Type type; - private final Map properties = new HashMap<>(); - private final String name; - private final ServiceFacade service; - - private Message(Type type, String name, ServiceFacade service) { - this.name = name; - this.type = type; - this.service = service; - } - - String getName() { - return name; - } - - Type getType() { - return type; - } - - public T property(String key, String value) { - if (key == null - || value == null) { - LOGGER.warn("Ignored property with key: " + key + " value: " + value); - } else { - properties.put(key, value); - } - return (T) this; - } - - String getProperty(String key) { - return properties.get(key); - } - - Map properties() { - return properties; - } - - protected boolean hasProperty(String key) { - return properties.containsKey(key); - } - - public TelemetryEvent send() { - TelemetryEvent event = new TelemetryEvent(type, name, new HashMap<>(properties)); - service.send(event); - return event; + private static class TelemetryMessage> extends Message { + protected TelemetryMessage(Type type, String name, IService service) { + super(type, name, service); } } - static class ServiceFacade { - private final ClassLoader classLoader; - private ITelemetryService service = null; + static class TelemetryServiceFacade extends Lazy implements IService { + + private final MessageBusConnection messageBusConnection; - protected ServiceFacade(final ClassLoader classLoader) { - this.classLoader = classLoader; + protected TelemetryServiceFacade(final TelemetryConfiguration configuration, IMessageBroker broker) { + this(() -> ApplicationManager.getApplication().getService(TelemetryServiceFactory.class).create(configuration, broker), + ApplicationManager.getApplication().getMessageBus().connect() + ); } - public void send(final TelemetryEvent event) { - if (service == null) { - this.service = createService(classLoader); - sendStartup(); - onShutdown(); - } - service.send(event); + protected TelemetryServiceFacade(final Supplier supplier, MessageBusConnection connection) { + super(supplier); + this.messageBusConnection = connection; } - protected ITelemetryService createService(ClassLoader classLoader) { - TelemetryServiceFactory factory = ApplicationManager.getApplication().getService(TelemetryServiceFactory.class); - return factory.create(classLoader); + @Override + protected void onCreated(IService service) { + sendStartup(); + onShutdown(); } private void sendStartup() { @@ -263,8 +190,7 @@ private void sendStartup() { } private void onShutdown() { - MessageBusConnection connection = createMessageBusConnection(); - connection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() { + messageBusConnection.subscribe(AppLifecycleListener.TOPIC, new AppLifecycleListener() { @Override public void appWillBeClosed(boolean isRestart) { sendShutdown(); @@ -273,12 +199,36 @@ public void appWillBeClosed(boolean isRestart) { } protected void sendShutdown() { - new ShutdownMessage(ServiceFacade.this).send(); + new ShutdownMessage(TelemetryServiceFacade.this).send(); + } + + @Override + public void send(Event event) { + get().send(event); + } + } + + static class FeedbackServiceFacade extends Lazy implements IService { + + protected FeedbackServiceFacade(final IMessageBroker broker) { + this(() -> ApplicationManager.getApplication().getService(FeedbackServiceFactory.class).create(broker)); } - protected MessageBusConnection createMessageBusConnection() { - return ApplicationManager.getApplication().getMessageBus().connect(); + protected FeedbackServiceFacade(final Supplier supplier) { + super(supplier); } + @Override + public void send(Event event) { + get().send(event); + } + } + + public static class FeedbackMessage extends Message{ + + FeedbackMessage(String name, IService service) { + super(ACTION, name, service); + } } + } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryService.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryService.java index 428d5ef0..b11d4d4e 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryService.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryService.java @@ -18,15 +18,15 @@ import com.intellij.openapi.diagnostic.Logger; import com.intellij.util.messages.MessageBusConnection; import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; -import com.redhat.devtools.intellij.telemetry.core.ITelemetryService; +import com.redhat.devtools.intellij.telemetry.core.IService; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration.ConfigurationChangedListener; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration.Mode; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type; +import com.redhat.devtools.intellij.telemetry.core.service.Event.Type; import com.redhat.devtools.intellij.telemetry.core.util.CircularBuffer; import com.redhat.devtools.intellij.telemetry.ui.TelemetryNotifications; -public class TelemetryService implements ITelemetryService { +public class TelemetryService implements IService { private static final Logger LOGGER = Logger.getInstance(TelemetryService.class); @@ -36,16 +36,17 @@ public class TelemetryService implements ITelemetryService { private final TelemetryConfiguration configuration; protected final IMessageBroker broker; private final AtomicBoolean userQueried = new AtomicBoolean(false); - private final CircularBuffer onHold = new CircularBuffer<>(BUFFER_SIZE); + private final CircularBuffer onHold = new CircularBuffer<>(BUFFER_SIZE); public TelemetryService(final TelemetryConfiguration configuration, final IMessageBroker broker) { this(configuration, broker, ApplicationManager.getApplication().getMessageBus().connect(), new TelemetryNotifications()); } - TelemetryService(final TelemetryConfiguration configuration, - final IMessageBroker broker, - final MessageBusConnection connection, - final TelemetryNotifications notifications) { + TelemetryService( + final TelemetryConfiguration configuration, + final IMessageBroker broker, + final MessageBusConnection connection, + final TelemetryNotifications notifications) { this.configuration = configuration; this.broker = broker; this.notifications = notifications; @@ -62,14 +63,14 @@ private void onConfigurationChanged(MessageBusConnection connection) { } @Override - public void send(TelemetryEvent event) { + public void send(Event event) { sendUserInfo(); doSend(event); queryUserConsent(); } private void sendUserInfo() { - doSend(new TelemetryEvent( + doSend(new Event( Type.USER, "Anonymous ID: " + UserId.INSTANCE.get())); } @@ -81,7 +82,7 @@ private void queryUserConsent() { } } - private void doSend(TelemetryEvent event) { + private void doSend(Event event) { if (isEnabled()) { flushOnHold(); broker.send(event); diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceFactory.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceFactory.java index 7754245c..cc22e0fe 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceFactory.java @@ -13,32 +13,10 @@ import com.intellij.openapi.project.DumbAware; import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; -import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker; -import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentConfiguration; public class TelemetryServiceFactory implements DumbAware { - private final IDE ide = new IDE.Factory() - .create() - .setJavaVersion(); - - public TelemetryService create(ClassLoader classLoader) { - Environment environment = new Environment.Builder() - .ide(ide) - .plugin(classLoader) - .build(); - TelemetryConfiguration configuration = TelemetryConfiguration.getInstance(); - IMessageBroker broker = createSegmentBroker(configuration.isDebug(), classLoader, environment); + public TelemetryService create(TelemetryConfiguration configuration, IMessageBroker broker) { return new TelemetryService(configuration, broker); } - - private IMessageBroker createSegmentBroker(boolean isDebug, ClassLoader classLoader, Environment environment) { - SegmentConfiguration brokerConfiguration = new SegmentConfiguration(classLoader); - return new SegmentBroker( - isDebug, - UserId.INSTANCE.get(), - environment, - brokerConfiguration); - } - } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/ISegmentConfiguration.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/ISegmentConfiguration.java index 5ca0178d..130cdba6 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/ISegmentConfiguration.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/ISegmentConfiguration.java @@ -13,4 +13,13 @@ public interface ISegmentConfiguration { String getNormalWriteKey(); String getDebugWriteKey(); + + default String getWriteKey(boolean isDebug) { + if (isDebug) { + return getDebugWriteKey(); + } else { + return getNormalWriteKey(); + } + } + } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBroker.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBroker.java index 2a23bc3c..bceff031 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBroker.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBroker.java @@ -14,7 +14,7 @@ import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; import com.redhat.devtools.intellij.telemetry.core.service.Application; import com.redhat.devtools.intellij.telemetry.core.service.Environment; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent; +import com.redhat.devtools.intellij.telemetry.core.service.Event; import com.redhat.devtools.intellij.telemetry.core.util.Lazy; import com.redhat.devtools.intellij.telemetry.core.util.MapBuilder; import com.segment.analytics.Analytics; @@ -23,6 +23,7 @@ import com.segment.analytics.messages.PageMessage; import com.segment.analytics.messages.TrackMessage; +import java.util.Collections; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.function.Function; @@ -53,25 +54,25 @@ public class SegmentBroker implements IMessageBroker { enum SegmentType { IDENTIFY { - public MessageBuilder toMessage(TelemetryEvent event, Map context, SegmentBroker broker) { + public MessageBuilder toMessage(Event event, Map context, SegmentBroker broker) { return broker.toMessage(IdentifyMessage.builder(), event, context); } }, TRACK { - public MessageBuilder toMessage(TelemetryEvent event, Map context, SegmentBroker broker) { + public MessageBuilder toMessage(Event event, Map context, SegmentBroker broker) { return broker.toMessage(TrackMessage.builder(event.getName()), event, context); } }, PAGE { - public MessageBuilder toMessage(TelemetryEvent event, Map context, SegmentBroker broker) { + public MessageBuilder toMessage(Event event, Map context, SegmentBroker broker) { return broker.toMessage(PageMessage.builder(event.getName()), event, context); } }; - public abstract MessageBuilder toMessage(TelemetryEvent event, Map context, SegmentBroker broker); + public abstract MessageBuilder toMessage(Event event, Map context, SegmentBroker broker); - public static SegmentType valueOf(TelemetryEvent.Type eventType) { + public static SegmentType valueOf(Event.Type eventType) { switch (eventType) { case USER: return IDENTIFY; @@ -87,21 +88,28 @@ public static SegmentType valueOf(TelemetryEvent.Type eventType) { private final String userId; private final IdentifyTraitsPersistence identifyTraitsPersistence; private final Environment environment; - private Lazy analytics; + private final Lazy analytics; public SegmentBroker(boolean isDebug, String userId, Environment environment, ISegmentConfiguration configuration) { this(isDebug, userId, IdentifyTraitsPersistence.INSTANCE, environment, configuration, new AnalyticsFactory()); } - public SegmentBroker(boolean isDebug, String userId, IdentifyTraitsPersistence identifyTraitsPersistence, Environment environment, ISegmentConfiguration configuration, Function analyticsFactory) { + public SegmentBroker( + boolean isDebug, + String userId, + IdentifyTraitsPersistence identifyTraitsPersistence, + Environment environment, + ISegmentConfiguration configuration, + Function analyticsFactory + ) { this.userId = userId; this.identifyTraitsPersistence = identifyTraitsPersistence; this.environment = environment; - this.analytics = new Lazy<>(() -> analyticsFactory.apply(getWriteKey(isDebug, configuration))); + this.analytics = new Lazy<>(() -> analyticsFactory.apply(configuration.getWriteKey(isDebug))); } @Override - public void send(TelemetryEvent event) { + public void send(Event event) { try { if (analytics.get() == null) { LOGGER.warn("Could not send " + event.getType() + " event '" + event.getName() + "': no analytics instance present."); @@ -121,21 +129,31 @@ public void send(TelemetryEvent event) { } } - private MessageBuilder toMessage(IdentifyMessage.Builder builder, TelemetryEvent event, Map context) { - IdentifyTraits identifyTraits = new IdentifyTraits( - environment.getLocale(), - environment.getTimezone(), - environment.getPlatform().getName(), - environment.getPlatform().getVersion(), - environment.getPlatform().getDistribution()); - if (!haveChanged(identifyTraits, identifyTraitsPersistence)) { - LOGGER.debug("Skipping identify message: already sent." + identifyTraits); + private MessageBuilder toMessage(IdentifyMessage.Builder builder, Event event, Map context) { + if (!addTraits(builder, event)) { return null; } return builder .userId(userId) - .traits(addIdentifyTraits(identifyTraits, event.getProperties())) - .context(context); + .context(context == null ? Collections.emptyMap() : context); + + } + + private boolean addTraits(IdentifyMessage.Builder builder, Event event) { + if (environment != null) { + IdentifyTraits identifyTraits = new IdentifyTraits( + environment.getLocale(), + environment.getTimezone(), + environment.getPlatform().getName(), + environment.getPlatform().getVersion(), + environment.getPlatform().getDistribution()); + if (!haveChanged(identifyTraits, identifyTraitsPersistence)) { + LOGGER.debug("Skipping identify message: already sent." + identifyTraits); + return false; + } + builder.traits(addIdentifyTraits(identifyTraits, event.getProperties())); + } + return true; } /** @@ -164,7 +182,7 @@ private synchronized boolean haveChanged(IdentifyTraits identifyTraits, Identify return properties; } - private MessageBuilder toMessage(TrackMessage.Builder builder, TelemetryEvent event, Map context) { + private MessageBuilder toMessage(TrackMessage.Builder builder, Event event, Map context) { return builder .userId(userId) .properties(addTrackProperties(event.getProperties())) @@ -172,17 +190,19 @@ private MessageBuilder toMessage(TrackMessage.Builder builder, TelemetryEvent ev } private Map addTrackProperties(final Map properties) { - Application application = environment.getIde(); - putIfNotNull(PROP_APP_NAME, application.getName(), properties); - putIfNotNull(PROP_APP_VERSION, application.getVersion(), properties); - application.getProperties().forEach( - appProperty -> putIfNotNull(appProperty.getKey(), String.valueOf(appProperty.getValue()), properties)); - putIfNotNull(PROP_EXTENSION_NAME, environment.getPlugin().getName(), properties); - putIfNotNull(PROP_EXTENSION_VERSION, environment.getPlugin().getVersion(), properties); + if (environment != null) { + Application application = environment.getIde(); + putIfNotNull(PROP_APP_NAME, application.getName(), properties); + putIfNotNull(PROP_APP_VERSION, application.getVersion(), properties); + application.getProperties().forEach( + appProperty -> putIfNotNull(appProperty.getKey(), String.valueOf(appProperty.getValue()), properties)); + putIfNotNull(PROP_EXTENSION_NAME, environment.getPlugin().getName(), properties); + putIfNotNull(PROP_EXTENSION_VERSION, environment.getPlugin().getVersion(), properties); + } return properties; } - private MessageBuilder toMessage(PageMessage.Builder builder, TelemetryEvent event, Map context) { + private MessageBuilder toMessage(PageMessage.Builder builder, Event event, Map context) { return builder .userId(userId) .properties(event.getProperties()) @@ -199,6 +219,9 @@ private void putIfNotNull(String key, String value, Map properti } private Map createContext(Environment environment) { + if (environment == null) { + return Collections.emptyMap(); + } return new MapBuilder() .mapPair(PROP_APP) .pair(PROP_NAME, environment.getIde().getName()) @@ -218,20 +241,12 @@ private Map createContext(Environment environment) { .build(); } + @Override public void dispose() { analytics.get().flush(); analytics.get().shutdown(); } - - private String getWriteKey(boolean isDebug, ISegmentConfiguration configuration) { - if (isDebug) { - return configuration.getDebugWriteKey(); - } else { - return configuration.getNormalWriteKey(); - } - } - private static class AnalyticsFactory implements Function { private static final int FLUSH_INTERVAL = 10000; diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerFactory.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerFactory.java new file mode 100644 index 00000000..137c1adb --- /dev/null +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerFactory.java @@ -0,0 +1,32 @@ +package com.redhat.devtools.intellij.telemetry.core.service.segment; + +import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; +import com.redhat.devtools.intellij.telemetry.core.service.Environment; +import com.redhat.devtools.intellij.telemetry.core.service.IDE; +import com.redhat.devtools.intellij.telemetry.core.service.UserId; + +import static com.redhat.devtools.intellij.telemetry.core.IMessageBroker.*; + +public class SegmentBrokerFactory implements IMessageBrokerFactory { + + @Override + public IMessageBroker create(boolean isDebug, ClassLoader classLoader) { + Environment environment = createEnvironment(classLoader); + SegmentConfiguration configuration = new SegmentConfiguration(classLoader); + return new SegmentBroker( + isDebug, + UserId.INSTANCE.get(), + environment, + configuration); + } + + private static Environment createEnvironment(ClassLoader classLoader) { + IDE ide = new IDE.Factory() + .create() + .setJavaVersion(); + return new Environment.Builder() + .ide(ide) + .plugin(classLoader) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/FileUtils.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/FileUtils.java index 15aad373..f577f590 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/FileUtils.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/FileUtils.java @@ -17,6 +17,9 @@ public class FileUtils { + private FileUtils() { + } + /** * Creates the file for the given path and the folder that contains it. * Does nothing if it any of those already exist. diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Lazy.java b/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Lazy.java index 01d07b6a..ccfa04ef 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Lazy.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/core/util/Lazy.java @@ -25,7 +25,12 @@ public Lazy(Supplier factory) { public T get() { if (value == null) { this.value = factory.get(); + onCreated(value); } return value; } + + protected void onCreated(T value) { + // override to customized + } } diff --git a/src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/NotificationGroupFactory.java b/src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/NotificationGroupFactory.java index f6fcfb1e..473240a5 100644 --- a/src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/NotificationGroupFactory.java +++ b/src/main/java/com/redhat/devtools/intellij/telemetry/ui/utils/NotificationGroupFactory.java @@ -37,6 +37,8 @@ */ public class NotificationGroupFactory { + private NotificationGroupFactory() {} + public static NotificationGroup create(String displayId, NotificationDisplayType type, boolean logByDefault) { try { // < IC-2021.3 diff --git a/src/main/resources/META-INF/plugin.xml b/src/main/resources/META-INF/plugin.xml index c15d0bd1..2d86daf5 100644 --- a/src/main/resources/META-INF/plugin.xml +++ b/src/main/resources/META-INF/plugin.xml @@ -42,5 +42,7 @@ displayName="Red Hat Telemetry"/> + diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfigurationTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfigurationTest.java index 5d45966e..312ae1c0 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfigurationTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/ClasspathConfigurationTest.java @@ -24,13 +24,13 @@ class ClasspathConfigurationTest { private ClasspathConfiguration config; @BeforeEach - public void beforeEach() throws IOException { + void beforeEach() throws IOException { Path path = Paths.get("segment.properties"); this.config = new ClasspathConfiguration(path); } @Test - public void get_loads_property_file() throws IOException { + void get_loads_property_file() throws IOException { // given // when String value = config.get("writeKey"); diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfigurationTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfigurationTest.java index 910315a4..6f1abf36 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfigurationTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/FileConfigurationTest.java @@ -30,14 +30,14 @@ class FileConfigurationTest { private static final Pair property1 = new Pair<>("luke", "jedy"); @BeforeEach - public void beforeEach() throws IOException { + void beforeEach() throws IOException { this.path = Paths.get(System.getProperty("java.io.tmpdir"), getClass().getSimpleName() + ".properties"); createPropertyFile(path, property1); this.config = new FileConfiguration(path); } @Test - public void get_loads_property_file() throws IOException { + void get_loads_property_file() throws IOException { // given // when String value = config.get(property1.first); @@ -46,7 +46,7 @@ public void get_loads_property_file() throws IOException { } @Test - public void get_returns_null_if_no_file_exists() throws IOException { + void get_returns_null_if_no_file_exists() throws IOException { // given Files.delete(path); // when diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfigurationTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfigurationTest.java index bea670cb..abd410cb 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfigurationTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SaveableFileConfigurationTest.java @@ -31,14 +31,14 @@ class SaveableFileConfigurationTest { private static final Pair property2 = new Pair<>("anakin", "sith"); @BeforeEach - public void beforeEach() throws IOException { + void beforeEach() throws IOException { this.path = Paths.get(System.getProperty("java.io.tmpdir"), getClass().getSimpleName() + ".properties"); createPropertyFile(path, property1); this.config = new SaveableFileConfiguration(path); } @Test - public void save_creates_property_file_if_it_doesnt_exist() throws IOException { + void save_creates_property_file_if_it_doesnt_exist() throws IOException { // given Files.delete(path); assertThat(Files.exists(path)).isFalse(); @@ -49,7 +49,7 @@ public void save_creates_property_file_if_it_doesnt_exist() throws IOException { } @Test - public void save_saves_additional_properties() throws IOException { + void save_saves_additional_properties() throws IOException { // given assertThat(config.get(property2.first)).isNull(); config.put(property2.first, property2.second); diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SegmentConfigurationTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SegmentConfigurationTest.java index 6211d06a..d5ed6bc8 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SegmentConfigurationTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/configuration/SegmentConfigurationTest.java @@ -23,7 +23,7 @@ class SegmentConfigurationTest { private SegmentConfiguration config; @BeforeEach - public void beforeEach() { + void beforeEach() { this.config = new SegmentConfiguration(getClass().getClassLoader()); } diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/Fakes.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/Fakes.java index b8df33ba..4cb1c1e5 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/Fakes.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/Fakes.java @@ -2,7 +2,9 @@ import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; import com.redhat.devtools.intellij.telemetry.core.service.segment.ISegmentConfiguration; +import org.mockito.stubbing.Answer; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -45,6 +47,27 @@ public static ISegmentConfiguration segmentConfiguration(String normalWriteKey, .thenReturn(normalWriteKey); when(configuration.getDebugWriteKey()) .thenReturn(debugWriteKey); + when(configuration.getWriteKey(anyBoolean())) + .thenAnswer((Answer) invocation -> { + Boolean isDebug = invocation.getArgument(0); + if (isDebug) { + return debugWriteKey; + } else { + return normalWriteKey; + } + }); return configuration; } + + public static TelemetryConfiguration telemetryConfiguration(TelemetryConfiguration.Mode mode) { + TelemetryConfiguration configuration = mock(TelemetryConfiguration.class); + when(configuration.isEnabled()) + .thenReturn(mode != TelemetryConfiguration.Mode.DISABLED); + when(configuration.isConfigured()) + .thenReturn(true); + when(configuration.getMode()) + .thenReturn(mode); + return configuration; + } + } diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceIntegrationTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderIntegrationTest.java similarity index 66% rename from src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceIntegrationTest.java rename to src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderIntegrationTest.java index 83725933..53383097 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceIntegrationTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderIntegrationTest.java @@ -12,7 +12,7 @@ import com.intellij.util.messages.MessageBusConnection; import com.jakewharton.retrofit.Ok3Client; -import com.redhat.devtools.intellij.telemetry.core.ITelemetryService; +import com.redhat.devtools.intellij.telemetry.core.IService; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; import com.redhat.devtools.intellij.telemetry.core.service.segment.ISegmentConfiguration; import com.redhat.devtools.intellij.telemetry.core.service.segment.IdentifyTraitsPersistence; @@ -22,9 +22,9 @@ import com.redhat.devtools.intellij.telemetry.util.StdOutLogging; import com.segment.analytics.Analytics; import okhttp3.OkHttpClient; -import org.junit.Ignore; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import retrofit.client.Client; @@ -32,16 +32,18 @@ import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.environment; import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.segmentConfiguration; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.ACTION; +import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.telemetryConfiguration; +import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.FeedbackServiceFacade; +import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.TelemetryServiceFacade; import static org.mockito.Mockito.mock; -@Ignore("For manual testing purposes only") -class TelemetryServiceIntegrationTest { +@Disabled("For manual testing purposes only") +class TelemetryMessageBuilderIntegrationTest { private static final String EXTENSION_NAME = "com.redhat.devtools.intellij.telemetry"; - private static final String EXTENSION_VERSION = "0.0.1"; + private static final String EXTENSION_VERSION = "1.0.0.44"; private static final String APPLICATION_VERSION = "1.0.0"; - private static final String APPLICATION_NAME = TelemetryServiceIntegrationTest.class.getSimpleName(); + private static final String APPLICATION_NAME = TelemetryMessageBuilderIntegrationTest.class.getSimpleName(); private static final String PLATFORM_NAME = "smurfOS"; private static final String PLATFORM_DISTRIBUTION = "red hats"; private static final String PLATFORM_VERSION = "0.1.0"; @@ -49,17 +51,17 @@ class TelemetryServiceIntegrationTest { private static final String TIMEZONE = "Europe/Bern"; private static final String COUNTRY = "Switzerland"; public static final String SEGMENT_WRITE_KEY = "HYuMCHlIpTvukCKZA42OubI1cvGIAap6"; + public static final String SEGMENT_DEBUG_WRITE_KEY = "ySk3bh8S8hDIGVKX9FQ1BMGOdFxbsufn"; private BlockingFlush blockingFlush; private Analytics analytics; - private ITelemetryService service; - private TelemetryEvent event; + private TelemetryMessageBuilder messageBuilder; @BeforeEach void before() { this.blockingFlush = BlockingFlush.create(); this.analytics = createAnalytics(blockingFlush, createClient()); - ISegmentConfiguration configuration = segmentConfiguration(SEGMENT_WRITE_KEY, ""); + ISegmentConfiguration segmentConfiguration = segmentConfiguration(SEGMENT_WRITE_KEY, SEGMENT_DEBUG_WRITE_KEY); Environment environment = environment( APPLICATION_NAME, APPLICATION_VERSION, @@ -76,14 +78,21 @@ void before() { UserId.INSTANCE.get(), IdentifyTraitsPersistence.INSTANCE, environment, - configuration, + segmentConfiguration, key -> analytics); - this.service = new TelemetryService( - TelemetryConfiguration.getInstance(), + TelemetryConfiguration telemetryConfiguration = telemetryConfiguration(TelemetryConfiguration.Mode.DEBUG); + + TelemetryService telemetryService = new TelemetryService( + telemetryConfiguration, broker, mock(MessageBusConnection.class), mock(TelemetryNotifications.class)); - this.event = new TelemetryEvent(ACTION, "Testing Telemetry"); + IService telemetryServiceFacade = new TelemetryServiceFacade(() -> telemetryService, mock(MessageBusConnection.class)); + + FeedbackService feedbackService = new FeedbackService(broker); + IService feedbackServiceFacade = new FeedbackServiceFacade(() -> feedbackService); + + this.messageBuilder = new TelemetryMessageBuilder(telemetryServiceFacade, feedbackServiceFacade); } @AfterEach @@ -92,15 +101,31 @@ void after() { } @Test - void should_send_track_event() { + void should_send_telemetry() { + // given + // when + messageBuilder.action("testing-telemetry") + .property("Wicked sorcerer", "Gargamel") + .property("Cat", "Azrael") + .success() + .send(); + // then + } + + @Test + void should_send_feedback() { // given // when - service.send(event); + messageBuilder.feedback("testing-feedback") + .property("Jedi", "Luke Skywalker") + .property("Sith", "Darth Vader") + .send(); // then } private Analytics createAnalytics(BlockingFlush blockingFlush, Client client) { return Analytics.builder(SEGMENT_WRITE_KEY) + .flushQueueSize(1) .plugin(new StdOutLogging()) .plugin(blockingFlush.plugin()) .client(client) diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderTest.java index 3ff8b4f4..bd93d529 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryMessageBuilderTest.java @@ -12,9 +12,11 @@ import com.intellij.ide.AppLifecycleListener; import com.intellij.util.messages.MessageBusConnection; -import com.redhat.devtools.intellij.telemetry.core.ITelemetryService; +import com.redhat.devtools.intellij.telemetry.core.IService; +import com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.FeedbackServiceFacade; import com.redhat.devtools.intellij.telemetry.core.util.AnonymizeUtils; import com.redhat.devtools.intellij.telemetry.core.util.TimeUtils; +import org.assertj.core.api.Condition; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; @@ -23,34 +25,37 @@ import java.time.LocalDateTime; import java.time.LocalTime; import java.time.temporal.ChronoUnit; +import java.util.Map; +import java.util.function.Predicate; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.ACTION; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.STARTUP; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.ACTION; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.STARTUP; +import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage; import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage.PROP_DURATION; import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage.PROP_RESULT; +import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.FeedbackMessage; import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ShutdownMessage; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ActionMessage; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.ServiceFacade; +import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryMessageBuilder.TelemetryServiceFacade; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; -import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; -public class TelemetryMessageBuilderTest { +class TelemetryMessageBuilderTest { - private ServiceFacade serviceFacadeMock = mock(ServiceFacade.class); - private TelemetryMessageBuilder builder = new TelemetryMessageBuilder(serviceFacadeMock); - - private ITelemetryService service = mock(ITelemetryService.class); - private MessageBusConnection bus = mock(MessageBusConnection.class); - private TestableServiceFacade serviceFacade = spy(new TestableServiceFacade(service, bus)); - private TelemetryEvent event = new TelemetryEvent(ACTION, "smurfette collects mushrooms"); + private final IService telemetryServiceFacade = mock(TelemetryServiceFacade.class); + private final IService feedbackServiceFacade = mock(FeedbackServiceFacade.class); + private final TelemetryMessageBuilder builder = new TelemetryMessageBuilder(telemetryServiceFacade, feedbackServiceFacade); + private final IService service = mock(IService.class); + private final MessageBusConnection bus = mock(MessageBusConnection.class); + private final TestableTelemetryServiceFacade serviceFacade = spy(new TestableTelemetryServiceFacade(service, bus)); + private final Event event = mock(Event.class); @Test - public void action_should_create_message_with_action_type() { + void action_should_create_message_with_action_type() { // given // when ActionMessage message = builder.action("azrael"); @@ -59,7 +64,7 @@ public void action_should_create_message_with_action_type() { } @Test - public void action_should_create_message_with_given_name() { + void action_should_create_message_with_given_name() { // given String name = "papa smurf"; // when @@ -69,7 +74,7 @@ public void action_should_create_message_with_given_name() { } @Test - public void property_should_add_property_with_given_key_and_name() { + void property_should_add_property_with_given_key_and_name() { // given String key = "likes"; String value = "papa smurf"; @@ -80,39 +85,39 @@ public void property_should_add_property_with_given_key_and_name() { } @Test - public void property_should_ignore_property_with_null_key() { + void property_should_ignore_property_with_null_key() { // given ActionMessage message = builder.action("smurfette"); int beforeAdding = message.properties().size(); // when message.property(null, "papa smurf"); // then - assertThat(message.properties().size()).isEqualTo(beforeAdding); + assertThat(message.properties()).hasSize(beforeAdding); } @Test - public void property_should_ignore_property_with_null_value() { + void property_should_ignore_property_with_null_value() { // given ActionMessage message = builder.action("smurfette"); int beforeAdding = message.properties().size(); // when message.property("likes", null); // then - assertThat(message.properties().size()).isEqualTo(beforeAdding); + assertThat(message.properties()).hasSize(beforeAdding); } @Test - public void send_should_send_message_via_service_facade() { + void send_should_send_message_via_service_facade() { // given ActionMessage message = builder.action("gargamel"); // when message.send(); // then - verify(serviceFacadeMock).send(any(TelemetryEvent.class)); + verify(telemetryServiceFacade).send(any(Event.class)); } @Test - public void send_should_send_event_with_given_type_name_and_properties() { + void send_should_send_event_with_given_type_name_and_properties() { // given String name = "gargamel"; String key1 = "the lovliest"; @@ -122,80 +127,80 @@ public void send_should_send_event_with_given_type_name_and_properties() { ActionMessage message = builder.action(name) .property(key1, value1) .property(key2, value2); - ArgumentCaptor eventArgument = ArgumentCaptor.forClass(TelemetryEvent.class); + ArgumentCaptor eventArgument = ArgumentCaptor.forClass(Event.class); // when message.send(); // then - verify(serviceFacadeMock).send(eventArgument.capture()); - TelemetryEvent event = eventArgument.getValue(); + verify(telemetryServiceFacade).send(eventArgument.capture()); + Event event = eventArgument.getValue(); assertThat(event.getType()).isEqualTo(ACTION); assertThat(event.getName()).isEqualTo(name); - assertThat(event.getProperties()).containsEntry(key1, value1); - assertThat(event.getProperties()).containsEntry(key2,value2); + assertThat(event.getProperties()) + .containsEntry(key1, value1) + .containsEntry(key2,value2); } @Test - public void send_should_set_duration() throws InterruptedException { + void send_should_set_duration() { // given ActionMessage message = builder.action("jolly jumper"); // when - TelemetryEvent event = message.send(); + Event event = message.send(); // then dont override existing duration assertThat(TimeUtils.toDuration(event.getProperties().get(PROP_DURATION))) .isNotNull(); } @Test - public void send_should_NOT_set_duration_if_already_exists() throws InterruptedException { + void send_should_NOT_set_duration_if_already_exists() { // given ActionMessage message = builder.action("jolly jumper"); Duration existing = Duration.ofDays(7); message.duration(existing); // when - TelemetryEvent event = message.send(); + Event event = message.send(); // then dont override existing duration assertThat(TimeUtils.toDuration(event.getProperties().get(PROP_DURATION))) .isEqualTo(existing); } @Test - public void send_should_set_result() throws InterruptedException { + void send_should_set_result() { // given ActionMessage message = builder.action("jolly jumper"); // when - TelemetryEvent event = message.send(); + Event event = message.send(); // then assertThat(event.getProperties().get(PROP_RESULT)) .isNotNull(); } @Test - public void send_should_NOT_set_result_if_error_exists() throws InterruptedException { + void send_should_NOT_set_result_if_error_exists() { // given ActionMessage message = builder.action("jolly jumper"); message.error("lost luky luke"); // when - TelemetryEvent event = message.send(); + Event event = message.send(); // then assertThat(event.getProperties().get(PROP_RESULT)) .isNull(); } @Test - public void send_should_NOT_set_result_if_result_exists() throws InterruptedException { + void send_should_NOT_set_result_if_result_exists() { // given ActionMessage message = builder.action("jolly jumper"); String result = "spits like a cowboy"; message.result(result); // when - TelemetryEvent event = message.send(); + Event event = message.send(); // then dont override existing result - assertThat(event.getProperties().get(PROP_RESULT)) - .isEqualTo(result); + assertThat(event.getProperties()).containsEntry(PROP_RESULT, result); } @Test - public void send_should_send_to_same_facade_instance() { + void send_should_send_to_same_facade_instance() { // given ActionMessage message1 = builder.action("gargamel"); ActionMessage message2 = builder.action("azrael"); @@ -205,11 +210,11 @@ public void send_should_send_to_same_facade_instance() { message2.send(); message3.send(); // then - verify(serviceFacadeMock, times(3)).send(any(TelemetryEvent.class)); + verify(telemetryServiceFacade, times(3)).send(any(Event.class)); } @Test - public void finished_should_set_duration() throws InterruptedException { + void finished_should_set_duration() throws InterruptedException { // given ActionMessage message = builder.action("inspector gadget"); final long delay = 1 * 1000; @@ -222,7 +227,7 @@ public void finished_should_set_duration() throws InterruptedException { } @Test - public void finished_should_set_duration_btw_given_start_and_finished_when_stop_is_new_day() { + void finished_should_set_duration_btw_given_start_and_finished_when_stop_is_new_day() { // given ActionMessage message = builder.action("inspector gadget hits the button"); LocalDateTime started = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 0)); @@ -237,7 +242,7 @@ public void finished_should_set_duration_btw_given_start_and_finished_when_stop_ } @Test - public void finished_should_set_duration_btw_given_start_and_finished_when_finished_is_in_new_year() { + void finished_should_set_duration_btw_given_start_and_finished_when_finished_is_in_new_year() { // given ActionMessage message = builder.action("the daltons break out"); LocalDateTime started = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 0)); @@ -252,7 +257,7 @@ public void finished_should_set_duration_btw_given_start_and_finished_when_finis } @Test - public void result_should_set_result_property() { + void result_should_set_result_property() { // given ActionMessage message = builder.action("flinstones"); String result = "crushed stones"; @@ -264,7 +269,27 @@ public void result_should_set_result_property() { } @Test - public void result_should_clear_error() { + void success_should_add_property_with_success_value() { + // given + ActionMessage message = builder.action("death star destruction"); + // when + message.success(); + // then + assertThat(message.getResult()).isEqualTo(Message.RESULT_SUCCESS); + } + + @Test + void aborted_should_add_property_with_aborted_value() { + // given + ActionMessage message = builder.action("hiding on the ice planet"); + // when + message.aborted(); + // then + assertThat(message.getResult()).isEqualTo(Message.RESULT_ABORTED); + } + + @Test + void result_should_clear_error() { // given ActionMessage message = builder.action("flinstones") .error("went bowling"); @@ -276,7 +301,7 @@ public void result_should_clear_error() { } @Test - public void error_should_set_error_property() { + void error_should_set_error_property() { // given ActionMessage message = builder.action("the simpsons"); String error = "nuclear plant emergency"; @@ -288,7 +313,7 @@ public void error_should_set_error_property() { } @Test - public void error_should_NOT_NPE_for_given_null_exception() { + void error_should_NOT_NPE_for_given_null_exception() { // given ActionMessage message = builder.action("the simpsons"); // when @@ -299,7 +324,7 @@ public void error_should_NOT_NPE_for_given_null_exception() { } @Test - public void error_should_clear_result() { + void error_should_clear_result() { // given ActionMessage message = builder.action("the simpsons") .result("went skateboarding"); @@ -311,7 +336,7 @@ public void error_should_clear_result() { } @Test - public void error_should_anonymize_email() { + void error_should_anonymize_email() { // given ActionMessage message = builder.action("the simpsons"); String email = "bart@simpsons.com"; @@ -319,11 +344,12 @@ public void error_should_anonymize_email() { message.error(email + " caused a nuclear plant emergency"); // then assertThat(message.getError()) - .doesNotContain(email); + .doesNotContain(email) + .contains(" caused a nuclear plant emergency"); } @Test - public void error_should_anonymize_username() { + void error_should_anonymize_username() { // given ActionMessage message = builder.action("the simpsons"); // when @@ -335,7 +361,7 @@ public void error_should_anonymize_username() { } @Test - public void error_should_anonymize_homedir() { + void error_should_anonymize_homedir() { // given ActionMessage message = builder.action("the smurfs"); // when @@ -347,7 +373,7 @@ public void error_should_anonymize_homedir() { } @Test - public void error_should_anonymize_tmpdir() { + void error_should_anonymize_tmpdir() { // given ActionMessage message = builder.action("the smurfs"); // when @@ -359,7 +385,7 @@ public void error_should_anonymize_tmpdir() { } @Test - public void error_should_anonymize_IP_address() { + void error_should_anonymize_IP_address() { // given ActionMessage message = builder.action("the smurfs"); // when @@ -371,20 +397,20 @@ public void error_should_anonymize_IP_address() { } @Test - public void serviceFacade_send_should_lazy_create_service() { + void serviceFacade_send_should_lazy_create_service() { // given // when serviceFacade.send(event); serviceFacade.send(event); serviceFacade.send(event); // then - verify(serviceFacade, times(1)).createService(any()); + verify(serviceFacade, times(1)).onCreated(any()); } @Test - public void serviceFacade_send_should_send_given_event_and_startup_message() { + void serviceFacade_send_should_send_given_event_and_startup_message() { // given - ArgumentCaptor events = ArgumentCaptor.forClass(TelemetryEvent.class); + ArgumentCaptor events = ArgumentCaptor.forClass(Event.class); // when serviceFacade.send(event); // then @@ -395,7 +421,7 @@ public void serviceFacade_send_should_send_given_event_and_startup_message() { } @Test - public void serviceFacade_send_should_subscribe_to_AppLifeCycle() { + void serviceFacade_send_should_subscribe_to_AppLifeCycle() { // given // when serviceFacade.send(event); @@ -404,7 +430,7 @@ public void serviceFacade_send_should_subscribe_to_AppLifeCycle() { } @Test - public void serviceFacade_send_should_register_IDE_shutdown_listener() { + void serviceFacade_send_should_register_IDE_shutdown_listener() { // given ArgumentCaptor listenerArgument = ArgumentCaptor.forClass(AppLifecycleListener.class); serviceFacade.send(event); @@ -417,40 +443,179 @@ public void serviceFacade_send_should_register_IDE_shutdown_listener() { } @Test - public void shutdownMessage_should_report_session_duration() { + void shutdownMessage_should_report_session_duration() { // given LocalDateTime startup = LocalDateTime.of(LocalDate.now(), LocalTime.of(23, 1)); LocalDateTime shutdown = LocalDateTime.of(LocalDate.now().plusDays(1), LocalTime.of(4, 2)); Duration duration = Duration.between(startup, shutdown); // when - ShutdownMessage message = new ShutdownMessage(startup, shutdown, serviceFacadeMock); + ShutdownMessage message = new ShutdownMessage(startup, shutdown, telemetryServiceFacade); // then assertThat(message.getSessionDuration()).isEqualTo(TimeUtils.toString(duration)); } - private class TestableServiceFacade extends ServiceFacade { - private final ITelemetryService service; - private final MessageBusConnection bus; + @Test + void feedback_should_create_message_with_action_type() { + // given + // when + FeedbackMessage message = builder.feedback("azrael"); + // then + assertThat(message.getType()).isEqualTo(ACTION); + } - protected TestableServiceFacade(ITelemetryService service, MessageBusConnection bus) { - super(mock(ClassLoader.class)); - this.service = service; - this.bus = bus; - } + @Test + void feedback_should_create_message_with_given_name() { + // given + String name = "papa smurf"; + // when + FeedbackMessage message = builder.feedback(name); + // then + assertThat(message.getName()).isEqualTo(name); + } - @Override - protected ITelemetryService createService(ClassLoader classLoader) { - return service; + @Test + void feedback_property_should_add_property_with_given_key_and_name() { + // given + String key = "likes"; + String value = "papa smurf"; + // when + FeedbackMessage message = builder.feedback("smurfette").property(key, value); + // then + assertThat(message.getProperty(key)).isEqualTo(value); + } + + @Test + void feedback_property_should_ignore_property_with_null_key() { + // given + FeedbackMessage message = builder.feedback("smurfette"); + int beforeAdding = message.properties().size(); + // when + message.property(null, "papa smurf"); + // then + assertThat(message.properties()).hasSize(beforeAdding); + } + + @Test + void feedback_property_should_ignore_property_with_null_value() { + // given + FeedbackMessage message = builder.feedback("smurfette"); + int beforeAdding = message.properties().size(); + // when + message.property("likes", null); + // then + assertThat(message.properties()).hasSize(beforeAdding); + } + + @Test + void feedback_result_should_add_property_with_given_value() { + // given + FeedbackMessage message = builder.feedback("death star destruction"); + // when + message.result("succeeded"); + // then + assertThat(message.getResult()).isEqualTo("succeeded"); + } + + @Test + void feedback_success_should_add_property_with_success_value() { + // given + FeedbackMessage message = builder.feedback("death star destruction"); + // when + message.success(); + // then + assertThat(message.getResult()).isEqualTo(Message.RESULT_SUCCESS); + } + + @Test + void feedback_aborted_should_add_property_with_aborted_value() { + // given + FeedbackMessage message = builder.feedback("hiding on the ice planet"); + // when + message.aborted(); + // then + assertThat(message.getResult()).isEqualTo(Message.RESULT_ABORTED); + } + + @Test + void feedback_send_should_send_message_via_service_facade() { + // given + FeedbackMessage message = builder.feedback("gargamel"); + // when + message.send(); + // then + verify(feedbackServiceFacade).send(any(Event.class)); + } + + @Test + void feedback_send_should_send_event_with_given_type_name_and_properties() { + // given + String name = "gargamel"; + String key1 = "the lovliest"; + String value1 = "smurfette"; + String key2 = "the smallest"; + String value2 = "baby smurf"; + FeedbackMessage message = builder.feedback(name) + .property(key1, value1) + .property(key2, value2); + ArgumentCaptor eventArgument = ArgumentCaptor.forClass(Event.class); + // when + message.send(); + // then + verify(feedbackServiceFacade).send(eventArgument.capture()); + Event event = eventArgument.getValue(); + assertThat(event.getType()).isEqualTo(ACTION); + assertThat(event.getName()).isEqualTo(name); + assertThat(event.getProperties()) + .containsEntry(key1, value1) + .containsEntry(key2,value2); + } + + @Test + void feedback_send_should_send_to_same_facade_instance() { + // given + FeedbackMessage message1 = builder.feedback("gargamel"); + FeedbackMessage message2 = builder.feedback("azrael"); + FeedbackMessage message3 = builder.feedback("papa smurf"); + // when + message1.send(); + message2.send(); + message3.send(); + // then + verify(feedbackServiceFacade, times(3)).send(any(Event.class)); + } + + @Test + void feedback_send_should_create_service_once_only() { + // given + TestableFeedbackServiceFacade facade = spy(new TestableFeedbackServiceFacade(mock(IService.class))); + // when + facade.send(event); + facade.send(event); + facade.send(event); + // then + verify(facade, times(1)).onCreated(any()); + } + + private static class TestableTelemetryServiceFacade extends TelemetryServiceFacade { + + protected TestableTelemetryServiceFacade(IService service, MessageBusConnection bus) { + super(() -> service, bus); } @Override - protected MessageBusConnection createMessageBusConnection() { - return bus; + public void sendShutdown() {} + } + + private static class TestableFeedbackServiceFacade extends FeedbackServiceFacade { + + protected TestableFeedbackServiceFacade(IService service) { + super(() -> service); } + /** public for testing purposes **/ @Override - public void sendShutdown() { + protected void onCreated(IService value) { + super.onCreated(value); } } - } diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceTest.java index 6df0fd9a..93d38132 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/TelemetryServiceTest.java @@ -11,7 +11,7 @@ package com.redhat.devtools.intellij.telemetry.core.service; import com.intellij.util.messages.MessageBusConnection; -import com.redhat.devtools.intellij.telemetry.core.ITelemetryService; +import com.redhat.devtools.intellij.telemetry.core.IService; import com.redhat.devtools.intellij.telemetry.core.configuration.TelemetryConfiguration; import com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker; import com.redhat.devtools.intellij.telemetry.ui.TelemetryNotifications; @@ -22,8 +22,8 @@ import java.util.List; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.USER; import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.telemetryConfiguration; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.USER; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.Mockito.atLeastOnce; @@ -34,23 +34,20 @@ class TelemetryServiceTest { - private static final String TIMEZONE = "GMT+2:00"; - private static final String LOCALE = "en/US"; - private static final String COUNTRY = "Switzerland"; private SegmentBroker broker; private MessageBusConnection bus; - private ITelemetryService service; - private TelemetryEvent event; + private IService service; + private Event event; private TelemetryNotifications notifications; @BeforeEach - public void before() { + void before() { this.broker = createSegmentBroker(); this.bus = createMessageBusConnection(); this.notifications = createTelemetryNotifications(); TelemetryConfiguration configuration = telemetryConfiguration(true, true); this.service = new TelemetryService(configuration, broker, bus, notifications); - this.event = new TelemetryEvent(null, "Testing Telemetry", null); + this.event = new Event(null, "Testing Telemetry", null); } @Test @@ -59,7 +56,7 @@ void send_should_send_if_is_enabled() { // when service.send(event); // then - verify(broker, atLeastOnce()).send(any(TelemetryEvent.class)); + verify(broker, atLeastOnce()).send(any(Event.class)); } @Test @@ -70,7 +67,7 @@ void send_should_NOT_send_if_is_NOT_configured() { // when service.send(event); // then - verify(broker, never()).send(any(TelemetryEvent.class)); + verify(broker, never()).send(any(Event.class)); } @Test @@ -82,24 +79,24 @@ void send_should_send_all_events_once_it_gets_enabled() { service.send(event); service.send(event); // then - verify(broker, never()).send(any(TelemetryEvent.class)); + verify(broker, never()).send(any(Event.class)); // when config gets enabled doReturn(true) .when(configuration).isEnabled(); service.send(event); // then - verify(broker, VerificationModeFactory.atLeast(3)).send(any(TelemetryEvent.class)); + verify(broker, VerificationModeFactory.atLeast(3)).send(any(Event.class)); } @Test void send_should_send_userinfo() { // given - ArgumentCaptor eventArgument = ArgumentCaptor.forClass(TelemetryEvent.class); + ArgumentCaptor eventArgument = ArgumentCaptor.forClass(Event.class); // when service.send(event); // then verify(broker, atLeastOnce()).send(eventArgument.capture()); - List allArguments = eventArgument.getAllValues(); + List allArguments = eventArgument.getAllValues(); assertThat(allArguments.size()).isGreaterThanOrEqualTo(2); assertThat(allArguments.get(0).getType()).isEqualTo(USER); } diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerTest.java index e66ba231..c78793b8 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/service/segment/SegmentBrokerTest.java @@ -12,7 +12,7 @@ import com.redhat.devtools.intellij.telemetry.core.IMessageBroker; import com.redhat.devtools.intellij.telemetry.core.service.Environment; -import com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent; +import com.redhat.devtools.intellij.telemetry.core.service.Event; import com.segment.analytics.Analytics; import com.segment.analytics.messages.IdentifyMessage; import com.segment.analytics.messages.Message; @@ -27,10 +27,10 @@ import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.environment; import static com.redhat.devtools.intellij.telemetry.core.service.Fakes.segmentConfiguration; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.ACTION; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.SHUTDOWN; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.STARTUP; -import static com.redhat.devtools.intellij.telemetry.core.service.TelemetryEvent.Type.USER; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.ACTION; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.SHUTDOWN; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.STARTUP; +import static com.redhat.devtools.intellij.telemetry.core.service.Event.Type.USER; import static com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker.PROP_APP; import static com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker.PROP_APP_NAME; import static com.redhat.devtools.intellij.telemetry.core.service.segment.SegmentBroker.PROP_APP_VERSION; @@ -73,14 +73,14 @@ class SegmentBrokerTest { private IdentifyTraitsPersistence identifyTraitsPersistence; private Environment environment; private SegmentBroker broker; - private TelemetryEvent actionEvent; - private TelemetryEvent userEvent; - private TelemetryEvent startupEvent; - private TelemetryEvent shutdownEvent; + private Event actionEvent; + private Event userEvent; + private Event startupEvent; + private Event shutdownEvent; private ISegmentConfiguration configuration; @BeforeEach - public void before() { + void before() { this.analytics = createAnalytics(); this.identifyTraitsPersistence = mock(IdentifyTraitsPersistence.class); this.environment = environment( @@ -96,10 +96,10 @@ public void before() { COUNTRY); this.configuration = segmentConfiguration(NORMAL_WRITE_KEY, DEBUG_WRITE_KEY); this.broker = new SegmentBroker(false, USER_ID, identifyTraitsPersistence, environment, configuration, key -> analytics); - this.actionEvent = new TelemetryEvent(ACTION, "Action event"); - this.userEvent = new TelemetryEvent(USER, "User event"); - this.startupEvent = new TelemetryEvent(STARTUP, "Startup event"); - this.shutdownEvent = new TelemetryEvent(SHUTDOWN, "Startup event"); + this.actionEvent = new Event(ACTION, "Action event"); + this.userEvent = new Event(USER, "User event"); + this.startupEvent = new Event(STARTUP, "Startup event"); + this.shutdownEvent = new Event(SHUTDOWN, "Startup event"); } @Test @@ -112,7 +112,13 @@ public Analytics apply(String key) { return analytics; } }); - SegmentBroker broker = new SegmentBroker(false, USER_ID, identifyTraitsPersistence, environment, configuration, analyticsFactory); + SegmentBroker broker = new SegmentBroker( + false, + USER_ID, + identifyTraitsPersistence, + environment, + configuration, + analyticsFactory); // when broker.send(actionEvent); // then @@ -298,7 +304,7 @@ void send_should_NOT_add_NULL_properties_to_identify_message() { } @Test - void send_should_enqueue_track_message_with_properties() { + void send_should_enqueue_track_message_with_traits() { // given ArgumentCaptor builder = ArgumentCaptor.forClass(TrackMessage.Builder.class); // when @@ -312,6 +318,23 @@ void send_should_enqueue_track_message_with_properties() { assertThat(traits.get(PROP_EXTENSION_VERSION)).isEqualTo(EXTENSION_VERSION); } + @Test + void send_should_enqueue_track_message_with_additional_properties() { + // given + ArgumentCaptor builder = ArgumentCaptor.forClass(TrackMessage.Builder.class); + Map properties = actionEvent.getProperties(); + properties.put("jedi", "yoda"); + properties.put("sith", "darth sidious"); + // when + broker.send(actionEvent); + // then + verify(analytics).enqueue(builder.capture()); + Map traits = builder.getValue().build().properties(); + assertThat(traits.get("jedi")).isEqualTo("yoda"); + assertThat(traits.get("sith")).isEqualTo("darth sidious"); + } + + @Test void send_should_NOT_add_NULL_properties_to_track_message() { // given diff --git a/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtilsTest.java b/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtilsTest.java index c96b5e76..444ee055 100644 --- a/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtilsTest.java +++ b/src/test/java/com/redhat/devtools/intellij/telemetry/core/util/AnonymizeUtilsTest.java @@ -16,7 +16,7 @@ public class AnonymizeUtilsTest { @Test - public void anonymizeEmail_should_replace_email() { + void anonymizeEmail_should_replace_email() { // given String email = "adietish@redhat.com"; String msgWithEmail = "This is the email address " + email + " within a message."; @@ -28,7 +28,7 @@ public void anonymizeEmail_should_replace_email() { } @Test - public void anonymizeEmail_should_NOT_replace_bogus_email() { + void anonymizeEmail_should_NOT_replace_bogus_email() { // given String bogusEmail = "adietish@redhat"; String msgWithBogusEmail = "This is the email address " + bogusEmail + " within a message."; @@ -40,7 +40,7 @@ public void anonymizeEmail_should_NOT_replace_bogus_email() { } @Test - public void anonymizeUserName_should_replace_username() { + void anonymizeUserName_should_replace_username() { // given String username = AnonymizeUtils.USER_NAME; String msgWithUsername = "This is the username " + username + " within a message."; @@ -52,7 +52,7 @@ public void anonymizeUserName_should_replace_username() { } @Test - public void anonymizeHomeDir_should_replace_homedir() { + void anonymizeHomeDir_should_replace_homedir() { // given String homeDir = AnonymizeUtils.HOME_DIR; String msgWithHomeDir = "This is the path to the " + homeDir + " within a message."; @@ -64,7 +64,7 @@ public void anonymizeHomeDir_should_replace_homedir() { } @Test - public void anonymizeHomeDir_should_NOT_replace_other_directory() { + void anonymizeHomeDir_should_NOT_replace_other_directory() { // given String otherDir = "C:\\\\Documents and Settings\\\\All Users"; String msgWithOtherDir = "This is the path to the " + otherDir + " within a message."; @@ -76,7 +76,7 @@ public void anonymizeHomeDir_should_NOT_replace_other_directory() { } @Test - public void anonymizeTmpDir_should_replace_tmpDir() { + void anonymizeTmpDir_should_replace_tmpDir() { // given String tmpDir = AnonymizeUtils.TMP_DIR; String msgWithTmpDir = "This is the path to the " + tmpDir + " within a message."; @@ -88,7 +88,7 @@ public void anonymizeTmpDir_should_replace_tmpDir() { } @Test - public void replaceIP_should_replace_IP() { + void replaceIP_should_replace_IP() { // given String ip = "192.168.0.1"; String msgWithIp = "This is the ip " + ip + " within a message."; @@ -100,7 +100,7 @@ public void replaceIP_should_replace_IP() { } @Test - public void replaceIP_should_NOT_replace_bogus_IP() { + void replaceIP_should_NOT_replace_bogus_IP() { // given String bogusIP = "10.0.12"; String msgWithIp = "This is the ip " + bogusIP + " within a message."; @@ -112,7 +112,7 @@ public void replaceIP_should_NOT_replace_bogus_IP() { } @Test - public void anonymizeResource_should_anonymize_resource_and_namespace() { + void anonymizeResource_should_anonymize_resource_and_namespace() { // given String resource = "smurf village"; String namespace = "blue county";