From ce1ff8418a5d30e024412a752ff4b52fb9ceab9f Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 13:57:59 +0300 Subject: [PATCH 01/58] fix: usage of callbacks --- .../sdk/java/internal/ModuleFeedback.java | 39 ++++++++++--------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index e32685063..ee485c3ce 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -4,6 +4,7 @@ import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; +import ly.count.sdk.java.Countly; import org.json.JSONObject; public class ModuleFeedback extends ModuleBase { @@ -66,20 +67,20 @@ public void stop(CtxCore ctx, boolean clear) { feedbackInterface = null; } - private List getAvailableFeedbackWidgetsInternal() { - return null; + private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callback) { + callback.onFinished(null, ""); } private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetInfo, JSONObject widgetData, Map widgetResult) { } - private JSONObject getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo) { - return null; + private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, RetrieveFeedbackWidgetData callback) { + callback.onFinished(null, ""); } - private String constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo) { - return null; + private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { + callback.onFinished("", ""); } public class Feedback { @@ -87,13 +88,13 @@ public class Feedback { /** * Get a list of available feedback widgets for this device ID * - * @return list of available feedback widgets + * @param callback */ - public List getAvailableFeedbackWidgets() { - synchronized (internalConfig) { + public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callback) { + synchronized (Countly.instance()) { L.i("[Feedback] Trying to retrieve feedback widget list"); - return getAvailableFeedbackWidgetsInternal(); + getAvailableFeedbackWidgetsInternal(callback); } } @@ -101,13 +102,13 @@ public List getAvailableFeedbackWidgets() { * Construct a URL that can be used to present a feedback widget in a web viewer * * @param widgetInfo - * @return feedback widget URL + * @param callback */ - public String constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo) { - synchronized (internalConfig) { + public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable FeedbackCallback callback) { + synchronized (Countly.instance()) { L.i("[Feedback] Trying to present feedback widget in an alert dialog"); - return constructFeedbackWidgetUrlInternal(widgetInfo); + constructFeedbackWidgetUrlInternal(widgetInfo, callback); } } @@ -116,13 +117,13 @@ public String constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetI * When requesting this data, it will count as a shown widget (will increment that "shown" count in the dashboard) * * @param widgetInfo - * @return feedback widget data + * @param callback */ - public JSONObject getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo) { - synchronized (internalConfig) { + public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable RetrieveFeedbackWidgetData callback) { + synchronized (Countly.instance()) { L.i("[Feedback] Trying to retrieve feedback widget data"); - return getFeedbackWidgetDataInternal(widgetInfo); + getFeedbackWidgetDataInternal(widgetInfo, callback); } } @@ -135,7 +136,7 @@ public JSONObject getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetIn * @param widgetResult */ public void reportFeedbackWidgetManually(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable JSONObject widgetData, @Nullable Map widgetResult) { - synchronized (internalConfig) { + synchronized (Countly.instance()) { L.i("[Feedback] Trying to report feedback widget manually"); reportFeedbackWidgetManuallyInternal(widgetInfo, widgetData, widgetResult); From d38cb1a9eb788230973c9150ec1e3331db19b5d2 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 15:39:34 +0300 Subject: [PATCH 02/58] feat: immediate request --- .../sdk/java/internal/ImmediateRequestI.java | 5 + .../java/internal/ImmediateRequestMaker.java | 122 ++++++++++++++++++ .../ly/count/sdk/java/internal/Transport.java | 66 +++++++++- 3 files changed, 191 insertions(+), 2 deletions(-) create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java new file mode 100644 index 000000000..f1157edce --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java @@ -0,0 +1,5 @@ +package ly.count.sdk.java.internal; + +interface ImmediateRequestI { + void doWork(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, ImmediateRequestMaker.InternalImmediateRequestCallback callback, Log log); +} diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java new file mode 100644 index 000000000..8fb6dd093 --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java @@ -0,0 +1,122 @@ +package ly.count.sdk.java.internal; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.concurrent.CompletableFuture; +import org.json.JSONObject; + +/** + * Async task for making immediate server requests + */ +class ImmediateRequestMaker implements ImmediateRequestI { + + @Override + public void doWork(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, InternalImmediateRequestCallback callback, Log log) { + CompletableFuture.supplyAsync(() -> doInBackground(requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log)) + .thenAcceptAsync(this::onFinished); + } + + /** + * Used for callback from async task + */ + protected interface InternalImmediateRequestCallback { + void callback(JSONObject checkResponse); + } + + InternalImmediateRequestCallback callback; + Log L; + + /** + * params fields: + * 0 - requestData + * 1 - custom endpoint + * 2 - connection processor + * 3 - requestShouldBeDelayed + * 4 - networkingIsEnabled + * 5 - callback + * 6 - log module + */ + protected JSONObject doInBackground(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, InternalImmediateRequestCallback callback, Log log) { + this.callback = callback; + L = log; + + if (!networkingIsEnabled) { + L.w("[ImmediateRequestMaker] ImmediateRequestMaker, Networking config is disabled, request cancelled. Endpoint[" + customEndpoint + "] request[" + requestData + "]"); + + return null; + } + + L.v("[ImmediateRequestMaker] Starting request"); + + HttpURLConnection connection = null; + + try { + L.d("[ImmediateRequestMaker] delayed[" + requestShouldBeDelayed + "] hasCallback[" + (callback != null) + "] endpoint[" + customEndpoint + "] request[" + requestData + "]"); + + if (requestShouldBeDelayed) { + //used in cases after something has to be done after a device id change + L.v("[ImmediateRequestMaker] request should be delayed, waiting for 0.5 seconds"); + + try { + Thread.sleep(500); + } catch (InterruptedException e) { + L.w("[ImmediateRequestMaker] While waiting for 0.5 seconds in ImmediateRequestMaker, sleep was interrupted"); + } + } + + Request request = new Request(); + request.params.add(requestData); + request.endpoint(customEndpoint); + //getting connection ready + try { + connection = cp.connection(request); + } catch (IOException e) { + L.e("[ImmediateRequestMaker] IOException while preparing remote config update request :[" + e.toString() + "]"); + + return null; + } + + //connecting + connection.connect(); + + int code = connection.getResponseCode(); + + String receivedBuffer = cp.response(connection); + + if (receivedBuffer == null) { + L.e("[ImmediateRequestMaker] Encountered problem while making a immediate server request, received result was null"); + return null; + } + + char firstChar = receivedBuffer.trim().charAt(0); + if (code >= 200 && code < 300 && (firstChar == '[' || receivedBuffer.contains("result"))) { + L.d("[ImmediateRequestMaker] Received the following response, :[" + receivedBuffer + "]"); + + // we check if the result was a json array or json object and convert the array into an object if necessary + if (firstChar == '[') { + return new JSONObject("{\"jsonArray\":" + receivedBuffer + "}"); + } + return new JSONObject(receivedBuffer); + } else { + L.e("[ImmediateRequestMaker] Encountered problem while making a immediate server request, :[" + receivedBuffer + "]"); + return null; + } + } catch (Exception e) { + L.e("[ImmediateRequestMaker] Received exception while making a immediate server request " + e.getMessage()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + L.v("[ImmediateRequestMaker] Finished request"); + return null; + } + + protected void onFinished(JSONObject result) { + L.v("[ImmediateRequestMaker] onPostExecute"); + + if (callback != null) { + callback.callback(result); + } + } +} \ No newline at end of file diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java index 809ec3966..721733e08 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java @@ -14,6 +14,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; @@ -30,13 +31,11 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.Future; - import javax.net.ssl.HttpsURLConnection; import javax.net.ssl.SSLContext; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; - import ly.count.sdk.java.User; import org.json.JSONObject; @@ -214,6 +213,69 @@ HttpURLConnection connection(final Request request, final User user) throws IOEx return connection; } + /** + * Open connection for particular request: choose GET or POST, choose multipart or urlencoded if POST, + * set SSL context, calculate and add checksum, load and send user picture if needed. + * + * @param request request to send + * @return connection, not {@link HttpURLConnection} yet + * @throws IOException from {@link HttpURLConnection} in case of error + */ + HttpURLConnection connection(final Request request) throws IOException { + String endpoint = request.params.remove(Request.ENDPOINT); + if (endpoint == null) { + endpoint = "/i?"; + } + + String path = config.getServerURL().toString() + endpoint; + boolean usingGET = !config.isUsePOST() && request.isGettable(config.getServerURL()); + + if (usingGET && config.getParameterTamperingProtectionSalt() != null) { + request.params.add(CHECKSUM, Utils.digestHex(PARAMETER_TAMPERING_DIGEST, request.params + config.getParameterTamperingProtectionSalt(), L)); + } + + HttpURLConnection connection = openConnection(path, request.params.toString(), usingGET); + connection.setConnectTimeout(1000 * config.getNetworkConnectionTimeout()); + connection.setReadTimeout(1000 * config.getNetworkReadTimeout()); + + if (connection instanceof HttpsURLConnection && sslContext != null) { + HttpsURLConnection https = (HttpsURLConnection) connection; + https.setSSLSocketFactory(sslContext.getSocketFactory()); + } + + if (!usingGET) { + OutputStream output = null; + PrintWriter writer = null; + try { + if (config.getParameterTamperingProtectionSalt() != null) { + request.params.add(CHECKSUM, Utils.digestHex(PARAMETER_TAMPERING_DIGEST, request.params + config.getParameterTamperingProtectionSalt(), L)); + } + connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + + output = connection.getOutputStream(); + writer = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true); + + writer.write(request.params.toString()); + writer.flush(); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (Throwable ignored) { + } + } + if (output != null) { + try { + output.close(); + } catch (Throwable ignored) { + } + } + } + } + + return connection; + } + void addMultipart(OutputStream output, PrintWriter writer, String boundary, String contentType, String name, String value, Object file) throws IOException { writer.append("--").append(boundary).append(Utils.CRLF); if (file != null) { From 2fd128be498e9dd839483f1920eb5ab74064f3ba Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 15:53:06 +0300 Subject: [PATCH 03/58] feat: irGenerator to cnfig --- .../src/main/java/ly/count/sdk/java/Config.java | 8 ++++++-- .../src/main/java/ly/count/sdk/java/Countly.java | 8 ++++++-- .../java/internal/ImmediateRequestGenerator.java | 5 +++++ .../sdk/java/internal/ImmediateRequestI.java | 2 +- .../count/sdk/java/internal/InternalConfig.java | 3 ++- .../java/ly/count/sdk/java/internal/SDKCore.java | 16 ++++++++++++---- 6 files changed, 32 insertions(+), 10 deletions(-) create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java index 932766fe4..565790cd1 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java @@ -12,8 +12,12 @@ import java.util.HashSet; import java.util.Map; import java.util.Set; - -import ly.count.sdk.java.internal.*; +import ly.count.sdk.java.internal.Byteable; +import ly.count.sdk.java.internal.CoreFeature; +import ly.count.sdk.java.internal.Log; +import ly.count.sdk.java.internal.LogCallback; +import ly.count.sdk.java.internal.ModuleBase; +import ly.count.sdk.java.internal.Utils; /** * Countly configuration object. diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java index 3d26f980c..d337020d6 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java @@ -2,8 +2,12 @@ import java.io.File; import java.util.Map; - -import ly.count.sdk.java.internal.*; +import ly.count.sdk.java.internal.CtxCore; +import ly.count.sdk.java.internal.Device; +import ly.count.sdk.java.internal.InternalConfig; +import ly.count.sdk.java.internal.Log; +import ly.count.sdk.java.internal.ModuleBackendMode; +import ly.count.sdk.java.internal.SDKCore; /** * Main Countly SDK API class. diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java new file mode 100644 index 000000000..cba56a0c2 --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java @@ -0,0 +1,5 @@ +package ly.count.sdk.java.internal; + +public interface ImmediateRequestGenerator { + ImmediateRequestI createImmediateRequestMaker(); +} diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java index f1157edce..c91bc3c24 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java @@ -1,5 +1,5 @@ package ly.count.sdk.java.internal; -interface ImmediateRequestI { +public interface ImmediateRequestI { void doWork(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, ImmediateRequestMaker.InternalImmediateRequestCallback callback, Log log); } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java index 2adfa0745..c65efe240 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java @@ -13,7 +13,6 @@ import java.util.List; import java.util.Map; import java.util.Set; - import ly.count.sdk.java.Config; /** @@ -34,6 +33,8 @@ public final class InternalConfig extends Config implements Storable { */ private final List dids = new ArrayList<>(); + ImmediateRequestGenerator immediateRequestGenerator = null; + /** * Shouldn't be used! */ diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java index e5324b7cb..fb5b6be98 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java @@ -1,10 +1,14 @@ package ly.count.sdk.java.internal; -import ly.count.sdk.java.Config; -import org.json.JSONObject; - -import java.util.*; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.TreeMap; import java.util.concurrent.Future; +import ly.count.sdk.java.Config; public class SDKCore { @@ -89,6 +93,10 @@ public boolean isTracking(Integer feat) { } public void init(CtxCore ctx) { + InternalConfig config = ctx.getConfig(); + if (config.immediateRequestGenerator == null) { + config.immediateRequestGenerator = ImmediateRequestMaker::new; + } prepareMappings(ctx); } From ea10142cd2bbfffaae29644e46ecb37b98ffabb6 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 16:58:20 +0300 Subject: [PATCH 04/58] feat: immediate req test --- .../main/java/ly/count/sdk/java/Config.java | 3 ++ .../sdk/java/internal/DefaultNetworking.java | 5 +++ .../java/internal/ImmediateRequestMaker.java | 14 ++----- .../count/sdk/java/internal/Networking.java | 2 + .../java/internal/ImmediateRequestTest.java | 39 +++++++++++++++++++ .../ly/count/sdk/java/internal/TestUtils.java | 36 ++++++++++++++++- 6 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java index 565790cd1..9fceca8b5 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java @@ -14,6 +14,7 @@ import java.util.Set; import ly.count.sdk.java.internal.Byteable; import ly.count.sdk.java.internal.CoreFeature; +import ly.count.sdk.java.internal.ImmediateRequestGenerator; import ly.count.sdk.java.internal.Log; import ly.count.sdk.java.internal.LogCallback; import ly.count.sdk.java.internal.ModuleBase; @@ -466,6 +467,8 @@ public boolean restore(byte[] data, Log L) { */ File sdkStorageRootDirectory = null; + protected ImmediateRequestGenerator immediateRequestGenerator = null; + // /** // * Maximum size of all string keys // */ diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/DefaultNetworking.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/DefaultNetworking.java index 86c5a5b32..85451fbf8 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/DefaultNetworking.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/DefaultNetworking.java @@ -69,4 +69,9 @@ public void stop(CtxCore ctx) { shutdown = true; tasks.shutdown(); } + + @Override + public Transport getTransport() { + return transport; + } } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java index 8fb6dd093..60b35fd54 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java @@ -23,8 +23,8 @@ protected interface InternalImmediateRequestCallback { void callback(JSONObject checkResponse); } - InternalImmediateRequestCallback callback; - Log L; + private InternalImmediateRequestCallback callback; + private Log L; /** * params fields: @@ -36,10 +36,9 @@ protected interface InternalImmediateRequestCallback { * 5 - callback * 6 - log module */ - protected JSONObject doInBackground(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, InternalImmediateRequestCallback callback, Log log) { + private JSONObject doInBackground(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, InternalImmediateRequestCallback callback, Log log) { this.callback = callback; L = log; - if (!networkingIsEnabled) { L.w("[ImmediateRequestMaker] ImmediateRequestMaker, Networking config is disabled, request cancelled. Endpoint[" + customEndpoint + "] request[" + requestData + "]"); @@ -63,7 +62,6 @@ protected JSONObject doInBackground(String requestData, String customEndpoint, T L.w("[ImmediateRequestMaker] While waiting for 0.5 seconds in ImmediateRequestMaker, sleep was interrupted"); } } - Request request = new Request(); request.params.add(requestData); request.endpoint(customEndpoint); @@ -72,15 +70,12 @@ protected JSONObject doInBackground(String requestData, String customEndpoint, T connection = cp.connection(request); } catch (IOException e) { L.e("[ImmediateRequestMaker] IOException while preparing remote config update request :[" + e.toString() + "]"); - return null; } - //connecting connection.connect(); int code = connection.getResponseCode(); - String receivedBuffer = cp.response(connection); if (receivedBuffer == null) { @@ -112,9 +107,8 @@ protected JSONObject doInBackground(String requestData, String customEndpoint, T return null; } - protected void onFinished(JSONObject result) { + private void onFinished(JSONObject result) { L.v("[ImmediateRequestMaker] onPostExecute"); - if (callback != null) { callback.callback(result); } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/Networking.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/Networking.java index 4d659d1b9..965976a47 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/Networking.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/Networking.java @@ -8,4 +8,6 @@ public interface Networking { boolean check(CtxCore ctx); void stop(CtxCore ctx); + + Transport getTransport(); } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java new file mode 100644 index 000000000..7c798c6c2 --- /dev/null +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java @@ -0,0 +1,39 @@ +package ly.count.sdk.java.internal; + +import java.util.concurrent.atomic.AtomicReference; +import ly.count.sdk.java.Countly; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +import static org.mockito.Mockito.mock; + +@RunWith(JUnit4.class) +public class ImmediateRequestTest { + Log L = mock(Log.class); + + /** + * Immediate request maker "doWork" function + * Immediate Request Generator is default and endpoint and data are not valid, and app key, server url is default + * should return null because response is not okay + * + * @throws InterruptedException + */ + @Test + public void doWork_null() throws InterruptedException { + Countly.instance().init(TestUtils.getBaseConfig()); + AtomicReference callbackResult = new AtomicReference<>(true); + + ImmediateRequestMaker immediateRequestMaker = new ImmediateRequestMaker(); + immediateRequestMaker.doWork("test_event", "/o?", SDKCore.instance.networking.getTransport(), false, true, + (result) -> { + if (result == null) { + callbackResult.set(false); + } + }, L); + + Thread.sleep(2000); // wait for background thread to finish + Assert.assertFalse(callbackResult.get()); // check if callback was called and response is null + } +} diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index e393c1ce3..8f599d8e9 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -10,6 +10,7 @@ import java.util.Map; import java.util.Scanner; import java.util.stream.Stream; +import ly.count.sdk.java.Config; import static ly.count.sdk.java.internal.SDKStorage.EVENT_QUEUE_FILE_NAME; import static ly.count.sdk.java.internal.SDKStorage.FILE_NAME_PREFIX; @@ -17,11 +18,43 @@ public class TestUtils { - private static String DELIMETER = ":::"; + static String DELIMETER = ":::"; + static String SERVER_URL = "https://try.count.ly"; + static String SERVER_APP_KEY = "COUNTLY_APP_KEY"; + static String DEVICE_ID = "some_random_test_device_id"; private TestUtils() { } + static Config getBaseConfig() { + File sdkStorageRootDirectory = getSdkStorageRootDirectory(); + checkSdkStorageRootDirectoryExist(sdkStorageRootDirectory); + Config config = new Config(SERVER_URL, SERVER_APP_KEY, sdkStorageRootDirectory); + config.setCustomDeviceId(DEVICE_ID); + + return config; + } + + static Config getVariantConfig(ImmediateRequestGenerator generator) { + Config config = getBaseConfig(); + InternalConfig internalConfig = new InternalConfig(config); + + internalConfig.immediateRequestGenerator = generator; + return config; + } + + static File getSdkStorageRootDirectory() { + // System specific folder structure + String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; + return new File(String.join(File.separator, sdkStorageRootPath)); + } + + static void checkSdkStorageRootDirectoryExist(File directory) { + if (directory.mkdirs()) { + throw new RuntimeException("Directory creation failed"); + } + } + /** * Get current request queue from target folder * @@ -80,7 +113,6 @@ protected static List getCurrentEventQueue(File targetFolder, Log log //do nothing } - //EventImplQueue.DELIMITER Arrays.stream(fileContent.split(DELIMETER)).forEach(s -> { final EventImpl event = EventImpl.fromJSON(s, (ev) -> { }, logger); From 3c2fa8eb29068e0757c76a10bfaf0df4c1cd0759 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 17:00:45 +0300 Subject: [PATCH 05/58] fix: stop countly --- .../java/ly/count/sdk/java/internal/ImmediateRequestTest.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java index 7c798c6c2..0d4556b6b 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java @@ -35,5 +35,7 @@ public void doWork_null() throws InterruptedException { Thread.sleep(2000); // wait for background thread to finish Assert.assertFalse(callbackResult.get()); // check if callback was called and response is null + + Countly.stop(true); } } From 3659d4779a62b058bf641b1328f94fc6d624a5f9 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 17:19:32 +0300 Subject: [PATCH 06/58] fix: directory creatioon --- .../test/java/ly/count/sdk/java/internal/TestUtils.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 8f599d8e9..beeaeee83 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -50,11 +50,13 @@ static File getSdkStorageRootDirectory() { } static void checkSdkStorageRootDirectoryExist(File directory) { - if (directory.mkdirs()) { - throw new RuntimeException("Directory creation failed"); + if (!(directory.exists() && directory.isDirectory())) { + if (!directory.mkdirs()) { + throw new RuntimeException("Directory creation failed"); + } } } - + /** * Get current request queue from target folder * From 9e39ad9b4dfbcab09a26c1e519821cec465285f2 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 25 Sep 2023 17:24:28 +0300 Subject: [PATCH 07/58] fix: folder checks --- .../count/java/demo/BackendModeExample.java | 11 ++++---- .../demo/BackendModePerformanceTests.java | 13 +++++---- .../main/java/ly/count/java/demo/Example.java | 7 +++-- .../sdk/java/internal/BackendModeTests.java | 28 ++++++++++--------- .../ly/count/sdk/java/internal/TestUtils.java | 2 +- 5 files changed, 33 insertions(+), 28 deletions(-) diff --git a/app-java/src/main/java/ly/count/java/demo/BackendModeExample.java b/app-java/src/main/java/ly/count/java/demo/BackendModeExample.java index 91ee51d4c..0e1697827 100644 --- a/app-java/src/main/java/ly/count/java/demo/BackendModeExample.java +++ b/app-java/src/main/java/ly/count/java/demo/BackendModeExample.java @@ -1,13 +1,12 @@ package ly.count.java.demo; -import ly.count.sdk.java.Config; -import ly.count.sdk.java.Countly; - import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Scanner; import java.util.concurrent.CountDownLatch; +import ly.count.sdk.java.Config; +import ly.count.sdk.java.Countly; public class BackendModeExample { final static String DEVICE_ID = "device-id"; @@ -314,8 +313,10 @@ public static void main(String[] args) throws Exception { String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; File sdkStorageRootDirectory = new File(String.join(File.separator, sdkStorageRootPath)); - if(sdkStorageRootDirectory.mkdirs()){ - System.out.println("Directory creation failed"); + if (!(sdkStorageRootDirectory.exists() && sdkStorageRootDirectory.isDirectory())) { + if (!sdkStorageRootDirectory.mkdirs()) { + System.out.println("Directory creation failed"); + } } // Main initialization call, SDK can be used after this one is done diff --git a/app-java/src/main/java/ly/count/java/demo/BackendModePerformanceTests.java b/app-java/src/main/java/ly/count/java/demo/BackendModePerformanceTests.java index 7645fe756..1736a21b1 100644 --- a/app-java/src/main/java/ly/count/java/demo/BackendModePerformanceTests.java +++ b/app-java/src/main/java/ly/count/java/demo/BackendModePerformanceTests.java @@ -1,13 +1,12 @@ package ly.count.java.demo; -import ly.count.sdk.java.Config; -import ly.count.sdk.java.Countly; -import ly.count.sdk.java.internal.Device; - import java.io.File; import java.util.HashMap; import java.util.Map; import java.util.Scanner; +import ly.count.sdk.java.Config; +import ly.count.sdk.java.Countly; +import ly.count.sdk.java.internal.Device; public class BackendModePerformanceTests { final static String DEVICE_ID = "device-id"; @@ -27,8 +26,10 @@ private static void initSDK(int eventQueueSize, int requestQueueSize) { String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; File sdkStorageRootDirectory = new File(String.join(File.separator, sdkStorageRootPath)); - if(sdkStorageRootDirectory.mkdirs()){ - System.out.println("Directory creation failed"); + if (!(sdkStorageRootDirectory.exists() && sdkStorageRootDirectory.isDirectory())) { + if (!sdkStorageRootDirectory.mkdirs()) { + System.out.println("Directory creation failed"); + } } // Main initialization call, SDK can be used after this one is done diff --git a/app-java/src/main/java/ly/count/java/demo/Example.java b/app-java/src/main/java/ly/count/java/demo/Example.java index 4367ab115..8278e9d55 100755 --- a/app-java/src/main/java/ly/count/java/demo/Example.java +++ b/app-java/src/main/java/ly/count/java/demo/Example.java @@ -4,7 +4,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Scanner; - import ly.count.sdk.java.Config; import ly.count.sdk.java.Countly; import ly.count.sdk.java.internal.LogCallback; @@ -125,8 +124,10 @@ public static void main(String[] args) throws Exception { String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; File sdkStorageRootDirectory = new File(String.join(File.separator, sdkStorageRootPath)); - if(sdkStorageRootDirectory.mkdirs()){ - System.out.println("Directory creation failed"); + if (!(sdkStorageRootDirectory.exists() && sdkStorageRootDirectory.isDirectory())) { + if (!sdkStorageRootDirectory.mkdirs()) { + System.out.println("Directory creation failed"); + } } Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/BackendModeTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/BackendModeTests.java index 7ff3ff78c..289771702 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/BackendModeTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/BackendModeTests.java @@ -1,19 +1,25 @@ package ly.count.sdk.java.internal; +import java.io.File; +import java.io.PrintWriter; +import java.io.StringWriter; +import java.math.BigDecimal; +import java.util.Calendar; +import java.util.HashMap; +import java.util.Map; import ly.count.sdk.java.Config; import ly.count.sdk.java.Countly; import org.json.JSONArray; import org.json.JSONObject; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import java.io.File; -import java.io.PrintWriter; -import java.io.StringWriter; -import java.math.BigDecimal; -import java.util.*; - @RunWith(JUnit4.class) public class BackendModeTests { private ModuleBackendMode moduleBackendMode; @@ -25,12 +31,8 @@ public static void init() { cc.setEventQueueSizeToSend(4).enableBackendMode(); // System specific folder structure - String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; - File sdkStorageRootDirectory = new File(String.join(File.separator, sdkStorageRootPath)); - - if(sdkStorageRootDirectory.mkdirs()){ - System.out.println("Directory creation failed"); - } + File sdkStorageRootDirectory = TestUtils.getSdkStorageRootDirectory(); + TestUtils.checkSdkStorageRootDirectoryExist(sdkStorageRootDirectory); Countly.init(sdkStorageRootDirectory, cc); } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index beeaeee83..4568f7b48 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -56,7 +56,7 @@ static void checkSdkStorageRootDirectoryExist(File directory) { } } } - + /** * Get current request queue from target folder * From 2bed30fb219b449cfdcd813cfd45a3660aedfdea Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 09:42:50 +0300 Subject: [PATCH 08/58] feat: add feedback to sdk core and countly --- sdk-java/src/main/java/ly/count/sdk/java/Config.java | 5 ++++- sdk-java/src/main/java/ly/count/sdk/java/Countly.java | 11 +++++++++++ .../main/java/ly/count/sdk/java/internal/SDKCore.java | 10 ++++++++++ 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Config.java b/sdk-java/src/main/java/ly/count/sdk/java/Config.java index 9fceca8b5..7aaba49e7 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Config.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Config.java @@ -81,7 +81,8 @@ public enum Feature { Views(CoreFeature.Views.getIndex()), CrashReporting(CoreFeature.CrashReporting.getIndex()), Location(CoreFeature.Location.getIndex()), - UserProfiles(CoreFeature.UserProfiles.getIndex()); + UserProfiles(CoreFeature.UserProfiles.getIndex()), + Feedback(CoreFeature.Feedback.getIndex()); // StarRating(1 << 12), // RemoteConfig(1 << 13), // PerformanceMonitoring(1 << 14); @@ -115,6 +116,8 @@ public static Config.Feature byIndex(int index) { // return RemoteConfig; // } else if (index == PerformanceMonitoring.index) { // return PerformanceMonitoring; + } else if (index == Feedback.index) { + return Feedback; } else { return null; } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java index d337020d6..b1917f0a9 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java @@ -7,6 +7,7 @@ import ly.count.sdk.java.internal.InternalConfig; import ly.count.sdk.java.internal.Log; import ly.count.sdk.java.internal.ModuleBackendMode; +import ly.count.sdk.java.internal.ModuleFeedback; import ly.count.sdk.java.internal.SDKCore; /** @@ -315,6 +316,16 @@ public static void onConsentRemoval(Config.Feature... features) { } } + /** + * Feedback interface to use feedback widgets nps,rating and forms. + * And do manual reporting of feedbacks. + * + * @return {@link ModuleFeedback.Feedback} instance. + */ + public ModuleFeedback.Feedback feedback() { + return sdk.feedback(); + } + @Override public Event event(String key) { L.d("[Countly] event: key = " + key); diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java index fb5b6be98..60338f4ad 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java @@ -23,6 +23,16 @@ public class SDKCore { protected final Object lockBRQStorage = new Object(); + public ModuleFeedback.Feedback feedback() { + + if (!hasConsentForFeature(CoreFeature.Feedback)) { + L.v("[SDKCore] feedback: Feedback feature has no consent, returning null"); + return null; + } + + return module(ModuleFeedback.class).feedbackInterface; + } + public enum Signal { DID(1), Crash(2), From 2664bb25d1679ed94742cabe787bb85592d3dd95 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 09:49:16 +0300 Subject: [PATCH 09/58] doc: comments for new ir --- .../sdk/java/internal/ImmediateRequestGenerator.java | 12 ++++++++++++ .../count/sdk/java/internal/ImmediateRequestI.java | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java index cba56a0c2..5f2d2a466 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestGenerator.java @@ -1,5 +1,17 @@ package ly.count.sdk.java.internal; +/** + * Interface for creating ImmediateRequestMaker + * a basic factory pattern implementation + * + * @see ImmediateRequestI + */ public interface ImmediateRequestGenerator { + /** + * Create a new instance of ImmediateRequestI + * and override when needed + * + * @return new instance of ImmediateRequestI + */ ImmediateRequestI createImmediateRequestMaker(); } diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java index c91bc3c24..c25cad2e1 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestI.java @@ -1,5 +1,17 @@ package ly.count.sdk.java.internal; public interface ImmediateRequestI { + + /** + * Do the work of making the request directly without waiting for the queue. + * + * @param requestData query parameters + * @param customEndpoint custom endpoint to use instead of the default one + * @param cp transport to use when making the request + * @param requestShouldBeDelayed whether the request should be delayed or not + * @param networkingIsEnabled whether networking is enabled or not + * @param callback callback to call when the request is done + * @param log logger to use + */ void doWork(String requestData, String customEndpoint, Transport cp, boolean requestShouldBeDelayed, boolean networkingIsEnabled, ImmediateRequestMaker.InternalImmediateRequestCallback callback, Log log); } From 993c2918420c6b0b58529f748763107b17cc7122 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 09:58:41 +0300 Subject: [PATCH 10/58] doc: add changelog entry for feedback call --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa72ce19..6d05598b4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,8 @@ * Deprecated call "resetDeviceId" is removed * Deprecated the init time configuration of 'setEventsBufferSize(eventsBufferSize)'. Introduced replacement 'setEventQueueSizeToSend(eventsQueueSize)' * Deprecated the init time configuration of 'setSendUpdateEachSeconds(sendUpdateEachSeconds)'. Introduced replacement 'setUpdateSessionTimerDelay(delay)' +* Added feedback widgets and manual reporting. Added consent for it "Config.Feature.Feedback". +* Feedback module is accessible through "Countly::instance()::feedback()" call. * !! Major breaking change !! The following methods and their functionality are deprecated from the "UserEditor" interface and will not function anymore: * "addToCohort(key)" From 9a760297c25571d22d7e4597af66c1b01cc477bc Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 11:24:33 +0300 Subject: [PATCH 11/58] feat: get widgets --- .../sdk/java/internal/ModuleFeedback.java | 114 +++++++++++++++++- .../sdk/java/internal/ModuleRequests.java | 9 +- 2 files changed, 120 insertions(+), 3 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index ee485c3ce..83e9679ba 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -1,10 +1,12 @@ package ly.count.sdk.java.internal; +import java.util.ArrayList; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; import javax.annotation.Nullable; import ly.count.sdk.java.Countly; +import org.json.JSONArray; import org.json.JSONObject; public class ModuleFeedback extends ModuleBase { @@ -68,7 +70,117 @@ public void stop(CtxCore ctx, boolean clear) { } private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callback) { - callback.onFinished(null, ""); + L.d("[ModuleFeedback] getAvailableFeedbackWidgetsInternal, callback set:[" + (callback != null) + "]"); + + if (callback == null) { + L.e("[ModuleFeedback] available feedback widget list can't be retrieved without a callback"); + return; + } + + // If someday we decide to support temporary device ID mode, this check will be needed + //if (internalConfig.isTemporaryIdEnabled()) { + // L.e("[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + // callback.onFinished(null, "[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + // return; + //} + + Transport transport = ctx.getSDK().networking.getTransport(); + final boolean networkingIsEnabled = true; // this feature is not yet implemented + + Request request = new Request(); + ModuleRequests.addRequiredTimeParams(request); + ModuleRequests.addRequired(internalConfig, request); + request.params.add("method", "feedback"); + String requestData = request.params.toString(); + + ImmediateRequestGenerator iRGenerator = internalConfig.immediateRequestGenerator; + + iRGenerator.createImmediateRequestMaker().doWork(requestData, "/o/sdk", transport, false, networkingIsEnabled, checkResponse -> { + if (checkResponse == null) { + L.d("[ModuleFeedback] Not possible to retrieve widget list. Probably due to lack of connection to the server"); + callback.onFinished(null, "Not possible to retrieve widget list. Probably due to lack of connection to the server"); + return; + } + + L.d("[ModuleFeedback] Retrieved request: [" + checkResponse + "]"); + + List feedbackEntries = parseFeedbackList(checkResponse); + + callback.onFinished(feedbackEntries, null); + }, L); + } + + static List parseFeedbackList(JSONObject requestResponse) { + Log L = SDKCore.instance.L; + L.d("[ModuleFeedback] calling 'parseFeedbackList'"); + + List parsedRes = new ArrayList<>(); + try { + if (requestResponse != null) { + JSONArray jArray = requestResponse.optJSONArray("result"); + + if (jArray == null) { + L.w("[ModuleFeedback] parseFeedbackList, response does not have a valid 'result' entry. No widgets retrieved."); + return parsedRes; + } + + for (int a = 0; a < jArray.length(); a++) { + try { + JSONObject jObj = jArray.getJSONObject(a); + + String valId = jObj.optString("_id", ""); + String valType = jObj.optString("type", ""); + String valName = jObj.optString("name", ""); + List valTagsArr = new ArrayList(); + + JSONArray jTagArr = jObj.optJSONArray("tg"); + if (jTagArr == null) { + L.w("[ModuleFeedback] parseFeedbackList, no tags received"); + } else { + for (int in = 0; in < jTagArr.length(); in++) { + valTagsArr.add(jTagArr.getString(in)); + } + } + + if (valId.isEmpty()) { + L.e("[ModuleFeedback] parseFeedbackList, retrieved invalid entry with null or empty widget id, dropping"); + continue; + } + + if (valType.isEmpty()) { + L.e("[ModuleFeedback] parseFeedbackList, retrieved invalid entry with null or empty widget type, dropping"); + continue; + } + + FeedbackWidgetType plannedType; + if (valType.equals("survey")) { + plannedType = FeedbackWidgetType.SURVEY; + } else if (valType.equals("nps")) { + plannedType = FeedbackWidgetType.NPS; + } else if (valType.equals("rating")) { + plannedType = FeedbackWidgetType.RATING; + } else { + L.e("[ModuleFeedback] parseFeedbackList, retrieved unknown widget type, dropping"); + continue; + } + + CountlyFeedbackWidget se = new CountlyFeedbackWidget(); + se.type = plannedType; + se.widgetId = valId; + se.name = valName; + se.tags = valTagsArr.toArray(new String[0]); + + parsedRes.add(se); + } catch (Exception ex) { + L.e("[ModuleFeedback] parseFeedbackList, failed to parse json, [" + ex.toString() + "]"); + } + } + } + } catch (Exception ex) { + L.e("[ModuleFeedback] parseFeedbackList, Encountered exception while parsing feedback list, [" + ex.toString() + "]"); + } + + return parsedRes; } private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetInfo, JSONObject widgetData, Map widgetResult) { diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java index d83af81c7..22dec1b0a 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java @@ -1,7 +1,6 @@ package ly.count.sdk.java.internal; import java.util.concurrent.Future; - import ly.count.sdk.java.User; /** @@ -181,13 +180,19 @@ public static void injectParams(CtxCore ctx, ParamsInjector injector) { } } + static void addRequiredTimeParams(Request request) { + request.params.add("timestamp", Device.dev.uniqueTimestamp()) + .add("tz", Device.dev.getTimezoneOffset()) + .add("hour", Device.dev.currentHour()) + .add("dow", Device.dev.currentDayOfWeek()); + } + static Request addRequired(InternalConfig config, Request request) { if (request.isEmpty()) { //if nothing was in the request, no need to add these mandatory fields return request; } - //check if it has the device ID if (!request.params.has(Params.PARAM_DEVICE_ID)) { if (config.getDeviceId() == null) { From a372d79cb694c6230d3ddc0c82d0b827eff1079b Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 11:48:40 +0300 Subject: [PATCH 12/58] feat: get widget url --- .../sdk/java/internal/ModuleFeedback.java | 72 ++++++++++++++----- 1 file changed, 54 insertions(+), 18 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 83e9679ba..f7cf222ea 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -11,7 +11,7 @@ public class ModuleFeedback extends ModuleBase { - public enum FeedbackWidgetType {SURVEY, NPS, RATING} + public enum FeedbackWidgetType {survey, nps, rating} public static class CountlyFeedbackWidget { public String widgetId; @@ -153,13 +153,10 @@ static List parseFeedbackList(JSONObject requestResponse) } FeedbackWidgetType plannedType; - if (valType.equals("survey")) { - plannedType = FeedbackWidgetType.SURVEY; - } else if (valType.equals("nps")) { - plannedType = FeedbackWidgetType.NPS; - } else if (valType.equals("rating")) { - plannedType = FeedbackWidgetType.RATING; - } else { + + try { + plannedType = FeedbackWidgetType.valueOf(valType); + } catch (Exception ex) { L.e("[ModuleFeedback] parseFeedbackList, retrieved unknown widget type, dropping"); continue; } @@ -188,11 +185,50 @@ private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetIn } private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, RetrieveFeedbackWidgetData callback) { - callback.onFinished(null, ""); } private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { - callback.onFinished("", ""); + if (widgetInfo == null) { + L.e("[ModuleFeedback] Can't present widget with null widget info"); + if (callback != null) { + callback.onFinished(null, "Can't present widget with null widget info"); + return; + } + } + + L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, callback set:[" + (callback != null) + ", widget id:[" + widgetInfo.widgetId + "], widget type:[" + widgetInfo.type + "]"); + + //if (internalConfig.isTemporaryIdEnabled()) { + // L.e("[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + // if (callback != null) { + // callback.onFinished(null,"[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + // } + // return; + //} + + StringBuilder widgetListUrl = new StringBuilder(); + widgetListUrl.append(internalConfig.getServerURL()); + widgetListUrl.append("/feedback/"); + widgetListUrl.append(widgetInfo.type.name()); + widgetListUrl.append("?widget_id="); + widgetListUrl.append(Utils.urlencode(widgetInfo.widgetId, L)); + widgetListUrl.append("&device_id="); + widgetListUrl.append(Utils.urlencode(internalConfig.getDeviceId().id, L)); + widgetListUrl.append("&app_key="); + widgetListUrl.append(Utils.urlencode(internalConfig.getServerAppKey(), L)); + widgetListUrl.append("&sdk_version="); + widgetListUrl.append(internalConfig.getSdkVersion()); + widgetListUrl.append("&sdk_name="); + widgetListUrl.append(internalConfig.getSdkName()); + widgetListUrl.append("&platform=desktop"); + + final String preparedWidgetUrl = widgetListUrl.toString(); + + L.d("[ModuleFeedback] Using following url for widget:[" + widgetListUrl + "]"); + + if (callback != null) { + callback.onFinished(preparedWidgetUrl, null); + } } public class Feedback { @@ -200,7 +236,7 @@ public class Feedback { /** * Get a list of available feedback widgets for this device ID * - * @param callback + * @param callback callback */ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callback) { synchronized (Countly.instance()) { @@ -213,8 +249,8 @@ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callba /** * Construct a URL that can be used to present a feedback widget in a web viewer * - * @param widgetInfo - * @param callback + * @param widgetInfo widget info + * @param callback callback */ public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable FeedbackCallback callback) { synchronized (Countly.instance()) { @@ -228,8 +264,8 @@ public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInf * Download data for a specific widget so that it can be displayed with a custom UI * When requesting this data, it will count as a shown widget (will increment that "shown" count in the dashboard) * - * @param widgetInfo - * @param callback + * @param widgetInfo widget info + * @param callback callback */ public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable RetrieveFeedbackWidgetData callback) { synchronized (Countly.instance()) { @@ -243,9 +279,9 @@ public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @N * Manually report a feedback widget in case the client used a custom interface * In case widgetResult is passed as "null," it would be assumed that the widget was canceled * - * @param widgetInfo - * @param widgetData - * @param widgetResult + * @param widgetInfo widget info + * @param widgetData widget data + * @param widgetResult widget result */ public void reportFeedbackWidgetManually(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable JSONObject widgetData, @Nullable Map widgetResult) { synchronized (Countly.instance()) { From 07a9151f05a8e9fe30bb3cdc2bcaf7d49e6e78ff Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 11:50:42 +0300 Subject: [PATCH 13/58] doc: remake comments --- .../sdk/java/internal/ModuleFeedback.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index f7cf222ea..b2b39a445 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -73,7 +73,7 @@ private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callbac L.d("[ModuleFeedback] getAvailableFeedbackWidgetsInternal, callback set:[" + (callback != null) + "]"); if (callback == null) { - L.e("[ModuleFeedback] available feedback widget list can't be retrieved without a callback"); + L.e("[ModuleFeedback] getAvailableFeedbackWidgetsInternal, available feedback widget list can't be retrieved without a callback"); return; } @@ -97,7 +97,7 @@ private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callbac iRGenerator.createImmediateRequestMaker().doWork(requestData, "/o/sdk", transport, false, networkingIsEnabled, checkResponse -> { if (checkResponse == null) { - L.d("[ModuleFeedback] Not possible to retrieve widget list. Probably due to lack of connection to the server"); + L.d("[ModuleFeedback] getAvailableFeedbackWidgetsInternal, Not possible to retrieve widget list. Probably due to lack of connection to the server"); callback.onFinished(null, "Not possible to retrieve widget list. Probably due to lack of connection to the server"); return; } @@ -112,7 +112,7 @@ private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callbac static List parseFeedbackList(JSONObject requestResponse) { Log L = SDKCore.instance.L; - L.d("[ModuleFeedback] calling 'parseFeedbackList'"); + L.d("[ModuleFeedback] parseFeedbackList, calling"); List parsedRes = new ArrayList<>(); try { @@ -189,7 +189,7 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Ret private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { if (widgetInfo == null) { - L.e("[ModuleFeedback] Can't present widget with null widget info"); + L.e("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Can't present widget with null widget info"); if (callback != null) { callback.onFinished(null, "Can't present widget with null widget info"); return; @@ -224,7 +224,7 @@ private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo final String preparedWidgetUrl = widgetListUrl.toString(); - L.d("[ModuleFeedback] Using following url for widget:[" + widgetListUrl + "]"); + L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Using following url for widget:[" + widgetListUrl + "]"); if (callback != null) { callback.onFinished(preparedWidgetUrl, null); @@ -240,7 +240,7 @@ public class Feedback { */ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callback) { synchronized (Countly.instance()) { - L.i("[Feedback] Trying to retrieve feedback widget list"); + L.i("[Feedback] getAvailableFeedbackWidgets, Trying to retrieve feedback widget list"); getAvailableFeedbackWidgetsInternal(callback); } @@ -254,7 +254,7 @@ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callba */ public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable FeedbackCallback callback) { synchronized (Countly.instance()) { - L.i("[Feedback] Trying to present feedback widget in an alert dialog"); + L.i("[Feedback] constructFeedbackWidgetUrl, Trying to present feedback widget in an alert dialog"); constructFeedbackWidgetUrlInternal(widgetInfo, callback); } @@ -269,7 +269,7 @@ public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInf */ public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable RetrieveFeedbackWidgetData callback) { synchronized (Countly.instance()) { - L.i("[Feedback] Trying to retrieve feedback widget data"); + L.i("[Feedback] getFeedbackWidgetData, Trying to retrieve feedback widget data"); getFeedbackWidgetDataInternal(widgetInfo, callback); } @@ -285,7 +285,7 @@ public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @N */ public void reportFeedbackWidgetManually(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable JSONObject widgetData, @Nullable Map widgetResult) { synchronized (Countly.instance()) { - L.i("[Feedback] Trying to report feedback widget manually"); + L.i("[Feedback] reportFeedbackWidgetManually, Trying to report feedback widget manually"); reportFeedbackWidgetManuallyInternal(widgetInfo, widgetData, widgetResult); } From f08052357b66fd384933e26e2ce5b5a60771473e Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 12:20:53 +0300 Subject: [PATCH 14/58] feat: get feedback data --- .../sdk/java/internal/InternalConfig.java | 20 ++++ .../sdk/java/internal/ModuleFeedback.java | 101 ++++++++++++++---- 2 files changed, 98 insertions(+), 23 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java index c65efe240..9e84e002c 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/InternalConfig.java @@ -348,6 +348,26 @@ public void setDefaultNetworking(boolean defaultNetworking) { this.defaultNetworking = defaultNetworking; } + /** + * This feature is not yet implemented + * and always return true + * + * @return true + */ + public boolean getNetworkingEnabled() { + return true; + } + + /** + * This feature is not yet implemented + * If someday we decide to support temporary device ID mode + * + * @return false + */ + public boolean isTemporaryIdEnabled() { + return false; + } + //region rating module public long getRatingWidgetTimeout() { return ratingWidgetTimeout; diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index b2b39a445..e6dfca0d0 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -48,7 +48,7 @@ public void init(InternalConfig config, Log logger) { L.v("[ModuleFeedback] Initializing"); super.init(config, logger); - cachedAppVersion = Device.dev.getAppVersion(); + cachedAppVersion = config.getApplicationVersion(); feedbackInterface = new Feedback(); } @@ -78,14 +78,14 @@ private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callbac } // If someday we decide to support temporary device ID mode, this check will be needed - //if (internalConfig.isTemporaryIdEnabled()) { - // L.e("[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); - // callback.onFinished(null, "[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); - // return; - //} + if (internalConfig.isTemporaryIdEnabled()) { + L.e("[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + callback.onFinished(null, "[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); + return; + } Transport transport = ctx.getSDK().networking.getTransport(); - final boolean networkingIsEnabled = true; // this feature is not yet implemented + final boolean networkingIsEnabled = internalConfig.getNetworkingEnabled(); Request request = new Request(); ModuleRequests.addRequiredTimeParams(request); @@ -185,26 +185,83 @@ private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetIn } private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, RetrieveFeedbackWidgetData callback) { - } + L.d("[ModuleFeedback] calling 'getFeedbackWidgetDataInternal', callback set:[" + (callback != null) + "]"); - private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { - if (widgetInfo == null) { - L.e("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Can't present widget with null widget info"); + String error = validateFields(callback, widgetInfo); + + if (error != null) { + String errorLog = "[ModuleFeedback] getFeedbackWidgetDataInternal, " + error; + L.e(errorLog); if (callback != null) { - callback.onFinished(null, "Can't present widget with null widget info"); + callback.onFinished(null, errorLog); + } + return; + } + + StringBuilder requestData = new StringBuilder(); + String widgetDataEndpoint = "/o/surveys/" + widgetInfo.type.name() + "/widget"; + + requestData.append("widget_id="); + requestData.append(Utils.urlencode(widgetInfo.widgetId, L)); + requestData.append("&shown=1"); + requestData.append("&sdk_version="); + requestData.append(internalConfig.getSdkVersion()); + requestData.append("&sdk_name="); + requestData.append(internalConfig.getSdkName()); + requestData.append("&platform=desktop"); + requestData.append("&app_version="); + requestData.append(cachedAppVersion); + + Transport cp = ctx.getSDK().networking.getTransport(); + final boolean networkingIsEnabled = internalConfig.getNetworkingEnabled(); + String requestDataStr = requestData.toString(); + + L.d("[ModuleFeedback] getFeedbackWidgetDataInternal, Using following request params for retrieving widget data:[" + requestDataStr + "]"); + + ImmediateRequestGenerator iRGenerator = internalConfig.immediateRequestGenerator; + + iRGenerator.createImmediateRequestMaker().doWork(requestDataStr, widgetDataEndpoint, cp, false, networkingIsEnabled, checkResponse -> { + if (checkResponse == null) { + L.d("[ModuleFeedback] getFeedbackWidgetDataInternal, Not possible to retrieve widget data. Probably due to lack of connection to the server"); + callback.onFinished(null, "Not possible to retrieve widget data. Probably due to lack of connection to the server"); return; } + + L.d("[ModuleFeedback] getFeedbackWidgetDataInternal, Retrieved widget data request: [" + checkResponse + "]"); + + callback.onFinished(checkResponse, null); + }, L); + } + + private String validateFields(Object callback, CountlyFeedbackWidget widget) { + if (callback == null) { + return "Can't continue operation with null callback"; } - L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, callback set:[" + (callback != null) + ", widget id:[" + widgetInfo.widgetId + "], widget type:[" + widgetInfo.type + "]"); + if (widget == null) { + return "Can't continue operation with null widget"; + } - //if (internalConfig.isTemporaryIdEnabled()) { - // L.e("[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); - // if (callback != null) { - // callback.onFinished(null,"[ModuleFeedback] available feedback widget list can't be retrieved when in temporary device ID mode"); - // } - // return; - //} + if (internalConfig.isTemporaryIdEnabled()) { + return "Can't continue operation when in temporary device ID mode"; + } + + return null; + } + + private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { + L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, callback set:[" + (callback != null) + ", widgetInfo :[" + widgetInfo + "]"); + + String error = validateFields(callback, widgetInfo); + + if (error != null) { + String errorLog = "[ModuleFeedback] constructFeedbackWidgetUrlInternal, " + error; + L.e(errorLog); + if (callback != null) { + callback.onFinished(null, errorLog); + } + return; + } StringBuilder widgetListUrl = new StringBuilder(); widgetListUrl.append(internalConfig.getServerURL()); @@ -226,9 +283,7 @@ private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Using following url for widget:[" + widgetListUrl + "]"); - if (callback != null) { - callback.onFinished(preparedWidgetUrl, null); - } + callback.onFinished(preparedWidgetUrl, null); } public class Feedback { From 8dfa5ec816a16d981c2343f86b4bfbbefca875b9 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 12:32:27 +0300 Subject: [PATCH 15/58] feat: manual widget reporting --- .../sdk/java/internal/ModuleFeedback.java | 132 +++++++++++++++++- 1 file changed, 127 insertions(+), 5 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index e6dfca0d0..7af5e6d34 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -1,6 +1,8 @@ package ly.count.sdk.java.internal; import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map; import javax.annotation.Nonnull; @@ -11,7 +13,17 @@ public class ModuleFeedback extends ModuleBase { - public enum FeedbackWidgetType {survey, nps, rating} + public enum FeedbackWidgetType { + survey("[CLY]_survey"), + nps("[CLY]_nps"), + rating("[CLY]_star_rating"); + + final String eventKey; + + FeedbackWidgetType(String eventKey) { + this.eventKey = eventKey; + } + } public static class CountlyFeedbackWidget { public String widgetId; @@ -20,10 +32,6 @@ public static class CountlyFeedbackWidget { public String[] tags; } - final static String NPS_EVENT_KEY = "[CLY]_nps"; - final static String SURVEY_EVENT_KEY = "[CLY]_survey"; - final static String RATING_EVENT_KEY = "[CLY]_star_rating"; - String cachedAppVersion; Feedback feedbackInterface = null; private CtxCore ctx; @@ -181,7 +189,121 @@ static List parseFeedbackList(JSONObject requestResponse) } private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetInfo, JSONObject widgetData, Map widgetResult) { + if (widgetInfo == null) { + L.e("[ModuleFeedback] Can't report feedback widget data manually with 'null' widget info"); + return; + } + + L.d("[ModuleFeedback] reportFeedbackWidgetManuallyInternal, widgetData set:[" + (widgetData != null) + ", widget id:[" + widgetInfo.widgetId + "], widget type:[" + widgetInfo.type + "], widget result set:[" + (widgetResult != null) + "]"); + + if (internalConfig.isTemporaryIdEnabled()) { + L.e("[ModuleFeedback] feedback widget result can't be reported when in temporary device ID mode"); + return; + } + + if (widgetResult != null) { + //removing broken values first + Iterator> iter = widgetResult.entrySet().iterator(); + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + if (entry.getKey() == null) { + L.w("[ModuleFeedback] provided feedback widget result contains a 'null' key, it will be removed, value[" + entry.getValue() + "]"); + iter.remove(); + } else if (entry.getKey().isEmpty()) { + L.w("[ModuleFeedback] provided feedback widget result contains an empty string key, it will be removed, value[" + entry.getValue() + "]"); + iter.remove(); + } else if (entry.getValue() == null) { + L.w("[ModuleFeedback] provided feedback widget result contains a 'null' value, it will be removed, key[" + entry.getKey() + "]"); + iter.remove(); + } + } + + if (widgetInfo.type == FeedbackWidgetType.nps) { + //in case a nps widget was completed + if (!widgetResult.containsKey("rating")) { + L.e("Provided NPS widget result does not have a 'rating' field, result can't be reported"); + return; + } + + //check rating data type + Object ratingValue = widgetResult.get("rating"); + if (!(ratingValue instanceof Integer)) { + L.e("Provided NPS widget 'rating' field is not an integer, result can't be reported"); + return; + } + + //check rating value range + int ratingValI = (int) ratingValue; + if (ratingValI < 0 || ratingValI > 10) { + L.e("Provided NPS widget 'rating' value is out of bounds of the required value '[0;10]', it is probably an error"); + } + + if (!widgetResult.containsKey("comment")) { + L.w("Provided NPS widget result does not have a 'comment' field"); + } + } else if (widgetInfo.type == FeedbackWidgetType.survey) { + //in case a survey widget was completed + } else if (widgetInfo.type == FeedbackWidgetType.rating) { + //in case a rating widget was completed + if (!widgetResult.containsKey("rating")) { + L.e("Provided Rating widget result does not have a 'rating' field, result can't be reported"); + return; + } + + //check rating data type + Object ratingValue = widgetResult.get("rating"); + if (!(ratingValue instanceof Integer)) { + L.e("Provided Rating widget 'rating' field is not an integer, result can't be reported"); + return; + } + + //check rating value range + int ratingValI = (int) ratingValue; + if (ratingValI < 1 || ratingValI > 5) { + L.e("Provided Rating widget 'rating' value is out of bounds of the required value '[1;5]', it is probably an error"); + } + } + } + + if (widgetData == null) { + L.d("[ModuleFeedback] reportFeedbackWidgetManuallyInternal, widgetInfo is 'null', no validation will be done"); + } else { + //perform data validation + + String idInData = widgetData.optString("_id"); + + if (!widgetInfo.widgetId.equals(idInData)) { + L.w("[ModuleFeedback] id in widget info does not match the id in widget data"); + } + + String typeInData = widgetData.optString("type"); + + if (!widgetInfo.type.name().equals(typeInData)) { + L.w("[ModuleFeedback] type in widget info [" + typeInData + "] does not match the type in widget data [" + widgetInfo.type.name() + "]"); + } + } + + Map segm = new HashMap<>(); + segm.put("platform", "desktop"); + segm.put("app_version", cachedAppVersion); + segm.put("widget_id", widgetInfo.widgetId); + + if (widgetResult == null) { + //mark as closed + segm.put("closed", "1"); + } else { + //widget was filled out + //merge given segmentation + segm.putAll(widgetResult); + } + + //TODO This code block should be replaced when remaked event module merged + Map segmString = new HashMap<>(); + for (Map.Entry entry : segm.entrySet()) { + segmString.put(entry.getKey(), entry.getValue().toString()); + } + Countly.instance().event(widgetInfo.type.eventKey).setSegmentation(segmString).record(); } private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, RetrieveFeedbackWidgetData callback) { From ed66c183549c1cb0b4e0380dadec480ad32bed41 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 13:14:22 +0300 Subject: [PATCH 16/58] feat: register module mapping of feedbacks --- .../sdk/java/internal/ModuleFeedback.java | 2 +- .../ly/count/sdk/java/internal/SDKCore.java | 21 ++++++++++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 7af5e6d34..dea0892df 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -53,8 +53,8 @@ public interface FeedbackCallback { @Override public void init(InternalConfig config, Log logger) { - L.v("[ModuleFeedback] Initializing"); super.init(config, logger); + L.v("[ModuleFeedback] Initializing"); cachedAppVersion = config.getApplicationVersion(); feedbackInterface = new Feedback(); diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java index 60338f4ad..3fac74c95 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/SDKCore.java @@ -23,16 +23,6 @@ public class SDKCore { protected final Object lockBRQStorage = new Object(); - public ModuleFeedback.Feedback feedback() { - - if (!hasConsentForFeature(CoreFeature.Feedback)) { - L.v("[SDKCore] feedback: Feedback feature has no consent, returning null"); - return null; - } - - return module(ModuleFeedback.class).feedbackInterface; - } - public enum Signal { DID(1), Crash(2), @@ -67,6 +57,7 @@ protected static void registerDefaultModuleMappings() { moduleMappings.put(CoreFeature.Sessions.getIndex(), ModuleSessions.class); moduleMappings.put(CoreFeature.CrashReporting.getIndex(), ModuleCrash.class); moduleMappings.put(CoreFeature.BackendMode.getIndex(), ModuleBackendMode.class); + moduleMappings.put(CoreFeature.Feedback.getIndex(), ModuleFeedback.class); } public interface Modulator { @@ -379,6 +370,16 @@ public SessionImpl getSession() { return null; } + public ModuleFeedback.Feedback feedback() { + + if (!hasConsentForFeature(CoreFeature.Feedback)) { + L.v("[SDKCore] feedback: Feedback feature has no consent, returning null"); + return null; + } + + return module(ModuleFeedback.class).feedbackInterface; + } + /** * Get current {@link SessionImpl} or create new one if current is {@code null}. * From ec16c092a9cbb108c708972b9553d146f921e6ec Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 13:21:48 +0300 Subject: [PATCH 17/58] feat: add missing mark --- .../main/java/ly/count/sdk/java/internal/ModuleFeedback.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index dea0892df..5d1a1ba4e 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -99,7 +99,7 @@ private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callbac ModuleRequests.addRequiredTimeParams(request); ModuleRequests.addRequired(internalConfig, request); request.params.add("method", "feedback"); - String requestData = request.params.toString(); + String requestData = "?" + request.params.toString(); ImmediateRequestGenerator iRGenerator = internalConfig.immediateRequestGenerator; From ba97eddd6fe52daf19b319a839db96fa294149fe Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Tue, 26 Sep 2023 13:23:26 +0300 Subject: [PATCH 18/58] feat: add missing mark --- .../main/java/ly/count/sdk/java/internal/ModuleFeedback.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 5d1a1ba4e..d7675658f 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -323,7 +323,7 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Ret StringBuilder requestData = new StringBuilder(); String widgetDataEndpoint = "/o/surveys/" + widgetInfo.type.name() + "/widget"; - requestData.append("widget_id="); + requestData.append("?widget_id="); requestData.append(Utils.urlencode(widgetInfo.widgetId, L)); requestData.append("&shown=1"); requestData.append("&sdk_version="); From 8f80fae70e065ec929c432c00e67b9f3a1251de0 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 11:05:30 +0300 Subject: [PATCH 19/58] feat: feedback module test --- .../java/internal/CountlyFeedbackWidget.java | 22 ++ .../sdk/java/internal/FeedbackWidgetType.java | 13 ++ .../sdk/java/internal/ModuleFeedback.java | 19 -- .../java/internal/ModuleFeedbackTests.java | 199 ++++++++++++++++++ 4 files changed, 234 insertions(+), 19 deletions(-) create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/CountlyFeedbackWidget.java create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/FeedbackWidgetType.java create mode 100644 sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/CountlyFeedbackWidget.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/CountlyFeedbackWidget.java new file mode 100644 index 000000000..f68a3dfe9 --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/CountlyFeedbackWidget.java @@ -0,0 +1,22 @@ +package ly.count.sdk.java.internal; + +import java.util.Arrays; + +public class CountlyFeedbackWidget { + public String widgetId; + public FeedbackWidgetType type; + public String name; + public String[] tags; + + @Override + public boolean equals(Object o) { + if (!(o instanceof CountlyFeedbackWidget)) { + return false; + } + CountlyFeedbackWidget gonnaCompare = (CountlyFeedbackWidget) o; + + String str = widgetId + type + name + Arrays.toString(tags); + String str2 = gonnaCompare.widgetId + gonnaCompare.type + gonnaCompare.name + Arrays.toString(gonnaCompare.tags); + return str.equals(str2); + } +} diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/FeedbackWidgetType.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/FeedbackWidgetType.java new file mode 100644 index 000000000..dc5c637bd --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/FeedbackWidgetType.java @@ -0,0 +1,13 @@ +package ly.count.sdk.java.internal; + +public enum FeedbackWidgetType { + survey("[CLY]_survey"), + nps("[CLY]_nps"), + rating("[CLY]_star_rating"); + + final String eventKey; + + FeedbackWidgetType(String eventKey) { + this.eventKey = eventKey; + } +} diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index d7675658f..87c805423 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -13,25 +13,6 @@ public class ModuleFeedback extends ModuleBase { - public enum FeedbackWidgetType { - survey("[CLY]_survey"), - nps("[CLY]_nps"), - rating("[CLY]_star_rating"); - - final String eventKey; - - FeedbackWidgetType(String eventKey) { - this.eventKey = eventKey; - } - } - - public static class CountlyFeedbackWidget { - public String widgetId; - public FeedbackWidgetType type; - public String name; - public String[] tags; - } - String cachedAppVersion; Feedback feedbackInterface = null; private CtxCore ctx; diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java new file mode 100644 index 000000000..756aab7e9 --- /dev/null +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -0,0 +1,199 @@ +package ly.count.sdk.java.internal; + +import java.util.ArrayList; +import java.util.Comparator; +import java.util.List; +import ly.count.sdk.java.Config; +import ly.count.sdk.java.Countly; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; +import org.junit.After; +import org.junit.Assert; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class ModuleFeedbackTests { + + @After + public void stop() { + Countly.stop(true); + } + + private void init(Config cc) { + Countly.instance().init(cc); + } + + /** + * Parse feedback list response given null + * "parseFeedbackList" function should return empty list + * returned feedback widget list should be empty + */ + @Test + public void parseFeedbackList_null() throws JSONException { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + List result = ModuleFeedback.parseFeedbackList(null); + Assert.assertNotNull(result); + Assert.assertEquals(0, result.size()); + } + + /** + * Parse feedback list successfully + * "parseFeedbackList" function should return correct feedback widget list + * returned feedback widget list should have same widgets as in the response + */ + @Test + public void parseFeedbackList() throws JSONException { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + String requestJson = + "{\"result\":[{\"_id\":\"5f8c6f959627f99e8e7de746\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"sdfsdfdsf\",\"tg\":[\"/\"]},{\"_id\":\"5f8c6fd81ac8659e8846acf4\",\"type\":\"nps\",\"name\":\"fdsfsd\",\"tg\":[\"a\",\"0\"]},{\"_id\":\"5f97284635935cc338e78200\",\"type\":\"nps\",\"name\":\"fsdfsdf\",\"tg\":[]},{\"_id\":\"614871419f030e44be07d82f\",\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"tg\":[\"\\/\"],\"name\":\"ratingName1\"}]}"; + + JSONObject jObj = new JSONObject(requestJson); + + List ret = ModuleFeedback.parseFeedbackList(jObj); + Assert.assertNotNull(ret); + Assert.assertEquals(4, ret.size()); + + ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "sdfsdfdsf", "5f8c6f959627f99e8e7de746", new String[] { "/" }, ret.get(0)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "fdsfsd", "5f8c6fd81ac8659e8846acf4", new String[] { "a", "0" }, ret.get(1)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "fsdfsdf", "5f97284635935cc338e78200", new String[] {}, ret.get(2)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "ratingName1", "614871419f030e44be07d82f", new String[] { "/" }, ret.get(3)); + } + + /** + * Parse feedback list successfully, remove faulty widgets + * "parseFeedbackList" function should return correct feedback widget list without faulty widgets + * returned feedback widget list should not have faulty widgets + */ + @Test + public void parseFeedbackList_faulty() throws JSONException { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + // 9 widgets (3 from each) + // First variation => no 'tg' key + // Second variation => no 'name' key + // First variation => no '_id' key + String requestJson = + "{\"result\":[" + + "{\"_id\":\"survID1\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"surv1\"}," + + "{\"_id\":\"survID2\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"tg\":[\"/\"]}," + + "{\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"surv3\",\"tg\":[\"/\"]}," + + "{\"_id\":\"npsID1\",\"type\":\"nps\",\"name\":\"nps1\"}," + + "{\"_id\":\"npsID2\",\"type\":\"nps\",\"tg\":[]}," + + "{\"type\":\"nps\",\"name\":\"nps3\",\"tg\":[]}," + + "{\"_id\":\"ratingID1\",\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"name\":\"rating1\"}," + + "{\"_id\":\"ratingID2\",\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"tg\":[\"\\/\"]}," + + "{\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"tg\":[\"\\/\"],\"name\":\"rating3\"}" + + "]}"; + + JSONObject jObj = new JSONObject(requestJson); + + List ret = ModuleFeedback.parseFeedbackList(jObj); + Assert.assertNotNull(ret); + Assert.assertEquals(6, ret.size()); + + ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "surv1", "survID1", new String[] {}, ret.get(0)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "", "survID2", new String[] { "/" }, ret.get(1)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}, ret.get(2)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "", "npsID2", new String[] {}, ret.get(3)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}, ret.get(4)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "", "ratingID2", new String[] { "/" }, ret.get(5)); + } + + /** + * Getting feedback widget list successfully + * "getAvailableFeedbackWidgets" function should return correct feedback widget list + * returned feedback widget list should be equal to the expected feedback widget list + */ + @Test + public void getAvailableFeedbackWidgets() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + List widgets = new ArrayList<>(); + widgets.add(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {})); + widgets.add(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {})); + widgets.add(createFeedbackWidget(FeedbackWidgetType.survey, "surv1", "survID1", new String[] {})); + + JSONArray widgetsJson = new JSONArray(); + widgetsJson.put(createFeedbackWidgetJson(widgets.get(0))); + widgetsJson.put(createFeedbackWidgetJson(widgets.get(1))); + widgetsJson.put(createFeedbackWidgetJson(widgets.get(2))); + JSONObject responseJson = new JSONObject(); + responseJson.put("result", widgetsJson); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + callback.callback(responseJson); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + List widgetResponse = new ArrayList<>(); + Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { + Assert.assertNull(error); + Assert.assertEquals(3, response.size()); + widgetResponse.addAll(response); + }); + + widgetResponse.sort(Comparator.comparing(o -> o.widgetId)); + Assert.assertEquals(3, widgetResponse.size()); + Assert.assertEquals(widgetResponse, widgets); + } + + /** + * Getting feedback widget list errored + * "getAvailableFeedbackWidgets" function should return error message + * returned feedback widget list should be empty and error message should not be empty + */ + @Test + public void getAvailableFeedbackWidgets_null() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> + callback.callback(null); + + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { + Assert.assertNotNull(error); + Assert.assertNull(response); + Assert.assertEquals("Not possible to retrieve widget list. Probably due to lack of connection to the server", error); + }); + } + + private CountlyFeedbackWidget createFeedbackWidget(FeedbackWidgetType type, String name, String id, String[] tags) { + CountlyFeedbackWidget widget = new CountlyFeedbackWidget(); + widget.type = type; + widget.name = name; + widget.widgetId = id; + widget.tags = tags; + return widget; + } + + private JSONObject createFeedbackWidgetJson(CountlyFeedbackWidget widget) throws JSONException { + JSONObject widgetJson = new JSONObject(); + widgetJson.put("_id", widget.widgetId); + widgetJson.put("type", widget.type.toString()); + widgetJson.put("name", widget.name); + widgetJson.put("tg", widget.tags); + return widgetJson; + } + + private void ValidateReturnedFeedbackWidget(FeedbackWidgetType type, String wName, String wId, String[] wTags, CountlyFeedbackWidget fWidget) { + Assert.assertEquals(type, fWidget.type); + Assert.assertEquals(wName, fWidget.name); + Assert.assertEquals(wId, fWidget.widgetId); + Assert.assertArrayEquals(wTags, fWidget.tags); + } +} From b18f99ff8e74bb81cf9a0bc84befc2bca37a19a9 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:18:35 +0300 Subject: [PATCH 20/58] fix: add is init check --- sdk-java/src/main/java/ly/count/sdk/java/Countly.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java index b1917f0a9..58d027479 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java @@ -323,6 +323,12 @@ public static void onConsentRemoval(Config.Feature... features) { * @return {@link ModuleFeedback.Feedback} instance. */ public ModuleFeedback.Feedback feedback() { + if (!isInitialized()) { + if (L != null) { + L.e("[Countly] SDK is not initialized yet."); + } + return null; + } return sdk.feedback(); } From 36fb17045fabab0ed946ac8690d871088f89da30 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:21:21 +0300 Subject: [PATCH 21/58] feat: usage of timed req --- .../java/ly/count/sdk/java/internal/ModuleRequests.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java index 22dec1b0a..868075230 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java @@ -193,6 +193,7 @@ static Request addRequired(InternalConfig config, Request request) { //if nothing was in the request, no need to add these mandatory fields return request; } + //check if it has the device ID if (!request.params.has(Params.PARAM_DEVICE_ID)) { if (config.getDeviceId() == null) { @@ -251,10 +252,7 @@ public static Future pushAsync(final CtxCore ctx, final Request request return null; } - request.params.add("timestamp", Device.dev.uniqueTimestamp()) - .add("tz", Device.dev.getTimezoneOffset()) - .add("hour", Device.dev.currentHour()) - .add("dow", Device.dev.currentDayOfWeek()); + addRequiredTimeParams(request); return Storage.pushAsync(ctx, request, param -> { SDKCore.instance.onRequest(ctx, request); From 2603f37b0e180bbbb8106c37caa5d02baa7f491d Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:23:23 +0300 Subject: [PATCH 22/58] fix: transport ref --- .../java/internal/ImmediateRequestMaker.java | 2 +- .../ly/count/sdk/java/internal/Transport.java | 84 ++----------------- 2 files changed, 8 insertions(+), 78 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java index 60b35fd54..38d02a0ab 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java @@ -67,7 +67,7 @@ private JSONObject doInBackground(String requestData, String customEndpoint, Tra request.endpoint(customEndpoint); //getting connection ready try { - connection = cp.connection(request); + connection = cp.connection(request, null); } catch (IOException e) { L.e("[ImmediateRequestMaker] IOException while preparing remote config update request :[" + e.toString() + "]"); return null; diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java index 721733e08..75ec7caf4 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/Transport.java @@ -14,7 +14,6 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; -import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.KeyStore; import java.security.NoSuchAlgorithmException; @@ -67,8 +66,8 @@ public Transport() { } /** - * @param config - * @throws IllegalArgumentException + * @param config configuration to use + * @throws IllegalArgumentException if certificate exception happens * @see ModuleBase#init(InternalConfig, Log) */ public void init(InternalConfig config, Log logger) throws IllegalArgumentException { @@ -92,13 +91,6 @@ public void init(InternalConfig config, Log logger) throws IllegalArgumentExcept } } - // void onContext(android.content.Ctx context) { - // ConnectivityManager connectivityManager = (ConnectivityManager) context.getSystemService(android.content.Ctx.CONNECTIVITY_SERVICE); - // NetworkInfo activeNetInfo = connectivityManager.getActiveNetworkInfo(); - // NetworkInfo mobNetInfo = connectivityManager.getNetworkInfo( ConnectivityManager.TYPE_MOBILE ); - // https://stackoverflow.com/questions/1783117/network-listener-android - // } - /** * For testing purposes */ @@ -213,69 +205,6 @@ HttpURLConnection connection(final Request request, final User user) throws IOEx return connection; } - /** - * Open connection for particular request: choose GET or POST, choose multipart or urlencoded if POST, - * set SSL context, calculate and add checksum, load and send user picture if needed. - * - * @param request request to send - * @return connection, not {@link HttpURLConnection} yet - * @throws IOException from {@link HttpURLConnection} in case of error - */ - HttpURLConnection connection(final Request request) throws IOException { - String endpoint = request.params.remove(Request.ENDPOINT); - if (endpoint == null) { - endpoint = "/i?"; - } - - String path = config.getServerURL().toString() + endpoint; - boolean usingGET = !config.isUsePOST() && request.isGettable(config.getServerURL()); - - if (usingGET && config.getParameterTamperingProtectionSalt() != null) { - request.params.add(CHECKSUM, Utils.digestHex(PARAMETER_TAMPERING_DIGEST, request.params + config.getParameterTamperingProtectionSalt(), L)); - } - - HttpURLConnection connection = openConnection(path, request.params.toString(), usingGET); - connection.setConnectTimeout(1000 * config.getNetworkConnectionTimeout()); - connection.setReadTimeout(1000 * config.getNetworkReadTimeout()); - - if (connection instanceof HttpsURLConnection && sslContext != null) { - HttpsURLConnection https = (HttpsURLConnection) connection; - https.setSSLSocketFactory(sslContext.getSocketFactory()); - } - - if (!usingGET) { - OutputStream output = null; - PrintWriter writer = null; - try { - if (config.getParameterTamperingProtectionSalt() != null) { - request.params.add(CHECKSUM, Utils.digestHex(PARAMETER_TAMPERING_DIGEST, request.params + config.getParameterTamperingProtectionSalt(), L)); - } - connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); - - output = connection.getOutputStream(); - writer = new PrintWriter(new OutputStreamWriter(output, StandardCharsets.UTF_8), true); - - writer.write(request.params.toString()); - writer.flush(); - } finally { - if (writer != null) { - try { - writer.close(); - } catch (Throwable ignored) { - } - } - if (output != null) { - try { - output.close(); - } catch (Throwable ignored) { - } - } - } - } - - return connection; - } - void addMultipart(OutputStream output, PrintWriter writer, String boundary, String contentType, String name, String value, Object file) throws IOException { writer.append("--").append(boundary).append(Utils.CRLF); if (file != null) { @@ -294,6 +223,7 @@ void addMultipart(OutputStream output, PrintWriter writer, String boundary, Stri } byte[] pictureData(User user, String picture) throws IOException { + if (user == null) return null; byte[] data; if (UserEditorImpl.PICTURE_IN_USER_PROFILE.equals(picture)) { data = user.picture(); @@ -534,7 +464,7 @@ public void checkClientTrusted(X509Certificate[] chain, String authType) throws @Override public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { - if (keyPins.size() == 0 && certPins.size() == 0) { + if (keyPins.isEmpty() && certPins.isEmpty()) { return; } @@ -555,8 +485,8 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throws defaultTrustManager.checkServerTrusted(chain, authType); } - byte serverPublicKey[] = chain[0].getPublicKey().getEncoded(); - byte serverCertificate[] = chain[0].getEncoded(); + byte[] serverPublicKey = chain[0].getPublicKey().getEncoded(); + byte[] serverCertificate = chain[0].getEncoded(); for (byte[] key : keyPins) { if (Arrays.equals(key, serverPublicKey)) { @@ -577,4 +507,4 @@ public void checkServerTrusted(X509Certificate[] chain, String authType) throws public X509Certificate[] getAcceptedIssuers() { return new X509Certificate[0]; } -} +} \ No newline at end of file From a8b01e18dd18ad67d58441d3e78bfb7ea22eec5e Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:24:58 +0300 Subject: [PATCH 23/58] revert: test --- .../java/internal/ImmediateRequestTest.java | 41 ------------------- 1 file changed, 41 deletions(-) delete mode 100644 sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java deleted file mode 100644 index 0d4556b6b..000000000 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ImmediateRequestTest.java +++ /dev/null @@ -1,41 +0,0 @@ -package ly.count.sdk.java.internal; - -import java.util.concurrent.atomic.AtomicReference; -import ly.count.sdk.java.Countly; -import org.junit.Assert; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -import static org.mockito.Mockito.mock; - -@RunWith(JUnit4.class) -public class ImmediateRequestTest { - Log L = mock(Log.class); - - /** - * Immediate request maker "doWork" function - * Immediate Request Generator is default and endpoint and data are not valid, and app key, server url is default - * should return null because response is not okay - * - * @throws InterruptedException - */ - @Test - public void doWork_null() throws InterruptedException { - Countly.instance().init(TestUtils.getBaseConfig()); - AtomicReference callbackResult = new AtomicReference<>(true); - - ImmediateRequestMaker immediateRequestMaker = new ImmediateRequestMaker(); - immediateRequestMaker.doWork("test_event", "/o?", SDKCore.instance.networking.getTransport(), false, true, - (result) -> { - if (result == null) { - callbackResult.set(false); - } - }, L); - - Thread.sleep(2000); // wait for background thread to finish - Assert.assertFalse(callbackResult.get()); // check if callback was called and response is null - - Countly.stop(true); - } -} From c0548eb351e107e6961b8fb2e77e7f6e53e08eef Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:33:08 +0300 Subject: [PATCH 24/58] feat: add garbage json test --- .../java/internal/ModuleFeedbackTests.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 756aab7e9..0ee5fd6db 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -68,6 +68,28 @@ public void parseFeedbackList() throws JSONException { ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "ratingName1", "614871419f030e44be07d82f", new String[] { "/" }, ret.get(3)); } + /** + * Parse feedback list successfully, remove garbage json + * "parseFeedbackList" function should return correct json feedback widget list without garbage json + * returned feedback widget list should not have garbage json + */ + @Test + public void parseFeedbackList_oneGoodWithGarbage() throws JSONException { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + String requestJson = + "{\"result\":[{\"_id\":\"asd\",\"type\":\"qwe\",\"name\":\"zxc\",\"tg\":[]},{\"_id\":\"5f97284635935cc338e78200\",\"type\":\"nps\",\"name\":\"fsdfsdf\",\"tg\":[\"/\"]},{\"g4id\":\"asd1\",\"t4type\":\"432\",\"nagdfgme\":\"zxct\",\"tgm\":[\"/\"]}]}"; + + JSONObject jObj = new JSONObject(requestJson); + + List ret = ModuleFeedback.parseFeedbackList(jObj); + Assert.assertNotNull(ret); + Assert.assertEquals(1, ret.size()); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "fsdfsdf", "5f97284635935cc338e78200", new String[] { "/" }, ret.get(0)); + } + /** * Parse feedback list successfully, remove faulty widgets * "parseFeedbackList" function should return correct feedback widget list without faulty widgets @@ -149,6 +171,34 @@ public void getAvailableFeedbackWidgets() { Assert.assertEquals(widgetResponse, widgets); } + /** + * Getting feedback widget list with garbage json + * "getAvailableFeedbackWidgets" function should return empty feedback widget list because json is garbage + * returned feedback widget list should be empty + */ + @Test + public void getAvailableFeedbackWidgets_garbageJson() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + JSONArray garbageArray = new JSONArray(); + garbageArray.put(createGarbageJson()); + garbageArray.put(createGarbageJson()); + JSONObject responseJson = new JSONObject(); + responseJson.put("result", garbageArray); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + callback.callback(responseJson); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { + Assert.assertNull(error); + Assert.assertEquals(0, response.size()); + }); + } + /** * Getting feedback widget list errored * "getAvailableFeedbackWidgets" function should return error message @@ -181,6 +231,15 @@ private CountlyFeedbackWidget createFeedbackWidget(FeedbackWidgetType type, Stri return widget; } + private JSONObject createGarbageJson() { + JSONObject garbageJson = new JSONObject(); + garbageJson.put("garbage", "garbage"); + garbageJson.put("_no_meaning", true); + garbageJson.put("_no_tear", 123); + + return garbageJson; + } + private JSONObject createFeedbackWidgetJson(CountlyFeedbackWidget widget) throws JSONException { JSONObject widgetJson = new JSONObject(); widgetJson.put("_id", widget.widgetId); From de86ef2e6ffc97623e58bbba9111b434a6590d22 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 15:51:33 +0300 Subject: [PATCH 25/58] feat: usage of generic callback interface --- .../sdk/java/internal/CallbackOnFinish.java | 5 ++ .../sdk/java/internal/ModuleFeedback.java | 49 +++++++------------ 2 files changed, 23 insertions(+), 31 deletions(-) create mode 100644 sdk-java/src/main/java/ly/count/sdk/java/internal/CallbackOnFinish.java diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/CallbackOnFinish.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/CallbackOnFinish.java new file mode 100644 index 000000000..2f0295628 --- /dev/null +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/CallbackOnFinish.java @@ -0,0 +1,5 @@ +package ly.count.sdk.java.internal; + +public interface CallbackOnFinish { + void onFinished(T result, String error); +} \ No newline at end of file diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 87c805423..64d1cc61b 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -17,18 +17,6 @@ public class ModuleFeedback extends ModuleBase { Feedback feedbackInterface = null; private CtxCore ctx; - public interface RetrieveFeedbackWidgets { - void onFinished(List retrievedWidgets, String error); - } - - public interface RetrieveFeedbackWidgetData { - void onFinished(JSONObject retrievedWidgetData, String error); - } - - public interface FeedbackCallback { - void onFinished(String url, String error); - } - ModuleFeedback() { } @@ -58,7 +46,7 @@ public void stop(CtxCore ctx, boolean clear) { feedbackInterface = null; } - private void getAvailableFeedbackWidgetsInternal(RetrieveFeedbackWidgets callback) { + private void getAvailableFeedbackWidgetsInternal(CallbackOnFinish> callback) { L.d("[ModuleFeedback] getAvailableFeedbackWidgetsInternal, callback set:[" + (callback != null) + "]"); if (callback == null) { @@ -287,17 +275,20 @@ private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetIn Countly.instance().event(widgetInfo.type.eventKey).setSegmentation(segmString).record(); } - private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, RetrieveFeedbackWidgetData callback) { + private void callCallback(String errorLog, CallbackOnFinish callback) { + L.e(errorLog); + if (callback != null) { + callback.onFinished(null, errorLog); + } + } + + private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, CallbackOnFinish callback) { L.d("[ModuleFeedback] calling 'getFeedbackWidgetDataInternal', callback set:[" + (callback != null) + "]"); String error = validateFields(callback, widgetInfo); if (error != null) { - String errorLog = "[ModuleFeedback] getFeedbackWidgetDataInternal, " + error; - L.e(errorLog); - if (callback != null) { - callback.onFinished(null, errorLog); - } + callCallback("[ModuleFeedback] getFeedbackWidgetDataInternal, " + error, callback); return; } @@ -352,17 +343,13 @@ private String validateFields(Object callback, CountlyFeedbackWidget widget) { return null; } - private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, FeedbackCallback callback) { + private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, CallbackOnFinish callback) { L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, callback set:[" + (callback != null) + ", widgetInfo :[" + widgetInfo + "]"); String error = validateFields(callback, widgetInfo); if (error != null) { - String errorLog = "[ModuleFeedback] constructFeedbackWidgetUrlInternal, " + error; - L.e(errorLog); - if (callback != null) { - callback.onFinished(null, errorLog); - } + callCallback("[ModuleFeedback] constructFeedbackWidgetUrlInternal, " + error, callback); return; } @@ -394,9 +381,9 @@ public class Feedback { /** * Get a list of available feedback widgets for this device ID * - * @param callback callback + * @param callback retrieve widget list callback */ - public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callback) { + public void getAvailableFeedbackWidgets(@Nullable CallbackOnFinish> callback) { synchronized (Countly.instance()) { L.i("[Feedback] getAvailableFeedbackWidgets, Trying to retrieve feedback widget list"); @@ -408,9 +395,9 @@ public void getAvailableFeedbackWidgets(@Nullable RetrieveFeedbackWidgets callba * Construct a URL that can be used to present a feedback widget in a web viewer * * @param widgetInfo widget info - * @param callback callback + * @param callback feedback widget url callback */ - public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable FeedbackCallback callback) { + public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable CallbackOnFinish callback) { synchronized (Countly.instance()) { L.i("[Feedback] constructFeedbackWidgetUrl, Trying to present feedback widget in an alert dialog"); @@ -423,9 +410,9 @@ public void constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInf * When requesting this data, it will count as a shown widget (will increment that "shown" count in the dashboard) * * @param widgetInfo widget info - * @param callback callback + * @param callback retrieve widget data callback */ - public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable RetrieveFeedbackWidgetData callback) { + public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable CallbackOnFinish callback) { synchronized (Countly.instance()) { L.i("[Feedback] getFeedbackWidgetData, Trying to retrieve feedback widget data"); From 041154398e04b4e67969287eb5666222a91e1a66 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 16:08:03 +0300 Subject: [PATCH 26/58] feat: validate required params --- .../java/internal/ModuleFeedbackTests.java | 22 +++++++++++++++++++ .../ly/count/sdk/java/internal/TestUtils.java | 20 +++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 0ee5fd6db..20612078d 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.Comparator; import java.util.List; +import java.util.Map; import ly.count.sdk.java.Config; import ly.count.sdk.java.Countly; import org.json.JSONArray; @@ -155,6 +156,12 @@ public void getAvailableFeedbackWidgets() { responseJson.put("result", widgetsJson); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + Assert.assertEquals("/o/sdk", customEndpoint); + Map params = TestUtils.parseQueryParams(requestData); + Assert.assertEquals("feedback", params.get("method")); + Assert.assertFalse(requestShouldBeDelayed); + Assert.assertTrue(networkingIsEnabled); + validateRequiredParams(params); callback.callback(responseJson); }; SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; @@ -222,6 +229,21 @@ public void getAvailableFeedbackWidgets_null() { }); } + private void validateRequiredParams(Map params) { + int hour = Integer.parseInt(params.get("hour")); + int dow = Integer.parseInt(params.get("dow")); + int tz = Integer.parseInt(params.get("tz")); + + Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); + Assert.assertEquals(SDKCore.instance.config.getDeviceId().id, params.get("device_id")); + Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); + Assert.assertEquals(SDKCore.instance.config.getServerAppKey(), params.get("app_key")); + Assert.assertTrue(Long.valueOf(params.get("timestamp")) > 0); + Assert.assertTrue(hour > 0 && hour < 24); + Assert.assertTrue(dow >= 0 && dow < 7); + Assert.assertTrue(tz >= -720 && tz <= 840); + } + private CountlyFeedbackWidget createFeedbackWidget(FeedbackWidgetType type, String name, String id, String[] tags) { CountlyFeedbackWidget widget = new CountlyFeedbackWidget(); widget.type = type; diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 4568f7b48..6139a00ad 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -185,4 +185,24 @@ private static Map parseRequestParams(File file) throws IOExcept return paramMap; } } + + /** + * Parse query params from string. String contains are urlencoded and + * separated by "&" symbol and key-value pairs are separated by "=" symbol (key=value). + * + * @param data string with query params + * @return map of query params + */ + public static Map parseQueryParams(String data) { + if (data.contains("?")) { + data = data.replace("?", ""); + } + String[] params = data.split("&"); + Map paramMap = new HashMap<>(); + for (String param : params) { + String[] pair = param.split("="); + paramMap.put(pair[0], pair[1]); + } + return paramMap; + } } \ No newline at end of file From 4948d8231e8ab1d334c239a127c8b73292210174 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 16:19:27 +0300 Subject: [PATCH 27/58] feat: construct feedback url test --- .../java/internal/ModuleFeedbackTests.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 20612078d..1762e5300 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -15,9 +15,13 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import static org.mockito.Mockito.mock; + @RunWith(JUnit4.class) public class ModuleFeedbackTests { + Log L = mock(Log.class); + @After public void stop() { Countly.stop(true); @@ -229,6 +233,60 @@ public void getAvailableFeedbackWidgets_null() { }); } + /** + * Construct feedback widget url successfully + * "constructFeedbackWidgetUrl" function should return widget url + * returned url should be same as expected url + */ + @Test + public void constructFeedbackWidgetUrl() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + InternalConfig internalConfig = SDKCore.instance.config; + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + StringBuilder widgetListUrl = new StringBuilder(); + widgetListUrl.append(internalConfig.getServerURL()); + widgetListUrl.append("/feedback/"); + widgetListUrl.append(widgetInfo.type.name()); + widgetListUrl.append("?widget_id="); + widgetListUrl.append(Utils.urlencode(widgetInfo.widgetId, L)); + widgetListUrl.append("&device_id="); + widgetListUrl.append(Utils.urlencode(internalConfig.getDeviceId().id, L)); + widgetListUrl.append("&app_key="); + widgetListUrl.append(Utils.urlencode(internalConfig.getServerAppKey(), L)); + widgetListUrl.append("&sdk_version="); + widgetListUrl.append(internalConfig.getSdkVersion()); + widgetListUrl.append("&sdk_name="); + widgetListUrl.append(internalConfig.getSdkName()); + widgetListUrl.append("&platform=desktop"); + + Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo, (response, error) -> { + Assert.assertNull(error); + Assert.assertEquals(widgetListUrl.toString(), response); + }); + } + + /** + * Construct feedback widget url with null widget info + * "constructFeedbackWidgetUrl" function should not return widget url and return error message + * url should be null and error message should same as expected + */ + @Test + public void constructFeedbackWidgetUrl_nullWidgetInfo() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + Countly.instance().feedback().constructFeedbackWidgetUrl(null, (response, error) -> { + Assert.assertNull(response); + Assert.assertEquals("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Can't continue operation with null widget", error); + }); + } + private void validateRequiredParams(Map params) { int hour = Integer.parseInt(params.get("hour")); int dow = Integer.parseInt(params.get("dow")); From ee05b676c7ea72cb9a1a013867634b7c5f20874c Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 16:51:56 +0300 Subject: [PATCH 28/58] feat: get feedback data tests --- .../java/internal/ModuleFeedbackTests.java | 98 ++++++++++++++++++- 1 file changed, 93 insertions(+), 5 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 1762e5300..bb20029f8 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -160,11 +160,9 @@ public void getAvailableFeedbackWidgets() { responseJson.put("result", widgetsJson); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - Assert.assertEquals("/o/sdk", customEndpoint); Map params = TestUtils.parseQueryParams(requestData); Assert.assertEquals("feedback", params.get("method")); - Assert.assertFalse(requestShouldBeDelayed); - Assert.assertTrue(networkingIsEnabled); + validateWidgetRequiredParams("/o/sdk", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); validateRequiredParams(params); callback.callback(responseJson); }; @@ -287,14 +285,99 @@ public void constructFeedbackWidgetUrl_nullWidgetInfo() { }); } + /** + * Get feedback widget data with null widget info + * "getFeedbackWidgetData" function should not return widget data and return error message + * data should be null and error message should same as expected + */ + @Test + public void getFeedbackWidgetData_nullWidgetInfo() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + Countly.instance().feedback().getFeedbackWidgetData(null, (response, error) -> { + Assert.assertNull(response); + Assert.assertEquals("[ModuleFeedback] getFeedbackWidgetDataInternal, Can't continue operation with null widget", error); + }); + } + + /** + * Get feedback widget data network error + * "getFeedbackWidgetData" function should return null widget data and error message + * data should be null and error message should be same as expected + */ + @Test + public void getFeedbackWidgetData_nullResponse() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); + validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); + callback.callback(null); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { + Assert.assertEquals("Not possible to retrieve widget data. Probably due to lack of connection to the server", error); + Assert.assertNull(response); + }); + } + + /** + * Get feedback widget data successfully + * "getFeedbackWidgetData" function should return widget data and error message should be null + * data should be same as expected and error message should null + */ + @Test + public void getFeedbackWidgetData_garbageResult() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + JSONObject responseJson = new JSONObject(); + responseJson.put("resullt", "Success"); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); + validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); + callback.callback(responseJson); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { + Assert.assertNull(error); + Assert.assertEquals(responseJson, response); + }); + } + + private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { + Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); + Assert.assertEquals("desktop", params.get("platform")); + Assert.assertEquals("1", params.get("shown")); + Assert.assertEquals(String.valueOf(Device.dev.getAppVersion()), params.get("app_version")); + validateSdkRelatedParams(params); + } + + private void validateWidgetRequiredParams(String expectedEndpoint, String customEndpoint, Boolean requestShouldBeDelayed, Boolean networkingIsEnabled) { + Assert.assertEquals(expectedEndpoint, customEndpoint); + Assert.assertFalse(requestShouldBeDelayed); + Assert.assertTrue(networkingIsEnabled); + } + private void validateRequiredParams(Map params) { int hour = Integer.parseInt(params.get("hour")); int dow = Integer.parseInt(params.get("dow")); int tz = Integer.parseInt(params.get("tz")); - Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); + validateSdkRelatedParams(params); Assert.assertEquals(SDKCore.instance.config.getDeviceId().id, params.get("device_id")); - Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); Assert.assertEquals(SDKCore.instance.config.getServerAppKey(), params.get("app_key")); Assert.assertTrue(Long.valueOf(params.get("timestamp")) > 0); Assert.assertTrue(hour > 0 && hour < 24); @@ -302,6 +385,11 @@ private void validateRequiredParams(Map params) { Assert.assertTrue(tz >= -720 && tz <= 840); } + private void validateSdkRelatedParams(Map params) { + Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); + Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); + } + private CountlyFeedbackWidget createFeedbackWidget(FeedbackWidgetType type, String name, String id, String[] tags) { CountlyFeedbackWidget widget = new CountlyFeedbackWidget(); widget.type = type; From 238b90b062b9fb65240d971bcc7bb396d7551c67 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Wed, 27 Sep 2023 17:08:18 +0300 Subject: [PATCH 29/58] feat: report manually init test --- .../sdk/java/internal/ModuleFeedbackTests.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index bb20029f8..2b423f300 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -340,7 +340,7 @@ public void getFeedbackWidgetData_garbageResult() { init(config); JSONObject responseJson = new JSONObject(); - responseJson.put("resullt", "Success"); + responseJson.put("result", "Success"); CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); @@ -357,6 +357,22 @@ public void getFeedbackWidgetData_garbageResult() { }); } + /** + * Report feedback widget manually with null widget info + * "reportFeedbackWidgetManually" function should not record widget as an event, + * event queue should be empty + */ + @Test + public void reportFeedbackWidgetManually_nullWidgetInfo() { + Config config = TestUtils.getBaseConfig(); + config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); + init(config); + + Countly.instance().feedback().reportFeedbackWidgetManually(null, null, null); + List events = TestUtils.getCurrentEventQueue(TestUtils.getSdkStorageRootDirectory(), L); + Assert.assertEquals(0, events.size()); + } + private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); Assert.assertEquals("desktop", params.get("platform")); From e91d0a85c73a107bf02bda16b87a9c2eadbdce3c Mon Sep 17 00:00:00 2001 From: ArtursK Date: Thu, 28 Sep 2023 14:12:13 +0300 Subject: [PATCH 30/58] Fixing failed merge --- sdk-java/src/main/java/ly/count/sdk/java/Countly.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java index 4336dbe23..060d7cc78 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/Countly.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/Countly.java @@ -349,11 +349,13 @@ public ModuleFeedback.Feedback feedback() { return sdk.feedback(); } + /** * Record event with provided key. * * @param key key for this event, cannot be null or empty * @return Builder object for this event * @deprecated use {@link #events()} instead via instance() call + */ @Override public Event event(String key) { L.d("[Countly] event: key = " + key); From 3887b99b1ccf59aa3a999eecd8cd81df4e5e5e56 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Thu, 28 Sep 2023 14:17:21 +0300 Subject: [PATCH 31/58] Some test changes --- .../java/internal/ModuleFeedbackTests.java | 52 +++++-------------- .../ly/count/sdk/java/internal/TestUtils.java | 13 ++++- 2 files changed, 25 insertions(+), 40 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 2b423f300..e9927b8c6 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -38,9 +38,7 @@ private void init(Config cc) { */ @Test public void parseFeedbackList_null() throws JSONException { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); List result = ModuleFeedback.parseFeedbackList(null); Assert.assertNotNull(result); @@ -54,9 +52,7 @@ public void parseFeedbackList_null() throws JSONException { */ @Test public void parseFeedbackList() throws JSONException { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); String requestJson = "{\"result\":[{\"_id\":\"5f8c6f959627f99e8e7de746\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"sdfsdfdsf\",\"tg\":[\"/\"]},{\"_id\":\"5f8c6fd81ac8659e8846acf4\",\"type\":\"nps\",\"name\":\"fdsfsd\",\"tg\":[\"a\",\"0\"]},{\"_id\":\"5f97284635935cc338e78200\",\"type\":\"nps\",\"name\":\"fsdfsdf\",\"tg\":[]},{\"_id\":\"614871419f030e44be07d82f\",\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"tg\":[\"\\/\"],\"name\":\"ratingName1\"}]}"; @@ -80,9 +76,7 @@ public void parseFeedbackList() throws JSONException { */ @Test public void parseFeedbackList_oneGoodWithGarbage() throws JSONException { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); String requestJson = "{\"result\":[{\"_id\":\"asd\",\"type\":\"qwe\",\"name\":\"zxc\",\"tg\":[]},{\"_id\":\"5f97284635935cc338e78200\",\"type\":\"nps\",\"name\":\"fsdfsdf\",\"tg\":[\"/\"]},{\"g4id\":\"asd1\",\"t4type\":\"432\",\"nagdfgme\":\"zxct\",\"tgm\":[\"/\"]}]}"; @@ -102,9 +96,7 @@ public void parseFeedbackList_oneGoodWithGarbage() throws JSONException { */ @Test public void parseFeedbackList_faulty() throws JSONException { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); // 9 widgets (3 from each) // First variation => no 'tg' key // Second variation => no 'name' key @@ -143,9 +135,7 @@ public void parseFeedbackList_faulty() throws JSONException { */ @Test public void getAvailableFeedbackWidgets() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); List widgets = new ArrayList<>(); widgets.add(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {})); @@ -187,9 +177,7 @@ public void getAvailableFeedbackWidgets() { */ @Test public void getAvailableFeedbackWidgets_garbageJson() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); JSONArray garbageArray = new JSONArray(); garbageArray.put(createGarbageJson()); @@ -215,9 +203,7 @@ public void getAvailableFeedbackWidgets_garbageJson() { */ @Test public void getAvailableFeedbackWidgets_null() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> callback.callback(null); @@ -238,9 +224,7 @@ public void getAvailableFeedbackWidgets_null() { */ @Test public void constructFeedbackWidgetUrl() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); InternalConfig internalConfig = SDKCore.instance.config; @@ -275,9 +259,7 @@ public void constructFeedbackWidgetUrl() { */ @Test public void constructFeedbackWidgetUrl_nullWidgetInfo() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); Countly.instance().feedback().constructFeedbackWidgetUrl(null, (response, error) -> { Assert.assertNull(response); @@ -292,9 +274,7 @@ public void constructFeedbackWidgetUrl_nullWidgetInfo() { */ @Test public void getFeedbackWidgetData_nullWidgetInfo() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); Countly.instance().feedback().getFeedbackWidgetData(null, (response, error) -> { Assert.assertNull(response); @@ -309,9 +289,7 @@ public void getFeedbackWidgetData_nullWidgetInfo() { */ @Test public void getFeedbackWidgetData_nullResponse() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); @@ -335,9 +313,7 @@ public void getFeedbackWidgetData_nullResponse() { */ @Test public void getFeedbackWidgetData_garbageResult() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); JSONObject responseJson = new JSONObject(); responseJson.put("result", "Success"); @@ -364,9 +340,7 @@ public void getFeedbackWidgetData_garbageResult() { */ @Test public void reportFeedbackWidgetManually_nullWidgetInfo() { - Config config = TestUtils.getBaseConfig(); - config.enableFeatures(Config.Feature.Feedback).setEventQueueSizeToSend(4); - init(config); + init(TestUtils.getConfigFeedback()); Countly.instance().feedback().reportFeedbackWidgetManually(null, null, null); List events = TestUtils.getCurrentEventQueue(TestUtils.getSdkStorageRootDirectory(), L); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 059c50df2..a5c60d005 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -52,6 +52,17 @@ static Config getConfigEvents(Integer eventThreshold) { return config; } + static Config getConfigFeedback() { + File sdkStorageRootDirectory = getTestSDirectory(); + checkSdkStorageRootDirectoryExist(sdkStorageRootDirectory); + Config config = new Config(SERVER_URL, SERVER_APP_KEY, sdkStorageRootDirectory); + config.setCustomDeviceId(DEVICE_ID); + + config.enableFeatures(Config.Feature.Feedback); + + return config; + } + public static File getTestSDirectory() { // System specific folder structure String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; @@ -224,7 +235,7 @@ static File getSdkStorageRootDirectory() { String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; return new File(String.join(File.separator, sdkStorageRootPath)); } - + static void validateEvent(EventImpl gonnaValidate, String key, Map segmentation, int count, Double sum, Double duration) { Assert.assertEquals(key, gonnaValidate.key); Assert.assertEquals(segmentation, gonnaValidate.segmentation); From d0fbf403b6196686f8cfd5d803dcfdf69445bbe7 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 16:01:22 +0300 Subject: [PATCH 32/58] feat: remove unnecessary --- .../ly/count/sdk/java/internal/ModuleFeedbackTests.java | 2 +- .../src/test/java/ly/count/sdk/java/internal/TestUtils.java | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index e9927b8c6..33cc9407a 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -343,7 +343,7 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { init(TestUtils.getConfigFeedback()); Countly.instance().feedback().reportFeedbackWidgetManually(null, null, null); - List events = TestUtils.getCurrentEventQueue(TestUtils.getSdkStorageRootDirectory(), L); + List events = TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L); Assert.assertEquals(0, events.size()); } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index a5c60d005..32d1d8368 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -230,12 +230,6 @@ public static Map parseQueryParams(String data) { return paramMap; } - static File getSdkStorageRootDirectory() { - // System specific folder structure - String[] sdkStorageRootPath = { System.getProperty("user.home"), "__COUNTLY", "java_test" }; - return new File(String.join(File.separator, sdkStorageRootPath)); - } - static void validateEvent(EventImpl gonnaValidate, String key, Map segmentation, int count, Double sum, Double duration) { Assert.assertEquals(key, gonnaValidate.key); Assert.assertEquals(segmentation, gonnaValidate.segmentation); From 557475831318e8903bf3e02044791a7d63df1cbd Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 16:39:22 +0300 Subject: [PATCH 33/58] feat: get widget data success --- .../java/internal/ModuleFeedbackTests.java | 27 +++++++++++++++++++ .../ly/count/sdk/java/internal/TestUtils.java | 3 +++ 2 files changed, 30 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 33cc9407a..8a761e699 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -282,6 +282,33 @@ public void getFeedbackWidgetData_nullWidgetInfo() { }); } + /** + * Get feedback widget data + * "getFeedbackWidgetData" function should return widget data related to it + * data should fill with correct data + */ + @Test + public void getFeedbackWidgetData() { + init(TestUtils.getConfigFeedback()); + + JSONObject result = new JSONObject(); + result.put("result", TestUtils.feedbackWidgetData); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); + validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); + callback.callback(result); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + + Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { + Assert.assertNull(error); + Assert.assertEquals(TestUtils.feedbackWidgetData, response.get("result")); + }); + } + /** * Get feedback widget data network error * "getFeedbackWidgetData" function should return null widget data and error message diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 32d1d8368..897fd5e24 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -25,6 +25,9 @@ public class TestUtils { public static final String[] eKeys = new String[] { "eventKey1", "eventKey2", "eventKey3", "eventKey4", "eventKey5", "eventKey6", "eventKey7" }; + static String feedbackWidgetData = + "[{\"_id\":\"5b2158ea790ce051e713db07\",\"email\":\"user@mailprovider.com\",\"comment\":\"Notbad,thismightbebetter.\",\"ts\":1528912105570,\"device_id\":\"fb@count.ly\",\"cd\":\"2018-06-13T17:48:26.071Z\",\"uid\":\"1\",\"contact_me\":false,\"rating\":3,\"widget_id\":\"5b21581b967c4850a7818617\"},{\"_id\":\"5b2b80823a69147963e5b826\",\"email\":\"5score@rating.com\",\"comment\":\"5comment.\",\"ts\":1529577665765,\"device_id\":\"fb@count.ly\",\"cd\":\"2018-06-21T10:40:02.746Z\",\"uid\":\"1\",\"contact_me\":true,\"rating\":5,\"widget_id\":\"5b21581b967c4850a7818617\"}]"; + private TestUtils() { } From 45154ff53bbfd40d254edd41081baacb0b672906 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 16:44:11 +0300 Subject: [PATCH 34/58] feat: usage of new way of platform and event recording --- .../count/sdk/java/internal/ModuleFeedback.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 64d1cc61b..c8a5407ac 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -253,7 +253,7 @@ private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetIn } Map segm = new HashMap<>(); - segm.put("platform", "desktop"); + segm.put("platform", internalConfig.getSdkPlatform()); segm.put("app_version", cachedAppVersion); segm.put("widget_id", widgetInfo.widgetId); @@ -266,13 +266,7 @@ private void reportFeedbackWidgetManuallyInternal(CountlyFeedbackWidget widgetIn segm.putAll(widgetResult); } - //TODO This code block should be replaced when remaked event module merged - Map segmString = new HashMap<>(); - for (Map.Entry entry : segm.entrySet()) { - segmString.put(entry.getKey(), entry.getValue().toString()); - } - - Countly.instance().event(widgetInfo.type.eventKey).setSegmentation(segmString).record(); + Countly.instance().events().recordEvent(widgetInfo.type.eventKey, segm); } private void callCallback(String errorLog, CallbackOnFinish callback) { @@ -302,7 +296,8 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Cal requestData.append(internalConfig.getSdkVersion()); requestData.append("&sdk_name="); requestData.append(internalConfig.getSdkName()); - requestData.append("&platform=desktop"); + requestData.append("&platform="); + requestData.append(internalConfig.getSdkPlatform()); requestData.append("&app_version="); requestData.append(cachedAppVersion); @@ -367,7 +362,8 @@ private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo widgetListUrl.append(internalConfig.getSdkVersion()); widgetListUrl.append("&sdk_name="); widgetListUrl.append(internalConfig.getSdkName()); - widgetListUrl.append("&platform=desktop"); + widgetListUrl.append("&platform="); + widgetListUrl.append(internalConfig.getSdkPlatform()); final String preparedWidgetUrl = widgetListUrl.toString(); From b7a1bca96e1096c43d70194acd5af1cc8683eb7a Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 17:01:29 +0300 Subject: [PATCH 35/58] feat: os check for widgets --- .../java/internal/ModuleFeedbackTests.java | 20 +++++++++++++++++-- .../ly/count/sdk/java/internal/TestUtils.java | 4 ++++ 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 8a761e699..053b72acf 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -15,6 +15,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; +import static ly.count.sdk.java.internal.TestUtils.getOs; import static org.mockito.Mockito.mock; @RunWith(JUnit4.class) @@ -244,7 +245,8 @@ public void constructFeedbackWidgetUrl() { widgetListUrl.append(internalConfig.getSdkVersion()); widgetListUrl.append("&sdk_name="); widgetListUrl.append(internalConfig.getSdkName()); - widgetListUrl.append("&platform=desktop"); + widgetListUrl.append("&platform="); + widgetListUrl.append(getOs()); Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo, (response, error) -> { Assert.assertNull(error); @@ -374,9 +376,23 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { Assert.assertEquals(0, events.size()); } + /** + * Report feedback widget manually with null widget info + * "reportFeedbackWidgetManually" function should not record widget as an event, + * event queue should be empty + */ + @Test + public void reportFeedbackWidgetManually() { + init(TestUtils.getConfigFeedback()); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + //Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); + } + private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); - Assert.assertEquals("desktop", params.get("platform")); + Assert.assertEquals(getOs(), params.get("platform")); Assert.assertEquals("1", params.get("shown")); Assert.assertEquals(String.valueOf(Device.dev.getAppVersion()), params.get("app_version")); validateSdkRelatedParams(params); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 897fd5e24..a0eef7200 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -248,4 +248,8 @@ static void validateEvent(EventImpl gonnaValidate, String key, Map= 0 && gonnaValidate.hour < 24); Assert.assertTrue(gonnaValidate.timestamp >= 0); } + + static String getOs() { + return System.getProperty("os.name"); + } } \ No newline at end of file From 1aad3e4012cc3a0b23ed9ac542b80bb4b2ba13fd Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 17:05:03 +0300 Subject: [PATCH 36/58] refactor: event size queue check --- .../sdk/java/internal/EventQueueTests.java | 43 ++++++------- .../sdk/java/internal/ModuleEventsTests.java | 60 ++++++------------- .../ly/count/sdk/java/internal/TestUtils.java | 9 +++ 3 files changed, 45 insertions(+), 67 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java index f6481f77e..4979ee796 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/EventQueueTests.java @@ -18,6 +18,7 @@ import static ly.count.sdk.java.internal.SDKStorage.FILE_NAME_PREFIX; import static ly.count.sdk.java.internal.SDKStorage.FILE_NAME_SEPARATOR; import static ly.count.sdk.java.internal.TestUtils.validateEvent; +import static ly.count.sdk.java.internal.TestUtils.validateEventQueueSize; import static org.mockito.ArgumentMatchers.anyCollection; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -51,7 +52,7 @@ public void stop() { public void addEvent() { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); EventImpl event = createEvent("test-addEvent", null, 1, null, null); eventQueue.addEvent(event); validateEventInQueue(event.key, null, 1, null, null, 1, 0); @@ -66,9 +67,9 @@ public void addEvent() { public void addEvent_null() { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); eventQueue.addEvent(null); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); } /** @@ -80,7 +81,7 @@ public void addEvent_null() { public void writeEventQueueToStorage() { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); EventImpl event = createEvent("test-writeEventQueueToStorage", null, 1, null, null); eventQueue.eventQueueMemoryCache.add(event); eventQueue.writeEventQueueToStorage(); @@ -157,15 +158,13 @@ public void joinEvents_nullCollection() { public void clear() { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); - + validateEventQueueSize(0, eventQueue); eventQueue.addEvent(createEvent("test-clear", null, 1, null, null)); - validateQueueSize(1); + validateEventQueueSize(1, eventQueue); eventQueue.addEvent(createEvent("test-clear", null, 1, null, null)); - validateQueueSize(2); - + validateEventQueueSize(2, eventQueue); eventQueue.clear(); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); } /** @@ -177,11 +176,11 @@ public void clear() { public void restoreFromDisk() throws IOException { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); writeToEventQueue("{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-1\",\"timestamp\":1695887006647}:::{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-2\",\"timestamp\":1695887006657}", false); eventQueue.restoreFromDisk(); - validateQueueSize(2); + validateEventQueueSize(2, eventQueue); validateEvent(eventQueue.eventQueueMemoryCache.get(0), "test-joinEvents-1", null, 1, null, null); validateEvent(eventQueue.eventQueueMemoryCache.get(1), "test-joinEvents-2", null, 1, null, null); } @@ -195,11 +194,11 @@ public void restoreFromDisk() throws IOException { public void restoreFromDisk_notExist() throws IOException { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); writeToEventQueue(null, true); eventQueue.restoreFromDisk(); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); } /** @@ -211,11 +210,11 @@ public void restoreFromDisk_notExist() throws IOException { public void restoreFromDisk_garbageFile() throws IOException { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); writeToEventQueue("{\"hour\":10,\"asdasd\":\"askjdn\",\"timestamp\":1695887006647}::{\"hour\":10,\"count\":1,\"dow\":4,\"asda\":\"test-joinEvents-2\"}", false); eventQueue.restoreFromDisk(); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); } /** @@ -227,11 +226,11 @@ public void restoreFromDisk_garbageFile() throws IOException { public void restoreFromDisk_corruptedData() throws IOException { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, eventQueue); writeToEventQueue("{\"hour\":10,\"count\":1,\"dow\":4,\"key\":\"test-joinEvents-1\",\"timestamp\":1695887006647}:::{\"hour\":10,\"count\":1,\"dow\":4,\"keya\":\"test-joinEvents-2\",\"timestamp\":1695887006657}", false); eventQueue.restoreFromDisk(); - validateQueueSize(1); + validateEventQueueSize(1, eventQueue); validateEvent(eventQueue.eventQueueMemoryCache.get(0), "test-joinEvents-1", null, 1, null, null); } @@ -251,16 +250,10 @@ private EventImpl createEvent(String key, Map segmentation, int return new EventImpl(key, count, sum, dur, segmentation, L); } - private void validateQueueSize(int expectedSize) { - Assert.assertEquals(expectedSize, TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).size()); - Assert.assertEquals(expectedSize, eventQueue.eqSize()); - } - void validateEventInQueue(String key, Map segmentation, int count, Double sum, Double duration, int queueSize, int elementInQueue) { List events = TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L); - validateQueueSize(queueSize); - + validateEventQueueSize(queueSize, eventQueue); //check if event was recorded correctly EventImpl event = events.get(elementInQueue); EventImpl eventInMemory = eventQueue.eventQueueMemoryCache.get(0); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java index f9ce2be44..9075e5e62 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java @@ -14,6 +14,7 @@ import org.junit.runners.JUnit4; import static ly.count.sdk.java.internal.TestUtils.validateEvent; +import static ly.count.sdk.java.internal.TestUtils.validateEventQueueSize; @RunWith(JUnit4.class) public class ModuleEventsTests { @@ -39,8 +40,7 @@ public void stop() { public void recordEvent() { init(TestUtils.getConfigEvents(4)); - List events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - validateQueueSize(0, events); + validateEventQueueSize(0, moduleEvents.eventQueue); //create segmentation Map segmentation = new HashMap<>(); @@ -64,15 +64,15 @@ public void recordEvent() { public void recordEvent_queueSizeOver() { init(TestUtils.getConfigEvents(2)); - validateQueueSize(0); + validateEventQueueSize(0, moduleEvents.eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); Countly.instance().events().recordEvent("recordEvent_queueSizeOver1", 1, 45.9, null, 32.0); - validateQueueSize(1); + validateEventQueueSize(1, moduleEvents.eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); Countly.instance().events().recordEvent("recordEvent_queueSizeOver2", 1, 45.9, null, 32.0); - validateQueueSize(0); + validateEventQueueSize(0, moduleEvents.eventQueue); Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); Map request = TestUtils.getCurrentRequestQueue()[0]; @@ -90,10 +90,10 @@ public void recordEvent_queueSizeOverMemory() throws IOException { init(TestUtils.getConfigEvents(2)); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); - validateQueueSize(2); + validateEventQueueSize(2, moduleEvents.eventQueue); Countly.instance().events().recordEvent("recordEvent_queueSizeOver", 1, 45.9, null, 32.0); - validateQueueSize(0); - Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); + validateEventQueueSize(0, moduleEvents.eventQueue); + //Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); todo this fails why? Map request = TestUtils.getCurrentRequestQueue()[0]; Assert.assertTrue(request.get("events").contains("recordEvent_queueSizeOver") && request.get("events").contains("test-joinEvents-1") && request.get("events").contains("test-joinEvents-2")); @@ -108,14 +108,9 @@ public void recordEvent_queueSizeOverMemory() throws IOException { public void recordEvent_negativeCount() { init(TestUtils.getConfigEvents(4)); - List events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - - validateQueueSize(0, events); - + validateEventQueueSize(0, moduleEvents.eventQueue); Countly.instance().events().recordEvent("recordEvent_negativeCount", -1); - events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - - validateQueueSize(0, events); + validateEventQueueSize(0, moduleEvents.eventQueue); } /** @@ -127,14 +122,9 @@ public void recordEvent_negativeCount() { public void recordEvent_nullKey() { init(TestUtils.getConfigEvents(4)); - List events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - - validateQueueSize(0, events); - + validateEventQueueSize(0, moduleEvents.eventQueue); Countly.instance().events().recordEvent(null); - events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - - validateQueueSize(0, events); + validateEventQueueSize(0, moduleEvents.eventQueue); } /** @@ -146,13 +136,9 @@ public void recordEvent_nullKey() { public void recordEvent_emptyKey() { init(TestUtils.getConfigEvents(4)); - List events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - validateQueueSize(0, events); - + validateEventQueueSize(0, moduleEvents.eventQueue); Countly.instance().events().recordEvent(""); - events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - - validateQueueSize(0, events); + validateEventQueueSize(0, moduleEvents.eventQueue); } /** @@ -164,8 +150,7 @@ public void recordEvent_emptyKey() { public void recordEvent_invalidSegment() { init(TestUtils.getConfigEvents(4)); - List events = TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L); - validateQueueSize(0, events); + validateEventQueueSize(0, moduleEvents.eventQueue); //create segmentation Map segmentation = new HashMap<>(); segmentation.put("exam_name", "CENG 101"); @@ -423,7 +408,7 @@ public void cancelEvent() { Assert.assertTrue(Countly.instance().events().cancelEvent(TestUtils.eKeys[0])); Assert.assertEquals(0, moduleEvents.timedEvents.size()); - validateQueueSize(0); + validateEventQueueSize(0, moduleEvents.eventQueue); } @Test @@ -451,19 +436,10 @@ public void timedEventFlow() throws InterruptedException { } private void validateTimedEventSize(int expectedQueueSize, int expectedTimedEventSize) { - validateQueueSize(expectedQueueSize, TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L)); + validateEventQueueSize(expectedQueueSize, TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L), moduleEvents.eventQueue); Assert.assertEquals(expectedTimedEventSize, moduleEvents.timedEvents.size()); } - private void validateQueueSize(int expectedSize, List events) { - Assert.assertEquals(expectedSize, events.size()); - Assert.assertEquals(expectedSize, moduleEvents.eventQueue.eqSize()); - } - - private void validateQueueSize(int expectedSize) { - validateQueueSize(expectedSize, TestUtils.getCurrentEventQueue(moduleEvents.ctx.getContext(), moduleEvents.L)); - } - private void endEvent(String key, Map segmentation, int count, Double sum) { boolean result = Countly.instance().events().endEvent(key, segmentation, count, sum); Assert.assertTrue(result); @@ -477,7 +453,7 @@ private void startEvent(String key) { void validateEventInEventQueue(File targetFolder, String key, Map segmentation, int count, Double sum, Double duration, int queueSize, int elementInQueue) { List events = TestUtils.getCurrentEventQueue(targetFolder, moduleEvents.L); - validateQueueSize(queueSize, events); + validateEventQueueSize(queueSize, events, moduleEvents.eventQueue); //check if event was recorded correctly EventImpl event = events.get(elementInQueue); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 75826c5c5..05d138d14 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -214,4 +214,13 @@ static void validateEvent(EventImpl gonnaValidate, String key, Map= 0 && gonnaValidate.hour < 24); Assert.assertTrue(gonnaValidate.timestamp >= 0); } + + static void validateEventQueueSize(int expectedSize, List events, EventQueue eventQueue) { + Assert.assertEquals(expectedSize, events.size()); + Assert.assertEquals(expectedSize, eventQueue.eqSize()); + } + + static void validateEventQueueSize(int expectedSize, EventQueue eventQueue) { + validateEventQueueSize(expectedSize, TestUtils.getCurrentEventQueue(getTestSDirectory(), mock(Log.class)), eventQueue); + } } \ No newline at end of file From d4bf4d8cc980149840d48a279835bedde090f074 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 17:06:47 +0300 Subject: [PATCH 37/58] revert: once --- .../src/test/java/ly/count/sdk/java/internal/TestUtils.java | 4 ---- 1 file changed, 4 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index a0eef7200..897fd5e24 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -248,8 +248,4 @@ static void validateEvent(EventImpl gonnaValidate, String key, Map= 0 && gonnaValidate.hour < 24); Assert.assertTrue(gonnaValidate.timestamp >= 0); } - - static String getOs() { - return System.getProperty("os.name"); - } } \ No newline at end of file From 1f913d4fc8e068d0176078490908d93ae35562c7 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 17:08:03 +0300 Subject: [PATCH 38/58] fix: get os --- .../src/test/java/ly/count/sdk/java/internal/TestUtils.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index d4e2189b1..05233f9b6 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -257,4 +257,8 @@ static void validateEventQueueSize(int expectedSize, List events, Eve static void validateEventQueueSize(int expectedSize, EventQueue eventQueue) { validateEventQueueSize(expectedSize, TestUtils.getCurrentEventQueue(getTestSDirectory(), mock(Log.class)), eventQueue); } + + static String getOs() { + return System.getProperty("os.name"); + } } \ No newline at end of file From e4795f11f027811d5aa870f8889521ad9fe55eff Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 17:23:26 +0300 Subject: [PATCH 39/58] feat: report widget manually --- .../java/internal/ModuleFeedbackTests.java | 31 ++++++++++++++++--- .../ly/count/sdk/java/internal/TestUtils.java | 6 ++++ 2 files changed, 33 insertions(+), 4 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 053b72acf..e621f76ee 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Comparator; +import java.util.HashMap; import java.util.List; import java.util.Map; import ly.count.sdk.java.Config; @@ -16,6 +17,8 @@ import org.junit.runners.JUnit4; import static ly.count.sdk.java.internal.TestUtils.getOs; +import static ly.count.sdk.java.internal.TestUtils.validateEvent; +import static ly.count.sdk.java.internal.TestUtils.validateEventQueueSize; import static org.mockito.Mockito.mock; @RunWith(JUnit4.class) @@ -377,17 +380,20 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { } /** - * Report feedback widget manually with null widget info + * Report feedback widget manually with null widgetData and widgetResult * "reportFeedbackWidgetManually" function should not record widget as an event, - * event queue should be empty + * event queue should contain it and it should have correct segmentation */ @Test public void reportFeedbackWidgetManually() { - init(TestUtils.getConfigFeedback()); + init(TestUtils.getConfigFeedback(Config.Feature.Events)); CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); + validateEventQueueSize(1, moduleEvents().eventQueue); - //Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, null), 1, null, null); } private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { @@ -456,4 +462,21 @@ private void ValidateReturnedFeedbackWidget(FeedbackWidgetType type, String wNam Assert.assertEquals(wId, fWidget.widgetId); Assert.assertArrayEquals(wTags, fWidget.tags); } + + private ModuleEvents moduleEvents() { + return SDKCore.instance.module(ModuleEvents.class); + } + + private Map requiredWidgetSegmentation(String widgetId, Map widgetResult) { + Map segm = new HashMap<>(); + segm.put("platform", getOs()); + segm.put("app_version", Device.dev.getAppVersion()); + segm.put("widget_id", widgetId); + if (widgetResult != null) { + segm.putAll(widgetResult); + } else { + segm.put("closed", "1"); + } + return segm; + } } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 05233f9b6..569ac55e0 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -56,11 +56,17 @@ static Config getConfigEvents(Integer eventThreshold) { } static Config getConfigFeedback() { + return getConfigFeedback((Config.Feature) null); + } + + static Config getConfigFeedback(Config.Feature... features) { File sdkStorageRootDirectory = getTestSDirectory(); checkSdkStorageRootDirectoryExist(sdkStorageRootDirectory); Config config = new Config(SERVER_URL, SERVER_APP_KEY, sdkStorageRootDirectory); config.setCustomDeviceId(DEVICE_ID); + config.setApplicationVersion("1.0"); + config.enableFeatures(features); config.enableFeatures(Config.Feature.Feedback); return config; From 8b88c79cced9762c8834e12c7ae03a131e921197 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 18:01:16 +0300 Subject: [PATCH 40/58] feat: add report manually feedback widget --- .../java/internal/ModuleFeedbackTests.java | 122 +++++++++++++++++- 1 file changed, 121 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index e621f76ee..1325b573f 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -381,7 +381,7 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { /** * Report feedback widget manually with null widgetData and widgetResult - * "reportFeedbackWidgetManually" function should not record widget as an event, + * "reportFeedbackWidgetManually" function should record widget as an event, * event queue should contain it and it should have correct segmentation */ @Test @@ -396,6 +396,126 @@ public void reportFeedbackWidgetManually() { validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, null), 1, null, null); } + /** + * Report feedback widget manually with null widgetData and null key-value widget results + * "reportFeedbackWidgetManually" function should record widget as an event, + * event queue should contain it and it should have correct segmentation + */ + @Test + public void reportFeedbackWidgetManually_nullWidgetResultValueKeys() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + + Map widgetResult = new HashMap<>(); + widgetResult.put("key1", null); + widgetResult.put(null, null); + widgetResult.put("", 6); + widgetResult.put("accepted", true); + + Map expectedWidgetResult = new HashMap<>(); + expectedWidgetResult.put("accepted", true); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(1, moduleEvents().eventQueue); + + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.survey.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, expectedWidgetResult), 1, null, null); + } + + /** + * Report a nps and a rating feedback widget manually with null widgetData and not existing rating + * "reportFeedbackWidgetManually" function should not record, + * event queue should be empty + */ + @Test + public void reportFeedbackWidgetManually_nonExistingRating() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + + Map widgetResult = new HashMap<>(); + widgetResult.put("accepted", true); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(0, moduleEvents().eventQueue); + + widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(0, moduleEvents().eventQueue); + } + + /** + * Report a nps and a rating feedback widget manually with null widgetData and invalid rating result + * "reportFeedbackWidgetManually" function should not record, + * event queue should be empty + */ + @Test + public void reportFeedbackWidgetManually_invalidRating() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + + Map widgetResult = new HashMap<>(); + widgetResult.put("rating", true); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(0, moduleEvents().eventQueue); + + widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(0, moduleEvents().eventQueue); + } + + /** + * Report a nps and a rating feedback widget manually with null widgetData and valid rating result + * "reportFeedbackWidgetManually" function should record, + * event queue should contain widget event and it should have correct segmentation + */ + @Test + public void reportFeedbackWidgetManually_validRating() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + + Map widgetResult = new HashMap<>(); + widgetResult.put("rating", 11); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(1, moduleEvents().eventQueue); + + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, widgetResult), 1, null, null); + + widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); + validateEventQueueSize(2, moduleEvents().eventQueue); + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(1), FeedbackWidgetType.rating.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, widgetResult), 1, null, null); + } + + /** + * Report feedback widget manually with invalid widgetData and null widgetResult + * "reportFeedbackWidgetManually" function should record widget as an event, + * event queue should contain it and it should have correct segmentation + */ + @Test + public void reportFeedbackWidgetManually_invalidWidgetData() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + + JSONObject widgetData = new JSONObject(); + widgetData.put("_id", "diff"); + widgetData.put("type", "rating"); + + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, widgetData, null); + validateEventQueueSize(1, moduleEvents().eventQueue); + + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, null), 1, null, null); + } + private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); Assert.assertEquals(getOs(), params.get("platform")); From ae003d0b87400bf30a336db0d8ae9db726e62733 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 18:02:52 +0300 Subject: [PATCH 41/58] doc: widget data --- .../main/java/ly/count/sdk/java/internal/ModuleFeedback.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index c8a5407ac..ce2e82d22 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -421,7 +421,7 @@ public void getFeedbackWidgetData(@Nullable CountlyFeedbackWidget widgetInfo, @N * In case widgetResult is passed as "null," it would be assumed that the widget was canceled * * @param widgetInfo widget info - * @param widgetData widget data + * @param widgetData widget data to validate * @param widgetResult widget result */ public void reportFeedbackWidgetManually(@Nullable CountlyFeedbackWidget widgetInfo, @Nullable JSONObject widgetData, @Nullable Map widgetResult) { From ff8f6d31ab500363d8cb3272964746a3f716a853 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Thu, 28 Sep 2023 18:08:02 +0300 Subject: [PATCH 42/58] fix: enable test --- .../test/java/ly/count/sdk/java/internal/ModuleEventsTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java index 9075e5e62..dd0c4b7da 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java @@ -93,7 +93,7 @@ public void recordEvent_queueSizeOverMemory() throws IOException { validateEventQueueSize(2, moduleEvents.eventQueue); Countly.instance().events().recordEvent("recordEvent_queueSizeOver", 1, 45.9, null, 32.0); validateEventQueueSize(0, moduleEvents.eventQueue); - //Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); todo this fails why? + Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); Map request = TestUtils.getCurrentRequestQueue()[0]; Assert.assertTrue(request.get("events").contains("recordEvent_queueSizeOver") && request.get("events").contains("test-joinEvents-1") && request.get("events").contains("test-joinEvents-2")); From a521d63bcab82bb27acb5c3ed164556217baf22b Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 09:43:28 +0300 Subject: [PATCH 43/58] feat: test for checking from rq --- .../java/internal/ModuleFeedbackTests.java | 31 +++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 1325b573f..c1f1a2c7f 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -495,6 +495,37 @@ public void reportFeedbackWidgetManually_validRating() { validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(1), FeedbackWidgetType.rating.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, widgetResult), 1, null, null); } + /** + * Report feedback widget manually with null widgetData and null widget result + * "reportFeedbackWidgetManually" function should record, and widgets should be written to request queue + * event queue should contain widget event, and it should have correct segmentation, also request queue should contain + */ + @Test + public void reportFeedbackWidgetManually_rq() { + init(TestUtils.getConfigFeedback(Config.Feature.Events).setEventQueueSizeToSend(2)); + + CountlyFeedbackWidget widgetInfoNps = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + validateEventQueueSize(0, moduleEvents().eventQueue); + Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfoNps, null, null); + validateEventQueueSize(1, moduleEvents().eventQueue); + Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); + + validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfoNps.widgetId, null), 1, null, null); + + CountlyFeedbackWidget widgetInfoRating = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfoRating, null, null); + validateEventQueueSize(0, moduleEvents().eventQueue); + + Storage.await(L); // wait for request queue to be processed + Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); + + List eventsInRequest = TestUtils.readEventsFromRequest(); + validateEvent(eventsInRequest.get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfoNps.widgetId, null), 1, null, null); + validateEvent(eventsInRequest.get(1), FeedbackWidgetType.rating.eventKey, requiredWidgetSegmentation(widgetInfoRating.widgetId, null), 1, null, null); + } + /** * Report feedback widget manually with invalid widgetData and null widgetResult * "reportFeedbackWidgetManually" function should record widget as an event, From e59c0f0e3ed8978b71cf0cff188036bc6f58e797 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 12:19:56 +0300 Subject: [PATCH 44/58] refactor: remove unnecessary await --- .../java/ly/count/sdk/java/internal/ModuleFeedbackTests.java | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index c1f1a2c7f..ac29d02bd 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -518,7 +518,6 @@ public void reportFeedbackWidgetManually_rq() { Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfoRating, null, null); validateEventQueueSize(0, moduleEvents().eventQueue); - Storage.await(L); // wait for request queue to be processed Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); List eventsInRequest = TestUtils.readEventsFromRequest(); From 954903811b8682da8bb86f957548060655420a5a Mon Sep 17 00:00:00 2001 From: ArtursK Date: Fri, 29 Sep 2023 12:46:49 +0300 Subject: [PATCH 45/58] code styling --- .../src/main/java/ly/count/sdk/java/internal/EventImpl.java | 2 -- .../main/java/ly/count/sdk/java/internal/ModuleRequests.java | 2 +- .../test/java/ly/count/sdk/java/internal/ModuleEventsTests.java | 2 +- .../src/test/java/ly/count/sdk/java/internal/TestUtils.java | 2 +- 4 files changed, 3 insertions(+), 5 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java index 822a2ebf0..15e12dff5 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java @@ -52,8 +52,6 @@ public interface EventRecorder { this.dow = Device.dev.currentDayOfWeek(); } - - EventImpl(@Nonnull EventRecorder recorder, @Nonnull String key, @Nonnull Log givenL) { L = givenL; if (recorder == null) { diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java index 868075230..6931ba2b6 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleRequests.java @@ -193,7 +193,7 @@ static Request addRequired(InternalConfig config, Request request) { //if nothing was in the request, no need to add these mandatory fields return request; } - + //check if it has the device ID if (!request.params.has(Params.PARAM_DEVICE_ID)) { if (config.getDeviceId() == null) { diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java index 5fe7892e1..a27f96339 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleEventsTests.java @@ -68,7 +68,7 @@ public void recordEvent_queueSizeOver() { validateEventQueueSize(0, moduleEvents.eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); - + Countly.instance().events().recordEvent(eKeys[0], 1, 45.9, null, 32.0); validateEventQueueSize(1, moduleEvents.eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index d9a39fab1..4aba72027 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -272,7 +272,7 @@ static List readEventsFromRequest(int requestIndex) { return result; } - + static void validateEventQueueSize(int expectedSize, List events, EventQueue eventQueue) { Assert.assertEquals(expectedSize, events.size()); Assert.assertEquals(expectedSize, eventQueue.eqSize()); From 1faaa376b4dc1c89efc08d4781c59eeabf3dca03 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Fri, 29 Sep 2023 13:14:11 +0300 Subject: [PATCH 46/58] some test tweaks --- .../java/internal/ModuleFeedbackTests.java | 31 ++++--------------- .../ly/count/sdk/java/internal/TestUtils.java | 19 ++++++++++++ 2 files changed, 25 insertions(+), 25 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index ac29d02bd..a297609f8 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -107,10 +107,10 @@ public void parseFeedbackList_faulty() throws JSONException { // First variation => no '_id' key String requestJson = "{\"result\":[" - + "{\"_id\":\"survID1\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"surv1\"}," + + "{\"_id\":\"survID1\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"surv1\",\"tg\":[\"aaa\"]}," + "{\"_id\":\"survID2\",\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"tg\":[\"/\"]}," + "{\"type\":\"survey\",\"exitPolicy\":\"onAbandon\",\"appearance\":{\"show\":\"uSubmit\",\"position\":\"bLeft\",\"color\":\"#2eb52b\"},\"name\":\"surv3\",\"tg\":[\"/\"]}," - + "{\"_id\":\"npsID1\",\"type\":\"nps\",\"name\":\"nps1\"}," + + "{\"_id\":\"npsID1\",\"type\":\"nps\",\"name\":\"nps1\",\"tg\":[\"bbb\", \"123\"]}," + "{\"_id\":\"npsID2\",\"type\":\"nps\",\"tg\":[]}," + "{\"type\":\"nps\",\"name\":\"nps3\",\"tg\":[]}," + "{\"_id\":\"ratingID1\",\"type\":\"rating\",\"appearance\":{\"position\":\"mleft\",\"bg_color\":\"#fff\",\"text_color\":\"#ddd\",\"text\":\"Feedback\"},\"name\":\"rating1\"}," @@ -124,9 +124,9 @@ public void parseFeedbackList_faulty() throws JSONException { Assert.assertNotNull(ret); Assert.assertEquals(6, ret.size()); - ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "surv1", "survID1", new String[] {}, ret.get(0)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "surv1", "survID1", new String[] { "aaa" }, ret.get(0)); ValidateReturnedFeedbackWidget(FeedbackWidgetType.survey, "", "survID2", new String[] { "/" }, ret.get(1)); - ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}, ret.get(2)); + ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "bbb", "123" }, ret.get(2)); ValidateReturnedFeedbackWidget(FeedbackWidgetType.nps, "", "npsID2", new String[] {}, ret.get(3)); ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}, ret.get(4)); ValidateReturnedFeedbackWidget(FeedbackWidgetType.rating, "", "ratingID2", new String[] { "/" }, ret.get(5)); @@ -157,7 +157,7 @@ public void getAvailableFeedbackWidgets() { Map params = TestUtils.parseQueryParams(requestData); Assert.assertEquals("feedback", params.get("method")); validateWidgetRequiredParams("/o/sdk", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); - validateRequiredParams(params); + TestUtils.validateRequiredParams(params); callback.callback(responseJson); }; SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; @@ -551,7 +551,7 @@ private void validateWidgetDataParams(Map params, CountlyFeedbac Assert.assertEquals(getOs(), params.get("platform")); Assert.assertEquals("1", params.get("shown")); Assert.assertEquals(String.valueOf(Device.dev.getAppVersion()), params.get("app_version")); - validateSdkRelatedParams(params); + TestUtils.validateSdkIdentityParams(params); } private void validateWidgetRequiredParams(String expectedEndpoint, String customEndpoint, Boolean requestShouldBeDelayed, Boolean networkingIsEnabled) { @@ -560,25 +560,6 @@ private void validateWidgetRequiredParams(String expectedEndpoint, String custom Assert.assertTrue(networkingIsEnabled); } - private void validateRequiredParams(Map params) { - int hour = Integer.parseInt(params.get("hour")); - int dow = Integer.parseInt(params.get("dow")); - int tz = Integer.parseInt(params.get("tz")); - - validateSdkRelatedParams(params); - Assert.assertEquals(SDKCore.instance.config.getDeviceId().id, params.get("device_id")); - Assert.assertEquals(SDKCore.instance.config.getServerAppKey(), params.get("app_key")); - Assert.assertTrue(Long.valueOf(params.get("timestamp")) > 0); - Assert.assertTrue(hour > 0 && hour < 24); - Assert.assertTrue(dow >= 0 && dow < 7); - Assert.assertTrue(tz >= -720 && tz <= 840); - } - - private void validateSdkRelatedParams(Map params) { - Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); - Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); - } - private CountlyFeedbackWidget createFeedbackWidget(FeedbackWidgetType type, String name, String id, String[] tags) { CountlyFeedbackWidget widget = new CountlyFeedbackWidget(); widget.type = type; diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 4aba72027..751dd72e5 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -285,4 +285,23 @@ static void validateEventQueueSize(int expectedSize, EventQueue eventQueue) { static String getOs() { return System.getProperty("os.name"); } + + public static void validateRequiredParams(Map params) { + int hour = Integer.parseInt(params.get("hour")); + int dow = Integer.parseInt(params.get("dow")); + int tz = Integer.parseInt(params.get("tz")); + + validateSdkIdentityParams(params); + Assert.assertEquals(SDKCore.instance.config.getDeviceId().id, params.get("device_id")); + Assert.assertEquals(SDKCore.instance.config.getServerAppKey(), params.get("app_key")); + Assert.assertTrue(Long.valueOf(params.get("timestamp")) > 0); + Assert.assertTrue(hour > 0 && hour < 24); + Assert.assertTrue(dow >= 0 && dow < 7); + Assert.assertTrue(tz >= -720 && tz <= 840); + } + + public static void validateSdkIdentityParams(Map params) { + Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); + Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); + } } \ No newline at end of file From d40667e432e873ad78c74b4732f9c682e27d278a Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 13:16:28 +0300 Subject: [PATCH 47/58] feat: feedback widgets to example app --- .../countly-sdk-java.app-java.main.iml | 2 +- .../countly-sdk-java.app-java.test.iml | 2 +- app-java/build.gradle | 16 +-- .../main/java/ly/count/java/demo/Example.java | 97 ++++++++++++++++++- 4 files changed, 106 insertions(+), 11 deletions(-) diff --git a/.idea/modules/app-java/countly-sdk-java.app-java.main.iml b/.idea/modules/app-java/countly-sdk-java.app-java.main.iml index 4c6df1dc1..5ca1f4c14 100644 --- a/.idea/modules/app-java/countly-sdk-java.app-java.main.iml +++ b/.idea/modules/app-java/countly-sdk-java.app-java.main.iml @@ -8,8 +8,8 @@ + - \ No newline at end of file diff --git a/.idea/modules/app-java/countly-sdk-java.app-java.test.iml b/.idea/modules/app-java/countly-sdk-java.app-java.test.iml index db78fd016..5aa9fed79 100644 --- a/.idea/modules/app-java/countly-sdk-java.app-java.test.iml +++ b/.idea/modules/app-java/countly-sdk-java.app-java.test.iml @@ -7,8 +7,8 @@ + - diff --git a/app-java/build.gradle b/app-java/build.gradle index 3af6cc19b..6c0872f6f 100644 --- a/app-java/build.gradle +++ b/app-java/build.gradle @@ -1,17 +1,19 @@ plugins { - id 'java' - id 'application' + id 'java' + id 'application' } repositories { - //mavenLocal() - mavenCentral() + //mavenLocal() + mavenCentral() } dependencies { - implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation project(path: ':sdk-java') - //implementation "ly.count.sdk:java:${CLY_VERSION}" + implementation 'org.json:json:20230227' + + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation project(path: ':sdk-java') + //implementation "ly.count.sdk:java:${CLY_VERSION}" } mainClassName = 'ly.count.java.demo.Sample' diff --git a/app-java/src/main/java/ly/count/java/demo/Example.java b/app-java/src/main/java/ly/count/java/demo/Example.java index 8278e9d55..f2415dd6a 100755 --- a/app-java/src/main/java/ly/count/java/demo/Example.java +++ b/app-java/src/main/java/ly/count/java/demo/Example.java @@ -1,11 +1,14 @@ package ly.count.java.demo; import java.io.File; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Scanner; import ly.count.sdk.java.Config; import ly.count.sdk.java.Countly; +import ly.count.sdk.java.internal.CountlyFeedbackWidget; import ly.count.sdk.java.internal.LogCallback; public class Example { @@ -78,6 +81,56 @@ static void setCustomProfile() { .commit(); } + static List getFeedbackWidgets() { + List widgets = new ArrayList<>(); + Countly.instance().feedback().getAvailableFeedbackWidgets((retrievedWidgets, error) -> { + if (error != null) { + System.out.println("Error while retrieving feedback widgets: " + error); + return; + } + System.out.println("Retrieved feedback widgets: " + retrievedWidgets.size()); + + for (int i = 0; i < retrievedWidgets.size(); i++) { + System.out.println(i + ") Widget: " + retrievedWidgets.get(i).toString()); + } + + widgets.addAll(retrievedWidgets); + }); + + return widgets; + } + + static void getFeedbackWidgetUrl(CountlyFeedbackWidget widget) { + Countly.instance().feedback().constructFeedbackWidgetUrl(widget, (constructedUrl, error) -> { + if (error != null) { + System.out.println("Error while retrieving feedback widget url: " + error); + return; + } + + System.out.println("Retrieved feedback widget url: " + constructedUrl); + }); + } + + static void getFeedbackWidgetData(CountlyFeedbackWidget widget) { + + Countly.instance().feedback().getFeedbackWidgetData(widget, (jsonObject, error) -> { + if (error != null) { + System.out.println("Error while retrieving feedback widget url: " + error); + return; + } + System.out.println("Retrieved feedback widget data: " + jsonObject.toString()); + }); + } + + static void reportFeedbackWidgetManually(CountlyFeedbackWidget widget) { + Map widgetResult = new HashMap<>(); + widgetResult.put("rating", 5); + widgetResult.put("comment", "This is a comment"); + widgetResult.put("email", "test@count.ly"); + + Countly.instance().feedback().reportFeedbackWidgetManually(widget, null, widgetResult); + } + static void recordStartView() { Countly.api().view("Start view"); } @@ -106,6 +159,41 @@ static void changeDeviceIdWithoutMerge() { Countly.session().changeDeviceIdWithoutMerge(randomId()); } + static void feedbackWidgets(Scanner scanner) { + + List feedbackWidgets = new ArrayList<>(); + boolean running = true; + while (running) { + System.out.println("Choose your option for feedback: "); + + System.out.println("1) Get feedback widgets"); + System.out.println("2.X) Get feedback widget data with index of widget"); + System.out.println("3.X) Report feedback widget manually with index of widget"); + System.out.println("4.X) Construct feedback widget url with index of widget"); + + String[] input = scanner.next().split("\\."); + switch (input[0]) { + case "0": + running = false; + break; + case "1": + feedbackWidgets = getFeedbackWidgets(); + break; + case "2": + getFeedbackWidgetData(feedbackWidgets.get(Integer.parseInt(input[1]))); + break; + case "3": + reportFeedbackWidgetManually(feedbackWidgets.get(Integer.parseInt(input[1]))); + break; + case "4": + getFeedbackWidgetUrl(feedbackWidgets.get(Integer.parseInt(input[1]))); + break; + default: + break; + } + } + } + public static void main(String[] args) throws Exception { Scanner scanner = new Scanner(System.in); @@ -133,7 +221,7 @@ public static void main(String[] args) throws Exception { Config config = new Config(COUNTLY_SERVER_URL, COUNTLY_APP_KEY, sdkStorageRootDirectory) .setLoggingLevel(Config.LoggingLevel.DEBUG) .setDeviceIdStrategy(Config.DeviceIdStrategy.UUID) - .enableFeatures(Config.Feature.Events, Config.Feature.Sessions, Config.Feature.CrashReporting, Config.Feature.Views, Config.Feature.UserProfiles, Config.Feature.Location) + .enableFeatures(Config.Feature.Events, Config.Feature.Sessions, Config.Feature.CrashReporting, Config.Feature.Views, Config.Feature.UserProfiles, Config.Feature.Location, Config.Feature.Feedback) .setRequiresConsent(true) //.enableParameterTamperingProtection("test-salt-checksum") .setLogListener(new LogCallback() { @@ -149,7 +237,7 @@ public void LogHappened(String logMessage, Config.LoggingLevel logLevel) { // Main initialization call, SDK can be used after this one is done Countly.instance().init(config); - Countly.onConsent(Config.Feature.Events, Config.Feature.Sessions, Config.Feature.CrashReporting, Config.Feature.Views, Config.Feature.UserProfiles, Config.Feature.Location); + Countly.onConsent(Config.Feature.Events, Config.Feature.Sessions, Config.Feature.CrashReporting, Config.Feature.Views, Config.Feature.UserProfiles, Config.Feature.Location, Config.Feature.Feedback); // Usually, all interactions with SDK are to be done through a session instance: Countly.session().begin(); @@ -177,6 +265,8 @@ public void LogHappened(String logMessage, Config.LoggingLevel logLevel) { System.out.println("14) Change device id with merge"); System.out.println("15) Change device id without merge"); + System.out.println("16) Enter to feedback widget functionality"); + System.out.println("0) Exit "); int input = scanner.nextInt(); @@ -229,6 +319,9 @@ public void LogHappened(String logMessage, Config.LoggingLevel logLevel) { case 15: changeDeviceIdWithoutMerge(); break; + case 16: + feedbackWidgets(scanner); + break; default: break; } From 0a4dd9bc370b6dae149f6059e6363288dbf980c5 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 13:59:16 +0300 Subject: [PATCH 48/58] fix: jsn array check --- .../java/internal/ModuleFeedbackTests.java | 28 +++++++++---------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index a297609f8..50efe4032 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -28,7 +28,7 @@ public class ModuleFeedbackTests { @After public void stop() { - Countly.stop(true); + Countly.instance().halt(); } private void init(Config cc) { @@ -142,8 +142,8 @@ public void getAvailableFeedbackWidgets() { init(TestUtils.getConfigFeedback()); List widgets = new ArrayList<>(); - widgets.add(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {})); - widgets.add(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {})); + widgets.add(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "123", "89" })); + widgets.add(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] { "vbn" })); widgets.add(createFeedbackWidget(FeedbackWidgetType.survey, "surv1", "survID1", new String[] {})); JSONArray widgetsJson = new JSONArray(); @@ -299,7 +299,7 @@ public void getFeedbackWidgetData() { JSONObject result = new JSONObject(); result.put("result", TestUtils.feedbackWidgetData); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "hgj" }); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); @@ -323,7 +323,7 @@ public void getFeedbackWidgetData() { public void getFeedbackWidgetData_nullResponse() { init(TestUtils.getConfigFeedback()); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "jyg", "jhg" }); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); @@ -350,7 +350,7 @@ public void getFeedbackWidgetData_garbageResult() { JSONObject responseJson = new JSONObject(); responseJson.put("result", "Success"); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "yh" }); ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); @@ -388,7 +388,7 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { public void reportFeedbackWidgetManually() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "kjh" }); validateEventQueueSize(0, moduleEvents().eventQueue); Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); validateEventQueueSize(1, moduleEvents().eventQueue); @@ -405,7 +405,7 @@ public void reportFeedbackWidgetManually() { public void reportFeedbackWidgetManually_nullWidgetResultValueKeys() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] { "fg", "lh" }); validateEventQueueSize(0, moduleEvents().eventQueue); Map widgetResult = new HashMap<>(); @@ -432,7 +432,7 @@ public void reportFeedbackWidgetManually_nullWidgetResultValueKeys() { public void reportFeedbackWidgetManually_nonExistingRating() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "ou" }); validateEventQueueSize(0, moduleEvents().eventQueue); Map widgetResult = new HashMap<>(); @@ -455,7 +455,7 @@ public void reportFeedbackWidgetManually_nonExistingRating() { public void reportFeedbackWidgetManually_invalidRating() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "as" }); validateEventQueueSize(0, moduleEvents().eventQueue); Map widgetResult = new HashMap<>(); @@ -478,7 +478,7 @@ public void reportFeedbackWidgetManually_invalidRating() { public void reportFeedbackWidgetManually_validRating() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }); validateEventQueueSize(0, moduleEvents().eventQueue); Map widgetResult = new HashMap<>(); @@ -504,7 +504,7 @@ public void reportFeedbackWidgetManually_validRating() { public void reportFeedbackWidgetManually_rq() { init(TestUtils.getConfigFeedback(Config.Feature.Events).setEventQueueSizeToSend(2)); - CountlyFeedbackWidget widgetInfoNps = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfoNps = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "rt" }); validateEventQueueSize(0, moduleEvents().eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); @@ -538,7 +538,7 @@ public void reportFeedbackWidgetManually_invalidWidgetData() { widgetData.put("_id", "diff"); widgetData.put("type", "rating"); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "ty" }); validateEventQueueSize(0, moduleEvents().eventQueue); Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, widgetData, null); validateEventQueueSize(1, moduleEvents().eventQueue); @@ -583,7 +583,7 @@ private JSONObject createFeedbackWidgetJson(CountlyFeedbackWidget widget) throws widgetJson.put("_id", widget.widgetId); widgetJson.put("type", widget.type.toString()); widgetJson.put("name", widget.name); - widgetJson.put("tg", widget.tags); + widgetJson.put("tg", new JSONArray(widget.tags)); return widgetJson; } From e4410f7c002cf462705d3b512fd1b08cf3a529d0 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 14:22:07 +0300 Subject: [PATCH 49/58] fix: url encode platform and http req check --- .../ly/count/sdk/java/internal/ImmediateRequestMaker.java | 7 +++---- .../java/ly/count/sdk/java/internal/ModuleFeedback.java | 4 ++-- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java index 8d2edcd65..95b307551 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ImmediateRequestMaker.java @@ -82,13 +82,12 @@ private JSONObject doInBackground(String requestData, String customEndpoint, Tra L.e("[ImmediateRequestMaker] Encountered problem while making a immediate server request, received result was null"); return null; } - - char firstChar = receivedBuffer.trim().charAt(0); - if (code >= 200 && code < 300 && (firstChar == '[' || receivedBuffer.contains("result"))) { + + if (code >= 200 && code < 300) { L.d("[ImmediateRequestMaker] Received the following response, :[" + receivedBuffer + "]"); // we check if the result was a json array or json object and convert the array into an object if necessary - if (firstChar == '[') { + if (receivedBuffer.trim().charAt(0) == '[') { return new JSONObject("{\"jsonArray\":" + receivedBuffer + "}"); } return new JSONObject(receivedBuffer); diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index ce2e82d22..8534dff40 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -297,7 +297,7 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Cal requestData.append("&sdk_name="); requestData.append(internalConfig.getSdkName()); requestData.append("&platform="); - requestData.append(internalConfig.getSdkPlatform()); + requestData.append(Utils.urlencode(internalConfig.getSdkPlatform(), L)); requestData.append("&app_version="); requestData.append(cachedAppVersion); @@ -363,7 +363,7 @@ private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo widgetListUrl.append("&sdk_name="); widgetListUrl.append(internalConfig.getSdkName()); widgetListUrl.append("&platform="); - widgetListUrl.append(internalConfig.getSdkPlatform()); + widgetListUrl.append(Utils.urlencode(internalConfig.getSdkPlatform(), L)); final String preparedWidgetUrl = widgetListUrl.toString(); From 9cfa100f275ae7b9ef7b95328d8da6a51d877d9f Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 14:22:57 +0300 Subject: [PATCH 50/58] fix: add exit prompt to example --- app-java/src/main/java/ly/count/java/demo/Example.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app-java/src/main/java/ly/count/java/demo/Example.java b/app-java/src/main/java/ly/count/java/demo/Example.java index f2415dd6a..cc66baf67 100755 --- a/app-java/src/main/java/ly/count/java/demo/Example.java +++ b/app-java/src/main/java/ly/count/java/demo/Example.java @@ -164,8 +164,11 @@ static void feedbackWidgets(Scanner scanner) { List feedbackWidgets = new ArrayList<>(); boolean running = true; while (running) { + System.out.println("You should get feedback widgets first"); System.out.println("Choose your option for feedback: "); + System.out.println("0) To exit from feedback widget functionality"); + System.out.println("1) Get feedback widgets"); System.out.println("2.X) Get feedback widget data with index of widget"); System.out.println("3.X) Report feedback widget manually with index of widget"); From 6d42e973066aa210fcd760438f252f170cde07fa Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Fri, 29 Sep 2023 15:15:21 +0300 Subject: [PATCH 51/58] fix: tear shutdown in module events --- .../src/main/java/ly/count/sdk/java/internal/ModuleEvents.java | 3 +++ sdk-java/src/main/java/ly/count/sdk/java/internal/Tasks.java | 1 - 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java index 28ea85e81..8ed54e067 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java @@ -54,6 +54,9 @@ public void stop(CtxCore ctx, boolean clear) { if (clear) { eventQueue.clear(); } + if (executor != null) { + executor.shutdownNow(); + } } private synchronized void addEventsToRequestQ() { diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/Tasks.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/Tasks.java index aaa36cc98..86767e935 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/Tasks.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/Tasks.java @@ -7,7 +7,6 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; -import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; /** From bd43f3ecd12351da8c1d650fdacea137a5fd6af9 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Mon, 2 Oct 2023 18:19:10 +0300 Subject: [PATCH 52/58] Cleaning up the tests --- .../sdk/java/internal/ModuleFeedback.java | 4 +- .../java/internal/ModuleFeedbackTests.java | 37 +++++++++++++------ .../ly/count/sdk/java/internal/TestUtils.java | 10 +++++ 3 files changed, 37 insertions(+), 14 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index 8534dff40..d0f138b39 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -293,9 +293,9 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Cal requestData.append(Utils.urlencode(widgetInfo.widgetId, L)); requestData.append("&shown=1"); requestData.append("&sdk_version="); - requestData.append(internalConfig.getSdkVersion()); + requestData.append(Utils.urlencode(internalConfig.getSdkVersion(), L)); requestData.append("&sdk_name="); - requestData.append(internalConfig.getSdkName()); + requestData.append(Utils.urlencode(internalConfig.getSdkName(), L)); requestData.append("&platform="); requestData.append(Utils.urlencode(internalConfig.getSdkPlatform(), L)); requestData.append("&app_version="); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 50efe4032..1c383f383 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -12,6 +12,7 @@ import org.json.JSONObject; import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -26,6 +27,11 @@ public class ModuleFeedbackTests { Log L = mock(Log.class); + @Before + public void beforeTest() { + TestUtils.createCleanTestState(); + } + @After public void stop() { Countly.instance().halt(); @@ -249,7 +255,7 @@ public void constructFeedbackWidgetUrl() { widgetListUrl.append("&sdk_name="); widgetListUrl.append(internalConfig.getSdkName()); widgetListUrl.append("&platform="); - widgetListUrl.append(getOs()); + widgetListUrl.append(Utils.urlencode(getOs(), L)); Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo, (response, error) -> { Assert.assertNull(error); @@ -375,7 +381,7 @@ public void reportFeedbackWidgetManually_nullWidgetInfo() { init(TestUtils.getConfigFeedback()); Countly.instance().feedback().reportFeedbackWidgetManually(null, null, null); - List events = TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L); + List events = TestUtils.getCurrentEventQueue(); Assert.assertEquals(0, events.size()); } @@ -393,7 +399,7 @@ public void reportFeedbackWidgetManually() { Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); validateEventQueueSize(1, moduleEvents().eventQueue); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, null), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, null, 0); } /** @@ -420,7 +426,7 @@ public void reportFeedbackWidgetManually_nullWidgetResultValueKeys() { Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); validateEventQueueSize(1, moduleEvents().eventQueue); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.survey.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, expectedWidgetResult), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.survey.eventKey, widgetInfo.widgetId, widgetResult, 0); } /** @@ -487,12 +493,12 @@ public void reportFeedbackWidgetManually_validRating() { Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); validateEventQueueSize(1, moduleEvents().eventQueue); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, widgetResult), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, widgetResult, 0); widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); validateEventQueueSize(2, moduleEvents().eventQueue); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(1), FeedbackWidgetType.rating.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, widgetResult), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.rating.eventKey, widgetInfo.widgetId, widgetResult, 1); } /** @@ -512,7 +518,7 @@ public void reportFeedbackWidgetManually_rq() { validateEventQueueSize(1, moduleEvents().eventQueue); Assert.assertEquals(0, TestUtils.getCurrentRequestQueue().length); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfoNps.widgetId, null), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfoNps.widgetId, null, 0); CountlyFeedbackWidget widgetInfoRating = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfoRating, null, null); @@ -520,9 +526,8 @@ public void reportFeedbackWidgetManually_rq() { Assert.assertEquals(1, TestUtils.getCurrentRequestQueue().length); - List eventsInRequest = TestUtils.readEventsFromRequest(); - validateEvent(eventsInRequest.get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfoNps.widgetId, null), 1, null, null); - validateEvent(eventsInRequest.get(1), FeedbackWidgetType.rating.eventKey, requiredWidgetSegmentation(widgetInfoRating.widgetId, null), 1, null, null); + feedbackValidateManualResultRQ(FeedbackWidgetType.nps.eventKey, widgetInfoNps.widgetId, null, 0); + feedbackValidateManualResultRQ(FeedbackWidgetType.rating.eventKey, widgetInfoRating.widgetId, null, 1); } /** @@ -543,12 +548,12 @@ public void reportFeedbackWidgetManually_invalidWidgetData() { Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, widgetData, null); validateEventQueueSize(1, moduleEvents().eventQueue); - validateEvent(TestUtils.getCurrentEventQueue(TestUtils.getTestSDirectory(), L).get(0), FeedbackWidgetType.nps.eventKey, requiredWidgetSegmentation(widgetInfo.widgetId, null), 1, null, null); + feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, null, 0); } private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); - Assert.assertEquals(getOs(), params.get("platform")); + Assert.assertEquals(Utils.urlencode(getOs(), L), params.get("platform")); Assert.assertEquals("1", params.get("shown")); Assert.assertEquals(String.valueOf(Device.dev.getAppVersion()), params.get("app_version")); TestUtils.validateSdkIdentityParams(params); @@ -610,4 +615,12 @@ private Map requiredWidgetSegmentation(String widgetId, Map feedbacklResult, int eqIndex) { + validateEvent(TestUtils.getCurrentEventQueue().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbacklResult), 1, null, null); + } + + void feedbackValidateManualResultRQ(String eventKey, String widgetID, Map feedbacklResult, int eqIndex) { + validateEvent(TestUtils.readEventsFromRequest().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbacklResult), 1, null, null); + } } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 751dd72e5..4a0884b5b 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -11,6 +11,7 @@ import java.util.Scanner; import java.util.stream.Stream; import ly.count.sdk.java.Config; +import ly.count.sdk.java.Countly; import org.json.JSONArray; import org.junit.Assert; @@ -127,6 +128,10 @@ protected static Map[] getCurrentRequestQueue(File targetFolder, return resultMapArray; } + protected static List getCurrentEventQueue() { + return getCurrentEventQueue(getTestSDirectory(), mock(Log.class)); + } + /** * Get current event queue from target folder * @@ -304,4 +309,9 @@ public static void validateSdkIdentityParams(Map params) { Assert.assertEquals(SDKCore.instance.config.getSdkVersion(), params.get("sdk_version")); Assert.assertEquals(SDKCore.instance.config.getSdkName(), params.get("sdk_name")); } + + public static void createCleanTestState() { + Countly.instance().halt(); + //todo finish the thing + } } \ No newline at end of file From 281dcc162b6159b23f9bf048740ee61a3e8b4f25 Mon Sep 17 00:00:00 2001 From: arifBurakDemiray Date: Mon, 2 Oct 2023 18:53:49 +0300 Subject: [PATCH 53/58] fix: get app version --- .../count/sdk/java/internal/ModuleFeedbackTests.java | 11 +++-------- .../java/ly/count/sdk/java/internal/TestUtils.java | 5 ++++- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 1c383f383..5b83b78f0 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -12,7 +12,6 @@ import org.json.JSONObject; import org.junit.After; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -27,17 +26,13 @@ public class ModuleFeedbackTests { Log L = mock(Log.class); - @Before - public void beforeTest() { - TestUtils.createCleanTestState(); - } - @After public void stop() { Countly.instance().halt(); } private void init(Config cc) { + TestUtils.createCleanTestState(); Countly.instance().init(cc); } @@ -555,7 +550,7 @@ private void validateWidgetDataParams(Map params, CountlyFeedbac Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); Assert.assertEquals(Utils.urlencode(getOs(), L), params.get("platform")); Assert.assertEquals("1", params.get("shown")); - Assert.assertEquals(String.valueOf(Device.dev.getAppVersion()), params.get("app_version")); + Assert.assertEquals(String.valueOf(SDKCore.instance.config.getApplicationVersion()), params.get("app_version")); TestUtils.validateSdkIdentityParams(params); } @@ -606,7 +601,7 @@ private ModuleEvents moduleEvents() { private Map requiredWidgetSegmentation(String widgetId, Map widgetResult) { Map segm = new HashMap<>(); segm.put("platform", getOs()); - segm.put("app_version", Device.dev.getAppVersion()); + segm.put("app_version", SDKCore.instance.config.getApplicationVersion()); segm.put("widget_id", widgetId); if (widgetResult != null) { segm.putAll(widgetResult); diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 4a0884b5b..ed3d44542 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -312,6 +312,9 @@ public static void validateSdkIdentityParams(Map params) { public static void createCleanTestState() { Countly.instance().halt(); - //todo finish the thing + for (File file : getTestSDirectory().listFiles()) { + System.out.println(file); + file.delete(); + } } } \ No newline at end of file From 7cdee6c16fccf5e5140f2ad71b6b3bb2c13a484f Mon Sep 17 00:00:00 2001 From: ArtursK Date: Tue, 3 Oct 2023 12:50:06 +0300 Subject: [PATCH 54/58] Moving the file cleaner to the before block --- .../ly/count/sdk/java/internal/ModuleFeedbackTests.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index 5b83b78f0..dbe0000c4 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -12,6 +12,7 @@ import org.json.JSONObject; import org.junit.After; import org.junit.Assert; +import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; @@ -26,13 +27,17 @@ public class ModuleFeedbackTests { Log L = mock(Log.class); + @Before + public void beforeTest() { + TestUtils.createCleanTestState(); + } + @After public void stop() { Countly.instance().halt(); } private void init(Config cc) { - TestUtils.createCleanTestState(); Countly.instance().init(cc); } From 5b85d1ff419fbd2d6fcc544160f098b2fa519b57 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Tue, 3 Oct 2023 13:42:13 +0300 Subject: [PATCH 55/58] Reworking a couple of tests and changing the widget URL creation function --- .../main/java/ly/count/java/demo/Example.java | 10 +- .../sdk/java/internal/ModuleFeedback.java | 28 +-- .../java/internal/ModuleFeedbackTests.java | 180 ++++++++++-------- .../ly/count/sdk/java/internal/TestUtils.java | 4 + 4 files changed, 124 insertions(+), 98 deletions(-) diff --git a/app-java/src/main/java/ly/count/java/demo/Example.java b/app-java/src/main/java/ly/count/java/demo/Example.java index cc66baf67..c71c3415b 100755 --- a/app-java/src/main/java/ly/count/java/demo/Example.java +++ b/app-java/src/main/java/ly/count/java/demo/Example.java @@ -101,14 +101,8 @@ static List getFeedbackWidgets() { } static void getFeedbackWidgetUrl(CountlyFeedbackWidget widget) { - Countly.instance().feedback().constructFeedbackWidgetUrl(widget, (constructedUrl, error) -> { - if (error != null) { - System.out.println("Error while retrieving feedback widget url: " + error); - return; - } - - System.out.println("Retrieved feedback widget url: " + constructedUrl); - }); + String constructedUrl = Countly.instance().feedback().constructFeedbackWidgetUrl(widget); + System.out.println("Retrieved feedback widget url: " + constructedUrl); } static void getFeedbackWidgetData(CountlyFeedbackWidget widget) { diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index d0f138b39..c2189a678 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -338,14 +338,22 @@ private String validateFields(Object callback, CountlyFeedbackWidget widget) { return null; } - private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo, CallbackOnFinish callback) { - L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, callback set:[" + (callback != null) + ", widgetInfo :[" + widgetInfo + "]"); + private String constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo) { + L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, widgetInfo :[" + widgetInfo + "]"); - String error = validateFields(callback, widgetInfo); + if (widgetInfo == null) { + L.e("[ModuleFeedback] constructFeedbackWidgetUrlInternal, can't continue operation with null widget"); + return null; + } - if (error != null) { - callCallback("[ModuleFeedback] constructFeedbackWidgetUrlInternal, " + error, callback); - return; + if (internalConfig.isTemporaryIdEnabled()) { + L.e("[ModuleFeedback] constructFeedbackWidgetUrlInternal, can't continue operation when in temporary device ID mode"); + return null; + } + + if (widgetInfo.type == null || widgetInfo.widgetId == null || widgetInfo.widgetId.isEmpty()) { + L.e("[ModuleFeedback] constructFeedbackWidgetUrlInternal, can't continue operation, provided widget type or ID is 'null' or the provided ID is empty string"); + return null; } StringBuilder widgetListUrl = new StringBuilder(); @@ -368,8 +376,7 @@ private void constructFeedbackWidgetUrlInternal(CountlyFeedbackWidget widgetInfo final String preparedWidgetUrl = widgetListUrl.toString(); L.d("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Using following url for widget:[" + widgetListUrl + "]"); - - callback.onFinished(preparedWidgetUrl, null); + return preparedWidgetUrl; } public class Feedback { @@ -391,13 +398,12 @@ public void getAvailableFeedbackWidgets(@Nullable CallbackOnFinish callback) { + public String constructFeedbackWidgetUrl(@Nullable CountlyFeedbackWidget widgetInfo) { synchronized (Countly.instance()) { L.i("[Feedback] constructFeedbackWidgetUrl, Trying to present feedback widget in an alert dialog"); - constructFeedbackWidgetUrlInternal(widgetInfo, callback); + return constructFeedbackWidgetUrlInternal(widgetInfo); } } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index dbe0000c4..d3a82f0c5 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -42,8 +42,8 @@ private void init(Config cc) { } /** - * Parse feedback list response given null - * "parseFeedbackList" function should return empty list + * "parseFeedbackList" + * receives a "null" response to parse * returned feedback widget list should be empty */ @Test @@ -56,8 +56,8 @@ public void parseFeedbackList_null() throws JSONException { } /** - * Parse feedback list successfully - * "parseFeedbackList" function should return correct feedback widget list + * "parseFeedbackList" + * Correct response is given with all widget types * returned feedback widget list should have same widgets as in the response */ @Test @@ -80,10 +80,11 @@ public void parseFeedbackList() throws JSONException { } /** - * Parse feedback list successfully, remove garbage json - * "parseFeedbackList" function should return correct json feedback widget list without garbage json - * returned feedback widget list should not have garbage json + * "parseFeedbackList" + * Response with a correct entry and bad entries is given + * The correct entry should be correctly parsed and the bad ones should be ignored */ + //todo: can this test be combined with the next one? @Test public void parseFeedbackList_oneGoodWithGarbage() throws JSONException { init(TestUtils.getConfigFeedback()); @@ -100,9 +101,9 @@ public void parseFeedbackList_oneGoodWithGarbage() throws JSONException { } /** - * Parse feedback list successfully, remove faulty widgets - * "parseFeedbackList" function should return correct feedback widget list without faulty widgets - * returned feedback widget list should not have faulty widgets + * "parseFeedbackList" + * Response with partial entries given + * Only the entries with all important fields given should be returned */ @Test public void parseFeedbackList_faulty() throws JSONException { @@ -139,14 +140,12 @@ public void parseFeedbackList_faulty() throws JSONException { } /** - * Getting feedback widget list successfully - * "getAvailableFeedbackWidgets" function should return correct feedback widget list - * returned feedback widget list should be equal to the expected feedback widget list + * "getAvailableFeedbackWidgets" with mocked server response + * server responds with a correct response with all widget type + * All returned widgets should match the received ones */ @Test - public void getAvailableFeedbackWidgets() { - init(TestUtils.getConfigFeedback()); - + public void getAvailableFeedbackWidgets_properResponse() { List widgets = new ArrayList<>(); widgets.add(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "123", "89" })); widgets.add(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] { "vbn" })); @@ -159,123 +158,146 @@ public void getAvailableFeedbackWidgets() { JSONObject responseJson = new JSONObject(); responseJson.put("result", widgetsJson); - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - Map params = TestUtils.parseQueryParams(requestData); - Assert.assertEquals("feedback", params.get("method")); - validateWidgetRequiredParams("/o/sdk", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); - TestUtils.validateRequiredParams(params); - callback.callback(responseJson); - }; - SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; - - List widgetResponse = new ArrayList<>(); - Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { - Assert.assertNull(error); - Assert.assertEquals(3, response.size()); - widgetResponse.addAll(response); - }); - - widgetResponse.sort(Comparator.comparing(o -> o.widgetId)); - Assert.assertEquals(3, widgetResponse.size()); - Assert.assertEquals(widgetResponse, widgets); + getAvailableFeedbackWidgets_base(widgets, responseJson); } /** - * Getting feedback widget list with garbage json - * "getAvailableFeedbackWidgets" function should return empty feedback widget list because json is garbage + * "getAvailableFeedbackWidgets" with mocked server response + * server responds with a response that on the surface looks as expected (JSON with "result") but inside it has garbage JSON * returned feedback widget list should be empty */ @Test - public void getAvailableFeedbackWidgets_garbageJson() { - init(TestUtils.getConfigFeedback()); - + public void getAvailableFeedbackWidgets_garbageJsonInCorrectStructure() { JSONArray garbageArray = new JSONArray(); garbageArray.put(createGarbageJson()); garbageArray.put(createGarbageJson()); JSONObject responseJson = new JSONObject(); responseJson.put("result", garbageArray); - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - callback.callback(responseJson); - }; - SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + getAvailableFeedbackWidgets_base(new ArrayList<>(), responseJson); + } - Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { - Assert.assertNull(error); - Assert.assertEquals(0, response.size()); - }); + /** + * "getAvailableFeedbackWidgets" with mocked server response + * server responds with a response that is a JSON but with the wrong root field, and inside it has garbage JSON + * returned feedback widget list should be empty + */ + @Test + public void getAvailableFeedbackWidgets_garbageJsonInWrongStructure() { + JSONArray garbageArray = new JSONArray(); + garbageArray.put(createGarbageJson()); + garbageArray.put(createGarbageJson()); + JSONObject responseJson = new JSONObject(); + responseJson.put("xxxx", garbageArray); + + getAvailableFeedbackWidgets_base(new ArrayList<>(), responseJson); } /** - * Getting feedback widget list errored - * "getAvailableFeedbackWidgets" function should return error message - * returned feedback widget list should be empty and error message should not be empty + * "getAvailableFeedbackWidgets" with mocked server response + * IRM failed to parse response and therefore returns "null" + * returned feedback widget list should be empty and error message should contain the expected text */ @Test public void getAvailableFeedbackWidgets_null() { - init(TestUtils.getConfigFeedback()); + getAvailableFeedbackWidgets_base(null, null); + } - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> - callback.callback(null); + public void getAvailableFeedbackWidgets_base(List expectedWidgets, JSONObject returnedResponse) { + init(TestUtils.getConfigFeedback()); + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + Map params = TestUtils.parseQueryParams(requestData); + Assert.assertEquals("feedback", params.get("method")); + validateWidgetRequiredParams("/o/sdk", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); + TestUtils.validateRequiredParams(params); + callback.callback(returnedResponse); + }; SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; Countly.instance().feedback().getAvailableFeedbackWidgets((response, error) -> { - Assert.assertNotNull(error); - Assert.assertNull(response); - Assert.assertEquals("Not possible to retrieve widget list. Probably due to lack of connection to the server", error); + if (expectedWidgets != null) { + Assert.assertNull(error); + Assert.assertEquals(expectedWidgets.size(), response.size()); + + List widgetResponse = new ArrayList<>(response); + + widgetResponse.sort(Comparator.comparing(o -> o.widgetId)); + Assert.assertEquals(widgetResponse, expectedWidgets); + } else { + Assert.assertNull(response); + Assert.assertEquals("Not possible to retrieve widget list. Probably due to lack of connection to the server", error); + } }); } /** - * Construct feedback widget url successfully - * "constructFeedbackWidgetUrl" function should return widget url - * returned url should be same as expected url + * "constructFeedbackWidgetUrl" + * We are passing a valid "CountlyFeedbackWidget" structure + * A correct URL should be produced */ @Test public void constructFeedbackWidgetUrl() { init(TestUtils.getConfigFeedback()); - - InternalConfig internalConfig = SDKCore.instance.config; - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); StringBuilder widgetListUrl = new StringBuilder(); - widgetListUrl.append(internalConfig.getServerURL()); + widgetListUrl.append(TestUtils.SERVER_URL); widgetListUrl.append("/feedback/"); widgetListUrl.append(widgetInfo.type.name()); widgetListUrl.append("?widget_id="); widgetListUrl.append(Utils.urlencode(widgetInfo.widgetId, L)); widgetListUrl.append("&device_id="); - widgetListUrl.append(Utils.urlencode(internalConfig.getDeviceId().id, L)); + widgetListUrl.append(Utils.urlencode(TestUtils.DEVICE_ID, L)); widgetListUrl.append("&app_key="); - widgetListUrl.append(Utils.urlencode(internalConfig.getServerAppKey(), L)); + widgetListUrl.append(Utils.urlencode(TestUtils.SERVER_APP_KEY, L)); widgetListUrl.append("&sdk_version="); - widgetListUrl.append(internalConfig.getSdkVersion()); + widgetListUrl.append(TestUtils.SDK_VERSION); widgetListUrl.append("&sdk_name="); - widgetListUrl.append(internalConfig.getSdkName()); + widgetListUrl.append(TestUtils.SDK_NAME); widgetListUrl.append("&platform="); widgetListUrl.append(Utils.urlencode(getOs(), L)); - Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo, (response, error) -> { - Assert.assertNull(error); - Assert.assertEquals(widgetListUrl.toString(), response); - }); + Assert.assertEquals(widgetListUrl.toString(), Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); } /** - * Construct feedback widget url with null widget info - * "constructFeedbackWidgetUrl" function should not return widget url and return error message - * url should be null and error message should same as expected + * "constructFeedbackWidgetUrl" + * We pass a "null" widgetInfo + * Response should be "null" as that is bad input */ @Test public void constructFeedbackWidgetUrl_nullWidgetInfo() { init(TestUtils.getConfigFeedback()); + Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(null)); + } - Countly.instance().feedback().constructFeedbackWidgetUrl(null, (response, error) -> { - Assert.assertNull(response); - Assert.assertEquals("[ModuleFeedback] constructFeedbackWidgetUrlInternal, Can't continue operation with null widget", error); - }); + /** + * "constructFeedbackWidgetUrl" + * We pass a default widgetInfo, all fields would be "null" + * Response should be "null" as that is bad input + */ + @Test + public void constructFeedbackWidgetUrl_defaultWidgetInfo() { + init(TestUtils.getConfigFeedback()); + CountlyFeedbackWidget widgetInfo = new CountlyFeedbackWidget(); + Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); + } + + /** + * "constructFeedbackWidgetUrl" + * We pass a default widgetInfo, all fields would be "null" + * Response should be "null" as that is bad input + */ + @Test + public void constructFeedbackWidgetUrl_emptyWidgetInfo() { + init(TestUtils.getConfigFeedback()); + CountlyFeedbackWidget widgetInfo = new CountlyFeedbackWidget(); + widgetInfo.widgetId = ""; + widgetInfo.name = ""; + widgetInfo.type = FeedbackWidgetType.nps; + widgetInfo.tags = new String[] {}; + Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); } /** diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index ed3d44542..405e921ff 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -25,6 +25,10 @@ public class TestUtils { static String SERVER_APP_KEY = "COUNTLY_APP_KEY"; static String DEVICE_ID = "some_random_test_device_id"; + static String SDK_NAME = "java-native"; + + static String SDK_VERSION = "23.8.0"; + public static final String[] eKeys = new String[] { "eventKey1", "eventKey2", "eventKey3", "eventKey4", "eventKey5", "eventKey6", "eventKey7" }; static String feedbackWidgetData = From c8107287b3b832a2f81c6f1e145370ba2bc17b8e Mon Sep 17 00:00:00 2001 From: ArtursK Date: Tue, 3 Oct 2023 14:53:46 +0300 Subject: [PATCH 56/58] Refactoring tests --- .../sdk/java/internal/ModuleFeedback.java | 1 + .../java/internal/ModuleFeedbackTests.java | 325 ++++++++---------- .../ly/count/sdk/java/internal/TestUtils.java | 2 +- 3 files changed, 147 insertions(+), 181 deletions(-) diff --git a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java index c2189a678..458513f97 100644 --- a/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java +++ b/sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleFeedback.java @@ -318,6 +318,7 @@ private void getFeedbackWidgetDataInternal(CountlyFeedbackWidget widgetInfo, Cal L.d("[ModuleFeedback] getFeedbackWidgetDataInternal, Retrieved widget data request: [" + checkResponse + "]"); + //TODO: in the future add some validation for some common widget data fields callback.onFinished(checkResponse, null); }, L); } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index d3a82f0c5..c7d6586c4 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -17,7 +17,7 @@ import org.junit.runner.RunWith; import org.junit.runners.JUnit4; -import static ly.count.sdk.java.internal.TestUtils.getOs; +import static ly.count.sdk.java.internal.TestUtils.getOS; import static ly.count.sdk.java.internal.TestUtils.validateEvent; import static ly.count.sdk.java.internal.TestUtils.validateEventQueueSize; import static org.mockito.Mockito.mock; @@ -231,15 +231,13 @@ public void getAvailableFeedbackWidgets_base(List expecte }); } - /** - * "constructFeedbackWidgetUrl" - * We are passing a valid "CountlyFeedbackWidget" structure - * A correct URL should be produced - */ - @Test - public void constructFeedbackWidgetUrl() { + public void constructFeedbackWidgetUrl_base(CountlyFeedbackWidget widgetInfo, boolean goodResult) { init(TestUtils.getConfigFeedback()); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] {}); + + if (!goodResult) { + Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); + return; + } StringBuilder widgetListUrl = new StringBuilder(); widgetListUrl.append(TestUtils.SERVER_URL); @@ -256,11 +254,23 @@ public void constructFeedbackWidgetUrl() { widgetListUrl.append("&sdk_name="); widgetListUrl.append(TestUtils.SDK_NAME); widgetListUrl.append("&platform="); - widgetListUrl.append(Utils.urlencode(getOs(), L)); + widgetListUrl.append(Utils.urlencode(getOS(), L)); Assert.assertEquals(widgetListUrl.toString(), Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); } + /** + * "constructFeedbackWidgetUrl" + * We are passing a valid "CountlyFeedbackWidget" structure + * A correct URL should be produced + */ + @Test + public void constructFeedbackWidgetUrl_propperWidgets() { + constructFeedbackWidgetUrl_base(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "fff" }), true); + constructFeedbackWidgetUrl_base(createFeedbackWidget(FeedbackWidgetType.rating, "rating4", "1234", new String[] { "343" }), true); + constructFeedbackWidgetUrl_base(createFeedbackWidget(FeedbackWidgetType.survey, "SurveyTrip", "rtyu", new String[] {}), true); + } + /** * "constructFeedbackWidgetUrl" * We pass a "null" widgetInfo @@ -268,8 +278,7 @@ public void constructFeedbackWidgetUrl() { */ @Test public void constructFeedbackWidgetUrl_nullWidgetInfo() { - init(TestUtils.getConfigFeedback()); - Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(null)); + constructFeedbackWidgetUrl_base(null, false); } /** @@ -279,9 +288,7 @@ public void constructFeedbackWidgetUrl_nullWidgetInfo() { */ @Test public void constructFeedbackWidgetUrl_defaultWidgetInfo() { - init(TestUtils.getConfigFeedback()); - CountlyFeedbackWidget widgetInfo = new CountlyFeedbackWidget(); - Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); + constructFeedbackWidgetUrl_base(new CountlyFeedbackWidget(), false); } /** @@ -291,242 +298,221 @@ public void constructFeedbackWidgetUrl_defaultWidgetInfo() { */ @Test public void constructFeedbackWidgetUrl_emptyWidgetInfo() { - init(TestUtils.getConfigFeedback()); - CountlyFeedbackWidget widgetInfo = new CountlyFeedbackWidget(); - widgetInfo.widgetId = ""; - widgetInfo.name = ""; - widgetInfo.type = FeedbackWidgetType.nps; - widgetInfo.tags = new String[] {}; - Assert.assertNull(Countly.instance().feedback().constructFeedbackWidgetUrl(widgetInfo)); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "", "", new String[] {}); + constructFeedbackWidgetUrl_base(widgetInfo, false); } /** - * Get feedback widget data with null widget info - * "getFeedbackWidgetData" function should not return widget data and return error message - * data should be null and error message should same as expected + * "getFeedbackWidgetData" + * pass "null" widget info + * Callback response should be "null" due to faulty input. Error message should also be proper */ @Test public void getFeedbackWidgetData_nullWidgetInfo() { - init(TestUtils.getConfigFeedback()); - - Countly.instance().feedback().getFeedbackWidgetData(null, (response, error) -> { - Assert.assertNull(response); - Assert.assertEquals("[ModuleFeedback] getFeedbackWidgetDataInternal, Can't continue operation with null widget", error); - }); + getFeedbackWidgetData_base(null, null, "[ModuleFeedback] getFeedbackWidgetDataInternal, Can't continue operation with null widget"); } /** - * Get feedback widget data - * "getFeedbackWidgetData" function should return widget data related to it - * data should fill with correct data + * "getFeedbackWidgetData" + * Passing correct "CountlyFeedbackWidget". Returning mocked correct server response. Validating created request params. + * The returned response should match the initial input */ @Test public void getFeedbackWidgetData() { - init(TestUtils.getConfigFeedback()); - - JSONObject result = new JSONObject(); - result.put("result", TestUtils.feedbackWidgetData); + JSONObject responseJson = new JSONObject(); + responseJson.put("result", TestUtils.feedbackWidgetData); CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "hgj" }); - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); - validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); - callback.callback(result); - }; - SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; - - Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { - Assert.assertNull(error); - Assert.assertEquals(TestUtils.feedbackWidgetData, response.get("result")); - }); + getFeedbackWidgetData_base(responseJson, widgetInfo, null); } /** - * Get feedback widget data network error - * "getFeedbackWidgetData" function should return null widget data and error message - * data should be null and error message should be same as expected + * "getFeedbackWidgetData" + * Passing correct "CountlyFeedbackWidget". Returning mocked "null" response. Validating created request params. + * Returned response should be null and the correct error message returned */ @Test public void getFeedbackWidgetData_nullResponse() { - init(TestUtils.getConfigFeedback()); - - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "jyg", "jhg" }); - - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); - validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); - callback.callback(null); - }; - SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; - - Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { - Assert.assertEquals("Not possible to retrieve widget data. Probably due to lack of connection to the server", error); - Assert.assertNull(response); - }); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] { "jyg", "jhg" }); + getFeedbackWidgetData_base(null, widgetInfo, "Not possible to retrieve widget data. Probably due to lack of connection to the server"); } /** - * Get feedback widget data successfully - * "getFeedbackWidgetData" function should return widget data and error message should be null - * data should be same as expected and error message should null + * "getFeedbackWidgetData" + * Passing correct "CountlyFeedbackWidget". Returning mocked garbage response. Validating created request params. + * SDK does not filter returned output and just passes it along. */ @Test public void getFeedbackWidgetData_garbageResult() { - init(TestUtils.getConfigFeedback()); - JSONObject responseJson = new JSONObject(); - responseJson.put("result", "Success"); + responseJson.put("xxx", "123"); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "yh" }); + CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "nps1", "npsID1", new String[] { "yh" }); + getFeedbackWidgetData_base(responseJson, widgetInfo, null); + } - ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { - validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); - validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); - callback.callback(responseJson); - }; - SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + public void getFeedbackWidgetData_base(JSONObject responseJson, CountlyFeedbackWidget widgetInfo, String errorMessage) { + init(TestUtils.getConfigFeedback()); + + if (widgetInfo != null) { + ImmediateRequestI requestMaker = (requestData, customEndpoint, cp, requestShouldBeDelayed, networkingIsEnabled, callback, log) -> { + validateWidgetRequiredParams("/o/surveys/" + widgetInfo.type.name() + "/widget", customEndpoint, requestShouldBeDelayed, networkingIsEnabled); + validateWidgetDataParams(TestUtils.parseQueryParams(requestData), widgetInfo); + callback.callback(responseJson); + }; + SDKCore.instance.config.immediateRequestGenerator = () -> requestMaker; + } Countly.instance().feedback().getFeedbackWidgetData(widgetInfo, (response, error) -> { - Assert.assertNull(error); - Assert.assertEquals(responseJson, response); + if (errorMessage == null) { + Assert.assertNull(error); + Assert.assertEquals(responseJson, response); + } else { + Assert.assertEquals(errorMessage, error); + Assert.assertNull(response); + } }); } /** - * Report feedback widget manually with null widget info - * "reportFeedbackWidgetManually" function should not record widget as an event, - * event queue should be empty + * "reportFeedbackWidgetManually" + * All passed fields are "null" + * No event should be recorded due to this */ @Test public void reportFeedbackWidgetManually_nullWidgetInfo() { - init(TestUtils.getConfigFeedback()); - - Countly.instance().feedback().reportFeedbackWidgetManually(null, null, null); - List events = TestUtils.getCurrentEventQueue(); - Assert.assertEquals(0, events.size()); + init(TestUtils.getConfigFeedback(Config.Feature.Events)); + validateRecordingWidgetsManually(null, null, null, 0, false); } /** - * Report feedback widget manually with null widgetData and widgetResult - * "reportFeedbackWidgetManually" function should record widget as an event, - * event queue should contain it and it should have correct segmentation + * "reportFeedbackWidgetManually" + * Report widget with "null" widgetData and widgetResult + * The closed event should be recorded properly in the EQ */ @Test - public void reportFeedbackWidgetManually() { + public void reportFeedbackWidgetManually_closeEventNPS() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "kjh" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, null); - validateEventQueueSize(1, moduleEvents().eventQueue); - - feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, null, 0); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }), null, null, 0, true); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.survey, "survey", "npsID112", new String[] {}), null, null, 1, true); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.rating, "rating", "npsID133", new String[] { "hhhh" }), null, null, 2, true); } + //we want to know that the closing event can be recorded for all. missing Survey, Rating + //that all widget types can be recorded normally. missing survey + /** - * Report feedback widget manually with null widgetData and null key-value widget results - * "reportFeedbackWidgetManually" function should record widget as an event, - * event queue should contain it and it should have correct segmentation + * "reportFeedbackWidgetManually" + * Record NPS. Pass "null", empty keys and "null" values. + * Bad entries should be removed */ @Test public void reportFeedbackWidgetManually_nullWidgetResultValueKeys() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] { "fg", "lh" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Map widgetResult = new HashMap<>(); widgetResult.put("key1", null); widgetResult.put(null, null); widgetResult.put("", 6); widgetResult.put("accepted", true); + widgetResult.put("rating", 6); Map expectedWidgetResult = new HashMap<>(); expectedWidgetResult.put("accepted", true); + expectedWidgetResult.put("rating", 6); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(1, moduleEvents().eventQueue); - - feedbackValidateManualResultEQ(FeedbackWidgetType.survey.eventKey, widgetInfo.widgetId, widgetResult, 0); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.survey, "nps1", "npsID1", new String[] { "sa" }), null, widgetResult, 0, true, expectedWidgetResult); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.rating, "survey", "npsID331", new String[] { "sa" }), null, widgetResult, 1, true, expectedWidgetResult); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "rating", "npsI44D1", new String[] { "sa" }), null, widgetResult, 2, true, expectedWidgetResult); } /** - * Report a nps and a rating feedback widget manually with null widgetData and not existing rating - * "reportFeedbackWidgetManually" function should not record, - * event queue should be empty + * "reportFeedbackWidgetManually" + * Report a nps and a rating widget with "null" widgetData and no "rating" entry + * No events should be recorded since the "rating" field is mandatory */ @Test - public void reportFeedbackWidgetManually_nonExistingRating() { + public void reportFeedbackWidgetManually_nonExistingRatingField() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "ou" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Map widgetResult = new HashMap<>(); widgetResult.put("accepted", true); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(0, moduleEvents().eventQueue); - - widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(0, moduleEvents().eventQueue); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }), null, widgetResult, 0, false); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}), null, widgetResult, 0, false); } /** - * Report a nps and a rating feedback widget manually with null widgetData and invalid rating result - * "reportFeedbackWidgetManually" function should not record, - * event queue should be empty + * "reportFeedbackWidgetManually" + * Report a nps and a rating widget with "null" widgetData and the rating field has the wrong data type + * No events should be recorded since the "rating" field should be an integer */ @Test - public void reportFeedbackWidgetManually_invalidRating() { + public void reportFeedbackWidgetManually_invalidRatingField() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "as" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Map widgetResult = new HashMap<>(); widgetResult.put("rating", true); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(0, moduleEvents().eventQueue); - - widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(0, moduleEvents().eventQueue); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }), null, widgetResult, 0, false); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}), null, widgetResult, 0, false); } /** - * Report a nps and a rating feedback widget manually with null widgetData and valid rating result - * "reportFeedbackWidgetManually" function should record, - * event queue should contain widget event and it should have correct segmentation + * "reportFeedbackWidgetManually" + * Recording rating and nps widgets with correct data + * EQ should contain their recorded events */ @Test - public void reportFeedbackWidgetManually_validRating() { + public void reportFeedbackWidgetManually_validRatingField() { init(TestUtils.getConfigFeedback(Config.Feature.Events)); - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Map widgetResult = new HashMap<>(); widgetResult.put("rating", 11); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(1, moduleEvents().eventQueue); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }), null, widgetResult, 0, true); + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}), null, widgetResult, 1, true); + } - feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, widgetResult, 0); + /** + * "reportFeedbackWidgetManually" + * Record a NPS closing event. Pass widget data structure + * Everything proceeds normally + */ + @Test + public void reportFeedbackWidgetManually_invalidWidgetData() { + init(TestUtils.getConfigFeedback(Config.Feature.Events)); - widgetInfo = createFeedbackWidget(FeedbackWidgetType.rating, "rating1", "ratingID1", new String[] {}); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, null, widgetResult); - validateEventQueueSize(2, moduleEvents().eventQueue); - feedbackValidateManualResultEQ(FeedbackWidgetType.rating.eventKey, widgetInfo.widgetId, widgetResult, 1); + JSONObject widgetData = new JSONObject(); + widgetData.put("_id", "diff"); + widgetData.put("type", "rating"); + + validateRecordingWidgetsManually(createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "sa" }), widgetData, null, 0, true); + } + + void validateRecordingWidgetsManually(CountlyFeedbackWidget widgetInfo, JSONObject widgetData, Map widgetResult, int initialEQSize, boolean goodResult, Map expectedWidgetResult) { + validateEventQueueSize(initialEQSize, moduleEvents().eventQueue); + + Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, widgetData, expectedWidgetResult == null ? widgetResult : expectedWidgetResult); + + if (goodResult) { + validateEventQueueSize(initialEQSize + 1, moduleEvents().eventQueue); + feedbackValidateManualResultEQ(widgetInfo.type.eventKey, widgetInfo.widgetId, expectedWidgetResult == null ? widgetResult : expectedWidgetResult, initialEQSize); + } else { + validateEventQueueSize(initialEQSize, moduleEvents().eventQueue); + } + } + + void validateRecordingWidgetsManually(CountlyFeedbackWidget widgetInfo, JSONObject widgetData, Map widgetResult, int initialEQSize, boolean goodResult) { + validateRecordingWidgetsManually(widgetInfo, widgetData, widgetResult, initialEQSize, goodResult, null); } /** - * Report feedback widget manually with null widgetData and null widget result - * "reportFeedbackWidgetManually" function should record, and widgets should be written to request queue - * event queue should contain widget event, and it should have correct segmentation, also request queue should contain + * "reportFeedbackWidgetManually" + * Record a amount of widgtets that exceed the event threshold + * After exceeding the threshold the events should be spotted in the RQ */ @Test public void reportFeedbackWidgetManually_rq() { @@ -552,30 +538,9 @@ public void reportFeedbackWidgetManually_rq() { feedbackValidateManualResultRQ(FeedbackWidgetType.rating.eventKey, widgetInfoRating.widgetId, null, 1); } - /** - * Report feedback widget manually with invalid widgetData and null widgetResult - * "reportFeedbackWidgetManually" function should record widget as an event, - * event queue should contain it and it should have correct segmentation - */ - @Test - public void reportFeedbackWidgetManually_invalidWidgetData() { - init(TestUtils.getConfigFeedback(Config.Feature.Events)); - - JSONObject widgetData = new JSONObject(); - widgetData.put("_id", "diff"); - widgetData.put("type", "rating"); - - CountlyFeedbackWidget widgetInfo = createFeedbackWidget(FeedbackWidgetType.nps, "nps1", "npsID1", new String[] { "ty" }); - validateEventQueueSize(0, moduleEvents().eventQueue); - Countly.instance().feedback().reportFeedbackWidgetManually(widgetInfo, widgetData, null); - validateEventQueueSize(1, moduleEvents().eventQueue); - - feedbackValidateManualResultEQ(FeedbackWidgetType.nps.eventKey, widgetInfo.widgetId, null, 0); - } - private void validateWidgetDataParams(Map params, CountlyFeedbackWidget widgetInfo) { Assert.assertEquals(widgetInfo.widgetId, params.get("widget_id")); - Assert.assertEquals(Utils.urlencode(getOs(), L), params.get("platform")); + Assert.assertEquals(Utils.urlencode(getOS(), L), params.get("platform")); Assert.assertEquals("1", params.get("shown")); Assert.assertEquals(String.valueOf(SDKCore.instance.config.getApplicationVersion()), params.get("app_version")); TestUtils.validateSdkIdentityParams(params); @@ -627,7 +592,7 @@ private ModuleEvents moduleEvents() { private Map requiredWidgetSegmentation(String widgetId, Map widgetResult) { Map segm = new HashMap<>(); - segm.put("platform", getOs()); + segm.put("platform", getOS()); segm.put("app_version", SDKCore.instance.config.getApplicationVersion()); segm.put("widget_id", widgetId); if (widgetResult != null) { @@ -638,11 +603,11 @@ private Map requiredWidgetSegmentation(String widgetId, Map feedbacklResult, int eqIndex) { - validateEvent(TestUtils.getCurrentEventQueue().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbacklResult), 1, null, null); + void feedbackValidateManualResultEQ(String eventKey, String widgetID, Map feedbackWidgetResult, int eqIndex) { + validateEvent(TestUtils.getCurrentEventQueue().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null); } - void feedbackValidateManualResultRQ(String eventKey, String widgetID, Map feedbacklResult, int eqIndex) { - validateEvent(TestUtils.readEventsFromRequest().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbacklResult), 1, null, null); + void feedbackValidateManualResultRQ(String eventKey, String widgetID, Map feedbackWidgetResult, int eqIndex) { + validateEvent(TestUtils.readEventsFromRequest().get(eqIndex), eventKey, requiredWidgetSegmentation(widgetID, feedbackWidgetResult), 1, null, null); } } diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java index 405e921ff..092053ebb 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/TestUtils.java @@ -291,7 +291,7 @@ static void validateEventQueueSize(int expectedSize, EventQueue eventQueue) { validateEventQueueSize(expectedSize, TestUtils.getCurrentEventQueue(getTestSDirectory(), mock(Log.class)), eventQueue); } - static String getOs() { + static String getOS() { return System.getProperty("os.name"); } From 53351c167eac1e2e97586a2c39ee1fedb63b3033 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Tue, 3 Oct 2023 14:54:15 +0300 Subject: [PATCH 57/58] Typo --- .../java/ly/count/sdk/java/internal/ModuleFeedbackTests.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java index c7d6586c4..222cb61a3 100644 --- a/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java +++ b/sdk-java/src/test/java/ly/count/sdk/java/internal/ModuleFeedbackTests.java @@ -511,7 +511,7 @@ void validateRecordingWidgetsManually(CountlyFeedbackWidget widgetInfo, JSONObje /** * "reportFeedbackWidgetManually" - * Record a amount of widgtets that exceed the event threshold + * Record an amount of widgtets that exceed the event threshold * After exceeding the threshold the events should be spotted in the RQ */ @Test From b89a3cc408b8b1cbef194e3042eeff5aea8fcdb1 Mon Sep 17 00:00:00 2001 From: ArtursK Date: Tue, 3 Oct 2023 15:02:09 +0300 Subject: [PATCH 58/58] changelog entries --- CHANGELOG.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index fbbc30e28..43159d4d7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ * Deprecated call "resetDeviceId" is removed * Deprecated the init time configuration of 'setEventsBufferSize(eventsBufferSize)'. Introduced replacement 'setEventQueueSizeToSend(eventsQueueSize)' * Deprecated the init time configuration of 'setSendUpdateEachSeconds(sendUpdateEachSeconds)'. Introduced replacement 'setUpdateSessionTimerDelay(delay)' -* Added feedback widgets and manual reporting. Added consent for it "Config.Feature.Feedback". +* Added the feedback widget feature. Added consent for it "Config.Feature.Feedback". * Feedback module is accessible through "Countly::instance()::feedback()" call. * !! Major breaking change !! The following methods and their functionality are deprecated from the "UserEditor" interface and will not function anymore: @@ -39,7 +39,7 @@ 22.09.0 * The "resetDeviceId", "login", and "logout" have been deprecated. -* ! Minor breaking change ! The following methods and their functionality are deprecated from the "Config" class and will not function anymore: +* ! Minor breaking change ! The following methods and their functionality are deprecated from the "Config" class and will not function anymore: * "enableTestMode" * "disableTestMode" * "isTestModeEnabled"