Skip to content

Commit

Permalink
[Java] Implement the new module for view tracking (#182)
Browse files Browse the repository at this point in the history
* feat: init view module

* feat: module views

* refactor: delete unused things

* refactor: delete unnecessary things for views java

* refactor: delete unnecessary things for views java

* feat: views to the flow

* fix: missing thingies

* fix: missing curly bracket

* fix: remove _idv

* feat: new functions

* fix: view segmentation

* fix: convert to linekd hash map

* feat: move convenient functions to utils

* fix: remove unnecesarry check

* feat: session changes

* fix: delete unncessary

* fix: whitespace

* fix: some of orders

* feat: random val

* Update ModuleViews.java

* Update ModuleViews.java

* Update SDKCore.java

* Update UtilsTests.java

* fix: revert test

* fix: changes from child pr

* feat: migrate test branch logic to main

* feat: ids

* feat: usage of ids

* feat: revert force send

* fix: nonull

* feat: migrate from test branch

* feat: global segmns

* feat: add log

* feat: add is empty checl

* fix: pr things

* feat: add old way call

* feat: test changes

* [Java] Implement the new module for view tracking - tests (#236)

* feat: view impl tests

* feat: on session began callback

* feat: add things to the stop

* feat: all bad values and non existing ones

* feat: scenario simple flow

* fix: simple flow

* fix: map usage

* fix: rename

* feat: segm testing1

* feat: mixed test flow 1

* feat: auto stopped ones

* feat: validate segmentation 1

* feat: validate segmentation 2

* feat: validate internal keys

* fix: int val check

* fix: remove unused var

* fix: delete unncessary things

* chore: remove whitespcae

* chore: whitespace

* feat: module views test

* feat: integrate views to flow

* fix: undo

* fix: add missing log

* fix: regex pattern

* fix: add changelog and deprecate

* fix: remove unused import

* fix: test thing

* fix: device id tests

* feat: test id generator

* fix: view impl tests

* fix: all view ids

* feat: additional tests

* fix: idx

* feat: missing test logic

* feat: new way of testing scneario

* doc: update comment of scenario id

* Update ScenarioManuelViewTests.java

* feat: test prefixes

* refactor: checkings

* Update InternalConfig.java

* Update InternalConfig.java

* chore: naming sc

* refactor: fix PR changes

* feat: basic test about global segm

* refactor: order

* feat: view case proposal

* feat: add missing test cases

* feat: views module changes

* fix: undo

* fix: conflict

* fix: conflict
  • Loading branch information
arifBurakDemiray authored Feb 1, 2024
1 parent 11d20ce commit 26eaa06
Show file tree
Hide file tree
Showing 25 changed files with 2,504 additions and 130 deletions.
10 changes: 9 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,16 @@
* "setLocale(String)"

* Added the user profiles feature interface, and it is accessible through "Countly::instance()::userProfile()" call.

* Added the location feature interface, and it is accessible through "Countly::instance()::location()" call.
* Added init time configuration for the location parameters:
* "setLocation(String countryCode, String city, String location, String ipAddress)"
* "setDisableLocation()"
* Crash Reporting interface added and accessible through "Countly::instance()::crash()" call.
* Added "disableUnhandledCrashReporting" function to the "Config" class to disable automatic uncaught crash reporting.
* Added "setMaxBreadcrumbCount(int)" function to the "Config" class to change allowed max breadcrumb count.
* Added the views feature interface, and it is accessible through "Countly::instance()::views()" call.
* Added a configuration function to set global view segmentation to the "Config" class:
* "views.setGlobalViewSegmentation(Map<String, Object>)"

* Fixed a bug where setting custom user properties would not work.
* Fixed a bug where setting organization of the user would not work.
Expand Down Expand Up @@ -47,6 +49,12 @@
* "setCustom(String, Object)" instead use "Countly::userProfile::setProperty" via "instance()" call
* "set(String, Object)" instead use "Countly::userProfile::setProperty" via "instance()" call
* "picture(byte[])" instead use "Countly::userProfile::setProperty" via "instance()" call
* Deprecated "View::start(bool)" call, use "Countly::views::startView" instead via "instance()" call.
* Deprecated "View::stop(bool)" call, use "Countly::views::stopViewWithName" or "Countly::views::stopViewWithID" instead via "instance()" call.
* Deprecated "Usage::view(String)" call, use "Countly::views::startView" instead via "instance()" call.
* Deprecated "Usage::view(String, bool)" call, use "Countly::views::startView" instead via "instance()" call.
* Deprecated "Countly::view(String)" call, use "Countly::views::startView" instead via "instance()" call.
* Deprecated "Countly::view(String, bool)" call, use "Countly::views::startView" instead via "instance()" call.

## 23.10.1

Expand Down
3 changes: 3 additions & 0 deletions sdk-java/src/main/java/ly/count/sdk/java/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import ly.count.sdk.java.internal.ConfigViews;
import ly.count.sdk.java.internal.CoreFeature;
import ly.count.sdk.java.internal.Log;
import ly.count.sdk.java.internal.LogCallback;
Expand Down Expand Up @@ -1456,4 +1457,6 @@ public Config disableLocation() {
locationEnabled = false;
return this;
}

public ConfigViews views = new ConfigViews(this);
}
16 changes: 16 additions & 0 deletions sdk-java/src/main/java/ly/count/sdk/java/Countly.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
import ly.count.sdk.java.internal.ModuleLocation;
import ly.count.sdk.java.internal.ModuleRemoteConfig;
import ly.count.sdk.java.internal.ModuleUserProfile;
import ly.count.sdk.java.internal.ModuleViews;
import ly.count.sdk.java.internal.SDKCore;

/**
Expand Down Expand Up @@ -475,6 +476,21 @@ public ModuleCrashes.Crashes crashes() {
return sdk.crashes();
}

/**
* <code>Views</code> interface to use views feature.
*
* @return {@link ModuleViews.Views} instance.
*/
public ModuleViews.Views views() {
if (!isInitialized()) {
if (L != null) {
L.e("[Countly] SDK is not initialized yet.");
}
return null;
}
return sdk.views();
}

/**
* Get existing or create new timed event object, don't record it.
*
Expand Down
1 change: 1 addition & 0 deletions sdk-java/src/main/java/ly/count/sdk/java/View.java
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
package ly.count.sdk.java;
import ly.count.sdk.java.internal.ModuleViews;

import ly.count.sdk.java.internal.ModuleViews;

Expand Down
23 changes: 23 additions & 0 deletions sdk-java/src/main/java/ly/count/sdk/java/internal/ConfigViews.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package ly.count.sdk.java.internal;

import java.util.Map;
import ly.count.sdk.java.Config;

public class ConfigViews {
private final Config config;

public ConfigViews(Config config) {
this.config = config;
}

protected Map<String, Object> globalViewSegmentation = null;

/**
* @param segmentation segmentation values that will be added for all recorded views (manual and automatic)
* @return Returns the same config object for convenient linking
*/
public Config setGlobalViewSegmentation(Map<String, Object> segmentation) {
globalViewSegmentation = segmentation;
return config;
}
}
46 changes: 45 additions & 1 deletion sdk-java/src/main/java/ly/count/sdk/java/internal/EventImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,11 @@ class EventImpl implements Event, JSONable {
protected int hour;
protected int dow;

protected String id;
protected String pvid;
protected String cvid;
protected String peid;

final Log L;

public interface EventRecorder {
Expand All @@ -38,7 +43,7 @@ public interface EventRecorder {
*/
private boolean invalid = false;

EventImpl(@Nonnull String key, int count, Double sum, Double duration, @Nonnull Map<String, Object> segmentation, @Nonnull Log givenL) {
EventImpl(@Nonnull String key, int count, Double sum, Double duration, @Nonnull Map<String, Object> segmentation, @Nonnull Log givenL, String id, String pvid, String cvid, String peid) {
L = givenL;

this.recorder = null;
Expand All @@ -51,6 +56,10 @@ public interface EventRecorder {
this.timestamp = instant.timestamp;
this.hour = instant.hour;
this.dow = instant.dow;
this.id = id;
this.pvid = pvid;
this.cvid = cvid;
this.peid = peid;
}

EventImpl(@Nonnull EventRecorder recorder, @Nonnull String key, @Nonnull Log givenL) {
Expand Down Expand Up @@ -239,6 +248,10 @@ public boolean equals(Object obj) {
protected static final String TIMESTAMP_KEY = "timestamp";
protected static final String DAY_OF_WEEK = "dow";
protected static final String HOUR = "hour";
protected static final String ID_KEY = "id";
protected static final String PV_ID_KEY = "pvid";
protected static final String CV_ID_KEY = "cvid";
protected static final String PE_ID_KEY = "peid";

/**
* Serialize to JSON format according to Countly server requirements
Expand Down Expand Up @@ -266,6 +279,23 @@ public String toJSON(@Nonnull Log log) {
if (duration != null) {
json.put(DUR_KEY, duration);
}

//set the ID's only if they are not 'null'
if (id != null) {
json.put(ID_KEY, id);
}

if (pvid != null) {
json.put(PV_ID_KEY, pvid);
}

if (cvid != null) {
json.put(CV_ID_KEY, cvid);
}

if (peid != null) {
json.put(PE_ID_KEY, peid);
}
} catch (JSONException e) {
log.e("[EventImpl] Cannot serialize event to JSON " + e);
}
Expand Down Expand Up @@ -304,6 +334,20 @@ public void recordEvent(Event event) {
event.hour = json.optInt(HOUR);
event.dow = json.optInt(DAY_OF_WEEK);

// the parsed ID's might not be set, or it might be set as null
if (!json.isNull(ID_KEY)) {
event.id = json.getString(ID_KEY);
}
if (!json.isNull(PV_ID_KEY)) {
event.pvid = json.getString(PV_ID_KEY);
}
if (!json.isNull(CV_ID_KEY)) {
event.cvid = json.getString(CV_ID_KEY);
}
if (!json.isNull(PE_ID_KEY)) {
event.peid = json.getString(PE_ID_KEY);
}

if (!json.isNull(SEGMENTATION_KEY)) {
final JSONObject segm = json.getJSONObject(SEGMENTATION_KEY);
final HashMap<String, Object> segmentation = new HashMap<>(segm.length());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public class InternalConfig extends Config {
public StorageProvider storageProvider;
protected IdGenerator viewIdGenerator;
protected IdGenerator eventIdGenerator;

protected ViewIdProvider viewIdProvider;
/**
* Shouldn't be used!
*/
Expand Down
67 changes: 41 additions & 26 deletions sdk-java/src/main/java/ly/count/sdk/java/internal/ModuleEvents.java
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package ly.count.sdk.java.internal;

import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import ly.count.sdk.java.Countly;
import ly.count.sdk.java.Session;
import ly.count.sdk.java.View;
Expand All @@ -12,6 +10,9 @@ public class ModuleEvents extends ModuleBase {
protected EventQueue eventQueue = null;
final Map<String, EventImpl> timedEvents = new ConcurrentHashMap<>();
protected Events eventsInterface = null;
ViewIdProvider viewIdProvider = null;
IdGenerator idGenerator = null;
String previousEventId = null;

@Override
public void init(InternalConfig config) {
Expand All @@ -20,6 +21,14 @@ public void init(InternalConfig config) {
eventQueue = new EventQueue(L, config.getEventsBufferSize());
eventQueue.restoreFromDisk();
eventsInterface = new Events();

idGenerator = config.eventIdGenerator;
}

@Override
public void initFinished(InternalConfig config) {
super.initFinished(config);
viewIdProvider = config.viewIdProvider;
}

@Override
Expand Down Expand Up @@ -85,37 +94,43 @@ private synchronized void addEventsToRequestQ(String deviceId) {
ModuleRequests.pushAsync(internalConfig, request);
}

protected void removeInvalidDataFromSegments(Map<String, Object> segments) {
protected void recordEventInternal(String key, int count, Double sum, Double dur, Map<String, Object> segmentation, String eventIdOverride) {
if (count <= 0) {
L.w("[ModuleEvents] recordEventInternal, Count can't be less than 1, ignoring this event.");
return;
}

if (segments == null || segments.isEmpty()) {
if (key == null || key.isEmpty()) {
L.w("[ModuleEvents] recordEventInternal, Key can't be null or empty, ignoring this event.");
return;
}

List<String> toRemove = segments.entrySet().stream()
.filter(entry -> !Utils.isValidDataType(entry.getValue()))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
L.d("[ModuleEvents] recordEventInternal, Recording event with key: [" + key + "] and provided event ID of:[" + eventIdOverride + "] and segmentation with:[" + (segmentation == null ? "null" : segmentation.size()) + "] keys");

toRemove.forEach(key -> {
L.w("[ModuleEvents] RemoveSegmentInvalidDataTypes: In segmentation Data type '" + segments.get(key) + "' of item '" + key + "' isn't valid.");
segments.remove(key);
});
}
Utils.removeInvalidDataFromSegments(segmentation, L);

protected void recordEventInternal(String key, int count, Double sum, Double dur, Map<String, Object> segmentation) {
if (count <= 0) {
L.w("[ModuleEvents] recordEventInternal: Count can't be less than 1, ignoring this event.");
return;
String eventId, pvid = null, cvid = null;
if (Utils.isEmptyOrNull(eventIdOverride)) {
L.d("[ModuleEvents] recordEventInternal, Generating new event id because it was null or empty");
eventId = idGenerator.generateId();
} else {
eventId = eventIdOverride;
}

if (key == null || key.isEmpty()) {
L.w("[ModuleEvents] recordEventInternal: Key can't be null or empty, ignoring this event.");
return;
if (key.equals(ModuleViews.KEY_VIEW_EVENT)) {
pvid = viewIdProvider.getPreviousViewId();
} else {
cvid = viewIdProvider.getCurrentViewId();
}

String previousEventIdToSend = this.previousEventId;
if (key.equals(FeedbackWidgetType.nps.eventKey) || key.equals(FeedbackWidgetType.survey.eventKey) || key.equals(ModuleViews.KEY_VIEW_EVENT) || key.equals(FeedbackWidgetType.rating.eventKey)) {
previousEventIdToSend = null;
} else {
this.previousEventId = eventId;
}

removeInvalidDataFromSegments(segmentation);
EventImpl event = new EventImpl(key, count, sum, dur, segmentation, L);
addEventToQueue(event);
addEventToQueue(new EventImpl(key, count, sum, dur, segmentation, L, eventId, pvid, cvid, previousEventIdToSend));
}

private void addEventToQueue(EventImpl event) {
Expand Down Expand Up @@ -148,7 +163,7 @@ boolean startEventInternal(final String key) {
L.w("startEventInternal, eventRecorder, No timed event with the name [" + key + "] is started, nothing to end. Will ignore call.");
return;
}
recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation);
recordEventInternal(eventImpl.key, eventImpl.count, eventImpl.sum, eventImpl.duration, eventImpl.segmentation, eventImpl.id);
}, key, L));

return true;
Expand Down Expand Up @@ -179,7 +194,7 @@ boolean endEventInternal(final String key, final Map<String, Object> segmentatio
long currentTimestamp = TimeUtils.timestampMs();
double duration = (currentTimestamp - event.timestamp) / 1000.0;

recordEventInternal(key, count, sum, duration, segmentation);
recordEventInternal(key, count, sum, duration, segmentation, null);
return true;
}

Expand Down Expand Up @@ -207,7 +222,7 @@ public class Events {
*/
public void recordEvent(String key, Map<String, Object> segmentation, int count, Double sum, Double dur) {
L.i("[Events] recordEvent: key = " + key + ", count = " + count + ", sum = " + sum + ", segmentation = " + segmentation + ", dur = " + dur);
recordEventInternal(key, count, sum, dur, segmentation);
recordEventInternal(key, count, sum, dur, segmentation, null);
}

/**
Expand Down
Loading

0 comments on commit 26eaa06

Please sign in to comment.