Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 24 additions & 1 deletion example/src/main/java/com/example/MainActivity.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import android.widget.EditText;
import android.widget.TextView;

import com.parsely.parselyandroid.ConversionType;
import com.parsely.parselyandroid.ParselyTracker;
import com.parsely.parselyandroid.SiteIdSource;
import com.parsely.parselyandroid.ParselyTrackerInternal;
Expand Down Expand Up @@ -100,7 +101,21 @@ public void trackPageview(View view) {
// the post has an internet-accessible URL, we will crawl it. urlMetadata is only used
// in the case of app-only content that we can't crawl.
ParselyTracker.sharedInstance().trackPageview(
"http://example.com/article1.html", "http://example.com/", null, null, getSiteId()
getUrl(), "http://example.com/", null, null, getSiteId()
);
}

public void trackConversion(View view) {
final Map<String, Object> extraData = new HashMap<>();
extraData.put("plan", "weekly");
ParselyTracker.sharedInstance().trackConversion(
getUrl(),
ConversionType.SUBSCRIPTION,
"demo_conversion",
"",
null,
extraData,
getSiteId()
);
}

Expand Down Expand Up @@ -147,4 +162,12 @@ private SiteIdSource getSiteId() {
}
return new SiteIdSource.Custom(fromEditText.toString());
}

private String getUrl() {
Editable fromEditText = ((EditText) findViewById(R.id.custom_url)).getText();
if (fromEditText == null || fromEditText.toString().isEmpty()) {
return "http://example.com/article1.html";
}
return fromEditText.toString();
}
}
22 changes: 20 additions & 2 deletions example/src/main/res/layout/activity_main.xml
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,20 @@
android:layout_centerHorizontal="true"
android:hint="Custom site id. Default if empty"/>

<EditText
android:id="@+id/custom_url"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/custom_site_id"
android:layout_centerHorizontal="true"
android:hint="Custom URL. Default if empty"
android:inputType="textUri"/>

<Button android:id="@+id/url_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/custom_site_id"
android:layout_below="@id/custom_url"
android:text="@string/button_track_url"
android:onClick="trackPageview" />

Expand Down Expand Up @@ -76,11 +85,20 @@
android:onClick="trackReset"
android:text="@string/button_reset_video" />

<Button
android:id="@+id/track_conversion_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@+id/reset_video_button"
android:layout_centerHorizontal="true"
android:onClick="trackConversion"
android:text="@string/button_track_conversion" />

<TextView android:id="@+id/interval"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_below="@id/reset_video_button"
android:layout_below="@id/track_conversion_button"
android:text="Flush timer inactive"/>

<TextView
Expand Down
1 change: 1 addition & 0 deletions example/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,6 @@
<string name="button_track_video">Track Video</string>
<string name="button_pause_video">Pause Video</string>
<string name="button_reset_video">Reset Video</string>
<string name="button_track_conversion">Track Conversion</string>

</resources>
14 changes: 14 additions & 0 deletions parsely/api/parsely.api
Original file line number Diff line number Diff line change
@@ -1,3 +1,15 @@
public final class com/parsely/parselyandroid/ConversionType : java/lang/Enum {
public static final field CUSTOM Lcom/parsely/parselyandroid/ConversionType;
public static final field LEAD_CAPTURE Lcom/parsely/parselyandroid/ConversionType;
public static final field LINK_CLICK Lcom/parsely/parselyandroid/ConversionType;
public static final field NEWSLETTER_SIGNUP Lcom/parsely/parselyandroid/ConversionType;
public static final field PURCHASE Lcom/parsely/parselyandroid/ConversionType;
public static final field SUBSCRIPTION Lcom/parsely/parselyandroid/ConversionType;
public static fun getEntries ()Lkotlin/enums/EnumEntries;
public static fun valueOf (Ljava/lang/String;)Lcom/parsely/parselyandroid/ConversionType;
public static fun values ()[Lcom/parsely/parselyandroid/ConversionType;
}

public final class com/parsely/parselyandroid/ParselyAlreadyInitializedException : java/lang/Exception {
public fun <init> ()V
}
Expand All @@ -21,6 +33,7 @@ public abstract interface class com/parsely/parselyandroid/ParselyTracker {
public static fun sharedInstance ()Lcom/parsely/parselyandroid/ParselyTracker;
public abstract fun startEngagement (Ljava/lang/String;Ljava/lang/String;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
public abstract fun stopEngagement ()V
public abstract fun trackConversion (Ljava/lang/String;Lcom/parsely/parselyandroid/ConversionType;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
public abstract fun trackPageview (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
public abstract fun trackPause ()V
public abstract fun trackPlay (Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyVideoMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;)V
Expand All @@ -36,6 +49,7 @@ public final class com/parsely/parselyandroid/ParselyTracker$Companion {

public final class com/parsely/parselyandroid/ParselyTracker$DefaultImpls {
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
public static synthetic fun trackConversion$default (Lcom/parsely/parselyandroid/ParselyTracker;Ljava/lang/String;Lcom/parsely/parselyandroid/ConversionType;Ljava/lang/String;Ljava/lang/String;Lcom/parsely/parselyandroid/ParselyMetadata;Ljava/util/Map;Lcom/parsely/parselyandroid/SiteIdSource;ILjava/lang/Object;)V
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
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
}
Expand Down
15 changes: 15 additions & 0 deletions parsely/src/main/java/com/parsely/parselyandroid/ConversionType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package com.parsely.parselyandroid

/**
* The category of a conversion event. The [wireValue] of each case is the string sent to
* Parse.ly over the wire and must match the values accepted by the Parse.ly conversions
* backend. Use [CUSTOM] for conversions that don't fit one of the named categories.
*/
public enum class ConversionType(internal val wireValue: String) {
NEWSLETTER_SIGNUP("newsletter_signup"),
LEAD_CAPTURE("lead_capture"),
LINK_CLICK("link_click"),
SUBSCRIPTION("subscription"),
PURCHASE("purchase"),
CUSTOM("custom"),
}
Comment thread
randyriback marked this conversation as resolved.
29 changes: 29 additions & 0 deletions parsely/src/main/java/com/parsely/parselyandroid/ParselyTracker.kt
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,35 @@ public interface ParselyTracker {
*/
public fun resetVideo()

/**
* Track a conversion event (e.g. newsletter signup, subscription, purchase).
*
* The category and label are merged into the event's `extra_data` under the reserved keys
* `_conversion_type` and `_conversion_label`, which the Parse.ly conversions backend uses to
* identify the goal. Reserved keys cannot be overridden via [extraData].
*
* @param url The URL at which the conversion occurred.
* @param conversionType The category of conversion. Use [ConversionType.CUSTOM] for
* conversions that don't fit the named categories.
* @param conversionLabel A customer-defined identifier for this conversion (e.g.
* "weekly_plan", "homepage_cta"). Events without a label are dropped
* by the Parse.ly conversions backend.
* @param urlRef The url of the page that linked to the conversion page. Analogous to HTTP referer.
* @param urlMetadata Optional metadata for the URL.
* @param extraData A map of additional information to send with the event. Reserved keys
* `_conversion_type` and `_conversion_label` will be overwritten.
* @param siteIdSource The source of the site ID to use for the event.
*/
public fun trackConversion(
url: String,
conversionType: ConversionType,
conversionLabel: String,
urlRef: String = "",
urlMetadata: ParselyMetadata? = null,
extraData: Map<String, Any>? = null,
siteIdSource: SiteIdSource = SiteIdSource.Default,
)

public companion object {
private const val DEFAULT_FLUSH_INTERVAL_SECS = 60
private var instance: ParselyTrackerInternal? = null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,6 +206,38 @@ internal class ParselyTrackerInternal internal constructor(
videoEngagementManager = null
}

override fun trackConversion(
url: String,
conversionType: ConversionType,
conversionLabel: String,
urlRef: String,
urlMetadata: ParselyMetadata?,
extraData: Map<String, Any>?,
siteIdSource: SiteIdSource,
) {
if (url.isBlank()) {
Log.e("url cannot be empty")
return
}

val mergedExtraData = (extraData ?: emptyMap()) + mapOf(
"_conversion_type" to conversionType.wireValue,
"_conversion_label" to conversionLabel,
)

enqueueEvent(
eventsBuilder.buildEvent(
url,
urlRef,
"conversion",
urlMetadata,
mergedExtraData,
generatePixelId(),
siteIdSource
)
)
}

/**
* Add an event Map to the queue.
* Place a data structure representing the event into the in-memory queue for later use.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,43 @@ internal class EventsBuilderTest {
}


@Test
fun `when building conversion event, then build the correct one`() {
// given
val extraData: Map<String, Any> = mapOf(
"_conversion_type" to "subscription",
"_conversion_label" to "weekly_plan",
"plan" to "Active",
)

// when
val event: Map<String, Any> = sut.buildEvent(
TEST_URL,
"",
"conversion",
null,
extraData,
TEST_UUID,
SiteIdSource.Default,
)

// then
assertThat(event)
.doesNotContainKey("pvid")
.doesNotContainKey("vsid")
.containsEntry("action", "conversion")
.containsEntry("url", TEST_URL)
.containsEntry("idsite", TEST_SITE_ID)
.hasEntrySatisfying("data") {
@Suppress("UNCHECKED_CAST")
it as Map<String, Any>
assertThat(it)
.containsEntry("_conversion_type", "subscription")
.containsEntry("_conversion_label", "weekly_plan")
.containsEntry("plan", "Active")
}
}

@Test
fun `given custom site id is provided, when creating a pixel, then use the custom site id`() {
// given
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,17 @@ class ParselyTrackerTest {
ParselyTracker.sharedInstance().startEngagement("url")
}

@Test
fun `given tracker initialized, when calling trackConversion, do not throw any exception`() {
ParselyTracker.init(siteId = "example.com", context = RuntimeEnvironment.getApplication())

ParselyTracker.sharedInstance().trackConversion(
url = "https://example.com/path/test-conversion",
conversionType = ConversionType.SUBSCRIPTION,
conversionLabel = "weekly_plan",
)
}

@After
fun tearDown() {
ParselyTracker.tearDown()
Expand Down
Loading