From b5354f9cdfcb77fa539058a118c5e39b4686bc27 Mon Sep 17 00:00:00 2001 From: Vishwajith Shettigar <76042077+Vishwajith-Shettigar@users.noreply.github.com> Date: Mon, 1 Jul 2024 16:48:59 +0530 Subject: [PATCH] Fix #5069: Add a "hint/solution viewed" event to complement the existing "offered"/"unlocked" events. (#5298) ## Explanation Fixes #5069: Added a new event called view existing for hints and solution. This event logs every time user views a hint or solution, except for the first time. Existing event log access_hint and access_solution renamed to reveal_hint and reveal_solution. ### Hints view log [hint.webm](https://github.com/oppia/oppia-android/assets/76042077/c7ab6b74-f5a9-46d9-a8fa-cf1c38fc9985) ### Solution view log [solution.webm](https://github.com/oppia/oppia-android/assets/76042077/e868e261-ef99-4742-a938-6a15a4a51723) ## Essential Checklist - [ ] 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: ...".) - [ ] Any changes to [scripts/assets](https://github.com/oppia/oppia-android/tree/develop/scripts/assets) files have their rationale included in the PR explanation. - [ ] The PR follows the [style guide](https://github.com/oppia/oppia-android/wiki/Coding-style-guide). - [ ] 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)). - [ ] The PR is made from a branch that's **not** called "develop" and is up-to-date with "develop". - [ ] 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)). ## For UI-specific PRs only If your PR includes UI-related changes, then: - Add screenshots for portrait/landscape for both a tablet & phone of the before & after UI changes - For the screenshots above, include both English and pseudo-localized (RTL) screenshots (see [RTL guide](https://github.com/oppia/oppia-android/wiki/RTL-Guidelines)) - Add a video showing the full UX flow with a screen reader enabled (see [accessibility guide](https://github.com/oppia/oppia-android/wiki/Accessibility-A11y-Guide)) - For PRs introducing new UI elements or color changes, both light and dark mode screenshots must be included - Add a screenshot demonstrating that you ran affected Espresso tests locally & that they're passing --------- Co-authored-by: Adhiambo Peres <59600948+adhiamboperes@users.noreply.github.com> --- ...HintsAndSolutionDialogFragmentPresenter.kt | 8 + .../app/hintsandsolution/ViewHintListener.kt | 10 ++ .../hintsandsolution/ViewSolutionInterface.kt | 9 + .../player/exploration/ExplorationActivity.kt | 12 ++ .../ExplorationActivityPresenter.kt | 16 ++ .../player/exploration/ExplorationFragment.kt | 6 + .../ExplorationFragmentPresenter.kt | 7 + .../android/app/player/state/StateFragment.kt | 8 + .../player/state/StateFragmentPresenter.kt | 8 + .../ExplorationProgressController.kt | 128 ++++++++++++++- .../analytics/LearnerAnalyticsLogger.kt | 22 ++- .../ExplorationProgressControllerTest.kt | 83 +++++++++- .../analytics/LearnerAnalyticsLoggerTest.kt | 89 ++++++++-- model/src/main/proto/oppia_logger.proto | 14 +- scripts/assets/test_file_exemptions.textproto | 8 + .../testing/logging/EventLogSubject.kt | 98 ++++++++--- .../util/logging/EventBundleCreator.kt | 13 +- ...entTypeToHumanReadableNameConverterImpl.kt | 6 +- ...entTypeToHumanReadableNameConverterImpl.kt | 6 +- .../util/logging/EventBundleCreatorTest.kt | 143 ++++++++++++++-- .../KenyaAlphaEventBundleCreatorTest.kt | 155 +++++++++++++++--- 21 files changed, 745 insertions(+), 104 deletions(-) create mode 100644 app/src/main/java/org/oppia/android/app/hintsandsolution/ViewHintListener.kt create mode 100644 app/src/main/java/org/oppia/android/app/hintsandsolution/ViewSolutionInterface.kt diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt index 46d7c3fd9c7..16f9dec7b19 100644 --- a/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/HintsAndSolutionDialogFragmentPresenter.kt @@ -185,11 +185,15 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( binding.expandableHintHeader.setOnClickListener { if (hintViewModel.isHintRevealed.get()) { expandOrCollapseItem(position) + if (position in expandedItemIndexes) + (fragment.requireActivity() as? ViewHintListener)?.viewHint(hintIndex = position) } } binding.expandHintListIcon.setOnClickListener { if (hintViewModel.isHintRevealed.get()) { expandOrCollapseItem(position) + if (position in expandedItemIndexes) + (fragment.requireActivity() as? ViewHintListener)?.viewHint(hintIndex = position) } } @@ -262,11 +266,15 @@ class HintsAndSolutionDialogFragmentPresenter @Inject constructor( binding.expandableSolutionHeader.setOnClickListener { if (solutionViewModel.isSolutionRevealed.get()) { expandOrCollapseItem(position) + if (position in expandedItemIndexes) + (fragment.requireActivity() as? ViewSolutionInterface)?.viewSolution() } } binding.expandSolutionListIcon.setOnClickListener { if (solutionViewModel.isSolutionRevealed.get()) { expandOrCollapseItem(position) + if (position in expandedItemIndexes) + (fragment.requireActivity() as? ViewSolutionInterface)?.viewSolution() } } diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewHintListener.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewHintListener.kt new file mode 100644 index 00000000000..ef3965321ce --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewHintListener.kt @@ -0,0 +1,10 @@ +package org.oppia.android.app.hintsandsolution + +/** Callback listener for when the user wishes to view a hint. */ +interface ViewHintListener { + /** + * Called when the user indicates they want to view the hint corresponding to the specified + * index. + */ + fun viewHint(hintIndex: Int) +} diff --git a/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewSolutionInterface.kt b/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewSolutionInterface.kt new file mode 100644 index 00000000000..eb1fefd3f2b --- /dev/null +++ b/app/src/main/java/org/oppia/android/app/hintsandsolution/ViewSolutionInterface.kt @@ -0,0 +1,9 @@ +package org.oppia.android.app.hintsandsolution + +/** Interface to check the preference regarding alert for [HintsAndSolutionDialogFragment]. */ +interface ViewSolutionInterface { + /** + * Called when the user indicates they want to view the solution. + */ + fun viewSolution() +} diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt index c657a4a35fb..4e73ac992e3 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivity.kt @@ -9,6 +9,8 @@ import org.oppia.android.app.hintsandsolution.HintsAndSolutionDialogFragment import org.oppia.android.app.hintsandsolution.HintsAndSolutionListener import org.oppia.android.app.hintsandsolution.RevealHintListener import org.oppia.android.app.hintsandsolution.RevealSolutionInterface +import org.oppia.android.app.hintsandsolution.ViewHintListener +import org.oppia.android.app.hintsandsolution.ViewSolutionInterface import org.oppia.android.app.model.ExplorationActivityParams import org.oppia.android.app.model.HelpIndex import org.oppia.android.app.model.ProfileId @@ -37,7 +39,9 @@ class ExplorationActivity : HintsAndSolutionListener, RouteToHintsAndSolutionListener, RevealHintListener, + ViewHintListener, RevealSolutionInterface, + ViewSolutionInterface, DefaultFontSizeStateListener, HintsAndSolutionExplorationManagerListener, ConceptCardListener, @@ -188,4 +192,12 @@ class ExplorationActivity : override fun requestVoiceOverIconSpotlight(numberOfLogins: Int) { explorationActivityPresenter.requestVoiceOverIconSpotlight(numberOfLogins) } + + override fun viewHint(hintIndex: Int) { + explorationActivityPresenter.viewHint(hintIndex) + } + + override fun viewSolution() { + explorationActivityPresenter.viewSolution() + } } diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt index e940dabb48c..76812ff7522 100644 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationActivityPresenter.kt @@ -401,6 +401,14 @@ class ExplorationActivityPresenter @Inject constructor( explorationFragment.revealHint(hintIndex) } + fun viewHint(hintIndex: Int) { + val explorationFragment = + activity.supportFragmentManager.findFragmentByTag( + TAG_EXPLORATION_FRAGMENT + ) as ExplorationFragment + explorationFragment.viewHint(hintIndex) + } + fun revealSolution() { val explorationFragment = activity.supportFragmentManager.findFragmentByTag( @@ -409,6 +417,14 @@ class ExplorationActivityPresenter @Inject constructor( explorationFragment.revealSolution() } + fun viewSolution() { + val explorationFragment = + activity.supportFragmentManager.findFragmentByTag( + TAG_EXPLORATION_FRAGMENT + ) as ExplorationFragment + explorationFragment.viewSolution() + } + private fun showProgressDatabaseFullDialogFragment() { val previousFragment = activity.supportFragmentManager.findFragmentByTag( TAG_PROGRESS_DATABASE_FULL_DIALOG diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt index b72f2f0da61..8411ab49c46 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragment.kt @@ -72,9 +72,15 @@ class ExplorationFragment : InjectableFragment() { explorationFragmentPresenter.revealHint(hintIndex) } + fun viewHint(hintIndex: Int) { + explorationFragmentPresenter.viewHint(hintIndex) + } fun revealSolution() { explorationFragmentPresenter.revealSolution() } + fun viewSolution() { + explorationFragmentPresenter.viewSolution() + } fun dismissConceptCard() = explorationFragmentPresenter.dismissConceptCard() diff --git a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt index 8796b96b672..a64fa466a65 100755 --- a/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/exploration/ExplorationFragmentPresenter.kt @@ -129,11 +129,18 @@ class ExplorationFragmentPresenter @Inject constructor( fun revealHint(hintIndex: Int) { getStateFragment()?.revealHint(hintIndex) } + fun viewHint(hintIndex: Int) { + getStateFragment()?.viewHint(hintIndex) + } fun revealSolution() { getStateFragment()?.revealSolution() } + fun viewSolution() { + getStateFragment()?.viewSolution() + } + fun dismissConceptCard() = getStateFragment()?.dismissConceptCard() fun getExplorationCheckpointState() = getStateFragment()?.getExplorationCheckpointState() diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt index ca7c1d55565..afd00a47f9b 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragment.kt @@ -141,8 +141,16 @@ class StateFragment : stateFragmentPresenter.revealHint(hintIndex) } + fun viewHint(hintIndex: Int) { + stateFragmentPresenter.viewHint(hintIndex) + } + fun revealSolution() = stateFragmentPresenter.revealSolution() + fun viewSolution() { + stateFragmentPresenter.viewSolution() + } + fun dismissConceptCard() = stateFragmentPresenter.dismissConceptCard() fun getExplorationCheckpointState() = stateFragmentPresenter.getExplorationCheckpointState() diff --git a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt index aea996137c5..a6726774c96 100755 --- a/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt +++ b/app/src/main/java/org/oppia/android/app/player/state/StateFragmentPresenter.kt @@ -268,10 +268,18 @@ class StateFragmentPresenter @Inject constructor( subscribeToHintSolution(explorationProgressController.submitHintIsRevealed(hintIndex)) } + fun viewHint(hintIndex: Int) { + explorationProgressController.submitHintIsViewed(hintIndex) + } + fun revealSolution() { subscribeToHintSolution(explorationProgressController.submitSolutionIsRevealed()) } + fun viewSolution() { + explorationProgressController.submitSolutionIsViewed() + } + private fun getAudioFragment(): Fragment? { return fragment.childFragmentManager.findFragmentByTag(TAG_AUDIO_FRAGMENT) } diff --git a/domain/src/main/java/org/oppia/android/domain/exploration/ExplorationProgressController.kt b/domain/src/main/java/org/oppia/android/domain/exploration/ExplorationProgressController.kt index 22a64fdf412..3c9ef56020a 100644 --- a/domain/src/main/java/org/oppia/android/domain/exploration/ExplorationProgressController.kt +++ b/domain/src/main/java/org/oppia/android/domain/exploration/ExplorationProgressController.kt @@ -61,8 +61,12 @@ private const val SUBMIT_ANSWER_RESULT_PROVIDER_ID = "ExplorationProgressController.submit_answer_result" private const val SUBMIT_HINT_REVEALED_RESULT_PROVIDER_ID = "ExplorationProgressController.submit_hint_revealed_result" +private const val SUBMIT_HINT_VIEWED_RESULT_PROVIDER_ID = + "ExplorationProgressController.submit_hint_revealed_result" private const val SUBMIT_SOLUTION_REVEALED_RESULT_PROVIDER_ID = "ExplorationProgressController.submit_solution_revealed_result" +private const val SUBMIT_SOLUTION_VIEWED_RESULT_PROVIDER_ID = + "ExplorationProgressController.submit_solution_revealed_result" private const val MOVE_TO_PREVIOUS_STATE_RESULT_PROVIDER_ID = "ExplorationProgressController.move_to_previous_state_result" private const val MOVE_TO_NEXT_STATE_RESULT_PROVIDER_ID = @@ -275,6 +279,24 @@ class ExplorationProgressController @Inject constructor( return submitResultFlow.convertToSessionProvider(SUBMIT_HINT_REVEALED_RESULT_PROVIDER_ID) } + /** + * Notifies the controller that the user has viewed a hint. + * + * @param hintIndex index of the hint that is being viewed + * + * @return a [DataProvider] that indicates success/failure of the operation (the actual payload of + * the result isn't relevant) + */ + + fun submitHintIsViewed(hintIndex: Int): DataProvider { + val submitResultFlow = createAsyncResultStateFlow() + val message = ControllerMessage.LogHintIsViewed(hintIndex, activeSessionId, submitResultFlow) + sendCommandForOperation(message) { + "Failed to schedule command for viewing hint: $hintIndex." + } + return submitResultFlow.convertToSessionProvider(SUBMIT_HINT_VIEWED_RESULT_PROVIDER_ID) + } + /** * Notifies the controller that the user has revealed the solution to the current state. * @@ -291,6 +313,18 @@ class ExplorationProgressController @Inject constructor( return submitResultFlow.convertToSessionProvider(SUBMIT_SOLUTION_REVEALED_RESULT_PROVIDER_ID) } + /** + * Notifies the controller that the user has viewed the answer. + * @return a [DataProvider] that indicates success/failure of the operation (the actual payload of + * the result isn't relevant) + */ + fun submitSolutionIsViewed(): DataProvider { + val submitResultFlow = createAsyncResultStateFlow() + val message = ControllerMessage.LogSolutionIsViewed(activeSessionId, submitResultFlow) + sendCommandForOperation(message) { "Failed to schedule command for viewing the solution." } + return submitResultFlow.convertToSessionProvider(SUBMIT_SOLUTION_VIEWED_RESULT_PROVIDER_ID) + } + /** * Navigates to the previous state in the graph. If the learner is currently on the initial state, * this method will throw an exception. Calling code is responsible for ensuring this method is @@ -419,7 +453,6 @@ class ExplorationProgressController @Inject constructor( @OptIn(ObsoleteCoroutinesApi::class) private fun createControllerCommandActor(): SendChannel> { lateinit var controllerState: ControllerState - // Use an unlimited capacity buffer so that commands can be sent asynchronously without blocking // the main thread or scheduling an extra coroutine. @Suppress("JoinDeclarationAndAssignment") // Warning is incorrect in this case. @@ -491,8 +524,14 @@ class ExplorationProgressController @Inject constructor( is ControllerMessage.HintIsRevealed -> { controllerState.submitHintIsRevealedImpl(message.callbackFlow, message.hintIndex) } + is ControllerMessage.LogHintIsViewed -> + controllerState.logViewedHintImpl( + activeSessionId, message.hintIndex, message.callbackFlow + ) is ControllerMessage.SolutionIsRevealed -> controllerState.submitSolutionIsRevealedImpl(message.callbackFlow) + is ControllerMessage.LogSolutionIsViewed -> + controllerState.logViewedSolutionImpl(activeSessionId, message.callbackFlow) is ControllerMessage.MoveToPreviousState -> controllerState.moveToPreviousStateImpl(message.callbackFlow) is ControllerMessage.MoveToNextState -> @@ -790,6 +829,43 @@ class ExplorationProgressController @Inject constructor( } } + private suspend fun ControllerState.logViewedHintImpl( + sessionId: String, + hintIndex: Int, + submitLogHintViewedResultFlow: MutableStateFlow> + ) { + tryOperation(submitLogHintViewedResultFlow) { + check(explorationProgress.playStage != NOT_PLAYING) { + "Cannot log hint viewed if an exploration is not being played." + } + check(explorationProgress.playStage != LOADING_EXPLORATION) { + "Cannot log hint viewed if an exploration is being loaded." + } + check(explorationProgress.playStage != SUBMITTING_ANSWER) { + "Cannot log hint viewed if an answer submission is pending." + } + maybeLogViewedHint(sessionId, hintIndex) + } + } + + private suspend fun ControllerState.logViewedSolutionImpl( + sessionId: String, + submitLogSolutionViewedResultFlow: MutableStateFlow> + ) { + tryOperation(submitLogSolutionViewedResultFlow) { + check(explorationProgress.playStage != NOT_PLAYING) { + "Cannot log solution viewed if an exploration is not being played." + } + check(explorationProgress.playStage != LOADING_EXPLORATION) { + "Cannot log solution viewed while the exploration is being loaded." + } + check(explorationProgress.playStage != SUBMITTING_ANSWER) { + "Cannot log solution viewed if an answer submission is pending." + } + maybeLogViewedSolution(sessionId) + } + } + private fun ControllerState.maybeLogUpdatedHelpIndex( helpIndex: HelpIndex, activeSessionId: String @@ -800,6 +876,25 @@ class ExplorationProgressController @Inject constructor( } } + private fun ControllerState.maybeLogViewedHint( + activeSessionId: String, + hintIndex: Int + ) { + // Only log if the current session is active. + if (sessionId == activeSessionId) { + stateAnalyticsLogger?.logViewHint(hintIndex) + } + } + + private fun ControllerState.maybeLogViewedSolution( + activeSessionId: String + ) { + // Only log if the current session is active. + if (sessionId == activeSessionId) { + stateAnalyticsLogger?.logViewSolution() + } + } + private suspend fun ControllerState.tryOperation( resultFlow: MutableStateFlow>, recomputeState: Boolean = true, @@ -1216,12 +1311,12 @@ class ExplorationProgressController @Inject constructor( NEXT_AVAILABLE_HINT_INDEX -> stateAnalyticsLogger?.logHintUnlocked(newHelpIndex.nextAvailableHintIndex) LATEST_REVEALED_HINT_INDEX -> - stateAnalyticsLogger?.logViewHint(newHelpIndex.latestRevealedHintIndex) + stateAnalyticsLogger?.logRevealHint(newHelpIndex.latestRevealedHintIndex) SHOW_SOLUTION -> stateAnalyticsLogger?.logSolutionUnlocked() EVERYTHING_REVEALED -> when (helpIndex.indexTypeCase) { - SHOW_SOLUTION -> stateAnalyticsLogger?.logViewSolution() + SHOW_SOLUTION -> stateAnalyticsLogger?.logRevealSolution() NEXT_AVAILABLE_HINT_INDEX -> // No solution, so revealing the hint ends available help. - stateAnalyticsLogger?.logViewHint(helpIndex.nextAvailableHintIndex) + stateAnalyticsLogger?.logRevealHint(helpIndex.nextAvailableHintIndex) // Nothing to do in these cases. LATEST_REVEALED_HINT_INDEX, EVERYTHING_REVEALED, INDEXTYPE_NOT_SET, null -> {} } @@ -1349,6 +1444,31 @@ class ExplorationProgressController @Inject constructor( override val callbackFlow: MutableStateFlow>? = null ) : ControllerMessage() + /** + * [ControllerMessage] to log cases when the user has viewed a hint for the current session. + * + * Specific measures are taken to ensure that the handler for this message does not log the + * change if the current active session has changed (since that's generally indicative of an + * error--hints can't continue to change after the session has ended). + */ + data class LogHintIsViewed( + val hintIndex: Int, + override val sessionId: String, + override val callbackFlow: MutableStateFlow> + ) : ControllerMessage() + + /** + * [ControllerMessage] to log cases when the user has viewed the solution for the current + * session. + * + * Specific measures are taken to ensure that the handler for this message does not log the + * change if the current active session has changed. + */ + data class LogSolutionIsViewed( + override val sessionId: String, + override val callbackFlow: MutableStateFlow> + ) : ControllerMessage() + /** * [ControllerMessage] to ensure a successfully saved checkpoint is reflected in other parts of * the app (e.g. that an exploration is considered 'in-progress' in such circumstances). diff --git a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt index f0fce6b94b0..99511b001ed 100644 --- a/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt +++ b/domain/src/main/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLogger.kt @@ -371,9 +371,17 @@ class LearnerAnalyticsLogger @Inject constructor( logStateEvent(hintIndex, ::createHintContext, EventBuilder::setHintUnlockedContext) } - /** Logs that the hint corresponding to [hintIndex] has been viewed by the learner. */ + /** Logs that the hint corresponding to [hintIndex] has been revealed by the learner. */ + fun logRevealHint(hintIndex: Int) { + logStateEvent(hintIndex, ::createHintContext, EventBuilder::setRevealHintContext) + } + + /** + * Logs the event indicating that the learner has viewed a hint corresponding to [hintIndex], + * excluding the first-time viewing. + */ fun logViewHint(hintIndex: Int) { - logStateEvent(hintIndex, ::createHintContext, EventBuilder::setAccessHintContext) + logStateEvent(hintIndex, ::createHintContext, EventBuilder::setViewExistingHintContext) } /** Logs that the solution to the current card has been unlocked by the learner. */ @@ -382,8 +390,16 @@ class LearnerAnalyticsLogger @Inject constructor( } /** Logs that the solution to the current card has been viewed by the learner. */ + fun logRevealSolution() { + logStateEvent(EventBuilder::setRevealSolutionContext) + } + + /** + * Logs the event indicating that the learner has viewed the solution to the current card, + * excluding the first-time viewing. + */ fun logViewSolution() { - logStateEvent(EventBuilder::setAccessSolutionContext) + logStateEvent(EventBuilder::setViewExistingSolutionContext) } /** diff --git a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt index a6b797562e4..97f81c0f3af 100644 --- a/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/exploration/ExplorationProgressControllerTest.kt @@ -2466,7 +2466,7 @@ class ExplorationProgressControllerTest { } @Test - fun testHint_offeredThenViewed_logsViewHintEvent_logsProgressSavingSuccessEvent() { + fun testHint_offeredThenViewed_logsRevealedHint_logsPgrssSavSuccEvent_logsExtingHintViwdEvent() { logIntoAnalyticsReadyAdminProfile() startPlayingNewExploration(TEST_TOPIC_ID_0, TEST_STORY_ID_0, TEST_EXPLORATION_ID_2) waitForGetCurrentStateSuccessfulLoad() @@ -2478,9 +2478,11 @@ class ExplorationProgressControllerTest { monitorFactory.ensureDataProviderExecutes( explorationProgressController.submitHintIsRevealed(hintIndex = 0) ) - - val eventLogList = fakeAnalyticsEventLogger.getMostRecentEvents(2) - assertThat(eventLogList[0]).hasAccessHintContextThat { + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitHintIsViewed(hintIndex = 0) + ) + val eventLogList = fakeAnalyticsEventLogger.getMostRecentEvents(3) + assertThat(eventLogList[0]).hasRevealHintContextThat { hasExplorationDetailsThat().containsTestExp2Details() hasExplorationDetailsThat().hasStateNameThat().isEqualTo("Fractions") hasHintIndexThat().isEqualTo(0) @@ -2488,6 +2490,34 @@ class ExplorationProgressControllerTest { assertThat(eventLogList[1]).hasProgressSavingSuccessContextThat { containsTestExp2Details() } + assertThat(eventLogList[2]).hasViewExistingHintContextThat { + hasExplorationDetailsThat().containsTestExp2Details() + hasExplorationDetailsThat().hasStateNameThat().isEqualTo("Fractions") + hasHintIndexThat().isEqualTo(0) + } + } + + @Test + fun testHint_existingHintViewed_logsExistingHintViewedEvent() { + logIntoAnalyticsReadyAdminProfile() + startPlayingNewExploration(TEST_TOPIC_ID_0, TEST_STORY_ID_0, TEST_EXPLORATION_ID_2) + waitForGetCurrentStateSuccessfulLoad() + playThroughPrototypeState1AndMoveToNextState() + // Submit 2 wrong answers to trigger a hint becoming available. + submitWrongAnswerForPrototypeState2() + submitWrongAnswerForPrototypeState2() + + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitHintIsViewed(hintIndex = 0) + ) + + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + + assertThat(eventLog).hasViewExistingHintContextThat { + hasExplorationDetailsThat().containsTestExp2Details() + hasExplorationDetailsThat().hasStateNameThat().isEqualTo("Fractions") + hasHintIndexThat().isEqualTo(0) + } } @Test @@ -2525,7 +2555,7 @@ class ExplorationProgressControllerTest { } @Test - fun testHint_lastHintWithNoSol_offeredThenViewed_logsViewHintEvt_logsProgressSavingSuccessEvt() { + fun testHint_lastHintWithNoSol_offeredThenViewed_logsRevealedHintEvt_logsPgrssSavingSucssEvt() { logIntoAnalyticsReadyAdminProfile() startPlayingNewExploration(FRACTIONS_TOPIC_ID, FRACTIONS_STORY_ID_0, FRACTIONS_EXPLORATION_ID_0) waitForGetCurrentStateSuccessfulLoad() @@ -2542,7 +2572,7 @@ class ExplorationProgressControllerTest { ) val eventLogList = fakeAnalyticsEventLogger.getMostRecentEvents(2) - assertThat(eventLogList[0]).hasAccessHintContextThat { + assertThat(eventLogList[0]).hasRevealHintContextThat { hasExplorationDetailsThat().containsFractionsExp0Details() hasExplorationDetailsThat().hasStateNameThat().isEqualTo("Parts of a whole") hasHintIndexThat().isEqualTo(0) @@ -2597,12 +2627,49 @@ class ExplorationProgressControllerTest { explorationProgressController.submitSolutionIsRevealed() ) - val eventLogList = fakeAnalyticsEventLogger.getMostRecentEvents(2) - assertThat(eventLogList[0]).hasAccessSolutionContextThat { + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitSolutionIsViewed() + ) + + val eventLogList = fakeAnalyticsEventLogger.getMostRecentEvents(3) + assertThat(eventLogList[0]).hasRevealSolutionContextThat { containsTestExp2Details() hasStateNameThat().isEqualTo("Fractions") } assertThat(eventLogList[1]).hasProgressSavingSuccessContextThat().containsTestExp2Details() + assertThat(eventLogList[2]).hasViewExistingSolutionContextThat { + containsTestExp2Details() + hasStateNameThat().isEqualTo("Fractions") + } + } + + @Test + fun testSolution_viewExistingSolution_logsExistingSolutionViewedEvent() { + logIntoAnalyticsReadyAdminProfile() + startPlayingNewExploration(TEST_TOPIC_ID_0, TEST_STORY_ID_0, TEST_EXPLORATION_ID_2) + waitForGetCurrentStateSuccessfulLoad() + playThroughPrototypeState1AndMoveToNextState() + // Submit 2 wrong answers to trigger the hint. + submitWrongAnswerForPrototypeState2() + submitWrongAnswerForPrototypeState2() + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitHintIsRevealed(hintIndex = 0) + ) + // Submit another wrong answer to trigger the solution. + submitWrongAnswerForPrototypeState2() + testCoroutineDispatchers.advanceTimeBy(TimeUnit.SECONDS.toMillis(10)) + + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitSolutionIsRevealed() + ) + monitorFactory.ensureDataProviderExecutes( + explorationProgressController.submitSolutionIsViewed() + ) + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).hasViewExistingSolutionContextThat { + containsTestExp2Details() + hasStateNameThat().isEqualTo("Fractions") + } } @Test diff --git a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt index 2562ecbfca5..7b6f52d63af 100644 --- a/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt +++ b/domain/src/test/java/org/oppia/android/domain/oppialogger/analytics/LearnerAnalyticsLoggerTest.kt @@ -829,18 +829,18 @@ class LearnerAnalyticsLoggerTest { } @Test - fun testStateAnalyticsLogger_logViewHint_logsStateEventWithHintIndex() { + fun testStateAnalyticsLogger_logRevealHint_logsStateEventWithHintIndex() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration(exploration5) val stateLogger = expLogger.startCard(exploration5.getStateByName(TEST_EXP_5_STATE_THREE_NAME)) testCoroutineDispatchers.runCurrent() - stateLogger.logViewHint(hintIndex = 1) + stateLogger.logRevealHint(hintIndex = 1) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAccessHintContextThat { + assertThat(eventLog).hasRevealHintContextThat { hasExplorationDetailsThat { hasTopicIdThat().isEqualTo(TEST_TOPIC_ID) hasStoryIdThat().isEqualTo(TEST_STORY_ID) @@ -858,18 +858,47 @@ class LearnerAnalyticsLoggerTest { } @Test - fun testStateAnalyticsLogger_logViewHint_diffIndex_logsStateEventWithHintIndex() { + fun testStateAnalyticsLogger_logRevealHint_diffIndex_logsStateEventWithHintIndex() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration(exploration5) val stateLogger = expLogger.startCard(exploration5.getStateByName(TEST_EXP_5_STATE_THREE_NAME)) testCoroutineDispatchers.runCurrent() - stateLogger.logViewHint(hintIndex = 2) + stateLogger.logRevealHint(hintIndex = 2) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAccessHintContextThat().hasHintIndexThat().isEqualTo(2) + assertThat(eventLog).hasRevealHintContextThat().hasHintIndexThat().isEqualTo(2) + } + + @Test + fun testStateAnalyticsLogger_logViewHint_logsStateEventWithHintIndex() { + val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) + val expLogger = learnerAnalyticsLogger.beginExploration(exploration5) + val stateLogger = expLogger.startCard(exploration5.getStateByName(TEST_EXP_5_STATE_THREE_NAME)) + testCoroutineDispatchers.runCurrent() + + stateLogger.logViewHint(hintIndex = 1) + testCoroutineDispatchers.runCurrent() + + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasViewExistingHintContextThat { + hasExplorationDetailsThat { + hasTopicIdThat().isEqualTo(TEST_TOPIC_ID) + hasStoryIdThat().isEqualTo(TEST_STORY_ID) + hasExplorationIdThat().isEqualTo(TEST_EXPLORATION_ID_5) + hasSessionIdThat().isEqualTo(DEFAULT_INITIAL_SESSION_ID) + hasVersionThat().isEqualTo(5) + hasStateNameThat().isEqualTo(TEST_EXP_5_STATE_THREE_NAME) + hasLearnerDetailsThat { + hasLearnerIdThat().isEqualTo(TEST_LEARNER_ID) + hasInstallationIdThat().isEqualTo(TEST_INSTALL_ID) + } + } + hasHintIndexThat().isEqualTo(1) + } } @Test @@ -898,6 +927,32 @@ class LearnerAnalyticsLoggerTest { } } + @Test + fun testStateAnalyticsLogger_logRevealSolution_logsStateEvent() { + val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) + val expLogger = learnerAnalyticsLogger.beginExploration(exploration5) + val stateLogger = expLogger.startCard(exploration5.getStateByName(TEST_EXP_5_STATE_THREE_NAME)) + testCoroutineDispatchers.runCurrent() + + stateLogger.logRevealSolution() + testCoroutineDispatchers.runCurrent() + + val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() + assertThat(eventLog).isEssentialPriority() + assertThat(eventLog).hasRevealSolutionContextThat { + hasTopicIdThat().isEqualTo(TEST_TOPIC_ID) + hasStoryIdThat().isEqualTo(TEST_STORY_ID) + hasExplorationIdThat().isEqualTo(TEST_EXPLORATION_ID_5) + hasSessionIdThat().isEqualTo(DEFAULT_INITIAL_SESSION_ID) + hasVersionThat().isEqualTo(5) + hasStateNameThat().isEqualTo(TEST_EXP_5_STATE_THREE_NAME) + hasLearnerDetailsThat { + hasLearnerIdThat().isEqualTo(TEST_LEARNER_ID) + hasInstallationIdThat().isEqualTo(TEST_INSTALL_ID) + } + } + } + @Test fun testStateAnalyticsLogger_logViewSolution_logsStateEvent() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) @@ -910,7 +965,7 @@ class LearnerAnalyticsLoggerTest { val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() assertThat(eventLog).isEssentialPriority() - assertThat(eventLog).hasAccessSolutionContextThat { + assertThat(eventLog).hasViewExistingSolutionContextThat() { hasTopicIdThat().isEqualTo(TEST_TOPIC_ID) hasStoryIdThat().isEqualTo(TEST_STORY_ID) hasExplorationIdThat().isEqualTo(TEST_EXPLORATION_ID_5) @@ -1398,7 +1453,7 @@ class LearnerAnalyticsLoggerTest { @Test @Iteration("no_install_id", "lid=learn", "iid=null", "elid=learn", "eid=") @Iteration("no_learner_id", "lid=null", "iid=install", "elid=", "eid=install") - fun testStateAnalyticsLogger_logViewHint_missingOneId_logsEventWithMissingId() { + fun testStateAnalyticsLogger_logRevealHint_missingOneId_logsEventWithMissingId() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration( @@ -1407,11 +1462,11 @@ class LearnerAnalyticsLoggerTest { testCoroutineDispatchers.runCurrent() val stateLogger = expLogger.startCard(exploration5.getStateByName(exploration5.initStateName)) - stateLogger.logViewHint(hintIndex = 1) + stateLogger.logRevealHint(hintIndex = 1) testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - assertThat(eventLog).hasAccessHintContextThat { + assertThat(eventLog).hasRevealHintContextThat { hasExplorationDetailsThat { hasLearnerDetailsThat { hasLearnerIdThat().isEqualTo(expectedLearnerIdParameter) @@ -1422,14 +1477,14 @@ class LearnerAnalyticsLoggerTest { } @Test - fun testStateAnalyticsLogger_logViewHint_noInstallOrLearnerIds_logsEventAndConsoleErrors() { + fun testStateAnalyticsLogger_logRevealHint_noInstallOrLearnerIds_logsEventAndConsoleErrors() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration(exploration5, learnerId = null, installationId = null) testCoroutineDispatchers.runCurrent() val stateLogger = expLogger.startCard(exploration5.getStateByName(exploration5.initStateName)) - stateLogger.logViewHint(hintIndex = 1) + stateLogger.logRevealHint(hintIndex = 1) testCoroutineDispatchers.runCurrent() // See testExpLogger_logExitExploration_noInstallOrLearnerIds_logsEventAndConsoleErrors. @@ -1486,7 +1541,7 @@ class LearnerAnalyticsLoggerTest { @Test @Iteration("no_install_id", "lid=learn", "iid=null", "elid=learn", "eid=") @Iteration("no_learner_id", "lid=null", "iid=install", "elid=", "eid=install") - fun testStateAnalyticsLogger_logViewSolution_missingOneId_logsEventWithMissingId() { + fun testStateAnalyticsLogger_logRevealSolution_missingOneId_logsEventWithMissingId() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration( @@ -1495,11 +1550,11 @@ class LearnerAnalyticsLoggerTest { testCoroutineDispatchers.runCurrent() val stateLogger = expLogger.startCard(exploration5.getStateByName(exploration5.initStateName)) - stateLogger.logViewSolution() + stateLogger.logRevealSolution() testCoroutineDispatchers.runCurrent() val eventLog = fakeAnalyticsEventLogger.getMostRecentEvent() - assertThat(eventLog).hasAccessSolutionContextThat { + assertThat(eventLog).hasRevealSolutionContextThat { hasLearnerDetailsThat { hasLearnerIdThat().isEqualTo(expectedLearnerIdParameter) hasInstallationIdThat().isEqualTo(expectedInstallIdParameter) @@ -1508,14 +1563,14 @@ class LearnerAnalyticsLoggerTest { } @Test - fun testStateAnalyticsLogger_logViewSolution_noInstallOrLearnerIds_logsEventAndConsoleErrors() { + fun testStateAnalyticsLogger_logRevealSolution_noInstallOrLearnerIds_logsEventAndConsoleErrors() { val exploration5 = loadExploration(TEST_EXPLORATION_ID_5) val expLogger = learnerAnalyticsLogger.beginExploration(exploration5, learnerId = null, installationId = null) testCoroutineDispatchers.runCurrent() val stateLogger = expLogger.startCard(exploration5.getStateByName(exploration5.initStateName)) - stateLogger.logViewSolution() + stateLogger.logRevealSolution() testCoroutineDispatchers.runCurrent() // See testExpLogger_logExitExploration_noInstallOrLearnerIds_logsEventAndConsoleErrors. diff --git a/model/src/main/proto/oppia_logger.proto b/model/src/main/proto/oppia_logger.proto index 07411ad365b..5ab3c9d32ff 100644 --- a/model/src/main/proto/oppia_logger.proto +++ b/model/src/main/proto/oppia_logger.proto @@ -102,14 +102,14 @@ message EventLog { // The event being logged is related to offering a hint when it becomes available. HintContext hint_unlocked_context = 18; - // The event being logged is related to accessing a hint. - HintContext access_hint_context = 19; + // The event being logged is related to revealing a hint. + HintContext reveal_hint_context = 19; // The event being logged is related to offering a solution when it becomes available. ExplorationContext solution_unlocked_context = 20; // The event being logged is related to accessing a solution. - ExplorationContext access_solution_context = 21; + ExplorationContext reveal_solution_context = 21; // The event being logged is related to submitting an answer. SubmitAnswerContext submit_answer_context = 22; @@ -213,6 +213,12 @@ message EventLog { // The event being logged is related to incorrect answer submission after returning back to // the lesson. ExplorationContext resume_lesson_submit_incorrect_answer_context = 53; + + // The event being logged is related to viewing a hint that was already unlocked. + HintContext view_existing_hint_context = 54; + + // The event being logged is related to viewing a solution that was already unlocked. + ExplorationContext view_existing_solution_context = 55; } } @@ -266,7 +272,7 @@ message EventLog { // Defined attributes that are common among other exploration related event log contexts. ExplorationContext exploration_details = 1; - // Corresponds to the index of the index being shown or last seen. + // Corresponds to the index of the hint being shown or last seen. int32 hint_index = 4; reserved 2; diff --git a/scripts/assets/test_file_exemptions.textproto b/scripts/assets/test_file_exemptions.textproto index 02da7bda12d..d20ba9e9482 100644 --- a/scripts/assets/test_file_exemptions.textproto +++ b/scripts/assets/test_file_exemptions.textproto @@ -846,6 +846,14 @@ test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/hintsandsolution/SolutionViewModel.kt" test_file_not_required: true } +test_file_exemption { + exempted_file_path:"app/src/main/java/org/oppia/android/app/hintsandsolution/ViewSolutionInterface.kt" + test_file_not_required: true +} +test_file_exemption { + exempted_file_path:"app/src/main/java/org/oppia/android/app/hintsandsolution/ViewHintListener.kt" + test_file_not_required: true +} test_file_exemption { exempted_file_path: "app/src/main/java/org/oppia/android/app/home/HomeActivityPresenter.kt" test_file_not_required: true diff --git a/testing/src/main/java/org/oppia/android/testing/logging/EventLogSubject.kt b/testing/src/main/java/org/oppia/android/testing/logging/EventLogSubject.kt index 3009fe88a79..62beee0a815 100644 --- a/testing/src/main/java/org/oppia/android/testing/logging/EventLogSubject.kt +++ b/testing/src/main/java/org/oppia/android/testing/logging/EventLogSubject.kt @@ -16,8 +16,6 @@ import org.oppia.android.app.model.AppLanguageSelection.SelectionTypeCase.USE_SY import org.oppia.android.app.model.AudioTranslationLanguageSelection import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ABANDON_SURVEY -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_HINT_CONTEXT -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_BACKGROUND_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_FOREGROUND_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.BEGIN_SURVEY @@ -50,12 +48,16 @@ import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REACH_IN import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_EXPLORATION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_LESSON_SUBMIT_CORRECT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_LESSON_SUBMIT_INCORRECT_ANSWER_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SHOW_SURVEY_POPUP import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_CARD_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_OVER_EXPLORATION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SUBMIT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SWITCH_IN_LESSON_LANGUAGE +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.FeatureFlagItemContext import org.oppia.android.app.model.MarketFitAnswer import org.oppia.android.app.model.OppiaLanguage @@ -505,29 +507,55 @@ class EventLogSubject private constructor( } /** - * Verifies that the [EventLog] under test has a context corresponding to [ACCESS_HINT_CONTEXT] + * Verifies that the [EventLog] under test has a context corresponding to [REVEAL_HINT_CONTEXT] * (per [EventLog.Context.getActivityContextCase]). */ - fun hasAccessHintContext() { - assertThat(actual.context.activityContextCase).isEqualTo(ACCESS_HINT_CONTEXT) + fun hasRevealHintContext() { + assertThat(actual.context.activityContextCase).isEqualTo(REVEAL_HINT_CONTEXT) } /** - * Verifies the [EventLog]'s context per [hasAccessHintContext] and returns a [HintContextSubject] + * Verifies the [EventLog]'s context per [hasRevealHintContext] and returns a [HintContextSubject] * to test the corresponding context. */ - fun hasAccessHintContextThat(): HintContextSubject { - hasAccessHintContext() - return HintContextSubject.assertThat(actual.context.accessHintContext) + fun hasRevealHintContextThat(): HintContextSubject { + hasRevealHintContext() + return HintContextSubject.assertThat(actual.context.revealHintContext) } /** * Verifies the [EventLog]'s context and executes [block] in the same way as * [hasOpenExplorationActivityContextThat] except for the conditions of, and subject returned by, - * [hasAccessHintContextThat]. + * [hasRevealHintContextThat]. */ - fun hasAccessHintContextThat(block: HintContextSubject.() -> Unit) { - hasAccessHintContextThat().block() + fun hasRevealHintContextThat(block: HintContextSubject.() -> Unit) { + hasRevealHintContextThat().block() + } + + /** + * Verifies that the [EventLog] under test has a context corresponding to [VIEW_EXISTING_HINT_CONTEXT] + * (per [EventLog.Context.getActivityContextCase]). + */ + fun hasViewExistingHintContext() { + assertThat(actual.context.activityContextCase).isEqualTo(VIEW_EXISTING_HINT_CONTEXT) + } + + /** + * Verifies the [EventLog]'s context per [hasViewExistingHintContext] and returns a [HintContextSubject] + * to test the corresponding context. + */ + fun hasViewExistingHintContextThat(): HintContextSubject { + hasViewExistingHintContext() + return HintContextSubject.assertThat(actual.context.viewExistingHintContext) + } + + /** + * Verifies the [EventLog]'s context and executes [block] in the same way as + * [hasOpenExplorationActivityContextThat] except for the conditions of, and subject returned by, + * [hasViewExistingHintContextThat]. + */ + fun hasViewExistingHintContextThat(block: HintContextSubject.() -> Unit) { + hasViewExistingHintContextThat().block() } /** @@ -558,28 +586,54 @@ class EventLogSubject private constructor( /** * Verifies that the [EventLog] under test has a context corresponding to - * [ACCESS_SOLUTION_CONTEXT] (per [EventLog.Context.getActivityContextCase]). + * [REVEAL_SOLUTION_CONTEXT] (per [EventLog.Context.getActivityContextCase]). + */ + fun hasRevealSolutionContext() { + assertThat(actual.context.activityContextCase).isEqualTo(REVEAL_SOLUTION_CONTEXT) + } + + /** + * Verifies the [EventLog]'s context per [hasRevealSolutionContext] and returns an + * [ExplorationContextSubject] to test the corresponding context. + */ + fun hasRevealSolutionContextThat(): ExplorationContextSubject { + hasRevealSolutionContext() + return ExplorationContextSubject.assertThat(actual.context.revealSolutionContext) + } + + /** + * Verifies the [EventLog]'s context and executes [block] in the same way as + * [hasOpenExplorationActivityContextThat] except for the conditions of, and subject returned by, + * [hasRevealSolutionContextThat]. + */ + fun hasRevealSolutionContextThat(block: ExplorationContextSubject.() -> Unit) { + hasRevealSolutionContextThat().block() + } + + /** + * Verifies that the [EventLog] under test has a context corresponding to + * [VIEW_EXISTING_SOLUTION_CONTEXT] (per [EventLog.Context.getActivityContextCase]). */ - fun hasAccessSolutionContext() { - assertThat(actual.context.activityContextCase).isEqualTo(ACCESS_SOLUTION_CONTEXT) + fun hasViewExistingSolutionContext() { + assertThat(actual.context.activityContextCase).isEqualTo(VIEW_EXISTING_SOLUTION_CONTEXT) } /** - * Verifies the [EventLog]'s context per [hasAccessSolutionContext] and returns an + * Verifies the [EventLog]'s context per [hasViewExistingSolutionContext] and returns an * [ExplorationContextSubject] to test the corresponding context. */ - fun hasAccessSolutionContextThat(): ExplorationContextSubject { - hasAccessSolutionContext() - return ExplorationContextSubject.assertThat(actual.context.accessSolutionContext) + fun hasViewExistingSolutionContextThat(): ExplorationContextSubject { + hasViewExistingSolutionContext() + return ExplorationContextSubject.assertThat(actual.context.viewExistingSolutionContext) } /** * Verifies the [EventLog]'s context and executes [block] in the same way as * [hasOpenExplorationActivityContextThat] except for the conditions of, and subject returned by, - * [hasAccessSolutionContextThat]. + * [hasViewExistingSolutionContextThat]. */ - fun hasAccessSolutionContextThat(block: ExplorationContextSubject.() -> Unit) { - hasAccessSolutionContextThat().block() + fun hasViewExistingSolutionContextThat(block: ExplorationContextSubject.() -> Unit) { + hasViewExistingSolutionContextThat().block() } /** diff --git a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt index 96c5899da27..b833b340206 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/EventBundleCreator.kt @@ -8,8 +8,6 @@ import org.oppia.android.app.model.AppLanguageSelection import org.oppia.android.app.model.AudioTranslationLanguageSelection import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ABANDON_SURVEY -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_HINT_CONTEXT -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACTIVITYCONTEXT_NOT_SET import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_BACKGROUND_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_FOREGROUND_CONTEXT @@ -49,12 +47,16 @@ import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_L import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_LESSON_SUBMIT_INCORRECT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RETROFIT_CALL_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RETROFIT_CALL_FAILED_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SHOW_SURVEY_POPUP import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_CARD_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_OVER_EXPLORATION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SUBMIT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SWITCH_IN_LESSON_LANGUAGE +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.SwitchInLessonLanguageEventContext import org.oppia.android.app.model.OppiaLanguage import org.oppia.android.app.model.OppiaMetricLog @@ -229,9 +231,12 @@ class EventBundleCreator @Inject constructor( START_CARD_CONTEXT -> CardContext(activityName, startCardContext) END_CARD_CONTEXT -> CardContext(activityName, endCardContext) HINT_UNLOCKED_CONTEXT -> HintContext(activityName, hintUnlockedContext) - ACCESS_HINT_CONTEXT -> HintContext(activityName, accessHintContext) + REVEAL_HINT_CONTEXT -> HintContext(activityName, revealHintContext) + VIEW_EXISTING_HINT_CONTEXT -> HintContext(activityName, viewExistingHintContext) + VIEW_EXISTING_SOLUTION_CONTEXT -> + ExplorationContext(activityName, viewExistingSolutionContext) SOLUTION_UNLOCKED_CONTEXT -> ExplorationContext(activityName, solutionUnlockedContext) - ACCESS_SOLUTION_CONTEXT -> ExplorationContext(activityName, accessSolutionContext) + REVEAL_SOLUTION_CONTEXT -> ExplorationContext(activityName, revealSolutionContext) SUBMIT_ANSWER_CONTEXT -> SubmitAnswerContext(activityName, submitAnswerContext) PLAY_VOICE_OVER_CONTEXT -> VoiceoverActionContext(activityName, playVoiceOverContext) PAUSE_VOICE_OVER_CONTEXT -> VoiceoverActionContext(activityName, pauseVoiceOverContext) diff --git a/utility/src/main/java/org/oppia/android/util/logging/KenyaAlphaEventTypeToHumanReadableNameConverterImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/KenyaAlphaEventTypeToHumanReadableNameConverterImpl.kt index 342333d65eb..da3ad145a46 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/KenyaAlphaEventTypeToHumanReadableNameConverterImpl.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/KenyaAlphaEventTypeToHumanReadableNameConverterImpl.kt @@ -28,9 +28,11 @@ class KenyaAlphaEventTypeToHumanReadableNameConverterImpl @Inject constructor() ActivityContextCase.START_CARD_CONTEXT -> "start_card_context" ActivityContextCase.END_CARD_CONTEXT -> "end_card_context" ActivityContextCase.HINT_UNLOCKED_CONTEXT -> "hint_offered_context" - ActivityContextCase.ACCESS_HINT_CONTEXT -> "access_hint_context" + ActivityContextCase.REVEAL_HINT_CONTEXT -> "reveal_hint_context" + ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT -> "view_existing_hint_context" ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT -> "solution_offered_context" - ActivityContextCase.ACCESS_SOLUTION_CONTEXT -> "access_solution_context" + ActivityContextCase.REVEAL_SOLUTION_CONTEXT -> "reveal_solution_context" + ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT -> "view_existing_solution_context" ActivityContextCase.SUBMIT_ANSWER_CONTEXT -> "submit_answer_context" ActivityContextCase.PLAY_VOICE_OVER_CONTEXT -> "play_voice_over_context" ActivityContextCase.PAUSE_VOICE_OVER_CONTEXT -> "pause_voice_over_context" diff --git a/utility/src/main/java/org/oppia/android/util/logging/StandardEventTypeToHumanReadableNameConverterImpl.kt b/utility/src/main/java/org/oppia/android/util/logging/StandardEventTypeToHumanReadableNameConverterImpl.kt index 55097655e74..2c4e72c10bf 100644 --- a/utility/src/main/java/org/oppia/android/util/logging/StandardEventTypeToHumanReadableNameConverterImpl.kt +++ b/utility/src/main/java/org/oppia/android/util/logging/StandardEventTypeToHumanReadableNameConverterImpl.kt @@ -38,9 +38,11 @@ class StandardEventTypeToHumanReadableNameConverterImpl @Inject constructor() : ActivityContextCase.START_CARD_CONTEXT -> "start_exploration_card" ActivityContextCase.END_CARD_CONTEXT -> "end_exploration_card" ActivityContextCase.HINT_UNLOCKED_CONTEXT -> "unlock_hint" - ActivityContextCase.ACCESS_HINT_CONTEXT -> "reveal_hint" + ActivityContextCase.REVEAL_HINT_CONTEXT -> "reveal_hint" + ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT -> "view_existing_hint" ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT -> "unlock_solution" - ActivityContextCase.ACCESS_SOLUTION_CONTEXT -> "reveal_solution" + ActivityContextCase.REVEAL_SOLUTION_CONTEXT -> "reveal_solution" + ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT -> "view_existing_solution" ActivityContextCase.SUBMIT_ANSWER_CONTEXT -> "submit_answer" ActivityContextCase.PLAY_VOICE_OVER_CONTEXT -> "click_play_voiceover_button" ActivityContextCase.PAUSE_VOICE_OVER_CONTEXT -> "click_pause_voiceover_button" diff --git a/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt b/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt index 8cab8e1778d..92c45550bd0 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/EventBundleCreatorTest.kt @@ -20,8 +20,6 @@ import org.oppia.android.app.model.AudioTranslationLanguageSelection import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.EventLog.CardContext import org.oppia.android.app.model.EventLog.ConceptCardContext -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_HINT_CONTEXT -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACTIVITYCONTEXT_NOT_SET import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_BACKGROUND_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_FOREGROUND_CONTEXT @@ -47,11 +45,15 @@ import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.PAUSE_VO import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.PLAY_VOICE_OVER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REACH_INVESTED_ENGAGEMENT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_EXPLORATION_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_CARD_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_OVER_EXPLORATION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SUBMIT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SWITCH_IN_LESSON_LANGUAGE +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.ExplorationContext import org.oppia.android.app.model.EventLog.HintContext import org.oppia.android.app.model.EventLog.LearnerDetailsContext @@ -1099,18 +1101,18 @@ class EventBundleCreatorTest { } @Test - fun testFillEventBundle_accessHintContextEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { + fun testFillEventBundle_revealHintContextEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessHintContext()) + val eventLog = createEventLog(context = createRevealHintContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_hint") assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_HINT_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -1124,18 +1126,70 @@ class EventBundleCreatorTest { } @Test - fun testFillEventBundle_accessHintContextEvent_studyOn_fillsAllFieldsAndReturnsName() { + fun testFillEventBundle_revealHintContextEvent_studyOn_fillsAllFieldsAndReturnsName() { setUpTestApplicationComponentWithLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessHintContext()) + val eventLog = createEventLog(context = createRevealHintContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_hint") assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_HINT_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("ed_topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("ed_story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("ed_exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("ed_session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("ed_exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("ed_state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("hint_index").isEqualTo(TEST_HINT_INDEX_STR) + assertThat(bundle).string("ed_ld_learner_id").isEqualTo(TEST_LEARNER_ID) + assertThat(bundle).string("ed_ld_install_id").isEqualTo(TEST_INSTALLATION_ID) + } + + @Test + fun testFillEventBundle_viewExistingHintEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { + setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingHintContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_hint") + assertThat(bundle).hasSize(18) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_HINT_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("ed_topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("ed_story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("ed_exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("ed_session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("ed_exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("ed_state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("hint_index").isEqualTo(TEST_HINT_INDEX_STR) + } + + @Test + fun testFillEventBundle_viewExistingHintEvent_studyOn_fillsAllFieldsAndReturnsName() { + setUpTestApplicationComponentWithLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingHintContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_hint") + assertThat(bundle).hasSize(20) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_HINT_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -1205,14 +1259,14 @@ class EventBundleCreatorTest { setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessSolutionContext()) + val eventLog = createEventLog(context = createRevealSolutionContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_solution") assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_SOLUTION_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -1229,14 +1283,64 @@ class EventBundleCreatorTest { setUpTestApplicationComponentWithLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessSolutionContext()) + val eventLog = createEventLog(context = createRevealSolutionContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) assertThat(typeName).isEqualTo("reveal_solution") assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("ld_learner_id").isEqualTo(TEST_LEARNER_ID) + assertThat(bundle).string("ld_install_id").isEqualTo(TEST_INSTALLATION_ID) + } + + @Test + fun testFillEventBundle_viewExistingSolutionEvent_studyOff_fillsNonSensitiveFieldsAndRetsName() { + setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingSolutionContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_solution") + assertThat(bundle).hasSize(17) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("state_name").isEqualTo(TEST_STATE_NAME) + } + + @Test + fun testFillEventBundle_viewExistingSolutionEvent_studyOn_fillsAllFieldsAndReturnsName() { + setUpTestApplicationComponentWithLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingSolutionContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_solution") + assertThat(bundle).hasSize(19) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_SOLUTION_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -2109,16 +2213,23 @@ class EventBundleCreatorTest { private fun createHintUnlockedContext(hintContext: HintContext = createHintContext()) = createEventContext(hintContext, EventContextBuilder::setHintUnlockedContext) - private fun createAccessHintContext(hintContext: HintContext = createHintContext()) = - createEventContext(hintContext, EventContextBuilder::setAccessHintContext) + private fun createRevealHintContext(hintContext: HintContext = createHintContext()) = + createEventContext(hintContext, EventContextBuilder::setRevealHintContext) + + private fun createViewExistingHintContext(hintContext: HintContext = createHintContext()) = + createEventContext(hintContext, EventContextBuilder::setViewExistingHintContext) private fun createSolutionUnlockedContext( explorationContext: ExplorationContext = createExplorationContext() ) = createEventContext(explorationContext, EventContextBuilder::setSolutionUnlockedContext) - private fun createAccessSolutionContext( + private fun createRevealSolutionContext( + explorationContext: ExplorationContext = createExplorationContext() + ) = createEventContext(explorationContext, EventContextBuilder::setRevealSolutionContext) + + private fun createViewExistingSolutionContext( explorationContext: ExplorationContext = createExplorationContext() - ) = createEventContext(explorationContext, EventContextBuilder::setAccessSolutionContext) + ) = createEventContext(explorationContext, EventContextBuilder::setViewExistingSolutionContext) private fun createSubmitAnswerContext( submitAnswerContext: SubmitAnswerContext = createSubmitAnswerContextDetails() diff --git a/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt b/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt index ccc9f65bf3f..4799ce8baca 100644 --- a/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt +++ b/utility/src/test/java/org/oppia/android/util/logging/KenyaAlphaEventBundleCreatorTest.kt @@ -19,8 +19,6 @@ import org.junit.runner.RunWith import org.oppia.android.app.model.EventLog import org.oppia.android.app.model.EventLog.CardContext import org.oppia.android.app.model.EventLog.ConceptCardContext -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_HINT_CONTEXT -import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACCESS_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.ACTIVITYCONTEXT_NOT_SET import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_BACKGROUND_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.APP_IN_FOREGROUND_CONTEXT @@ -45,11 +43,15 @@ import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.OPEN_STO import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.PAUSE_VOICE_OVER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.PLAY_VOICE_OVER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.RESUME_EXPLORATION_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.REVEAL_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SOLUTION_UNLOCKED_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_CARD_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.START_OVER_EXPLORATION_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SUBMIT_ANSWER_CONTEXT import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.SWITCH_IN_LESSON_LANGUAGE +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_HINT_CONTEXT +import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.VIEW_EXISTING_SOLUTION_CONTEXT import org.oppia.android.app.model.EventLog.ExplorationContext import org.oppia.android.app.model.EventLog.HintContext import org.oppia.android.app.model.EventLog.LearnerDetailsContext @@ -563,18 +565,18 @@ class KenyaAlphaEventBundleCreatorTest { } @Test - fun testFillEventBundle_accessHintContextEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { + fun testFillEventBundle_revealHintContextEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessHintContext()) + val eventLog = createEventLog(context = createRevealHintContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) - assertThat(typeName).isEqualTo("access_hint_context") + assertThat(typeName).isEqualTo("reveal_hint_context") assertThat(bundle).hasSize(18) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_HINT_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -588,18 +590,70 @@ class KenyaAlphaEventBundleCreatorTest { } @Test - fun testFillEventBundle_accessHintContextEvent_studyOn_fillsAllFieldsAndReturnsName() { + fun testFillEventBundle_revealHintContextEvent_studyOn_fillsAllFieldsAndReturnsName() { setUpTestApplicationComponentWithLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessHintContext()) + val eventLog = createEventLog(context = createRevealHintContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) - assertThat(typeName).isEqualTo("access_hint_context") + assertThat(typeName).isEqualTo("reveal_hint_context") assertThat(bundle).hasSize(20) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_HINT_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_HINT_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("ed_topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("ed_story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("ed_exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("ed_session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("ed_exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("ed_state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("hint_index").isEqualTo(TEST_HINT_INDEX_STR) + assertThat(bundle).string("ed_ld_learner_id").isEqualTo(TEST_LEARNER_ID) + assertThat(bundle).string("ed_ld_install_id").isEqualTo(TEST_INSTALLATION_ID) + } + + @Test + fun testFillEventBundle_viewExistingHintEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { + setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingHintContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_hint_context") + assertThat(bundle).hasSize(18) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_HINT_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("ed_topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("ed_story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("ed_exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("ed_session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("ed_exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("ed_state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("hint_index").isEqualTo(TEST_HINT_INDEX_STR) + } + + @Test + fun testFillEventBundle_viewExistingHintEvent_studyOn_fillsAllFieldsAndReturnsName() { + setUpTestApplicationComponentWithLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingHintContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_hint_context") + assertThat(bundle).hasSize(20) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_HINT_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -665,18 +719,18 @@ class KenyaAlphaEventBundleCreatorTest { } @Test - fun testFillEventBundle_accessSolutionEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { + fun testFillEventBundle_revealSolutionEvent_studyOff_fillsOnlyNonSensitiveFieldsAndRetsName() { setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessSolutionContext()) + val eventLog = createEventLog(context = createRevealSolutionContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) - assertThat(typeName).isEqualTo("access_solution_context") + assertThat(typeName).isEqualTo("reveal_solution_context") assertThat(bundle).hasSize(17) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_SOLUTION_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -689,18 +743,68 @@ class KenyaAlphaEventBundleCreatorTest { } @Test - fun testFillEventBundle_accessSolutionEvent_studyOn_fillsAllFieldsAndReturnsName() { + fun testFillEventBundle_revealSolutionEvent_studyOn_fillsAllFieldsAndReturnsName() { setUpTestApplicationComponentWithLearnerAnalyticsStudy() val bundle = Bundle() - val eventLog = createEventLog(context = createAccessSolutionContext()) + val eventLog = createEventLog(context = createRevealSolutionContext()) val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) - assertThat(typeName).isEqualTo("access_solution_context") + assertThat(typeName).isEqualTo("reveal_solution_context") assertThat(bundle).hasSize(19) assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) assertThat(bundle).string("priority").isEqualTo("essential") - assertThat(bundle).integer("event_type").isEqualTo(ACCESS_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("event_type").isEqualTo(REVEAL_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("state_name").isEqualTo(TEST_STATE_NAME) + assertThat(bundle).string("ld_learner_id").isEqualTo(TEST_LEARNER_ID) + assertThat(bundle).string("ld_install_id").isEqualTo(TEST_INSTALLATION_ID) + } + + @Test + fun testFillEventBundle_viewExistingSolutionEvent_studyOff_fillsNonSensitiveFieldsAndRetsName() { + setUpTestApplicationComponentWithoutLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingSolutionContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_solution_context") + assertThat(bundle).hasSize(17) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_SOLUTION_CONTEXT.number) + assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) + assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) + assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) + assertThat(bundle).string("topic_id").isEqualTo(TEST_TOPIC_ID) + assertThat(bundle).string("story_id").isEqualTo(TEST_STORY_ID) + assertThat(bundle).string("exploration_id").isEqualTo(TEST_EXPLORATION_ID) + assertThat(bundle).string("session_id").isEqualTo(TEST_LEARNER_SESSION_ID) + assertThat(bundle).string("exploration_version").isEqualTo(TEST_EXPLORATION_VERSION_STR) + assertThat(bundle).string("state_name").isEqualTo(TEST_STATE_NAME) + } + + @Test + fun testFillEventBundle_viewExistingSolutionEvent_studyOn_fillsAllFieldsAndReturnsName() { + setUpTestApplicationComponentWithLearnerAnalyticsStudy() + val bundle = Bundle() + + val eventLog = createEventLog(context = createViewExistingSolutionContext()) + + val typeName = eventBundleCreator.fillEventBundle(eventLog, bundle) + assertThat(typeName).isEqualTo("view_existing_solution_context") + assertThat(bundle).hasSize(19) + assertThat(bundle).longInt("timestamp").isEqualTo(TEST_TIMESTAMP_1) + assertThat(bundle).string("priority").isEqualTo("essential") + assertThat(bundle).integer("event_type").isEqualTo(VIEW_EXISTING_SOLUTION_CONTEXT.number) assertThat(bundle).integer("android_sdk").isEqualTo(TEST_ANDROID_SDK_VERSION) assertThat(bundle).string("app_version_name").isEqualTo(TEST_APP_VERSION_NAME) assertThat(bundle).integer("app_version_code").isEqualTo(TEST_APP_VERSION_CODE) @@ -1346,16 +1450,23 @@ class KenyaAlphaEventBundleCreatorTest { private fun createHintUnlockedContext(hintContext: HintContext = createHintContext()) = createEventContext(hintContext, EventContextBuilder::setHintUnlockedContext) - private fun createAccessHintContext(hintContext: HintContext = createHintContext()) = - createEventContext(hintContext, EventContextBuilder::setAccessHintContext) + private fun createRevealHintContext(hintContext: HintContext = createHintContext()) = + createEventContext(hintContext, EventContextBuilder::setRevealHintContext) + + private fun createViewExistingHintContext(hintContext: HintContext = createHintContext()) = + createEventContext(hintContext, EventContextBuilder::setViewExistingHintContext) private fun createSolutionUnlockedContext( explorationContext: ExplorationContext = createExplorationContext() ) = createEventContext(explorationContext, EventContextBuilder::setSolutionUnlockedContext) - private fun createAccessSolutionContext( + private fun createRevealSolutionContext( + explorationContext: ExplorationContext = createExplorationContext() + ) = createEventContext(explorationContext, EventContextBuilder::setRevealSolutionContext) + + private fun createViewExistingSolutionContext( explorationContext: ExplorationContext = createExplorationContext() - ) = createEventContext(explorationContext, EventContextBuilder::setAccessSolutionContext) + ) = createEventContext(explorationContext, EventContextBuilder::setViewExistingSolutionContext) private fun createSubmitAnswerContext( submitAnswerContext: SubmitAnswerContext = createSubmitAnswerContextDetails()