Skip to content

Commit 47696a4

Browse files
authored
Merge pull request #116 from Parsely/support_site_id_per_request
Support `siteId` per tracking request
2 parents 950109e + 9af20d9 commit 47696a4

13 files changed

Lines changed: 189 additions & 29 deletions

File tree

example/src/main/java/com/example/MainActivity.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,13 @@
44
import android.os.Bundle;
55
import android.os.Handler;
66
import android.os.Message;
7+
import android.text.Editable;
78
import android.view.View;
9+
import android.widget.EditText;
810
import android.widget.TextView;
911

1012
import com.parsely.parselyandroid.ParselyTracker;
13+
import com.parsely.parselyandroid.SiteIdSource;
1114
import com.parsely.parselyandroid.ParselyTrackerInternal;
1215
import com.parsely.parselyandroid.ParselyVideoMetadata;
1316

@@ -23,6 +26,7 @@
2326
*/
2427
public class MainActivity extends Activity {
2528

29+
public static final String DEFAULT_SITE_ID = "example.com";
2630
private InternalDebugOnlyData internalDebugOnlyData;
2731

2832
@Override
@@ -31,7 +35,7 @@ protected void onCreate(Bundle savedInstanceState) {
3135
setContentView(R.layout.activity_main);
3236

3337
// initialize the Parsely tracker with your site id and the current Context
34-
ParselyTracker.init("example.com", 30, this, true);
38+
ParselyTracker.init(DEFAULT_SITE_ID, 30, this, true);
3539
internalDebugOnlyData = new InternalDebugOnlyData((ParselyTrackerInternal) ParselyTracker.sharedInstance());
3640

3741
final TextView intervalView = (TextView) findViewById(R.id.interval);
@@ -96,14 +100,14 @@ public void trackPageview(View view) {
96100
// the post has an internet-accessible URL, we will crawl it. urlMetadata is only used
97101
// in the case of app-only content that we can't crawl.
98102
ParselyTracker.sharedInstance().trackPageview(
99-
"http://example.com/article1.html", "http://example.com/", null, null
103+
"http://example.com/article1.html", "http://example.com/", null, null, getSiteId()
100104
);
101105
}
102106

103107
public void startEngagement(View view) {
104108
final Map<String, Object> extraData = new HashMap<>();
105109
extraData.put("product-id", "12345");
106-
ParselyTracker.sharedInstance().startEngagement("http://example.com/article1.html", "http://example.com/", extraData);
110+
ParselyTracker.sharedInstance().startEngagement("http://example.com/article1.html", "http://example.com/", extraData, getSiteId());
107111
updateEngagementStrings();
108112
}
109113

@@ -124,7 +128,7 @@ public void trackPlay(View view) {
124128
90
125129
);
126130
// NOTE: For videos embedded in an article, "url" should be the URL for that article.
127-
ParselyTracker.sharedInstance().trackPlay("http://example.com/app-videos", "", metadata, null);
131+
ParselyTracker.sharedInstance().trackPlay("http://example.com/app-videos", "", metadata, null, getSiteId());
128132

129133
}
130134

@@ -135,4 +139,12 @@ public void trackPause(View view) {
135139
public void trackReset(View view) {
136140
ParselyTracker.sharedInstance().resetVideo();
137141
}
142+
143+
private SiteIdSource getSiteId() {
144+
Editable fromEditText = ((EditText) findViewById(R.id.custom_site_id)).getText();
145+
if (fromEditText == null || fromEditText.toString().isEmpty()) {
146+
return SiteIdSource.Default.INSTANCE;
147+
}
148+
return new SiteIdSource.Custom(fromEditText.toString());
149+
}
138150
}

example/src/main/res/layout/activity_main.xml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,19 +7,27 @@
77
android:paddingRight="@dimen/activity_horizontal_margin"
88
android:paddingTop="@dimen/activity_vertical_margin"
99
tools:context=".MainActivity" >
10-
10+
1111
<ImageView
1212
android:id="@+id/imageView1"
1313
android:layout_width="wrap_content"
1414
android:layout_height="wrap_content"
1515
android:layout_centerHorizontal="true"
1616
android:src="@drawable/parsely_logo_horizontal" />
17-
17+
18+
<EditText
19+
android:id="@+id/custom_site_id"
20+
android:layout_width="wrap_content"
21+
android:layout_height="wrap_content"
22+
android:layout_below="@id/imageView1"
23+
android:layout_centerHorizontal="true"
24+
android:hint="Custom site id. Default if empty"/>
25+
1826
<Button android:id="@+id/url_button"
1927
android:layout_width="wrap_content"
2028
android:layout_height="wrap_content"
2129
android:layout_centerHorizontal="true"
22-
android:layout_below="@id/imageView1"
30+
android:layout_below="@id/custom_site_id"
2331
android:text="@string/button_track_url"
2432
android:onClick="trackPageview" />
2533

parsely/api/parsely.api

Lines changed: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ public abstract interface class com/parsely/parselyandroid/ParselyTracker {
1919
public static fun init (Ljava/lang/String;Landroid/content/Context;)V
2020
public abstract fun resetVideo ()V
2121
public static fun sharedInstance ()Lcom/parsely/parselyandroid/ParselyTracker;
22-
public abstract fun startEngagement (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;)V
22+
public abstract fun startEngagement (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
2323
public abstract fun stopEngagement ()V
24-
public abstract fun trackPageview (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;)V
24+
public abstract fun trackPageview (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
2525
public abstract fun trackPause ()V
26-
public abstract fun trackPlay (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyVideoMetadata;Ljava/util/Map;)V
26+
public abstract fun trackPlay (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyVideoMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
2727
}
2828

2929
public final class com/parsely/parselyandroid/ParselyTracker$Companion {
@@ -35,13 +35,34 @@ public final class com/parsely/parselyandroid/ParselyTracker$Companion {
3535
}
3636

3737
public final class com/parsely/parselyandroid/ParselyTracker$DefaultImpls {
38-
public static synthetic fun startEngagement$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;ILjava/lang/Object;)V
39-
public static synthetic fun trackPageview$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;ILjava/lang/Object;)V
40-
public static synthetic fun trackPlay$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyVideoMetadata;Ljava/util/Map;ILjava/lang/Object;)V
38+
public static synthetic fun startEngagement$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;ILjava/lang/Object;)V
39+
public static synthetic fun trackPageview$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;ILjava/lang/Object;)V
40+
public static synthetic fun trackPlay$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyVideoMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;ILjava/lang/Object;)V
4141
}
4242

4343
public final class com/parsely/parselyandroid/ParselyVideoMetadata : com/parsely/parselyandroid/ParselyMetadata {
4444
public fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/util/Calendar;I)V
4545
public synthetic fun <init> (Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/util/List;Ljava/lang/String;Ljava/lang/String;Ljava/util/Calendar;IILkotlin/jvm/internal/DefaultConstructorMarker;)V
4646
}
4747

48+
public abstract class com/parsely/parselyandroid/SiteIdSource {
49+
}
50+
51+
public final class com/parsely/parselyandroid/SiteIdSource$Custom : com/parsely/parselyandroid/SiteIdSource {
52+
public fun <init> (Ljava/lang/String;)V
53+
public final fun component1 ()Ljava/lang/String;
54+
public final fun copy (Ljava/lang/String;)Lcom/parsely/parselyandroid/SiteIdSource$Custom;
55+
public static synthetic fun copy$default (Lcom/parsely/parselyandroid/SiteIdSource$Custom;Ljava/lang/String;ILjava/lang/Object;)Lcom/parsely/parselyandroid/SiteIdSource$Custom;
56+
public fun equals (Ljava/lang/Object;)Z
57+
public final fun getSiteId ()Ljava/lang/String;
58+
public fun hashCode ()I
59+
public fun toString ()Ljava/lang/String;
60+
}
61+
62+
public final class com/parsely/parselyandroid/SiteIdSource$Default : com/parsely/parselyandroid/SiteIdSource {
63+
public static final field INSTANCE Lcom/parsely/parselyandroid/SiteIdSource$Default;
64+
public fun equals (Ljava/lang/Object;)Z
65+
public fun hashCode ()I
66+
public fun toString ()Ljava/lang/String;
67+
}
68+

parsely/src/androidTest/java/com/parsely/parselyandroid/FunctionalTests.kt

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -282,6 +282,46 @@ class FunctionalTests {
282282
}
283283
}
284284

285+
/**
286+
* In this scenario, the consumer app starts an engagement session and configures siteId
287+
* with a custom value for the session.
288+
*
289+
* Intervals:
290+
* With current implementation of `HeartbeatIntervalCalculator`, the next intervals are:
291+
* - 10500ms for the first interval
292+
* - 13650ms for the second interval
293+
*
294+
* So after ~27,2s we should observe 2 `heartbeat` events from `startEngagement`.
295+
*
296+
* They both should have the same custom site id.
297+
*/
298+
@Test
299+
fun customSiteIdIsAppliedToConcurrentEventsInEngagementSession() {
300+
ActivityScenario.launch(SampleActivity::class.java).use { scenario ->
301+
// given
302+
val customSiteId = "customSiteId"
303+
val flushInterval = 30.seconds
304+
scenario.onActivity { activity: Activity ->
305+
beforeEach(activity)
306+
server.enqueue(MockResponse().setResponseCode(200))
307+
initializeTracker(activity, flushInterval)
308+
309+
// when
310+
parselyTracker.trackPageview("engagementUrl")
311+
parselyTracker.startEngagement("engagementUrl", siteIdSource = SiteIdSource.Custom(customSiteId))
312+
}
313+
314+
// then
315+
val request = server.takeRequest().toMap()["events"]!!
316+
317+
assertThat(request.filter { it.action=="heartbeat" }).hasSize(2)
318+
.allSatisfy {
319+
assertThat(it.idsite).isEqualTo(customSiteId)
320+
}
321+
}
322+
323+
}
324+
285325
private fun RecordedRequest.toMap(): Map<String, List<Event>> {
286326
val listType: TypeReference<Map<String, List<Event>>> =
287327
object : TypeReference<Map<String, List<Event>>>() {}

parsely/src/main/java/com/parsely/parselyandroid/ConnectivityStatusProvider.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ internal interface ConnectivityStatusProvider {
1010
fun isReachable(): Boolean
1111
}
1212

13-
internal class AndroidConnectivityStatusProvider(private val context: Context): ConnectivityStatusProvider {
13+
internal class AndroidConnectivityStatusProvider(private val context: Context):
14+
ConnectivityStatusProvider {
1415

1516
override fun isReachable(): Boolean {
1617
val cm = context.getSystemService(

parsely/src/main/java/com/parsely/parselyandroid/EventsBuilder.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
11
package com.parsely.parselyandroid
22

33
import com.parsely.parselyandroid.Logging.log
4-
import java.util.Calendar
5-
import java.util.TimeZone
64

75
internal class EventsBuilder(
86
private val deviceInfoRepository: DeviceInfoRepository,
9-
private val siteId: String,
7+
private val initializationSiteId: String,
108
private val clock: Clock,
119
) {
1210
/**
@@ -16,6 +14,8 @@ internal class EventsBuilder(
1614
* @param action Action to use (e.g. pageview, heartbeat, videostart, vheartbeat)
1715
* @param metadata Metadata to attach to the event.
1816
* @param extraData A Map of additional information to send with the event.
17+
* @param uuid A unique identifier for the event.
18+
* @param siteIdSource The source of the site ID to use for the event.
1919
* @return A Map object representing the event to be sent to Parse.ly.
2020
*/
2121
fun buildEvent(
@@ -24,15 +24,19 @@ internal class EventsBuilder(
2424
action: String,
2525
metadata: ParselyMetadata?,
2626
extraData: Map<String, Any>?,
27-
uuid: String
27+
uuid: String,
28+
siteIdSource: SiteIdSource,
2829
): Map<String, Any> {
2930
log("buildEvent called for %s/%s", action, url)
3031

3132
// Main event info
3233
val event: MutableMap<String, Any> = HashMap()
3334
event["url"] = url
3435
event["urlref"] = urlRef
35-
event["idsite"] = siteId
36+
event["idsite"] = when (siteIdSource) {
37+
is SiteIdSource.Default -> initializationSiteId
38+
is SiteIdSource.Custom -> siteIdSource.siteId
39+
}
3640
event["action"] = action
3741

3842
// Make a copy of extraData and add some things.

parsely/src/main/java/com/parsely/parselyandroid/FlushQueue.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,13 @@ internal class FlushQueue(
3131
return@launch
3232
}
3333

34+
val jsonPayload = toParselyEventsPayload(eventsToSend)
3435
if (skipSendingEvents) {
35-
log("Debug mode on. Not sending to Parse.ly. Otherwise, would sent ${eventsToSend.size} events")
36+
log("Debug mode on. Not sending to Parse.ly. Otherwise, would sent ${eventsToSend.size} events: $jsonPayload")
3637
repository.remove(eventsToSend)
3738
return@launch
3839
}
3940
log("Sending request with %d events", eventsToSend.size)
40-
val jsonPayload = toParselyEventsPayload(eventsToSend)
4141
log("POST Data %s", jsonPayload)
4242
log("Requested %s", ParselyTrackerInternal.ROOT_URL)
4343
restClient.send(jsonPayload)

parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,16 @@ public interface ParselyTracker {
3636
* content). Do not use this for **content also hosted on** URLs Parse.ly
3737
* would normally crawl.
3838
* @param extraData A Map of additional information to send with the event.
39+
* @param siteIdSource The source of the site ID to use for the event. If [SiteIdSource.Default],
40+
* the site ID provided during [init] will be used. Otherwise, the site ID
41+
* provided in the [SiteIdSource.Custom] object will be used.
3942
*/
4043
public fun trackPageview(
4144
url: String,
4245
urlRef: String = "",
4346
urlMetadata: ParselyMetadata? = null,
4447
extraData: Map<String, Any>? = null,
48+
siteIdSource: SiteIdSource = SiteIdSource.Default,
4549
)
4650

4751
/**
@@ -59,11 +63,13 @@ public interface ParselyTracker {
5963
* @param url The URL of the tracked article (eg: “http://example.com/some-old/article.html“)
6064
* @param urlRef The url of the page that linked to the page being engaged with. Analogous to HTTP referer
6165
* @param extraData A map of additional information to send with the generated `heartbeat` events
66+
* @param siteIdSource The source of the site ID to use for the event.
6267
*/
6368
public fun startEngagement(
6469
url: String,
6570
urlRef: String = "",
66-
extraData: Map<String, Any>? = null
71+
extraData: Map<String, Any>? = null,
72+
siteIdSource: SiteIdSource = SiteIdSource.Default,
6773
)
6874

6975
/**
@@ -96,12 +102,14 @@ public interface ParselyTracker {
96102
* @param urlRef The url of the page that linked to the page being engaged with. Analogous to HTTP referer
97103
* @param videoMetadata Metadata about the tracked video
98104
* @param extraData A Map of additional information to send with the event
105+
* @param siteIdSource The source of the site ID to use for the event.
99106
*/
100107
public fun trackPlay(
101108
url: String,
102109
urlRef: String = "",
103110
videoMetadata: ParselyVideoMetadata,
104111
extraData: Map<String, Any>? = null,
112+
siteIdSource: SiteIdSource = SiteIdSource.Default,
105113
)
106114

107115
/**

parsely/src/main/java/com/parsely/parselyandroid/ParselyTrackerInternal.kt

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ internal class ParselyTrackerInternal internal constructor(
8989
urlRef: String,
9090
urlMetadata: ParselyMetadata?,
9191
extraData: Map<String, Any>?,
92+
siteIdSource: SiteIdSource,
9293
) {
9394
if (url.isBlank()) {
9495
Logging.log("url cannot be empty")
@@ -105,15 +106,17 @@ internal class ParselyTrackerInternal internal constructor(
105106
"pageview",
106107
urlMetadata,
107108
extraData,
108-
pageViewUuid
109+
pageViewUuid,
110+
siteIdSource
109111
)
110112
)
111113
}
112114

113115
override fun startEngagement(
114116
url: String,
115117
urlRef: String,
116-
extraData: Map<String, Any>?
118+
extraData: Map<String, Any>?,
119+
siteIdSource: SiteIdSource,
117120
) {
118121
if (url.isBlank()) {
119122
Logging.log("url cannot be empty")
@@ -130,7 +133,7 @@ internal class ParselyTrackerInternal internal constructor(
130133

131134
// Start a new EngagementTask
132135
val event =
133-
eventsBuilder.buildEvent(url, urlRef, "heartbeat", null, extraData, pageViewUuid)
136+
eventsBuilder.buildEvent(url, urlRef, "heartbeat", null, extraData, pageViewUuid, siteIdSource)
134137
engagementManager = EngagementManager(
135138
this,
136139
DEFAULT_ENGAGEMENT_INTERVAL_MILLIS.toLong(),
@@ -154,6 +157,7 @@ internal class ParselyTrackerInternal internal constructor(
154157
urlRef: String,
155158
videoMetadata: ParselyVideoMetadata,
156159
extraData: Map<String, Any>?,
160+
siteIdSource: SiteIdSource,
157161
) {
158162
if (url.isBlank()) {
159163
Logging.log("url cannot be empty")
@@ -176,12 +180,12 @@ internal class ParselyTrackerInternal internal constructor(
176180

177181
// Enqueue the videostart
178182
val videostartEvent =
179-
eventsBuilder.buildEvent(url, urlRef, "videostart", videoMetadata, extraData, uuid)
183+
eventsBuilder.buildEvent(url, urlRef, "videostart", videoMetadata, extraData, uuid, siteIdSource)
180184
enqueueEvent(videostartEvent)
181185

182186
// Start a new engagement manager for the video.
183187
val hbEvent =
184-
eventsBuilder.buildEvent(url, urlRef, "vheartbeat", videoMetadata, extraData, uuid)
188+
eventsBuilder.buildEvent(url, urlRef, "vheartbeat", videoMetadata, extraData, uuid, siteIdSource)
185189
// TODO: Can we remove some metadata fields from this request?
186190
videoEngagementManager = EngagementManager(
187191
this,
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.parsely.parselyandroid
2+
3+
/**
4+
* Configuration for the site ID to be used for an event.
5+
*/
6+
public sealed class SiteIdSource {
7+
/**
8+
* Instruct SDK to use site ID provided during [ParselyTracker.init].
9+
*/
10+
public data object Default : SiteIdSource()
11+
12+
/**
13+
* Instruct SDK to override the site ID for the event.
14+
*
15+
* @param siteId The Parsely public site ID (e.g. "example.com") to use for the event.
16+
*/
17+
public data class Custom(val siteId: String) : SiteIdSource()
18+
}

0 commit comments

Comments
 (0)