Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(android): support custom events and attributes #1616

Merged
merged 2 commits into from
Dec 20, 2024
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
10 changes: 5 additions & 5 deletions .github/workflows/android.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ jobs:
java-version: ${{ env.JAVA_VERSION }}
cache: 'gradle'
- name: Check Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Run all checks
run: ./gradlew check

Expand All @@ -56,7 +56,7 @@ jobs:
distribution: ${{ env.JAVA_DISTRIBUTION }}
cache: 'gradle'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Assemble benchmarks
run: ./gradlew clean :benchmarks:benchmark:assembleMeasureEnabled --no-daemon --no-parallel --no-configuration-cache --stacktrace

Expand All @@ -76,7 +76,7 @@ jobs:
distribution: ${{ env.JAVA_DISTRIBUTION }}
cache: 'gradle'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Assemble benchmarks
# disable the upload of build to measure
run: ./gradlew clean :sample:assembleRelease --no-daemon --no-parallel --no-configuration-cache --stacktrace -x uploadReleaseBuildToMeasure
Expand All @@ -100,7 +100,7 @@ jobs:
distribution: ${{ env.JAVA_DISTRIBUTION }}
cache: 'gradle'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Publish measure-android
run: ./gradlew clean :measure:publish --no-daemon --no-parallel --no-configuration-cache --stacktrace
env:
Expand All @@ -126,7 +126,7 @@ jobs:
distribution: ${{ env.JAVA_DISTRIBUTION }}
cache: 'gradle'
- name: Validate Gradle wrapper
uses: gradle/wrapper-validation-action@v1
uses: gradle/actions/wrapper-validation@v3
- name: Publish measure-android-gradle to gradle plugin portal
run: ./gradlew clean :measure-android-gradle:publishPlugins -Pgradle.publish.key=$gradlePluginPortalKey -Pgradle.publish.secret=$gradlePluginPortalSecret --no-daemon --no-parallel --no-configuration-cache --stacktrace
env:
Expand Down
112 changes: 112 additions & 0 deletions android/measure/api/measure.api
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,125 @@ public final class sh/measure/android/Measure {
public final fun startSpan (Ljava/lang/String;)Lsh/measure/android/tracing/Span;
public final fun startSpan (Ljava/lang/String;J)Lsh/measure/android/tracing/Span;
public final fun stop ()V
public final fun trackEvent (Ljava/lang/String;Ljava/util/Map;Ljava/lang/Long;)V
public static synthetic fun trackEvent$default (Lsh/measure/android/Measure;Ljava/lang/String;Ljava/util/Map;Ljava/lang/Long;ILjava/lang/Object;)V
public static final fun trackHandledException (Ljava/lang/Throwable;)V
public static final fun trackNavigation (Ljava/lang/String;)V
public static final fun trackNavigation (Ljava/lang/String;Ljava/lang/String;)V
public static synthetic fun trackNavigation$default (Ljava/lang/String;Ljava/lang/String;ILjava/lang/Object;)V
public static final fun trackScreenView (Ljava/lang/String;)V
}

public abstract interface class sh/measure/android/attributes/AttributeValue {
public static final field Companion Lsh/measure/android/attributes/AttributeValue$Companion;
public abstract fun getValue ()Ljava/lang/Object;
}

public final class sh/measure/android/attributes/AttributeValue$Companion {
}

public final class sh/measure/android/attributes/AttributesBuilder {
public static final field $stable I
public fun <init> ()V
public final fun build ()Ljava/util/Map;
public final fun put (Ljava/lang/String;D)Lsh/measure/android/attributes/AttributesBuilder;
public final fun put (Ljava/lang/String;F)Lsh/measure/android/attributes/AttributesBuilder;
public final fun put (Ljava/lang/String;I)Lsh/measure/android/attributes/AttributesBuilder;
public final fun put (Ljava/lang/String;J)Lsh/measure/android/attributes/AttributesBuilder;
public final fun put (Ljava/lang/String;Ljava/lang/String;)Lsh/measure/android/attributes/AttributesBuilder;
public final fun put (Ljava/lang/String;Z)Lsh/measure/android/attributes/AttributesBuilder;
}

public final class sh/measure/android/attributes/BooleanAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (Z)Lsh/measure/android/attributes/BooleanAttr;
public static fun constructor-impl (Z)Z
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (ZLjava/lang/Object;)Z
public static final fun equals-impl0 (ZZ)Z
public fun getValue ()Ljava/lang/Boolean;
public synthetic fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (Z)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Z)Ljava/lang/String;
public final synthetic fun unbox-impl ()Z
}

public final class sh/measure/android/attributes/DoubleAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (D)Lsh/measure/android/attributes/DoubleAttr;
public static fun constructor-impl (D)D
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (DLjava/lang/Object;)Z
public static final fun equals-impl0 (DD)Z
public fun getValue ()Ljava/lang/Double;
public synthetic fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (D)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (D)Ljava/lang/String;
public final synthetic fun unbox-impl ()D
}

public final class sh/measure/android/attributes/FloatAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (F)Lsh/measure/android/attributes/FloatAttr;
public static fun constructor-impl (F)F
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (FLjava/lang/Object;)Z
public static final fun equals-impl0 (FF)Z
public fun getValue ()Ljava/lang/Float;
public synthetic fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (F)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (F)Ljava/lang/String;
public final synthetic fun unbox-impl ()F
}

public final class sh/measure/android/attributes/IntAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (I)Lsh/measure/android/attributes/IntAttr;
public static fun constructor-impl (I)I
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (ILjava/lang/Object;)Z
public static final fun equals-impl0 (II)Z
public fun getValue ()Ljava/lang/Integer;
public synthetic fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (I)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (I)Ljava/lang/String;
public final synthetic fun unbox-impl ()I
}

public final class sh/measure/android/attributes/LongAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (J)Lsh/measure/android/attributes/LongAttr;
public static fun constructor-impl (J)J
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (JLjava/lang/Object;)Z
public static final fun equals-impl0 (JJ)Z
public fun getValue ()Ljava/lang/Long;
public synthetic fun getValue ()Ljava/lang/Object;
public fun hashCode ()I
public static fun hashCode-impl (J)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (J)Ljava/lang/String;
public final synthetic fun unbox-impl ()J
}

public final class sh/measure/android/attributes/StringAttr : sh/measure/android/attributes/AttributeValue {
public static final synthetic fun box-impl (Ljava/lang/String;)Lsh/measure/android/attributes/StringAttr;
public static fun constructor-impl (Ljava/lang/String;)Ljava/lang/String;
public fun equals (Ljava/lang/Object;)Z
public static fun equals-impl (Ljava/lang/String;Ljava/lang/Object;)Z
public static final fun equals-impl0 (Ljava/lang/String;Ljava/lang/String;)Z
public synthetic fun getValue ()Ljava/lang/Object;
public fun getValue ()Ljava/lang/String;
public fun hashCode ()I
public static fun hashCode-impl (Ljava/lang/String;)I
public fun toString ()Ljava/lang/String;
public static fun toString-impl (Ljava/lang/String;)Ljava/lang/String;
public final synthetic fun unbox-impl ()Ljava/lang/String;
}

public final class sh/measure/android/config/MeasureConfig : sh/measure/android/config/IMeasureConfig {
public static final field $stable I
public fun <init> ()V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -620,10 +620,42 @@ class EventsTest {
// Implementation would go here if we could reliably trigger trim memory in tests
}

@Test
fun tracksCustomEvent() {
// Given
robot.initializeMeasure(MeasureConfig(enableLogging = true))
ActivityScenario.launch(TestActivity::class.java).use {
// When
robot.trackCustomEvent()
triggerExport()

// Then
assertEventTracked(EventType.CUSTOM)
}
}

@Test
fun tracksAttributesWithEvents() {
// Given
robot.initializeMeasure(MeasureConfig(enableLogging = true))
ActivityScenario.launch(TestActivity::class.java).use {
// When
robot.addAttribute("user_defined_attr_key", "user_defined_attr_value")
triggerExport()

// Then
assetAttribute("user_defined_attr_key", "user_defined_attr_value")
}
}

private fun String.containsEvent(eventType: String): Boolean {
return contains("\"type\":\"$eventType\"")
}

private fun String.containsAttribute(key: String, value: String): Boolean {
return contains("\"$key\":\"$value\"")
}

private fun triggerExport() {
Measure.simulateAppCrash(
type = EventType.EXCEPTION,
Expand Down Expand Up @@ -675,6 +707,11 @@ class EventsTest {
Assert.assertFalse(body.containsEvent(eventType))
}

private fun assetAttribute(key: String, value: String) {
val body = getLastRequestBody()
Assert.assertTrue(body.containsAttribute(key, value))
}

private fun getLastRequestBody(): String {
val request = mockWebServer.takeRequest(timeout = 1000, unit = TimeUnit.MILLISECONDS)
Assert.assertNotNull(request)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import androidx.test.uiautomator.By
import androidx.test.uiautomator.Direction
import androidx.test.uiautomator.UiDevice
import okhttp3.Headers
import sh.measure.android.attributes.AttributesBuilder
import sh.measure.android.config.MeasureConfig

/**
Expand Down Expand Up @@ -88,9 +89,9 @@ class EventsTestRobot {
if (network != null) {
val capabilities = connectivityManager.getNetworkCapabilities(network)
return capabilities != null && (
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || capabilities.hasTransport(
NetworkCapabilities.TRANSPORT_CELLULAR,
) || capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)
)
}
return false
Expand All @@ -117,4 +118,17 @@ class EventsTestRobot {
RuntimeException("Test exception"),
)
}

fun trackCustomEvent() {
Measure.trackEvent(
"custom_event",
AttributesBuilder().apply {
"custom_event_key" to "custom_event_value"
}.build(),
)
}

fun addAttribute(key: String, value: String) {
Measure.addAttribute("user_defined_attr_key", "user_defined_attr_value")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,9 +42,11 @@ class FakeConfigProvider : ConfigProvider {
override var defaultHttpHeadersBlocklist: List<String> = emptyList()
override var sessionEndLastEventThresholdMs: Long = 1_000_000
override var maxSessionDurationMs: Long = 6_000_000
override var maxUserDefinedAttributeKeyLength: Int = 1_000_000
override var maxUserDefinedAttributeValueLength: Int = 1_000_000
override var userDefinedAttributeKeyWithSpaces: Boolean = true
override var maxEventNameLength: Int = 64
override val customEventNameRegex: String = "^[a-zA-Z0-9_-]+\$"
override val maxUserDefinedAttributesPerEvent: Int = 100
override var maxUserDefinedAttributeKeyLength: Int = 64
override var maxUserDefinedAttributeValueLength: Int = 256
override var screenshotMaskHexColor: String = "#222222"
override var screenshotCompressionQuality: Int = 100
override var eventTypeExportAllowList: List<String> = emptyList()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -181,8 +181,6 @@ internal class TestMeasureInitializer(
override val userDefinedAttribute: UserDefinedAttribute = UserDefinedAttributeImpl(
logger,
configProvider,
database,
executorServiceRegistry.ioExecutor(),
),
override val userAttributeProcessor: UserAttributeProcessor = UserAttributeProcessor(
logger,
Expand Down
Loading
Loading