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

Fixes #4897: Follow up alpha MR6 fixes #4896

Merged
merged 60 commits into from
Mar 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
b39e47d
SW translation fixes + better UI.
BenHenning Jun 16, 2022
f2cbc0d
Expand concept card support.
BenHenning Aug 24, 2022
2e62688
Merge branch 'develop' into expand-concept-card-support
BenHenning Aug 24, 2022
76d38ed
Lint fixes.
BenHenning Aug 24, 2022
94a4140
Revert flag enabled for development.
BenHenning Aug 25, 2022
7322714
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Jan 12, 2023
19f0e69
Fixes a few things for the upcoming MR6 release.
BenHenning Jan 12, 2023
17efba6
Merge branch 'expand-concept-card-support' into assorted-alpha-mr6-fixes
BenHenning Jan 12, 2023
b58c798
Add mark chapter supported for admins.
BenHenning Jan 13, 2023
a502725
Fix in-lesson language switching.
BenHenning Jan 14, 2023
50615ab
Add support for multiple solution types.
BenHenning Jan 18, 2023
970dfaf
Lint & static check fixes.
BenHenning Jan 19, 2023
a3efa90
Remove TODO for soon-to-be-fixed issue.
BenHenning Jan 19, 2023
1d0c122
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Jan 19, 2023
20255ec
Multiple fixes.
BenHenning Jan 19, 2023
62a3ab3
Fix broken titles in subtopics after conversion.
BenHenning Jan 19, 2023
3a2dee0
Catalog tests to add.
BenHenning Jan 19, 2023
c40d89b
Fix test build breakages.
BenHenning Jan 19, 2023
87cd106
Fix test functional breakages.
BenHenning Jan 19, 2023
598a00c
Add new tests.
BenHenning Jan 20, 2023
c109c7c
Fix broken Gradle tests.
BenHenning Jan 20, 2023
51b83f3
Address reviewer comments.
BenHenning Jan 20, 2023
afc3817
Update version.bzl
BenHenning Jan 20, 2023
8065e64
Assorted fixes.
BenHenning Jan 20, 2023
1346329
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Jan 26, 2023
39dafc0
Many more fixes/changes per feedback.
BenHenning Jan 30, 2023
c0dc1a5
Address reviewer comments.
BenHenning Jan 30, 2023
a1a33d4
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Jan 30, 2023
5b220b7
Merge branch 'develop' into adjust-version-codes-based-on-flavors
BenHenning Jan 30, 2023
6eaa68c
Use latest commit for versioning, instead.
BenHenning Jan 30, 2023
a64d1a2
Fix broken tests.
BenHenning Jan 30, 2023
c021017
Fix more broken tests.
BenHenning Jan 30, 2023
c64a91d
Merge branch 'develop' into adjust-version-codes-based-on-flavors
BenHenning Jan 31, 2023
c4ad811
Fix broken Gradle tests.
BenHenning Jan 31, 2023
3f12d91
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Jan 31, 2023
b793d0d
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Feb 1, 2023
4a6b904
Merge branch 'develop' into adjust-version-codes-based-on-flavors
BenHenning Feb 1, 2023
c8eb925
Merge branch 'develop' into adjust-version-codes-based-on-flavors
BenHenning Feb 28, 2023
a8f24c3
Update versions for a Kenya alpha re-release.
BenHenning Feb 28, 2023
4539b0a
Merge branch 'adjust-version-codes-based-on-flavors' into assorted-al…
BenHenning Feb 28, 2023
8cd35d9
Merge branch 'assorted-alpha-mr6-fixes' of github.com:oppia/oppia-and…
BenHenning Mar 1, 2023
84bd2f1
Main follow-up alpha MR6 fixes.
BenHenning Mar 10, 2023
642030c
Remove AtomicEnum.
BenHenning Mar 10, 2023
e49a622
Merge branch 'develop' into adjust-version-codes-based-on-flavors
BenHenning Mar 10, 2023
ca3250e
Merge branch 'adjust-version-codes-based-on-flavors' into assorted-al…
BenHenning Mar 10, 2023
fbf2d09
Merge branch 'assorted-alpha-mr6-fixes' into follow-up-alpha-mr6-fixes
BenHenning Mar 10, 2023
3210121
Some follow-up test fixes.
BenHenning Mar 10, 2023
3a8d645
Test fixes following develop update.
BenHenning Mar 10, 2023
c78aa95
Merge branch 'assorted-alpha-mr6-fixes' into follow-up-alpha-mr6-fixes
BenHenning Mar 10, 2023
fbeeb0c
Gradle build & lint fixes.
BenHenning Mar 10, 2023
7ba388a
Fix testing module Gradle build.
BenHenning Mar 10, 2023
245d48a
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Mar 10, 2023
0f71b8c
Gate long-term event tracking.
BenHenning Mar 10, 2023
75debc2
Merge branch 'assorted-alpha-mr6-fixes' into follow-up-alpha-mr6-fixes
BenHenning Mar 10, 2023
679411e
More test fixes & clean-up, per event gating.
BenHenning Mar 10, 2023
136504a
Revert unnecessary change.
BenHenning Mar 10, 2023
8b00d64
Merge branch 'develop' into assorted-alpha-mr6-fixes
BenHenning Mar 10, 2023
a7c8374
Merge branch 'assorted-alpha-mr6-fixes' into follow-up-alpha-mr6-fixes
BenHenning Mar 10, 2023
1d04649
Gradle test build fix.
BenHenning Mar 10, 2023
d0eedea
Address reviewer comments.
BenHenning Mar 10, 2023
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
4 changes: 3 additions & 1 deletion app/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,10 @@ DATABINDING_LAYOUTS = ["src/main/res/layout*/**"]
# keep sorted
VIEW_MODELS_WITH_RESOURCE_IMPORTS = [
"src/main/java/org/oppia/android/app/administratorcontrols/appversion/AppVersionViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ControlButtonsViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/DeviceIdItemViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileLearnerIdItemViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ProfileListViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/ShareIdsViewModel.kt",
"src/main/java/org/oppia/android/app/administratorcontrols/learneranalytics/SyncStatusItemViewModel.kt",
"src/main/java/org/oppia/android/app/devoptions/forcenetworktype/ForceNetworkTypeViewModel.kt",
"src/main/java/org/oppia/android/app/devoptions/mathexpressionparser/MathExpressionParserViewModel.kt",
Expand Down Expand Up @@ -344,6 +344,7 @@ VIEW_MODELS = [
"src/main/java/org/oppia/android/app/story/StoryViewModel.kt",
"src/main/java/org/oppia/android/app/testing/BindableAdapterTestDataModel.kt",
"src/main/java/org/oppia/android/app/testing/BindableAdapterTestViewModel.kt",
"src/main/java/org/oppia/android/app/testing/CircularProgressIndicatorAdaptersTestViewModel.kt",
"src/main/java/org/oppia/android/app/topic/conceptcard/ConceptCardViewModel.kt",
"src/main/java/org/oppia/android/app/topic/lessons/TopicLessonViewModel.kt",
"src/main/java/org/oppia/android/app/topic/lessons/TopicLessonsItemViewModel.kt",
Expand Down Expand Up @@ -472,6 +473,7 @@ BINDING_ADAPTERS_WITH_RESOURCE_IMPORTS = [
# keep sorted
BINDING_ADAPTERS = [
"src/main/java/org/oppia/android/app/databinding/AppCompatCheckBoxBindingAdapters.java",
"src/main/java/org/oppia/android/app/databinding/CircularProgressIndicatorAdapters.java",
"src/main/java/org/oppia/android/app/databinding/ConstraintLayoutAdapters.java",
"src/main/java/org/oppia/android/app/databinding/EditTextBindingAdapters.java",
"src/main/java/org/oppia/android/app/databinding/GuidelineBindingAdapters.java",
Expand Down
1 change: 1 addition & 0 deletions app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@
<activity android:name=".app.testing.AppCompatCheckBoxBindingAdaptersTestActivity" />
<activity android:name=".app.testing.AudioFragmentTestActivity" />
<activity android:name=".app.testing.BindableAdapterTestActivity" />
<activity android:name=".app.testing.CircularProgressIndicatorAdaptersTestActivity" />
<activity android:name=".app.testing.ConceptCardFragmentTestActivity" />
<activity android:name=".app.testing.DragDropTestActivity" />
<activity android:name=".app.testing.DrawableBindingAdaptersTestActivity" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ import org.oppia.android.app.story.StoryActivity
import org.oppia.android.app.testing.AdministratorControlsFragmentTestActivity
import org.oppia.android.app.testing.AppCompatCheckBoxBindingAdaptersTestActivity
import org.oppia.android.app.testing.AudioFragmentTestActivity
import org.oppia.android.app.testing.CircularProgressIndicatorAdaptersTestActivity
import org.oppia.android.app.testing.ConceptCardFragmentTestActivity
import org.oppia.android.app.testing.DragDropTestActivity
import org.oppia.android.app.testing.DrawableBindingAdaptersTestActivity
Expand Down Expand Up @@ -123,6 +124,7 @@ interface ActivityComponentImpl :
fun inject(appVersionActivity: AppVersionActivity)
fun inject(audioFragmentTestActivity: AudioFragmentTestActivity)
fun inject(audioLanguageActivity: AudioLanguageActivity)
fun inject(circularProgressAdaptersTestActivity: CircularProgressIndicatorAdaptersTestActivity)
fun inject(completedStoryListActivity: CompletedStoryListActivity)
fun inject(conceptCardFragmentTestActivity: ConceptCardFragmentTestActivity)
fun inject(developerOptionsActivity: DeveloperOptionsActivity)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
package org.oppia.android.app.administratorcontrols.learneranalytics

import android.annotation.SuppressLint
import android.content.ActivityNotFoundException
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Transformations
import com.google.protobuf.MessageLite
import org.oppia.android.app.administratorcontrols.learneranalytics.ProfileListViewModel.ProfileListItemViewModel
import org.oppia.android.app.model.OppiaEventLogs
import org.oppia.android.domain.oppialogger.OppiaLogger
import org.oppia.android.domain.oppialogger.analytics.AnalyticsController
import org.oppia.android.util.data.AsyncResult
import org.oppia.android.util.data.DataProviders.Companion.toLiveData
import org.oppia.android.util.logging.SyncStatusManager
import org.oppia.android.util.logging.SyncStatusManager.SyncStatus
import java.io.ByteArrayOutputStream
import java.util.Base64
import java.util.zip.GZIPOutputStream
import javax.inject.Inject

/**
* [ProfileListItemViewModel] that represents the portion of the learner analytics admin page which
* provides some control buttons for ID/logs sharing and syncing.
*/
@SuppressLint("StaticFieldLeak") // False positive (Android doesn't manage this model's lifecycle).
class ControlButtonsViewModel private constructor(
private val oppiaLogger: OppiaLogger,
private val activity: AppCompatActivity,
private val analyticsController: AnalyticsController,
private val syncStatusManager: SyncStatusManager,
private val viewModels: List<ProfileListItemViewModel>
) : ProfileListItemViewModel(ProfileListViewModel.ProfileListItemViewType.SHARE_IDS) {
private var monitoredUploadProgress: LiveData<ForceSyncProgress> =
MutableLiveData(ForceSyncProgress())

/** The current [ForceSyncProgress], as affected by [onUploadLogsNowButtonClicked]. */
val forceUploadProgress: LiveData<ForceSyncProgress> get() = monitoredUploadProgress

/** The current [SyncStatus] of event logs. */
val syncStatus: LiveData<SyncStatus> by lazy {
Transformations.map(syncStatusManager.getSyncStatus().toLiveData(), this::processSyncStatus)
}

/**
* Indicates the user wants to share learner & device IDs, along with event log metrics, with
* another app.
*/
fun onShareButtonClicked() {
// Reference: https://developer.android.com/guide/components/intents-common#Email from
// https://stackoverflow.com/a/15022153/3689782.
val logs = retrieveEventLogs(viewModels.filterIsInstance<ProfileLearnerIdItemViewModel>())
val sharedText = viewModels.mapNotNull { viewModel ->
when (viewModel) {
is DeviceIdItemViewModel -> listOf("Oppia app installation ID: ${viewModel.deviceId.value}")
is ProfileLearnerIdItemViewModel -> {
val profile = viewModel.profile
val stats = viewModel.profileSpecificEventsUploadStats.value
val learnerStats = stats?.learnerStats
val uncategorizedStats = stats?.uncategorizedStats
listOfNotNull(
"- Profile name: ${profile.name}, learner ID: ${profile.learnerId}",
learnerStats?.awaitingUploadEventCountText?.let { " - Uploading learner events: $it" },
learnerStats?.uploadedEventCountText?.let { " - Uploaded learner events: $it" },
uncategorizedStats?.awaitingUploadEventCountText?.let {
" - Uploading uncategorized events: $it"
},
uncategorizedStats?.uploadedEventCountText?.let {
" - Uploaded uncategorized events: $it"
}
)
}
is SyncStatusItemViewModel -> {
listOf(
"Current sync status: ${viewModel.syncStatus.value}.",
"Encoded event logs:"
) + (logs?.toCompressedBase64()?.chunked(BASE64_LINE_WRAP_LIMIT) ?: listOf("Missing"))
}
else -> null
}
}.flatten().joinToString(separator = "\n")
try {
activity.startActivity(
Intent(Intent.ACTION_SEND).apply {
type = "text/plain"
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
putExtra(Intent.EXTRA_TEXT, sharedText)
}
)
} catch (e: ActivityNotFoundException) {
oppiaLogger.e("ControlButtonsViewModel", "No activity found to receive shared IDs.", e)
}
}

/**
* Indicates that the user wishes to upload any pending event logs immediately, rather than
* waiting for the app to schedule an event upload.
*
* The status of the upload result can be observed via [forceUploadProgress].
*
* [canStartUploadingLogs] should be used to determine whether now is a valid time to start
* uploading logs.
*/
fun onUploadLogsNowButtonClicked() {
monitoredUploadProgress =
Transformations.map(
analyticsController.uploadEventLogs().toLiveData(), this::processUploadedEventLogs
)
notifyChange() // Recompute bindings since the live data instance has changed.
}

/**
* Returns whether current conditions, per [isCurrentlyUploading] and [syncStatus], indicate that
* there are events that can be manually force-uploaded by the user.
*
* Note that both [isCurrentlyUploading] and [syncStatus] permit null values because of an issue
* with databinding wherein multiple [LiveData]s bound at the same time may result in one or both
* of the values correspond to the *current* [LiveData] value which may be null if the [LiveData]
* hasn't been updated yet (even if its type indicates non-nullness). Permitting null allows the
* implementation to work around this case by assuming that either value missing means no
* determination can be made whether logs can start being uploaded.
*/
fun canStartUploadingLogs(isCurrentlyUploading: Boolean?, syncStatus: SyncStatus?): Boolean =
isCurrentlyUploading == false && syncStatus == SyncStatus.WAITING_TO_START_UPLOADING

private fun retrieveEventLogs(viewModels: List<ProfileLearnerIdItemViewModel>): OppiaEventLogs? =
viewModels.firstOrNull()?.oppiaEventLogs?.value?.let { processEventLogs(it) }

private fun processUploadedEventLogs(result: AsyncResult<Pair<Int, Int>>): ForceSyncProgress {
return when (result) {
is AsyncResult.Pending -> ForceSyncProgress(eventsUploaded = 0, totalEventsToUpload = 0)
is AsyncResult.Success -> {
val (currentUploadedEventCount, totalEventCount) = result.value
ForceSyncProgress(currentUploadedEventCount, totalEventCount)
}
is AsyncResult.Failure -> ForceSyncProgress().also {
oppiaLogger.e(
"ControlButtonsViewModel", "Encountered failure while uploading events.", result.error
)
}
}
}

private fun processSyncStatus(result: AsyncResult<SyncStatus>): SyncStatus {
return when (result) {
is AsyncResult.Pending -> SyncStatus.INITIAL_UNKNOWN
is AsyncResult.Success -> result.value
is AsyncResult.Failure -> SyncStatus.INITIAL_UNKNOWN.also {
oppiaLogger.e(
"ControlButtonsViewModel",
"Encountered failure while retrieving sync status.",
result.error
)
}
}
}

private fun processEventLogs(result: AsyncResult<OppiaEventLogs>): OppiaEventLogs? {
return when (result) {
is AsyncResult.Pending -> null
is AsyncResult.Success -> result.value
is AsyncResult.Failure -> {
oppiaLogger.e(
"ControlButtonsViewModel", "Encountered failure while on-disk event logs.", result.error
)
null
}
}
}

/**
* Progress representation structure that provides the UI with an indication of how much of an
* upload event operation has completed, and how much remains.
*
* Note that a [totalEventsToUpload] value of 0 indicates there are 0 left in the **current**
* operation, but there may have been events to upload in the past.
*
* @property eventsUploaded the number of events that have uploaded so far
* @property totalEventsToUpload the total number of events to upload, or none, or
* [DEFAULT_UNKNOWN_EVENTS_TO_UPLOAD_COUNT] if it's not yet known how many events can be
* uploaded
*/
data class ForceSyncProgress(
val eventsUploaded: Int = 0,
val totalEventsToUpload: Int = DEFAULT_UNKNOWN_EVENTS_TO_UPLOAD_COUNT
) {
/** Returns whether there are events that can be uploaded. */
fun hasEventsToUpload(): Boolean = totalEventsToUpload != DEFAULT_UNKNOWN_EVENTS_TO_UPLOAD_COUNT

/** Returns whether there are events currently being uploaded. */
fun isCurrentlyUploading(): Boolean = eventsUploaded < totalEventsToUpload
}

/** Factory for creating new [ControlButtonsViewModel]s. */
class Factory @Inject constructor(
private val oppiaLogger: OppiaLogger,
private val activity: AppCompatActivity,
private val analyticsController: AnalyticsController,
private val syncStatusManager: SyncStatusManager
) {
/** Returns a new [ControlButtonsViewModel]. */
fun create(viewModels: List<ProfileListItemViewModel>): ControlButtonsViewModel {
return ControlButtonsViewModel(
oppiaLogger, activity, analyticsController, syncStatusManager, viewModels
)
}
}

private companion object {
private const val BASE64_LINE_WRAP_LIMIT = 80
private const val DEFAULT_UNKNOWN_EVENTS_TO_UPLOAD_COUNT = Integer.MIN_VALUE

// Copied from ProtoStringEncoder (which isn't available in production code).
private fun <M : MessageLite> M.toCompressedBase64(): String {
val compressedMessage = ByteArrayOutputStream().also { byteOutputStream ->
GZIPOutputStream(byteOutputStream).use(::writeTo)
}.toByteArray()
return Base64.getEncoder().encodeToString(compressedMessage)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ import org.oppia.android.app.administratorcontrols.learneranalytics.ProfileListV
import org.oppia.android.app.administratorcontrols.learneranalytics.ProfileListViewModel.ProfileListItemViewType
import org.oppia.android.app.recyclerview.BindableAdapter
import org.oppia.android.databinding.ProfileAndDeviceIdFragmentBinding
import org.oppia.android.databinding.ProfileListControlButtonsBinding
import org.oppia.android.databinding.ProfileListDeviceIdItemBinding
import org.oppia.android.databinding.ProfileListLearnerIdItemBinding
import org.oppia.android.databinding.ProfileListShareIdsItemBinding
import org.oppia.android.databinding.ProfileListSyncStatusItemBinding
import javax.inject.Inject

Expand Down Expand Up @@ -47,7 +47,7 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor(
is DeviceIdItemViewModel -> ProfileListItemViewType.DEVICE_ID
is ProfileLearnerIdItemViewModel -> ProfileListItemViewType.LEARNER_ID
is SyncStatusItemViewModel -> ProfileListItemViewType.SYNC_STATUS
is ShareIdsViewModel -> ProfileListItemViewType.SHARE_IDS
is ControlButtonsViewModel -> ProfileListItemViewType.SHARE_IDS
else -> error("Encountered unexpected view model: $viewModel")
}
}.registerViewDataBinder(
Expand All @@ -67,9 +67,9 @@ class ProfileAndDeviceIdFragmentPresenter @Inject constructor(
transformViewModel = { it as SyncStatusItemViewModel }
).registerViewDataBinder(
viewType = ProfileListItemViewType.SHARE_IDS,
inflateDataBinding = ProfileListShareIdsItemBinding::inflate,
setViewModel = ProfileListShareIdsItemBinding::setViewModel,
transformViewModel = { it as ShareIdsViewModel }
inflateDataBinding = ProfileListControlButtonsBinding::inflate,
setViewModel = ProfileListControlButtonsBinding::setViewModel,
transformViewModel = { it as ControlButtonsViewModel }
).build()
}
}
Loading