Skip to content

Commit

Permalink
Fix #4878: Add NPS Survey Events (#5080)
Browse files Browse the repository at this point in the history
## Explanation
Fixes #4878.

This is PR 4 of 6 planned PRs.

It adds eventlogs that are used to describe the survey, including
submitting the responses to the compulsory questions of the survey.

The data that needs to be recorded is tracked
[here](https://docs.google.com/spreadsheets/d/1oLP9vd8Lfd_wTUyhSFk0K6d5zhL-pVJEZJpRR-cvfXI/edit?resourcekey=0-Por1i0feHg0jff9Sps__Gw#gid=216732748):
- market_fit: MarketFit Answer
- nps_q1: NPS Score
- nps_user: UserType answer
- nps_served: Survey Popup Shown Event
- nps_abandoned: Abandon Survey Event
- language: App language is automatically added to events by the
EventBundleCreator
- region_locale: Country information is automatically added to events by
the EventBundleCreator
- app_version:  automatically added to events by the EventBundleCreator
- time_exposed/time_submitted: event timestamps are automatically added
to events by the AnalyticsController
- lesson_chapter: we're logging both topic and exploration IDs
- user_identifier: This PR adds profileId, although it is not unique.
This should be replaced by a unique ID from Core Metrics in a
fast-follow item.

The remaining data from this spreadsheet are calculated fields that will
be derived from the above parameters.

The nps_q2/ Feedback question is to be uploaded to Firestore on our new
Firestore infrastructure in PR #5003.

## Essential Checklist
<!-- Please tick the relevant boxes by putting an "x" in them. -->
- [x] The PR title and explanation each start with "Fix #bugnum: " (If
this PR fixes part of an issue, prefix the title with "Fix part of
#bugnum: ...".)
- [x] Any changes to
[scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets)
files have their rationale included in the PR explanation.
- [x] The PR follows the [style
guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide).
- [x] The PR does not contain any unnecessary code changes from Android
Studio
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#undo-unnecessary-changes)).
- [x] The PR is made from a branch that's **not** called "develop" and
is up-to-date with "develop".
- [x] The PR is **assigned** to the appropriate reviewers
([reference](https://github.com/oppia/oppia-android/wiki/Guidance-on-submitting-a-PR#clarification-regarding-assignees-and-reviewers-section)).

## Log Screenshots

### Event Logs from Developer Menu
|   |  |   
|---|---|
|
![Screenshot_1688920566](https://github.com/oppia/oppia-android/assets/59600948/fbbef767-3929-45cb-a7fc-aff89673b513)|
![Screenshot_1688758093](https://github.com/oppia/oppia-android/assets/59600948/5c3b23b1-81c7-4cde-93b4-c574c37952ba)|

### Event Logs from Firebase Debug View
|   |  |   
|---|---|
|![Screenshot 2023-07-09 at 21 13
20](https://github.com/oppia/oppia-android/assets/59600948/ef611b75-6c4f-4dd7-accd-4399f4cf1754)|![Screenshot
2023-07-09 at 21 14
04](https://github.com/oppia/oppia-android/assets/59600948/ad9d573a-215f-4120-877f-9ec11c8d2ac3)|
|![Screenshot 2023-07-09 at 21 17
08](https://github.com/oppia/oppia-android/assets/59600948/9f4fed24-a71f-45cf-b4e9-e3c6ddbce9d2)|![Screenshot
2023-07-09 at 21 18
25](https://github.com/oppia/oppia-android/assets/59600948/dcf59fb5-7ff7-4347-9a8d-6d9e0c9b6dc1)|
|![Screenshot 2023-07-09 at 22 25
27](https://github.com/oppia/oppia-android/assets/59600948/42687fca-b129-4261-ba9f-f11f0e62a63e)|
![Screenshot 2023-07-09 at 22 26
17](https://github.com/oppia/oppia-android/assets/59600948/ae462442-6ae0-4bff-9f6b-0f4f57017fca)|
  • Loading branch information
adhiamboperes authored Jul 12, 2023
1 parent 1862c4a commit f357362
Show file tree
Hide file tree
Showing 33 changed files with 1,206 additions and 87 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -529,7 +529,12 @@ class ExplorationActivityPresenter @Inject constructor(
is AsyncResult.Success -> {
if (gatingResult.value) {
val dialogFragment =
SurveyWelcomeDialogFragment.newInstance(profileId, topicId, SURVEY_QUESTIONS)
SurveyWelcomeDialogFragment.newInstance(
profileId,
topicId,
explorationId,
SURVEY_QUESTIONS
)
val transaction = activity.supportFragmentManager.beginTransaction()
transaction
.add(dialogFragment, TAG_SURVEY_WELCOME_DIALOG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,12 @@ class StateFragmentPresenter @Inject constructor(
is AsyncResult.Success -> {
if (gatingResult.value) {
val dialogFragment =
SurveyWelcomeDialogFragment.newInstance(profileId, topicId, SURVEY_QUESTIONS)
SurveyWelcomeDialogFragment.newInstance(
profileId,
topicId,
explorationId,
SURVEY_QUESTIONS
)
val transaction = activity.supportFragmentManager.beginTransaction()
transaction
.add(dialogFragment, TAG_SURVEY_WELCOME_DIALOG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,7 @@ class ExitSurveyConfirmationDialogFragment : InjectableDialogFragment() {

return exitSurveyConfirmationDialogFragmentPresenter.handleCreateView(
inflater,
container,
profileId
container
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import android.view.ViewGroup
import androidx.appcompat.app.AppCompatActivity
import androidx.fragment.app.Fragment
import org.oppia.android.app.fragment.FragmentScope
import org.oppia.android.app.model.ProfileId
import org.oppia.android.databinding.SurveyExitConfirmationDialogBinding
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.survey.SurveyController
Expand All @@ -28,8 +27,7 @@ class ExitSurveyConfirmationDialogFragmentPresenter @Inject constructor(
/** Sets up data binding. */
fun handleCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
profileId: ProfileId
container: ViewGroup?
): View {
val binding =
SurveyExitConfirmationDialogBinding.inflate(inflater, container, /* attachToRoot= */ false)
Expand Down Expand Up @@ -57,7 +55,7 @@ class ExitSurveyConfirmationDialogFragmentPresenter @Inject constructor(
}

private fun endSurveyWithCallback(callback: () -> Unit) {
surveyController.stopSurveySession().toLiveData().observe(
surveyController.stopSurveySession(surveyCompleted = false).toLiveData().observe(
activity,
{
when (it) {
Expand Down
11 changes: 7 additions & 4 deletions app/src/main/java/org/oppia/android/app/survey/SurveyActivity.kt
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,18 @@ import javax.inject.Inject

/** The activity for showing a survey. */
class SurveyActivity : InjectableAutoLocalizedAppCompatActivity() {
@Inject lateinit var surveyActivityPresenter: SurveyActivityPresenter
@Inject
lateinit var surveyActivityPresenter: SurveyActivityPresenter

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
(activityComponent as ActivityComponentImpl).inject(this)

val params = intent.extractParams()
surveyActivityPresenter.handleOnCreate(
this,
params.profileId,
params.topicId
params.topicId,
params.explorationId
)
}

Expand All @@ -39,11 +40,13 @@ class SurveyActivity : InjectableAutoLocalizedAppCompatActivity() {
fun createSurveyActivityIntent(
context: Context,
profileId: ProfileId,
topicId: String
topicId: String,
explorationId: String
): Intent {
val params = SurveyActivityParams.newBuilder().apply {
this.profileId = profileId
this.topicId = topicId
this.explorationId = explorationId
}.build()
return createSurveyActivityIntent(context, params)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,52 +1,41 @@
package org.oppia.android.app.survey

import android.content.Context
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import org.oppia.android.R
import org.oppia.android.app.activity.ActivityScope
import org.oppia.android.app.model.ProfileId
import org.oppia.android.databinding.SurveyActivityBinding
import org.oppia.android.domain.oppialogger.OppiaLogger
import javax.inject.Inject

private const val TAG_SURVEY_FRAGMENT = "TAG_SURVEY_FRAGMENT"

const val PROFILE_ID_ARGUMENT_KEY = "profile_id"
const val TOPIC_ID_ARGUMENT_KEY = "topic_id"
const val EXPLORATION_ID_ARGUMENT_KEY = "exploration_id"

/** The Presenter for [SurveyActivity]. */
@ActivityScope
class SurveyActivityPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val oppiaLogger: OppiaLogger
) {
private lateinit var profileId: ProfileId
private lateinit var topicId: String
private lateinit var context: Context

class SurveyActivityPresenter @Inject constructor(private val activity: AppCompatActivity) {
private lateinit var binding: SurveyActivityBinding

fun handleOnCreate(
context: Context,
profileId: ProfileId,
topicId: String
topicId: String,
explorationId: String
) {
binding = DataBindingUtil.setContentView(activity, R.layout.survey_activity)
binding.apply {
lifecycleOwner = activity
}

this.profileId = profileId
this.topicId = topicId
this.context = context

if (getSurveyFragment() == null) {
val surveyFragment = SurveyFragment()
val args = Bundle()
args.putInt(PROFILE_ID_ARGUMENT_KEY, profileId.internalId)
args.putString(TOPIC_ID_ARGUMENT_KEY, topicId)
args.putString(EXPLORATION_ID_ARGUMENT_KEY, explorationId)

surveyFragment.arguments = args
activity.supportFragmentManager.beginTransaction().add(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,11 +53,13 @@ class SurveyFragment :
): View? {
val internalProfileId = arguments!!.getInt(PROFILE_ID_ARGUMENT_KEY, -1)
val topicId = arguments!!.getStringFromBundle(TOPIC_ID_ARGUMENT_KEY)!!
val explorationId = arguments!!.getStringFromBundle(EXPLORATION_ID_ARGUMENT_KEY)!!

return surveyFragmentPresenter.handleCreateView(
inflater,
container,
internalProfileId,
explorationId,
topicId,
this
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import org.oppia.android.databinding.SurveyMarketFitQuestionLayoutBinding
import org.oppia.android.databinding.SurveyNpsScoreLayoutBinding
import org.oppia.android.databinding.SurveyUserTypeQuestionLayoutBinding
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.oppialogger.analytics.AnalyticsController
import org.oppia.android.domain.survey.SurveyProgressController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
Expand All @@ -43,30 +44,31 @@ class SurveyFragmentPresenter @Inject constructor(
private val surveyProgressController: SurveyProgressController,
private val surveyViewModel: SurveyViewModel,
private val multiTypeBuilderFactory: BindableAdapter.MultiTypeBuilder.Factory,
private val resourceHandler: AppLanguageResourceHandler
private val resourceHandler: AppLanguageResourceHandler,
private val analyticsController: AnalyticsController
) {
private val ephemeralQuestionLiveData: LiveData<AsyncResult<EphemeralSurveyQuestion>> by lazy {
surveyProgressController.getCurrentQuestion().toLiveData()
}

private lateinit var profileId: ProfileId
private lateinit var topicId: String
private lateinit var binding: SurveyFragmentBinding
private lateinit var surveyToolbar: Toolbar
private lateinit var answerAvailabilityReceiver: SelectedAnswerAvailabilityReceiver
private lateinit var answerHandler: SelectedAnswerHandler
private lateinit var questionSelectedAnswer: SurveySelectedAnswer
private var isCurrentQuestionTerminal: Boolean = false

/** Sets up data binding. */
fun handleCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
internalProfileId: Int,
explorationId: String,
topicId: String,
fragment: SurveyFragment
): View? {
profileId = ProfileId.newBuilder().setInternalId(internalProfileId).build()
this.topicId = topicId
this.answerAvailabilityReceiver = fragment
this.answerHandler = fragment

Expand Down Expand Up @@ -101,6 +103,8 @@ class SurveyFragmentPresenter @Inject constructor(
surveyProgressController.moveToPreviousQuestion()
}

logBeginSurveyEvent(explorationId, topicId, profileId)

subscribeToCurrentQuestion()

return binding.root
Expand Down Expand Up @@ -237,6 +241,8 @@ class SurveyFragmentPresenter @Inject constructor(
)
else -> {}
}

this.isCurrentQuestionTerminal = ephemeralQuestion.terminalQuestion
updateProgress(ephemeralQuestion.currentQuestionIndex, ephemeralQuestion.totalQuestionCount)
updateQuestionText(questionName)

Expand Down Expand Up @@ -332,4 +338,18 @@ class SurveyFragmentPresenter @Inject constructor(
InputMethodManager.SHOW_FORCED
)
}

private fun logBeginSurveyEvent(
explorationId: String,
topicId: String,
profileId: ProfileId
) {
analyticsController.logImportantEvent(
oppiaLogger.createBeginSurveyContext(
explorationId,
topicId
),
profileId = profileId
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ class SurveyOutroDialogFragmentPresenter @Inject constructor(
}

private fun endSurveyWithCallback(callback: () -> Unit) {
surveyController.stopSurveySession().toLiveData().observe(
surveyController.stopSurveySession(surveyCompleted = true).toLiveData().observe(
activity,
{
when (it) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ class SurveyWelcomeDialogFragment : InjectableDialogFragment() {
companion object {
internal const val PROFILE_ID_KEY = "SurveyWelcomeDialogFragment.profile_id"
internal const val TOPIC_ID_KEY = "SurveyWelcomeDialogFragment.topic_id"
internal const val EXPLORATION_ID_KEY = "SurveyWelcomeDialogFragment.exploration_id"
internal const val MANDATORY_QUESTION_NAMES_KEY = "SurveyWelcomeDialogFragment.question_names"

/**
Expand All @@ -34,12 +35,14 @@ class SurveyWelcomeDialogFragment : InjectableDialogFragment() {
fun newInstance(
profileId: ProfileId,
topicId: String,
mandatoryQuestionNames: List<SurveyQuestionName>,
explorationId: String,
mandatoryQuestionNames: List<SurveyQuestionName>
): SurveyWelcomeDialogFragment {
return SurveyWelcomeDialogFragment().apply {
arguments = Bundle().apply {
putProto(PROFILE_ID_KEY, profileId)
putString(TOPIC_ID_KEY, topicId)
putString(EXPLORATION_ID_KEY, explorationId)
putQuestions(MANDATORY_QUESTION_NAMES_KEY, extractQuestions(mandatoryQuestionNames))
}
}
Expand Down Expand Up @@ -76,13 +79,15 @@ class SurveyWelcomeDialogFragment : InjectableDialogFragment() {

val profileId = args.getProto(PROFILE_ID_KEY, ProfileId.getDefaultInstance())
val topicId = args.getStringFromBundle(TOPIC_ID_KEY)!!
val explorationId = args.getStringFromBundle(EXPLORATION_ID_KEY)!!
val surveyQuestions = args.getQuestions()

return surveyWelcomeDialogFragmentPresenter.handleCreateView(
inflater,
container,
profileId,
topicId,
explorationId,
surveyQuestions
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import org.oppia.android.app.model.ProfileId
import org.oppia.android.app.model.SurveyQuestionName
import org.oppia.android.databinding.SurveyWelcomeDialogFragmentBinding
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.oppialogger.analytics.AnalyticsController
import org.oppia.android.domain.survey.SurveyController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
Expand All @@ -23,16 +24,21 @@ class SurveyWelcomeDialogFragmentPresenter @Inject constructor(
private val activity: AppCompatActivity,
private val fragment: Fragment,
private val surveyController: SurveyController,
private val oppiaLogger: OppiaLogger
private val oppiaLogger: OppiaLogger,
private val analyticsController: AnalyticsController
) {
private lateinit var explorationId: String

/** Sets up data binding. */
fun handleCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
profileId: ProfileId,
topicId: String,
explorationId: String,
questionNames: List<SurveyQuestionName>,
): View {
this.explorationId = explorationId
val binding =
SurveyWelcomeDialogFragmentBinding.inflate(inflater, container, /* attachToRoot= */ false)

Expand All @@ -48,6 +54,8 @@ class SurveyWelcomeDialogFragmentPresenter @Inject constructor(
.commitNow()
}

logSurveyPopUpShownEvent(explorationId, topicId, profileId)

return binding.root
}

Expand All @@ -56,7 +64,7 @@ class SurveyWelcomeDialogFragmentPresenter @Inject constructor(
topicId: String,
questions: List<SurveyQuestionName>
) {
val startDataProvider = surveyController.startSurveySession(questions)
val startDataProvider = surveyController.startSurveySession(questions, profileId = profileId)
startDataProvider.toLiveData().observe(
activity,
{
Expand All @@ -74,7 +82,7 @@ class SurveyWelcomeDialogFragmentPresenter @Inject constructor(
is AsyncResult.Success -> {
oppiaLogger.d("SurveyWelcomeDialogFragment", "Successfully started a survey session")
val intent =
SurveyActivity.createSurveyActivityIntent(activity, profileId, topicId)
SurveyActivity.createSurveyActivityIntent(activity, profileId, topicId, explorationId)
fragment.startActivity(intent)
activity.finish()
val transaction = activity.supportFragmentManager.beginTransaction()
Expand All @@ -84,4 +92,18 @@ class SurveyWelcomeDialogFragmentPresenter @Inject constructor(
}
)
}

private fun logSurveyPopUpShownEvent(
explorationId: String,
topicId: String,
profileId: ProfileId
) {
analyticsController.logImportantEvent(
oppiaLogger.createShowSurveyPopupContext(
explorationId,
topicId
),
profileId = profileId
)
}
}
Loading

0 comments on commit f357362

Please sign in to comment.