Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Fix #4042: Implement success criteria metrics for lesson checkpointing #5336

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7fb6123
Introduce context fields for new events
theMr17 Feb 8, 2024
b1aecac
Fix lint issues
theMr17 Feb 8, 2024
b264f79
Fix lint issues
theMr17 Feb 8, 2024
e6e0fb8
Log the events via AnalyticsController
theMr17 Feb 8, 2024
22b5a65
Log progress save success & failure events
theMr17 Feb 8, 2024
275b18b
Log lesson saved advertently event
theMr17 Feb 8, 2024
7e968a6
Log correct & incorrect answer submission in a resumed exploration
theMr17 Feb 8, 2024
f20f96a
Update tests
theMr17 Feb 9, 2024
038b024
Fix lint issues
theMr17 Feb 9, 2024
12be213
Merge branch 'develop' into feat/lesson-checkpointing-metrics
theMr17 Feb 16, 2024
20e67da
Fix tests
theMr17 Feb 18, 2024
a7cc75f
Merge branch 'develop' into feat/lesson-checkpointing-metrics
theMr17 Feb 23, 2024
7cdc8e6
Add tests for resume lesson submit answer events
theMr17 Feb 26, 2024
3733bdf
Add tests for resume lesson submit answer events
theMr17 Feb 26, 2024
bd11701
Add test for lesson saved advertently event
theMr17 Feb 28, 2024
483e151
Add test for lesson saved advertently event
theMr17 Feb 28, 2024
935854e
Fix test
theMr17 Feb 28, 2024
b9f7800
Add tests for lesson saved advertently event
theMr17 Feb 28, 2024
4aae1b5
Fix test
theMr17 Feb 28, 2024
8752cd5
Merge branch 'develop' into feat/lesson-checkpointing-metrics
theMr17 Mar 4, 2024
6466c65
Fix tests
theMr17 Mar 5, 2024
5355dcf
Fix ktlint max line length issue
theMr17 Mar 5, 2024
594ded1
Shorten test name
theMr17 Apr 8, 2024
8f417de
Remove unnecessary import changes
theMr17 Apr 8, 2024
7e83455
Reposition logging of lesson saved advertently event
theMr17 Apr 8, 2024
4d4aa00
Simplify isResume logic and add comment
theMr17 Apr 8, 2024
f0599f6
Merge branch 'develop' into feat/lesson-checkpointing-metrics
theMr17 Apr 11, 2024
6dd6fdc
Update comment
theMr17 Apr 19, 2024
4c36c08
Remove get2ndMostRecentEvent function
theMr17 Apr 19, 2024
5f5443f
Remove getLoggedEvent function
theMr17 Apr 19, 2024
a3e7241
Merge remote-tracking branch 'origin/feat/lesson-checkpointing-metric…
theMr17 Apr 19, 2024
8bad89f
Fix lint checks
theMr17 Apr 19, 2024
3551963
Add tests for analytics logger
theMr17 Apr 19, 2024
e451e0a
Fix tests
theMr17 Apr 19, 2024
bdf3034
Remove extra blank space
theMr17 Apr 19, 2024
f598849
Revert "Remove getLoggedEvent function"
theMr17 Apr 30, 2024
f61a54c
Add imports
theMr17 Apr 30, 2024
6223f2e
Add tests for changes in FakeAnalyticsEventLogger
theMr17 May 1, 2024
059073f
Merge branch 'develop' into feat/lesson-checkpointing-metrics
theMr17 May 8, 2024
d2eeb17
Merge branch 'develop' into feat/lesson-checkpointing-metrics
adhiamboperes May 8, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.oppia.android.app.viewmodel.ViewModelProvider
import org.oppia.android.databinding.ExplorationActivityBinding
import org.oppia.android.domain.exploration.ExplorationDataController
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.oppialogger.analytics.LearnerAnalyticsLogger
import org.oppia.android.domain.survey.SurveyGatingController
import org.oppia.android.domain.translation.TranslationController
import org.oppia.android.util.accessibility.AccessibilityService
Expand All @@ -60,6 +61,7 @@ class ExplorationActivityPresenter @Inject constructor(
private val fontScaleConfigurationUtil: FontScaleConfigurationUtil,
private val translationController: TranslationController,
private val oppiaLogger: OppiaLogger,
private val learnerAnalyticsLogger: LearnerAnalyticsLogger,
private val resourceHandler: AppLanguageResourceHandler,
private val surveyGatingController: SurveyGatingController
) {
Expand Down Expand Up @@ -328,7 +330,7 @@ class ExplorationActivityPresenter @Inject constructor(
return
}
// If checkpointing is enabled, get the current checkpoint state to show an appropriate dialog
// fragment.
// fragment and log lesson saved advertently event.
showDialogFragmentBasedOnCurrentCheckpointState()
}

Expand Down Expand Up @@ -514,9 +516,11 @@ class ExplorationActivityPresenter @Inject constructor(
} else {
when (checkpointState) {
CheckpointState.CHECKPOINT_SAVED_DATABASE_NOT_EXCEEDED_LIMIT -> {
learnerAnalyticsLogger.explorationAnalyticsLogger.value?.logLessonSavedAdvertently()
stopExploration(isCompletion = false)
}
CheckpointState.CHECKPOINT_SAVED_DATABASE_EXCEEDED_LIMIT -> {
learnerAnalyticsLogger.explorationAnalyticsLogger.value?.logLessonSavedAdvertently()
showProgressDatabaseFullDialogFragment()
}
else -> showUnsavedExplorationDialogFragment()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ import org.oppia.android.app.application.testing.TestingBuildFlavorModule
import org.oppia.android.app.devoptions.DeveloperOptionsModule
import org.oppia.android.app.devoptions.DeveloperOptionsStarterModule
import org.oppia.android.app.help.HelpActivity
import org.oppia.android.app.model.EventLog.Context.ActivityContextCase.LESSON_SAVED_ADVERTENTLY_CONTEXT
import org.oppia.android.app.model.ExplorationActivityParams
import org.oppia.android.app.model.OppiaLanguage
import org.oppia.android.app.model.ProfileId
Expand Down Expand Up @@ -128,6 +129,7 @@ import org.oppia.android.domain.topic.TEST_TOPIC_ID_0
import org.oppia.android.domain.translation.TranslationController
import org.oppia.android.domain.workmanager.WorkManagerConfigurationModule
import org.oppia.android.testing.BuildEnvironment
import org.oppia.android.testing.FakeAnalyticsEventLogger
import org.oppia.android.testing.OppiaTestRule
import org.oppia.android.testing.RunOn
import org.oppia.android.testing.TestLogReportingModule
Expand Down Expand Up @@ -222,6 +224,9 @@ class ExplorationActivityTest {
@Inject
lateinit var fakeAccessibilityService: FakeAccessibilityService

@Inject
lateinit var fakeAnalyticsEventLogger: FakeAnalyticsEventLogger

private val internalProfileId: Int = 0

@Before
Expand Down Expand Up @@ -1932,6 +1937,129 @@ class ExplorationActivityTest {
assertThat(explorationActivityTestRule.activity.isFinishing).isTrue()
}

@Test
fun testExpActivity_startNewExploration_pressBack_logsLessonSavedAdvertentlyEvent() {
setUpAudioForFractionLesson()
explorationActivityTestRule.launchActivity(
createExplorationActivityIntent(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0,
shouldSavePartialProgress = true
)
)
explorationDataController.startPlayingNewExploration(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0
)
testCoroutineDispatchers.runCurrent()

pressBack()
testCoroutineDispatchers.runCurrent()

val lessonSavedAdvertentlyEventCount = fakeAnalyticsEventLogger.countEvents {
it.context.activityContextCase == LESSON_SAVED_ADVERTENTLY_CONTEXT
}
assertThat(lessonSavedAdvertentlyEventCount).isEqualTo(1)
}

@Test
fun testExpActivity_startNewExploration_pressToolbarBackIcon_logsLessonSavedAdvertentlyEvent() {
setUpAudioForFractionLesson()
markAllSpotlightsSeen()
explorationActivityTestRule.launchActivity(
createExplorationActivityIntent(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0,
shouldSavePartialProgress = true
)
)
explorationDataController.startPlayingNewExploration(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0
)
testCoroutineDispatchers.runCurrent()

// Click on 'X' icon on toolbar.
onView(withContentDescription(R.string.nav_app_bar_navigate_up_description)).perform(click())
testCoroutineDispatchers.runCurrent()

explorationDataController.stopPlayingExploration(isCompletion = false)
testCoroutineDispatchers.runCurrent()

val lessonSavedAdvertentlyEventCount = fakeAnalyticsEventLogger.countEvents {
it.context.activityContextCase == LESSON_SAVED_ADVERTENTLY_CONTEXT
}
assertThat(lessonSavedAdvertentlyEventCount).isEqualTo(1)
}

@Test
fun testExpActivity_replayExploration_pressBack_doesNotLogLessonSavedAdvertentlyEvent() {
setUpAudioForFractionLesson()
explorationActivityTestRule.launchActivity(
createExplorationActivityIntent(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0,
shouldSavePartialProgress = false
)
)
explorationDataController.replayExploration(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0
)
testCoroutineDispatchers.runCurrent()

pressBack()
testCoroutineDispatchers.runCurrent()

val lessonSavedAdvertentlyEventCount = fakeAnalyticsEventLogger.countEvents {
it.context.activityContextCase == LESSON_SAVED_ADVERTENTLY_CONTEXT
}
assertThat(lessonSavedAdvertentlyEventCount).isEqualTo(0)
}

@Test
fun testExpActivity_replayExp_pressToolbarBackIcon_doesNotLogLessonSavedAdvertentlyEvent() {
setUpAudioForFractionLesson()
markAllSpotlightsSeen()
explorationActivityTestRule.launchActivity(
createExplorationActivityIntent(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0,
shouldSavePartialProgress = false
)
)
explorationDataController.replayExploration(
internalProfileId,
FRACTIONS_TOPIC_ID,
FRACTIONS_STORY_ID_0,
FRACTIONS_EXPLORATION_ID_0
)
testCoroutineDispatchers.runCurrent()

// Click on 'X' icon on toolbar.
onView(withContentDescription(R.string.nav_app_bar_navigate_up_description)).perform(click())
testCoroutineDispatchers.runCurrent()

val lessonSavedAdvertentlyEventCount = fakeAnalyticsEventLogger.countEvents {
it.context.activityContextCase == LESSON_SAVED_ADVERTENTLY_CONTEXT
}
assertThat(lessonSavedAdvertentlyEventCount).isEqualTo(0)
}

adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
@Test
@RunOn(TestPlatform.ROBOLECTRIC) // TODO(#3858): Enable for Espresso.
fun testExpActivity_englishContentLang_contentIsInEnglish() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -443,6 +443,15 @@ class ExplorationProgressController @Inject constructor(
ControllerState(
ExplorationProgress(),
message.isRestart,
// The [message.explorationCheckpoint] is [ExplorationCheckpoint.getDefaultInstance()]
// in the following 3 cases.
// - New exploration is started.
// - Saved Exploration is restarted.
// - Completed exploration is replayed.
// The [message.explorationCheckpoint] will contain the exploration checkpoint
// only when a saved exploration is resumed.
isResume = message.explorationCheckpoint
!= ExplorationCheckpoint.getDefaultInstance(),
message.sessionId,
message.ephemeralStateFlow,
commandQueue,
Expand Down Expand Up @@ -634,6 +643,14 @@ class ExplorationProgressController @Inject constructor(
topPendingState.interaction, userAnswer, answerOutcome.labelledAsCorrectAnswer
)

// Log correct & incorrect answer submission in a resumed exploration.
if (isResume) {
if (answerOutcome.labelledAsCorrectAnswer)
explorationAnalyticsLogger.logResumeLessonSubmitCorrectAnswer()
else
explorationAnalyticsLogger.logResumeLessonSubmitIncorrectAnswer()
}

// Follow the answer's outcome to another part of the graph if it's different.
val ephemeralState = computeBaseCurrentEphemeralState()
when {
Expand Down Expand Up @@ -947,9 +964,11 @@ class ExplorationProgressController @Inject constructor(

deferred.invokeOnCompletion {
val checkpointState = if (it == null) {
explorationAnalyticsLogger.logProgressSavingSuccess()
deferred.getCompleted()
} else {
oppiaLogger.e("Lightweight checkpointing", "Failed to save checkpoint in exploration", it)
explorationAnalyticsLogger.logProgressSavingFailure()
// CheckpointState is marked as CHECKPOINT_UNSAVED because the deferred did not
// complete successfully.
CheckpointState.CHECKPOINT_UNSAVED
Expand Down Expand Up @@ -1094,6 +1113,7 @@ class ExplorationProgressController @Inject constructor(
private class ControllerState(
val explorationProgress: ExplorationProgress,
val isRestart: Boolean,
val isResume: Boolean,
val sessionId: String,
val ephemeralStateFlow: MutableStateFlow<AsyncResult<EphemeralState>>,
val commandQueue: SendChannel<ControllerMessage<*>>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,31 @@ class LearnerAnalyticsLogger @Inject constructor(
getExpectedStateLogger()?.logFinishExploration()
}

/** Logs that the current exploration progress has been saved successfully. */
fun logProgressSavingSuccess() {
getExpectedStateLogger()?.logProgressSavingSuccess()
}

/** Logs that the current exploration progress has failed to save. */
fun logProgressSavingFailure() {
getExpectedStateLogger()?.logProgressSavingFailure()
}

/** Logs that the user has left the lesson advertently (attempted to save). */
fun logLessonSavedAdvertently() {
getExpectedStateLogger()?.logLessonSavedAdvertently()
}

/** Logs that correct answer was submitted in a resumed lesson. */
fun logResumeLessonSubmitCorrectAnswer() {
getExpectedStateLogger()?.logResumeLessonSubmitCorrectAnswer()
}

/** Logs that incorrect answer was submitted in a resumed lesson. */
fun logResumeLessonSubmitIncorrectAnswer() {
getExpectedStateLogger()?.logResumeLessonSubmitIncorrectAnswer()
}

adhiamboperes marked this conversation as resolved.
Show resolved Hide resolved
/**
* Begins analytics logging for the specified [newState], returning the [StateAnalyticsLogger]
* that can be used to log events for the [State].
Expand Down Expand Up @@ -306,6 +331,31 @@ class LearnerAnalyticsLogger @Inject constructor(
logStateEvent(EventBuilder::setFinishExplorationContext)
}

/** Logs that the current exploration progress has been saved successfully. */
internal fun logProgressSavingSuccess() {
logStateEvent(EventBuilder::setProgressSavingSuccessContext)
}

/** Logs that the current exploration progress has failed to save. */
internal fun logProgressSavingFailure() {
logStateEvent(EventBuilder::setProgressSavingFailureContext)
}

/** Logs that the user has left the lesson advertently (attempted to save). */
internal fun logLessonSavedAdvertently() {
logStateEvent(EventBuilder::setLessonSavedAdvertentlyContext)
}

/** Logs that correct answer was submitted in a resumed lesson. */
internal fun logResumeLessonSubmitCorrectAnswer() {
logStateEvent(EventBuilder::setResumeLessonSubmitCorrectAnswerContext)
}

/** Logs that incorrect answer was submitted in a resumed lesson. */
internal fun logResumeLessonSubmitIncorrectAnswer() {
logStateEvent(EventBuilder::setResumeLessonSubmitIncorrectAnswerContext)
}

/** Logs that this card has been started. */
fun logStartCard() {
logStateEvent(linkedSkillId, ::createCardContext, EventBuilder::setStartCardContext)
Expand Down
Loading
Loading