From d756fb8d3814e7a918fedb03b308e1641519e84c Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Fri, 3 Oct 2025 00:49:39 +0800 Subject: [PATCH 1/3] Remove worked example from asset download script. --- .../scripts/gae/GaeAndroidEndpointJsonImpl.kt | 2 +- .../compat/StructureCompatibilityChecker.kt | 10 ---------- .../gae/compat/SubtitledHtmlCollector.kt | 7 +------ .../scripts/gae/json/GaeSkillContents.kt | 1 - .../scripts/gae/proto/JsonToProtoConverter.kt | 19 ------------------- 5 files changed, 2 insertions(+), 37 deletions(-) diff --git a/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt b/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt index 7e794fb709f..827acb4a736 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt @@ -862,7 +862,7 @@ class GaeAndroidEndpointJsonImpl( private val SUPPORTED_DEFAULT_LANGUAGES = setOf(LanguageType.ENGLISH) // From feconf. - private const val SUPPORTED_STATE_SCHEMA_VERSION = 56 + private const val SUPPORTED_STATE_SCHEMA_VERSION = 57 private fun ClientCompatibilityContextDto.verifyCompatibility() { check(topicListRequestResponseProtoVersion == createLatestTopicListProtoVersion()) { diff --git a/scripts/src/java/org/oppia/android/scripts/gae/compat/StructureCompatibilityChecker.kt b/scripts/src/java/org/oppia/android/scripts/gae/compat/StructureCompatibilityChecker.kt index 241eeff6dc3..8185401b0ff 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/compat/StructureCompatibilityChecker.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/compat/StructureCompatibilityChecker.kt @@ -38,7 +38,6 @@ import org.oppia.android.scripts.gae.json.GaeSubtopicPage import org.oppia.android.scripts.gae.json.GaeSubtopicPageContents import org.oppia.android.scripts.gae.json.GaeTopic import org.oppia.android.scripts.gae.json.GaeTranslatedContent -import org.oppia.android.scripts.gae.json.GaeWorkedExample import org.oppia.android.scripts.gae.json.GaeWrittenTranslation import org.oppia.android.scripts.gae.json.GaeWrittenTranslations import org.oppia.android.scripts.gae.json.VersionedStructure @@ -259,20 +258,11 @@ class StructureCompatibilityChecker( defaultLanguage: LanguageType ): List { return gaeSkillContents.explanation.checkHasValidHtml(origin) + - gaeSkillContents.workedExamples.flatMap { checkWorkedExampleCompatibility(origin, it) } + checkWrittenTranslationsCompatibility( origin, gaeSkillContents.writtenTranslations, expectedTranslatedContentIds, defaultLanguage ) + checkRecordedVoiceoversCompatibility(origin, gaeSkillContents.recordedVoiceovers) } - private fun checkWorkedExampleCompatibility( - origin: ContainerId, - gaeWorkedExample: GaeWorkedExample - ): List { - return gaeWorkedExample.question.checkHasValidHtml(origin) + - gaeWorkedExample.explanation.checkHasValidHtml(origin) - } - private fun checkWrittenTranslationsCompatibility( origin: ContainerId, gaeWrittenTranslations: GaeWrittenTranslations, diff --git a/scripts/src/java/org/oppia/android/scripts/gae/compat/SubtitledHtmlCollector.kt b/scripts/src/java/org/oppia/android/scripts/gae/compat/SubtitledHtmlCollector.kt index 6ae514907ec..a92db8f70fc 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/compat/SubtitledHtmlCollector.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/compat/SubtitledHtmlCollector.kt @@ -19,7 +19,6 @@ import org.oppia.android.scripts.gae.json.GaeSubtopic import org.oppia.android.scripts.gae.json.GaeSubtopicPage import org.oppia.android.scripts.gae.json.GaeTopic import org.oppia.android.scripts.gae.json.GaeTranslatedContent -import org.oppia.android.scripts.gae.json.GaeWorkedExample import org.oppia.android.scripts.gae.json.GaeWrittenTranslation import org.oppia.android.scripts.gae.json.GaeWrittenTranslations import org.oppia.android.scripts.gae.proto.LocalizationTracker @@ -143,14 +142,10 @@ class SubtitledHtmlCollector(private val localizationTracker: LocalizationTracke private fun GaeSkillContents.collectSubtitles(): Set { val explanationText = setOf(explanation.toSubtitle()) - val workedExampleTexts = workedExamples.flatSet { it.collectSubtitles() } val translations = writtenTranslations.collectSubtitles() - return explanationText + workedExampleTexts + translations + return explanationText + translations } - private fun GaeWorkedExample.collectSubtitles(): Set = - setOf(question.toSubtitle(), explanation.toSubtitle()) - private fun GaeWrittenTranslations.collectSubtitles(): Set { return translationsMapping.values.flatSet { it.values.flatSet { translation -> translation.collectSubtitles() } diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeSkillContents.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeSkillContents.kt index 71b186d6bd5..2147b34c5b9 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeSkillContents.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeSkillContents.kt @@ -6,7 +6,6 @@ import com.squareup.moshi.JsonClass @JsonClass(generateAdapter = true) data class GaeSkillContents( @Json(name = "explanation") val explanation: GaeSubtitledHtml, - @Json(name = "worked_examples") val workedExamples: List, @Json(name = "recorded_voiceovers") val recordedVoiceovers: GaeRecordedVoiceovers, @Json(name = "written_translations") val writtenTranslations: GaeWrittenTranslations ) diff --git a/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt b/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt index d779f511653..7b55b87892c 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt @@ -35,7 +35,6 @@ import org.oppia.android.scripts.gae.json.GaeSubtitledUnicode import org.oppia.android.scripts.gae.json.GaeSubtopic import org.oppia.android.scripts.gae.json.GaeSubtopicPage import org.oppia.android.scripts.gae.json.GaeTopic -import org.oppia.android.scripts.gae.json.GaeWorkedExample import org.oppia.android.scripts.gae.json.VersionedStructure import org.oppia.android.scripts.gae.proto.LocalizationTracker.Companion.resolveLanguageCode import org.oppia.android.scripts.gae.proto.LocalizationTracker.ContentContext.DESCRIPTION @@ -53,7 +52,6 @@ import org.oppia.proto.v1.structure.BaseSolutionDto import org.oppia.proto.v1.structure.ChapterSummaryDto import org.oppia.proto.v1.structure.ClassroomDto import org.oppia.proto.v1.structure.ConceptCardDto -import org.oppia.proto.v1.structure.ConceptCardDto.WorkedExampleDto import org.oppia.proto.v1.structure.ConceptCardLanguagePackDto import org.oppia.proto.v1.structure.ContinueInstanceDto import org.oppia.proto.v1.structure.DownloadableTopicSummaryDto @@ -316,10 +314,6 @@ class JsonToProtoConverter( val contents = skill.skillContents localizationTracker.initializeContainer(conceptCardContainerId, defaultLanguage) localizationTracker.trackContainerText(conceptCardContainerId, contents.explanation) - for (workedExample in contents.workedExamples) { - localizationTracker.trackContainerText(conceptCardContainerId, workedExample.question) - localizationTracker.trackContainerText(conceptCardContainerId, workedExample.explanation) - } // Track translations after all default strings have been established. localizationTracker.trackTranslations(conceptCardContainerId, contents.writtenTranslations) @@ -457,9 +451,6 @@ class JsonToProtoConverter( this.skillId = gaeSkill.id this.explanation = localizationTracker.convertContainerText(containerId, gaeSkill.skillContents.explanation) - this.addAllWorkedExamples( - gaeSkill.skillContents.workedExamples.map { it.toProto(containerId) } - ) this.defaultLocalization = localizationTracker.computeSpecificContentLocalization(containerId, defaultLanguage) this.contentVersion = gaeSkill.version @@ -537,16 +528,6 @@ class JsonToProtoConverter( val translations: Map> ) - private fun GaeWorkedExample.toProto( - containerId: LocalizationTracker.ContainerId - ): WorkedExampleDto? { - return WorkedExampleDto.newBuilder().apply { - this.question = localizationTracker.convertContainerText(containerId, this@toProto.question) - this.explanation = - localizationTracker.convertContainerText(containerId, this@toProto.explanation) - }.build() - } - private suspend fun GaeStory.toProto( defaultLanguage: LanguageType, availableExplorations: Map From 53ea3378896a92e319ec58b3df30c3977b51834b Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Fri, 3 Oct 2025 01:03:38 +0800 Subject: [PATCH 2/3] Lint fixes. --- .../scripts/assets/DownloadLessonList.kt | 4 +- .../android/scripts/assets/DownloadLessons.kt | 25 +++++++---- .../assets/DtoProtoToLegacyProtoConverter.kt | 6 +-- .../scripts/gae/GaeAndroidEndpointJsonImpl.kt | 6 ++- .../scripts/gae/compat/TopicPackRepository.kt | 19 ++++++--- .../gae/json/AndroidActivityHandlerService.kt | 14 +++++-- .../gae/json/GaeCustomizationArgValue.kt | 2 +- .../scripts/gae/json/GaeInteractionObject.kt | 19 ++++++--- .../android/scripts/gae/json/GaeState.kt | 4 +- .../scripts/gae/json/JsonReaderExtensions.kt | 4 +- .../scripts/gae/proto/JsonToProtoConverter.kt | 41 +++++++++++-------- 11 files changed, 97 insertions(+), 47 deletions(-) diff --git a/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessonList.kt b/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessonList.kt index 8820680f252..9215491301e 100644 --- a/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessonList.kt +++ b/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessonList.kt @@ -92,7 +92,9 @@ class LessonListDownloader( fun downloadLessonListAsync(lessonListOutputFile: File): Deferred { return CoroutineScope(scriptBgDispatcher).async { - if (apiDebugDir != null) println("Config: Using ${apiDebugDir.path}/ for storing API responses (for debugging).") + if (apiDebugDir != null) { + println("Config: Using ${apiDebugDir.path}/ for storing API responses (for debugging).") + } val listResponse = downloadTopicListResponseDto() println() diff --git a/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessons.kt b/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessons.kt index a75695722d4..89985a26fa7 100644 --- a/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessons.kt +++ b/scripts/src/java/org/oppia/android/scripts/assets/DownloadLessons.kt @@ -14,9 +14,10 @@ import kotlinx.coroutines.flow.consumeAsFlow import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.withIndex -import kotlinx.coroutines.launch import kotlinx.coroutines.runBlocking import kotlinx.coroutines.withContext +import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToClassroomIdList +import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToClassroomRecord import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToConceptCardList import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToExploration import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToStoryRecord @@ -95,12 +96,10 @@ import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ExecutorService import java.util.concurrent.Executors import java.util.concurrent.TimeUnit -import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToClassroomIdList -import org.oppia.android.scripts.assets.DtoProtoToLegacyProtoConverter.convertToClassroomRecord +import kotlin.system.exitProcess import org.oppia.proto.v1.api.DownloadRequestStructureIdentifierDto.Builder as DownloadReqStructIdDtoBuilder import org.oppia.proto.v1.structure.DragAndDropSortInputInstanceDto.RuleSpecDto as DragDropSortRuleSpecDto import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto as ItemSelRuleSpecDto -import kotlin.system.exitProcess /* TODO: Thoughts for improving the script: @@ -236,7 +235,9 @@ class LessonDownloader( private val memoizedLoadedImageData by lazy { ConcurrentHashMap() } fun downloadLessons(outputDir: File, failOnError: Boolean) { - val downloadDeferred = CoroutineScope(coroutineDispatcher).async { downloadAllLessons(outputDir, failOnError) } + val downloadDeferred = CoroutineScope(coroutineDispatcher).async { + downloadAllLessons(outputDir, failOnError) + } val exceptionResult = runBlocking { runCatching { downloadDeferred.await() }.also { shutdownBlocking() } } @@ -711,7 +712,10 @@ class LessonDownloader( issues.forEach { issue -> val missingLangs = issue.missingLanguages.joinToString { it.name } val presentLangs = issue.presentLanguages.joinToString { it.name } - println(" - Image ${issue.filename} exists in languages: $presentLangs, but is missing in: $missingLangs") + println( + " - Image ${issue.filename} exists in languages: $presentLangs, " + + "but is missing in: $missingLangs" + ) } } } else println("Images missing across translations: (Hidden)") @@ -766,9 +770,14 @@ class LessonDownloader( println("(look at above output for specific images that require verification).") } - val hasAnyFailure = issues.isNotEmpty() || imageDownloadFailures.isNotEmpty() || renamedImages.isNotEmpty() || convertedImages.isNotEmpty() + val hasAnyFailure = ( + issues.isNotEmpty() || imageDownloadFailures.isNotEmpty() || + renamedImages.isNotEmpty() || convertedImages.isNotEmpty() + ) if (hasAnyFailure && failOnError) { - throw Exception("Failed to cleanly download and convert all lessons and images. See failures above.") + throw Exception( + "Failed to cleanly download and convert all lessons and images. See failures above." + ) } // val translationMetrics = analyzer.computeTranslationsUsageReport() diff --git a/scripts/src/java/org/oppia/android/scripts/assets/DtoProtoToLegacyProtoConverter.kt b/scripts/src/java/org/oppia/android/scripts/assets/DtoProtoToLegacyProtoConverter.kt index 88dfe49f62d..f08821bd6a7 100644 --- a/scripts/src/java/org/oppia/android/scripts/assets/DtoProtoToLegacyProtoConverter.kt +++ b/scripts/src/java/org/oppia/android/scripts/assets/DtoProtoToLegacyProtoConverter.kt @@ -77,6 +77,7 @@ import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.I import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.ITEM_SELECTION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MATH_EQUATION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MULTIPLE_CHOICE_INPUT +import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.RATIO_EXPRESSION_INPUT @@ -89,6 +90,8 @@ import org.oppia.proto.v1.structure.MathEquationInputInstanceDto import org.oppia.proto.v1.structure.MisconceptionDto import org.oppia.proto.v1.structure.MultipleChoiceInputInstanceDto import org.oppia.proto.v1.structure.NormalizedPoint2dDto +import org.oppia.proto.v1.structure.NumberWithUnitsDto +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto import org.oppia.proto.v1.structure.NumericInputInstanceDto import org.oppia.proto.v1.structure.OutcomeDto @@ -112,13 +115,10 @@ import org.oppia.proto.v1.structure.AlgebraicExpressionInputInstanceDto.RuleSpec import org.oppia.proto.v1.structure.DragAndDropSortInputInstanceDto.RuleSpecDto as DragDropSortRuleSpecDto import org.oppia.proto.v1.structure.DragAndDropSortInputInstanceDto.RuleSpecDto.RuleTypeCase as DragDropSortRuleSpecTypeCase import org.oppia.proto.v1.structure.FractionInputInstanceDto.RuleSpecDto as FractionRuleSpecDto -import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto as ItemSelRuleSpecDto import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.RuleSpecDto as MathEqRuleSpecDto import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.RuleSpecDto.MatchesUpToTrivialManipulationsSpecDto as MathEqMatchesUpToTrivialManipulationsSpecDto import org.oppia.proto.v1.structure.MultipleChoiceInputInstanceDto.RuleSpecDto as MultChoiceRuleSpecDto -import org.oppia.proto.v1.structure.NumberWithUnitsDto -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto as NumUnitsRuleSpecDto import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto.RuleSpecDto as NumExpRuleSpecDto import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto.RuleSpecDto.MatchesUpToTrivialManipulationsSpecDto as NumExpMatchesUpToTrivialManipulationsSpecDto diff --git a/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt b/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt index 827acb4a736..e6c3e162e7d 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/GaeAndroidEndpointJsonImpl.kt @@ -241,7 +241,9 @@ class GaeAndroidEndpointJsonImpl( return CoroutineScope(coroutineDispatcher).async { SUPPORTED_CLASSROOMS.map { classroomName -> CoroutineScope(coroutineDispatcher).async { - val classroomResult = activityService.fetchLatestClassroomAsync(classroomName).await().also { + val classroomResult = activityService.fetchLatestClassroomAsync( + classroomName + ).await().also { tracker.reportDownloaded(classroomName) } checkNotNull(classroomResult?.payload) { "Failed to fetch classroom: $classroomName." } @@ -830,7 +832,7 @@ class GaeAndroidEndpointJsonImpl( "Continue", "FractionInput", "ItemSelectionInput", "MultipleChoiceInput", "NumericInput", "TextInput", "DragAndDropSortInput", "ImageClickInput", "RatioExpressionInput", "EndExploration", "NumericExpressionInput", - "AlgebraicExpressionInput", "MathEquationInput"//, "NumberWithUnits" + "AlgebraicExpressionInput", "MathEquationInput" // , "NumberWithUnits" ) // TODO: Remove gif and png since we only want to use svg(z) and webp moving forward. diff --git a/scripts/src/java/org/oppia/android/scripts/gae/compat/TopicPackRepository.kt b/scripts/src/java/org/oppia/android/scripts/gae/compat/TopicPackRepository.kt index 8b5a2faaf21..bc7174bbf4e 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/compat/TopicPackRepository.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/compat/TopicPackRepository.kt @@ -623,9 +623,12 @@ private sealed class VersionedStructureReference { Compatible -> LoadResult.Success(payload) is Incompatible -> { // TODO: Remove this once Oppia web supports pulling structures without schema migrations (see https://github.com/oppia/oppia/issues/21253). - val irrecoverableFailure = compatibilityResult.failures.firstOrNull { it is CompatibilityFailure.StateSchemaVersionTooNew } + val irrecoverableFailure = compatibilityResult.failures.firstOrNull { + it is CompatibilityFailure.StateSchemaVersionTooNew + } check(irrecoverableFailure == null) { - "Oppia web introduced a state schema upgrade. Cannot recover--this script needs to be updated: $irrecoverableFailure" + "Oppia web introduced a state schema upgrade. Cannot recover--this " + + "script needs to be updated: $irrecoverableFailure" } LoadResult.Failure(compatibilityResult.failures) } @@ -792,7 +795,9 @@ private class ExplorationFetcher( ): Deferred?> { return CoroutineScope(coroutineDispatcher).async { val latestExp = service.fetchLatestExplorationAsync(id.id).await() - return@async latestExp?.let { it.copyWithNewPayload(service.downloadExploration(id, it.payload)) } + return@async latestExp?.let { + it.copyWithNewPayload(service.downloadExploration(id, it.payload)) + } } } @@ -803,7 +808,9 @@ private class ExplorationFetcher( ): Deferred?> { return CoroutineScope(coroutineDispatcher).async { val latestExp = service.fetchSingleExplorationAsync(id.id, version).await() - return@async latestExp?.let { it.copyWithNewPayload(service.downloadExploration(id, it.payload)) } + return@async latestExp?.let { + it.copyWithNewPayload(service.downloadExploration(id, it.payload)) + } } } @@ -833,7 +840,9 @@ private class ExplorationFetcher( val missingTranslations = translations.mapIndexedNotNull { index, value -> index.takeIf { value == null } // Take only indexes corresponding to missing values. }.map { VALID_LANGUAGE_TYPES[it] } - check(missingTranslations.isEmpty()) { "Failed to fetch translations for exploration $id: $missingTranslations." } + check(missingTranslations.isEmpty()) { + "Failed to fetch translations for exploration $id: $missingTranslations." + } return CompleteExploration( exploration, receivedTranslations.associateBy { diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/AndroidActivityHandlerService.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/AndroidActivityHandlerService.kt index 0c19dcdf537..13a1cd626fb 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/AndroidActivityHandlerService.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/AndroidActivityHandlerService.kt @@ -321,7 +321,10 @@ class AndroidActivityHandlerService( val metadata = RequestMetadata(request().method, request().url.toUrl().toExternalForm()) val responseBodyText = memoizedRawResponses[metadata] if (responseBodyText?.contains("maxmemory") == true) { - throw IllegalStateException("Oppia web ran out of memory. Clear Redis memory cache and try again.", exception) + throw IllegalStateException( + "Oppia web ran out of memory. Clear Redis memory cache and try again.", + exception + ) } else throw IllegalStateException( "Failed to call: ${request()}. Response body:\n\n$responseBodyText".redact(), exception ) @@ -370,7 +373,9 @@ class AndroidActivityHandlerService( type: String, id: String, version: Int, - crossinline fetch: (AndroidActivityRequests.SingleNonLocalized) -> Call>>, + crossinline fetch: ( + AndroidActivityRequests.SingleNonLocalized + ) -> Call>>, noinline retrieveStructureVersion: ((T) -> Int)? ): Deferred?> { return CoroutineScope(dispatcher).async { @@ -423,7 +428,10 @@ class AndroidActivityHandlerService( val reqsCol = createRequests(requestsRequiringRemoteFetching.map { (_, req) -> req }) val fetchResult = if (reqsCol.requests.isNotEmpty()) { // Only fetch if there are versions to retrieve. - val versionsToRetrieve = requestsRequiringRemoteFetching.map { (index, _) -> versions[index] } + val versionsToRetrieve = requestsRequiringRemoteFetching.map { + (index, _) -> + versions[index] + } fetch(reqsCol).resolveAsyncVersionsAsync(id, versionsToRetrieve).await().map { structure -> // Ensure that the returned structures have the correct remote versions (since the web // controller isn't consistent in when it provides a version). diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeCustomizationArgValue.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeCustomizationArgValue.kt index 08cffc22dce..9a4ef6a6edd 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeCustomizationArgValue.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeCustomizationArgValue.kt @@ -18,11 +18,11 @@ import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.I import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.ITEM_SELECTION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MATH_EQUATION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MULTIPLE_CHOICE_INPUT +import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.RATIO_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.TEXT_INPUT -import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT // TODO: Mention parsing this requires setting customization key name & interaction type in the parsing context. sealed class GaeCustomizationArgValue { diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeInteractionObject.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeInteractionObject.kt index 80f674ab60f..0e7e3648446 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeInteractionObject.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeInteractionObject.kt @@ -18,11 +18,11 @@ import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.I import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.ITEM_SELECTION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MATH_EQUATION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MULTIPLE_CHOICE_INPUT +import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.RATIO_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.TEXT_INPUT -import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT @JsonClass(generateAdapter = false) sealed class GaeInteractionObject { @@ -102,8 +102,11 @@ sealed class GaeInteractionObject { sealed class NumberWithUnits : GaeInteractionObject() { abstract val units: List - data class FractionWithUnits(val fraction: Fraction, override val units: List): NumberWithUnits() - data class RealWithUnits(val real: Double, override val units: List): NumberWithUnits() + data class FractionWithUnits( + val fraction: Fraction, + override val units: List + ) : NumberWithUnits() + data class RealWithUnits(val real: Double, override val units: List) : NumberWithUnits() @JsonClass(generateAdapter = true) data class Unit( @@ -145,8 +148,12 @@ sealed class GaeInteractionObject { parsableNumberWithUnitsAdapter: JsonAdapter ) { val parsableNumberWithUnits = when (numberWithUnits) { - is FractionWithUnits -> ParsableNumberWithUnits(type = "real", real = null, numberWithUnits.fraction, numberWithUnits.units) - is RealWithUnits -> ParsableNumberWithUnits(type = "real", numberWithUnits.real, fraction = null, numberWithUnits.units) + is FractionWithUnits -> ParsableNumberWithUnits( + type = "real", real = null, numberWithUnits.fraction, numberWithUnits.units + ) + is RealWithUnits -> ParsableNumberWithUnits( + type = "real", numberWithUnits.real, fraction = null, numberWithUnits.units + ) } parsableNumberWithUnitsAdapter.toJson(jsonWriter, parsableNumberWithUnits) } @@ -176,7 +183,7 @@ sealed class GaeInteractionObject { setOfXlatableContentIdsAdapter: JsonAdapter ) { jsonWriter.beginArray() - setsOfXlatableContentIds.sets.forEach { + setsOfXlatableContentIds.sets.forEach { setOfXlatableContentIdsAdapter.toJson(jsonWriter, it) } jsonWriter.endArray() diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeState.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeState.kt index 8a4e30aba95..37961e90405 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/GaeState.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/GaeState.kt @@ -13,7 +13,9 @@ data class GaeState( @Json(name = "recorded_voiceovers") val recordedVoiceovers: GaeRecordedVoiceovers, @Json(name = "solicit_answer_details") val solicitAnswerDetails: Boolean, @Json(name = "card_is_checkpoint") val cardIsCheckpoint: Boolean, - @Json(name = "inapplicable_skill_misconception_ids") val inapplicableSkillMisconceptionIds: List? + @Json( + name = "inapplicable_skill_misconception_ids" + ) val inapplicableSkillMisconceptionIds: List? ) { fun computeReferencedSkillIds(): List = listOfNotNull(linkedSkillId) + (interaction?.computeReferencedSkillIds() ?: emptyList()) diff --git a/scripts/src/java/org/oppia/android/scripts/gae/json/JsonReaderExtensions.kt b/scripts/src/java/org/oppia/android/scripts/gae/json/JsonReaderExtensions.kt index e96051c05f1..0ac63e26e2e 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/json/JsonReaderExtensions.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/json/JsonReaderExtensions.kt @@ -23,7 +23,9 @@ inline fun JsonReader.nextCustomValue(adapter: JsonAdapter) private fun JsonReader.maybeReadElement(readElement: () -> T) = if (hasNext()) readElement() else null -private fun JsonReader.maybeReadObjectElement(readElement: (String) -> V): Pair? { +private fun JsonReader.maybeReadObjectElement( + readElement: (String) -> V +): Pair? { return maybeReadElement { nextName().let { name -> name to readElement(name) } } diff --git a/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt b/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt index 7b55b87892c..659f9997ccf 100644 --- a/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt +++ b/scripts/src/java/org/oppia/android/scripts/gae/proto/JsonToProtoConverter.kt @@ -15,6 +15,7 @@ import org.oppia.android.scripts.gae.json.GaeInteractionObject.Fraction import org.oppia.android.scripts.gae.json.GaeInteractionObject.MathExpression import org.oppia.android.scripts.gae.json.GaeInteractionObject.NonNegativeInt import org.oppia.android.scripts.gae.json.GaeInteractionObject.NormalizedString +import org.oppia.android.scripts.gae.json.GaeInteractionObject.NumberWithUnits import org.oppia.android.scripts.gae.json.GaeInteractionObject.RatioExpression import org.oppia.android.scripts.gae.json.GaeInteractionObject.Real import org.oppia.android.scripts.gae.json.GaeInteractionObject.SetOfXlatableContentIds @@ -22,7 +23,6 @@ import org.oppia.android.scripts.gae.json.GaeInteractionObject.SetsOfXlatableCon import org.oppia.android.scripts.gae.json.GaeInteractionObject.SignedInt import org.oppia.android.scripts.gae.json.GaeInteractionObject.TranslatableHtmlContentId import org.oppia.android.scripts.gae.json.GaeInteractionObject.TranslatableSetOfNormalizedString -import org.oppia.android.scripts.gae.json.GaeInteractionObject.NumberWithUnits import org.oppia.android.scripts.gae.json.GaeOutcome import org.oppia.android.scripts.gae.json.GaeRuleSpec import org.oppia.android.scripts.gae.json.GaeSkill @@ -42,8 +42,8 @@ import org.oppia.android.scripts.gae.proto.LocalizationTracker.ContentContext.TI import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.FRACTION import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.LIST_OF_SETS_OF_TRANSLATABLE_HTML_CONTENT_IDS import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.MATH_EXPRESSION -import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.NUMBER_WITH_UNITS import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.NORMALIZED_STRING +import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.NUMBER_WITH_UNITS import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.RATIO_EXPRESSION import org.oppia.android.scripts.gae.proto.SolutionAnswer.AnswerTypeCase.REAL import org.oppia.proto.v1.structure.AlgebraicExpressionInputInstanceDto @@ -60,7 +60,6 @@ import org.oppia.proto.v1.structure.EndExplorationInstanceDto import org.oppia.proto.v1.structure.ExplorationDto import org.oppia.proto.v1.structure.ExplorationLanguagePackDto import org.oppia.proto.v1.structure.FractionDto -import org.oppia.proto.v1.structure.NumberWithUnitsDto import org.oppia.proto.v1.structure.FractionInputInstanceDto import org.oppia.proto.v1.structure.HintDto import org.oppia.proto.v1.structure.ImageClickInputInstanceDto @@ -79,11 +78,11 @@ import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.I import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.ITEM_SELECTION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MATH_EQUATION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.MULTIPLE_CHOICE_INPUT +import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMERIC_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.RATIO_EXPRESSION_INPUT import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.TEXT_INPUT -import org.oppia.proto.v1.structure.InteractionInstanceDto.InteractionTypeCase.NUMBER_WITH_UNITS_INPUT import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto import org.oppia.proto.v1.structure.LanguageType import org.oppia.proto.v1.structure.ListOfSetsOfTranslatableHtmlContentIdsDto @@ -91,9 +90,10 @@ import org.oppia.proto.v1.structure.LocalizedConceptCardIdDto import org.oppia.proto.v1.structure.LocalizedExplorationIdDto import org.oppia.proto.v1.structure.LocalizedRevisionCardIdDto import org.oppia.proto.v1.structure.MathEquationInputInstanceDto -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto import org.oppia.proto.v1.structure.MultipleChoiceInputInstanceDto import org.oppia.proto.v1.structure.NormalizedPoint2dDto +import org.oppia.proto.v1.structure.NumberWithUnitsDto +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto import org.oppia.proto.v1.structure.NumericInputInstanceDto import org.oppia.proto.v1.structure.OutcomeDto @@ -140,10 +140,6 @@ import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto.Co import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto.DoesNotContainAtLeastOneOfSpecDto as ItemSelectionDoesNotContainAtLeastOneOfSpec import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto.EqualsSpecDto as ItemSelectionEqualsSpec import org.oppia.proto.v1.structure.ItemSelectionInputInstanceDto.RuleSpecDto.IsProperSubsetOfSpecDto as ItemSelectionIsProperSubsetOfSpec -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.AnswerGroupDto as NumberWithUnitsAnswerGroupDto -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto as NumberWithUnitsRuleSpecDto -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto.IsEqualToSpecDto as NumberWithUnitsIsEqualSpec -import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto.IsEquivalentToSpecDto as NumberWithUnitsIsEquivalentSpec import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.AnswerGroupDto as MathEquationAnswerGroupDto import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.CustomizationArgsDto as MathEquationCustomizationArgsDto import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.RuleSpecDto as MathEquationRuleSpecDto @@ -152,6 +148,10 @@ import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.RuleSpecDto.Mat import org.oppia.proto.v1.structure.MathEquationInputInstanceDto.RuleSpecDto.MatchesUpToTrivialManipulationsSpecDto as MathEquationTrivialManipsSpec import org.oppia.proto.v1.structure.MultipleChoiceInputInstanceDto.AnswerGroupDto as MultipleChoiceAnswerGroupDto import org.oppia.proto.v1.structure.MultipleChoiceInputInstanceDto.RuleSpecDto.EqualsSpecDto as MultipleChoiceEqualsSpec +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.AnswerGroupDto as NumberWithUnitsAnswerGroupDto +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto as NumberWithUnitsRuleSpecDto +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto.IsEqualToSpecDto as NumberWithUnitsIsEqualSpec +import org.oppia.proto.v1.structure.NumberWithUnitsInputInstanceDto.RuleSpecDto.IsEquivalentToSpecDto as NumberWithUnitsIsEquivalentSpec import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto.AnswerGroupDto as NumericExpressionAnswerGroupDto import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto.RuleSpecDto.IsEquivalentToSpecDto as NumericExpressionIsEquivalentSpec import org.oppia.proto.v1.structure.NumericExpressionInputInstanceDto.RuleSpecDto.MatchesExactlyWithSpecDto as NumericExpressionMatchesExactlySpec @@ -278,9 +278,10 @@ class JsonToProtoConverter( // Note that translatable content IDs objects are ignored because they don't provide // new translations and should already be tracked in the interaction's customization // arguments. - is Fraction, is MathExpression, is NonNegativeInt, is NormalizedString, - is RatioExpression, is Real, is SignedInt, is SetOfXlatableContentIds, - is SetsOfXlatableContentIds, is TranslatableHtmlContentId, is NumberWithUnits -> null + is Fraction, is MathExpression, is NonNegativeInt, + is NormalizedString, is RatioExpression, is Real, is SignedInt, + is SetOfXlatableContentIds, is SetsOfXlatableContentIds, + is TranslatableHtmlContentId, is NumberWithUnits -> null is TranslatableSetOfNormalizedString -> ruleInput.contentId?.let { it to ruleInput.normalizedStrSet } } @@ -1000,7 +1001,9 @@ class JsonToProtoConverter( private fun List.toNumberWithUnitsGroups( containerId: LocalizationTracker.ContainerId ): List { - return map { it.toProto(containerId, NUMBER_WITH_UNITS_INPUT).numberWithUnitsInputInstanceAnswerGroup } + return map { + it.toProto(containerId, NUMBER_WITH_UNITS_INPUT).numberWithUnitsInputInstanceAnswerGroup + } } // TODO: Simplify this & the other similar toProto() functions. @@ -1010,7 +1013,9 @@ class JsonToProtoConverter( ): AnswerGroup { return AnswerGroup.newBuilder().apply { when (interactionType) { - FRACTION_INPUT -> this.fractionInputInstanceAnswerGroup = toFractionAnswerGroup(containerId) + FRACTION_INPUT -> { + this.fractionInputInstanceAnswerGroup = toFractionAnswerGroup(containerId) + } ITEM_SELECTION_INPUT -> this.itemSelectionInputInstanceAnswerGroup = toItemSelectionAnswerGroup(containerId) MULTIPLE_CHOICE_INPUT -> @@ -1232,7 +1237,9 @@ class JsonToProtoConverter( private fun List.toMathEquationProtos(containerId: LocalizationTracker.ContainerId) = toProtos(MATH_EQUATION_INPUT, containerId).map { it.mathEquationInputInstanceRuleSpec } - private fun List.toNumberWithUnitsProto(containerId: LocalizationTracker.ContainerId) = + private fun List.toNumberWithUnitsProto( + containerId: LocalizationTracker.ContainerId + ) = toProtos(NUMBER_WITH_UNITS_INPUT, containerId).map { it.numberWithUnitsInputInstanceRuleSpec } private fun List.toProtos( @@ -1954,7 +1961,9 @@ class JsonToProtoConverter( private fun Map.getNumberWithUnits( name: String, containerId: LocalizationTracker.ContainerId - ): NumberWithUnitsDto = getRuleInput(name, RuleInputTypeCase.NUMBER_WITH_UNITS, containerId).numberWithUnits + ): NumberWithUnitsDto = getRuleInput( + name, RuleInputTypeCase.NUMBER_WITH_UNITS, containerId + ).numberWithUnits private fun Map.getRuleInput( name: String, From 3ef39b8c449440421c2e30002beaa40e4c74cece Mon Sep 17 00:00:00 2001 From: Sean Lip Date: Fri, 3 Oct 2025 09:11:43 +0800 Subject: [PATCH 3/3] Update topic proto --- model/src/main/proto/topic.proto | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/model/src/main/proto/topic.proto b/model/src/main/proto/topic.proto index 3587eb4e6ca..42384277f04 100755 --- a/model/src/main/proto/topic.proto +++ b/model/src/main/proto/topic.proto @@ -88,8 +88,9 @@ message ConceptCard { // The core explanation of the skill being reviewed. SubtitledHtml explanation = 3; - // A list of worked examples to present to the learner. - repeated SubtitledHtml worked_example = 4; + // This field used to be a list of worked examples to present to the learner. + // The field was never used, and was removed in Oct 2025. + reserved 4; // Mapping from content_id to a VoiceoverMapping for each SubtitledHtml in this concept card that // has corresponding recorded audio to play.