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

Implement analytics plan #4734

Merged
merged 25 commits into from
Jan 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
e416f10
Reformat code using AS
bmarty Dec 15, 2021
e487621
Analytics: create AnalyticsTracker interface
bmarty Dec 15, 2021
7a6f3cb
Analytics: Send the Event `Click(name = Click.Name.SendMessageButton)…
bmarty Dec 15, 2021
3e125bc
Analytics: Send the Event `CreatedRoom()` (#4716)
bmarty Dec 15, 2021
11f176e
Analytics: Create extension to compute JoinedRoom.RoomSize (#4716)
bmarty Dec 15, 2021
55a6257
Analytics: Send JoinedRoom event (#4716)
bmarty Dec 15, 2021
a8c29f5
Analytics: Send JoinedRoom event - room preview (#4716)
bmarty Dec 15, 2021
0a08a50
Analytics: Framework to send screen event
bmarty Dec 15, 2021
dfb8075
Analytics: Track some screen (#4715)
bmarty Dec 15, 2021
ebd4dc0
Analytics: Import the plan again
bmarty Dec 15, 2021
f307c48
Analytics: Track some screen (#4715)
bmarty Dec 15, 2021
67f4355
Analytics: Fix issue with the drawer
bmarty Dec 15, 2021
13b4a58
Analytics: Add more screen
bmarty Dec 15, 2021
db3353f
Analytics: Track some screen (#4715)
bmarty Dec 15, 2021
54108b8
Analytics: Track some screen (#4715)
bmarty Dec 15, 2021
1e3733f
Analytics: login/register screens
bmarty Dec 30, 2021
e3c70d1
Analytics: splashscreen
bmarty Dec 30, 2021
69a9643
Analytics: forgot password screen
bmarty Dec 30, 2021
c404699
Analytics: track call start and call end
bmarty Dec 31, 2021
880b97c
Analytics: import latest plan
bmarty Jan 19, 2022
c0aa0ce
Analytics: inject analyticsTracker, it has a better scope
bmarty Jan 19, 2022
9e57263
Changelog
bmarty Jan 19, 2022
81b8260
Add new class in analytics plan
bmarty Jan 20, 2022
0008065
Split long lines
bmarty Jan 20, 2022
58197b8
Fix enum class warning
bmarty Jan 20, 2022
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
1 change: 1 addition & 0 deletions changelog.d/4734.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Analytics: send more Events
2 changes: 1 addition & 1 deletion tools/check/forbidden_strings_in_code.txt
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ Formatter\.formatShortFileSize===1
# android\.text\.TextUtils

### This is not a rule, but a warning: the number of "enum class" has changed. For Json classes, it is mandatory that they have `@JsonClass(generateAdapter = false)`. If the enum is not used as a Json class, change the value in file forbidden_strings_in_code.txt
enum class===119
enum class===121

### Do not import temporary legacy classes
import org.matrix.android.sdk.internal.legacy.riot===3
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.call.webrtc.WebRtcCallManager
import im.vector.app.features.home.AvatarRenderer
import im.vector.app.features.navigation.Navigator
Expand Down Expand Up @@ -56,7 +56,7 @@ interface SingletonEntryPoint {

fun pinLocker(): PinLocker

fun analytics(): VectorAnalytics
fun analyticsTracker(): AnalyticsTracker
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍


fun webRtcCallManager(): WebRtcCallManager

Expand Down
4 changes: 4 additions & 0 deletions vector/src/main/java/im/vector/app/core/di/SingletonModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import im.vector.app.core.error.DefaultErrorFormatter
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.time.Clock
import im.vector.app.core.time.DefaultClock
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.impl.DefaultVectorAnalytics
import im.vector.app.features.invite.AutoAcceptInvites
Expand Down Expand Up @@ -64,6 +65,9 @@ abstract class VectorBindModule {
@Binds
abstract fun bindVectorAnalytics(analytics: DefaultVectorAnalytics): VectorAnalytics

@Binds
abstract fun bindAnalyticsTracker(analytics: DefaultVectorAnalytics): AnalyticsTracker

@Binds
abstract fun bindErrorFormatter(formatter: DefaultErrorFormatter): ErrorFormatter

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,9 @@ import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.utils.toast
import im.vector.app.features.MainActivity
import im.vector.app.features.MainActivityArgs
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.configuration.VectorConfiguration
import im.vector.app.features.consent.ConsentNotGivenHelper
import im.vector.app.features.navigation.Navigator
Expand All @@ -90,6 +92,15 @@ import timber.log.Timber
import javax.inject.Inject

abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */

protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null

protected lateinit var analyticsTracker: AnalyticsTracker

/* ==========================================================================================
* View
* ========================================================================================== */
Expand Down Expand Up @@ -133,7 +144,6 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
private lateinit var sessionListener: SessionListener
protected lateinit var bugReporter: BugReporter
private lateinit var pinLocker: PinLocker
protected lateinit var analytics: VectorAnalytics

@Inject
lateinit var rageShake: RageShake
Expand Down Expand Up @@ -189,7 +199,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
configurationViewModel = viewModelProvider.get(ConfigurationViewModel::class.java)
bugReporter = singletonEntryPoint.bugReporter()
pinLocker = singletonEntryPoint.pinLocker()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
navigator = singletonEntryPoint.navigator()
activeSessionHolder = singletonEntryPoint.activeSessionHolder()
vectorPreferences = singletonEntryPoint.vectorPreferences()
Expand Down Expand Up @@ -324,7 +334,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver
override fun onResume() {
super.onResume()
Timber.i("onResume Activity ${javaClass.simpleName}")

screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

out of interest, how come we track on exit rather than entry?

I'm not sure if this has already been defined but it might be helpful to write up what counts as a screen view event across the different platforms

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We track on exit because we want to report the duration, and this is managed by ScreenEvent. It's a bit weird, I know, generally the duration could be managed by the analytics tools (since a screen replaces another, duration can be deduced). But as we do not track every screens it probably better like that

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

makes sense, thanks for explaining!

configurationViewModel.onActivityResumed()

if (this !is BugReportActivity && vectorPreferences.useRageshake()) {
Expand Down Expand Up @@ -363,6 +373,7 @@ abstract class VectorBaseActivity<VB : ViewBinding> : AppCompatActivity(), Maver

override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker, analyticsScreenName)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do we need to be able to override the screen name in the send, can screens dynamically change at runtime?

if not we could use a val instead of var and drop the extra screenName parameter

// or non null and abstract to force all screens to supply a name
protected open val name: ScreenName? = null  

screenEvent = screenName?.let { ScreenEvent(it) }
...

screenEvent?.send(analyticsTracker)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, the screen name can change during the screen life. I made this for this particular case - LoginActivity
This is not ideal, I admit and a bit not clear...

Timber.i("onPause Activity ${javaClass.simpleName}")

rageShake.stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,9 @@ import im.vector.app.core.di.ActivityEntryPoint
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.core.utils.DimensionConverter
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import reactivecircus.flowbinding.android.view.clicks
Expand All @@ -47,6 +49,14 @@ import timber.log.Timber
* Add Mavericks capabilities, handle DI and bindings.
*/
abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomSheetDialogFragment(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */

protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null

protected lateinit var analyticsTracker: AnalyticsTracker

/* ==========================================================================================
* View
Expand Down Expand Up @@ -84,8 +94,6 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe

open val showExpanded = false

protected lateinit var analytics: VectorAnalytics

interface ResultListener {
fun onBottomSheetResult(resultCode: Int, data: Any?)

Expand Down Expand Up @@ -124,13 +132,19 @@ abstract class VectorBaseBottomSheetDialogFragment<VB : ViewBinding> : BottomShe
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
viewModelFactory = activityEntryPoint.viewModelFactory()
val singletonEntryPoint = context.singletonEntryPoint()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
super.onAttach(context)
}

override fun onResume() {
super.onResume()
Timber.i("onResume BottomSheet ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
}

override fun onPause() {
super.onPause()
screenEvent?.send(analyticsTracker)
}

override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ import im.vector.app.core.dialogs.UnrecognizedCertificateDialog
import im.vector.app.core.error.ErrorFormatter
import im.vector.app.core.extensions.singletonEntryPoint
import im.vector.app.core.extensions.toMvRxBundle
import im.vector.app.features.analytics.VectorAnalytics
import im.vector.app.features.analytics.AnalyticsTracker
import im.vector.app.features.analytics.plan.Screen
import im.vector.app.features.analytics.screen.ScreenEvent
import im.vector.app.features.navigation.Navigator
import im.vector.lib.ui.styles.dialogs.MaterialProgressDialog
import kotlinx.coroutines.flow.launchIn
Expand All @@ -51,6 +53,18 @@ import reactivecircus.flowbinding.android.view.clicks
import timber.log.Timber

abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView {
/* ==========================================================================================
* Analytics
* ========================================================================================== */

protected var analyticsScreenName: Screen.ScreenName? = null
private var screenEvent: ScreenEvent? = null

protected lateinit var analyticsTracker: AnalyticsTracker

/* ==========================================================================================
* Activity
* ========================================================================================== */

protected val vectorBaseActivity: VectorBaseActivity<*> by lazy {
activity as VectorBaseActivity<*>
Expand All @@ -61,7 +75,6 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
* ========================================================================================== */

protected lateinit var navigator: Navigator
protected lateinit var analytics: VectorAnalytics
protected lateinit var errorFormatter: ErrorFormatter
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog

Expand Down Expand Up @@ -98,7 +111,7 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
val activityEntryPoint = EntryPointAccessors.fromActivity(vectorBaseActivity, ActivityEntryPoint::class.java)
navigator = singletonEntryPoint.navigator()
errorFormatter = singletonEntryPoint.errorFormatter()
analytics = singletonEntryPoint.analytics()
analyticsTracker = singletonEntryPoint.analyticsTracker()
unrecognizedCertificateDialog = singletonEntryPoint.unrecognizedCertificateDialog()
viewModelFactory = activityEntryPoint.viewModelFactory()
childFragmentManager.fragmentFactory = activityEntryPoint.fragmentFactory()
Expand All @@ -125,12 +138,14 @@ abstract class VectorBaseFragment<VB : ViewBinding> : Fragment(), MavericksView
override fun onResume() {
super.onResume()
Timber.i("onResume Fragment ${javaClass.simpleName}")
screenEvent = analyticsScreenName?.let { ScreenEvent(it) }
}

@CallSuper
override fun onPause() {
super.onPause()
Timber.i("onPause Fragment ${javaClass.simpleName}")
screenEvent?.send(analyticsTracker)
}

@CallSuper
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics

import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen

interface AnalyticsTracker {
/**
* Capture an Event
*/
fun capture(event: VectorAnalyticsEvent)

/**
* Track a displayed screen
*/
fun screen(screen: VectorAnalyticsScreen)
}
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ private const val CHECK_INTERVAL = 2_000L
*/
@Singleton
class DecryptionFailureTracker @Inject constructor(
private val vectorAnalytics: VectorAnalytics,
private val analyticsTracker: AnalyticsTracker,
private val clock: Clock
) {

Expand Down Expand Up @@ -136,7 +136,7 @@ class DecryptionFailureTracker @Inject constructor(
// for now we ignore events already reported even if displayed again?
.filter { alreadyReported.contains(it).not() }
.forEach { failedEventId ->
vectorAnalytics.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
analyticsTracker.capture(Error(failedEventId, Error.Domain.E2EE, aggregation.key))
alreadyReported.add(failedEventId)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,9 @@

package im.vector.app.features.analytics

import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
import im.vector.app.features.analytics.itf.VectorAnalyticsScreen
import kotlinx.coroutines.flow.Flow

interface VectorAnalytics {
interface VectorAnalytics : AnalyticsTracker {
/**
* Return a Flow of Boolean, true if the user has given their consent
*/
Expand Down Expand Up @@ -60,14 +58,4 @@ interface VectorAnalytics {
* To be called when application is started
*/
fun init()

/**
* Capture an Event
*/
fun capture(event: VectorAnalyticsEvent)

/**
* Track a displayed screen
*/
fun screen(screen: VectorAnalyticsScreen)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (c) 2021 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package im.vector.app.features.analytics.extensions

import im.vector.app.features.analytics.plan.JoinedRoom
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.room.model.RoomSummary
import org.matrix.android.sdk.api.session.room.model.roomdirectory.PublicRoom

fun Int?.toAnalyticsRoomSize(): JoinedRoom.RoomSize {
return when (this) {
null,
2 -> JoinedRoom.RoomSize.Two
in 3..10 -> JoinedRoom.RoomSize.ThreeToTen
in 11..100 -> JoinedRoom.RoomSize.ElevenToOneHundred
in 101..1000 -> JoinedRoom.RoomSize.OneHundredAndOneToAThousand
else -> JoinedRoom.RoomSize.MoreThanAThousand
}
}

fun RoomSummary?.toAnalyticsJoinedRoom(): JoinedRoom {
return JoinedRoom(
isDM = this?.isDirect.orFalse(),
roomSize = this?.joinedMembersCount?.toAnalyticsRoomSize() ?: JoinedRoom.RoomSize.Two
)
}

fun PublicRoom.toAnalyticsJoinedRoom(): JoinedRoom {
return JoinedRoom(
isDM = false,
roomSize = numJoinedMembers.toAnalyticsRoomSize()
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,22 +25,22 @@ import im.vector.app.features.analytics.itf.VectorAnalyticsEvent
* Triggered when a call has ended.
*/
data class CallEnded(
/**
* The duration of the call in milliseconds.
*/
val durationMs: Int,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
/**
* The duration of the call in milliseconds.
*/
val durationMs: Int,
/**
* Whether its a video call or not.
*/
val isVideo: Boolean,
/**
* Number of participants in the call.
*/
val numParticipants: Int,
/**
* Whether this user placed it.
*/
val placed: Boolean,
) : VectorAnalyticsEvent {

override fun getName() = "CallEnded"
Expand Down
Loading