diff --git a/collect_app/src/main/java/org/odk/collect/android/projects/FileDebugLogger.kt b/collect_app/src/main/java/org/odk/collect/android/projects/FileDebugLogger.kt index a74a802edf8..fed448416a7 100644 --- a/collect_app/src/main/java/org/odk/collect/android/projects/FileDebugLogger.kt +++ b/collect_app/src/main/java/org/odk/collect/android/projects/FileDebugLogger.kt @@ -1,5 +1,6 @@ package org.odk.collect.android.projects +import org.odk.collect.analytics.Analytics import org.odk.collect.android.BuildConfig import org.odk.collect.shared.DebugLogger import java.io.File @@ -8,6 +9,15 @@ import java.time.LocalDateTime class FileDebugLogger(private val file: File) : DebugLogger { override fun log(tag: String, message: String) { + logToFile(tag, message) + } + + override fun logWithAnalytics(tag: String, message: String, analyticsEvent: String, analyticsKey: String) { + logToFile(tag, message) + Analytics.log(analyticsEvent, analyticsKey) + } + + private fun logToFile(tag: String, message: String) { if (enabled) { val line = "${LocalDateTime.now()} $tag \"$message\"\n" file.appendText(line) diff --git a/entities/build.gradle.kts b/entities/build.gradle.kts index 9fe4f5be585..56d7fdc7117 100644 --- a/entities/build.gradle.kts +++ b/entities/build.gradle.kts @@ -48,6 +48,7 @@ dependencies { implementation(project(":strings")) implementation(project(":shared")) implementation(project(":androidshared")) + implementation(project(":analytics")) implementation(project(":material")) implementation(project(":async")) implementation(project(":lists")) diff --git a/entities/src/main/java/org/odk/collect/entities/LocalEntityUseCases.kt b/entities/src/main/java/org/odk/collect/entities/LocalEntityUseCases.kt index 58eccf08882..2ca23b6ebd9 100644 --- a/entities/src/main/java/org/odk/collect/entities/LocalEntityUseCases.kt +++ b/entities/src/main/java/org/odk/collect/entities/LocalEntityUseCases.kt @@ -2,9 +2,11 @@ package org.odk.collect.entities import org.apache.commons.csv.CSVRecord import org.javarosa.core.model.instance.SecondaryInstanceCSVParserBuilder +import org.odk.collect.entities.analytics.AnalyticsEvents import org.odk.collect.entities.javarosa.finalization.EntitiesExtra import org.odk.collect.entities.javarosa.finalization.FormEntity import org.odk.collect.entities.javarosa.parse.EntitySchema +import org.odk.collect.entities.javarosa.parse.isV4UUID import org.odk.collect.entities.javarosa.spec.EntityAction import org.odk.collect.entities.server.EntitySource import org.odk.collect.entities.storage.EntitiesRepository @@ -24,32 +26,37 @@ object LocalEntityUseCases { debugLogger: DebugLogger? = null ) { formEntities?.entities?.forEach { formEntity -> - when (formEntity.action) { - EntityAction.CREATE -> saveNewEntity(formEntity, entitiesRepository, debugLogger) - - EntityAction.UPDATE -> { - val existing = entitiesRepository.findEntityById(formEntity.dataset, formEntity.id) - if (existing != null) { - saveUpdatedEntity(formEntity, existing, entitiesRepository) + if (formEntity.id.isV4UUID()) { + when (formEntity.action) { + EntityAction.CREATE -> saveNewEntity(formEntity, entitiesRepository, debugLogger) + + EntityAction.UPDATE -> { + val existing = entitiesRepository.findEntityById(formEntity.dataset, formEntity.id) + if (existing != null) { + saveUpdatedEntity(formEntity, existing, entitiesRepository) + } else { + debugLogger?.logInvalidEntity(AnalyticsEvents.ENTITY_UPDATE_NO_MATCH, formEntity) + } } - } - EntityAction.UPSERT -> { - val existing = entitiesRepository.findEntityById(formEntity.dataset, formEntity.id) - if (existing == null) { - saveNewEntity(formEntity, entitiesRepository, debugLogger) - } else { - saveUpdatedEntity(formEntity, existing, entitiesRepository) + EntityAction.UPSERT -> { + val existing = entitiesRepository.findEntityById(formEntity.dataset, formEntity.id) + if (existing == null) { + saveNewEntity(formEntity, entitiesRepository, debugLogger) + } else { + saveUpdatedEntity(formEntity, existing, entitiesRepository) + } } } - } - } + } else { + val event = if (formEntity.id.isNullOrBlank()) { + AnalyticsEvents.ENTITY_WITH_NO_ID + } else { + AnalyticsEvents.ENTITY_WITH_INVALID_ID + } - formEntities?.invalidEntities?.forEach { - debugLogger?.log( - "Entities", - "Failed to create/update dataset=${it.dataset}, id=${it.id}, label=${it.label}" - ) + debugLogger?.logInvalidEntity(event, formEntity) + } } } @@ -64,7 +71,7 @@ object LocalEntityUseCases { entitiesRepository.save( formEntity.dataset, Entity.New( - formEntity.id, + formEntity.id!!, formEntity.label, 1, formEntity.properties, @@ -73,10 +80,7 @@ object LocalEntityUseCases { ) } } else { - debugLogger?.log( - "Entities", - "Failed to create dataset=${formEntity.dataset}, id=${formEntity.id}, label=${formEntity.label}" - ) + debugLogger?.logInvalidEntity(AnalyticsEvents.ENTITY_CREATE_NO_LABEL, formEntity) } } @@ -228,3 +232,14 @@ private fun Map.removeReservedProperties(): Map { it.key == EntitySchema.ID || it.key == EntitySchema.LABEL || it.key.startsWith("__") } } + +private fun DebugLogger.logInvalidEntity(event: String, formEntity: FormEntity) { + val action = when (event) { + AnalyticsEvents.ENTITY_CREATE_NO_LABEL -> "create" + AnalyticsEvents.ENTITY_UPDATE_NO_MATCH -> "update" + else -> "create/update" + } + val message = "Failed to $action dataset=${formEntity.dataset}, id=${formEntity.id}, label=${formEntity.label}" + + this.logWithAnalytics("Entities", message, event, "form") +} diff --git a/entities/src/main/java/org/odk/collect/entities/analytics/AnalyticsEvents.kt b/entities/src/main/java/org/odk/collect/entities/analytics/AnalyticsEvents.kt new file mode 100644 index 00000000000..814d9d32dc6 --- /dev/null +++ b/entities/src/main/java/org/odk/collect/entities/analytics/AnalyticsEvents.kt @@ -0,0 +1,23 @@ +package org.odk.collect.entities.analytics + +object AnalyticsEvents { + /** + * Tracks how often an entity update is attempted but no entity with a matching ID is found. + */ + const val ENTITY_UPDATE_NO_MATCH = "EntityUpdateNoMatch" + + /** + * Tracks how often an entity creation is attempted but the label is blank. + */ + const val ENTITY_CREATE_NO_LABEL = "EntityCreateNoLabel" + + /** + * Tracks how often an entity is defined in a form but has no ID. + */ + const val ENTITY_WITH_NO_ID = "EntityWithNoId" + + /** + * Tracks how often an entity is defined in a form but has an invalid ID (not a V4 UUID). + */ + const val ENTITY_WITH_INVALID_ID = "EntityWithInvalidId" +} diff --git a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntitiesExtra.kt b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntitiesExtra.kt index c0fc4ea6d03..eff96b4db15 100644 --- a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntitiesExtra.kt +++ b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntitiesExtra.kt @@ -2,5 +2,4 @@ package org.odk.collect.entities.javarosa.finalization data class EntitiesExtra( val entities: List = emptyList(), - val invalidEntities: List = emptyList() ) diff --git a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntityFormFinalizationProcessor.kt b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntityFormFinalizationProcessor.kt index 8e378edb460..5bf9c5200eb 100644 --- a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntityFormFinalizationProcessor.kt +++ b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/EntityFormFinalizationProcessor.kt @@ -6,7 +6,6 @@ import org.javarosa.form.api.FormEntryFinalizationProcessor import org.javarosa.form.api.FormEntryModel import org.odk.collect.entities.javarosa.parse.EntityFormExtra import org.odk.collect.entities.javarosa.parse.SaveTo -import org.odk.collect.entities.javarosa.parse.isV4UUID import org.odk.collect.entities.javarosa.spec.EntityAction import org.odk.collect.entities.javarosa.spec.EntityFormParser @@ -37,12 +36,7 @@ class EntityFormFinalizationProcessor : FormEntryFinalizationProcessor { mainInstance ) - if (entity != null) { - extra.copy(entities = extra.entities + entity) - } else { - val invalidEntity = InvalidEntity(dataset, id, label) - extra.copy(invalidEntities = extra.invalidEntities + invalidEntity) - } + extra.copy(entities = extra.entities + entity) } else { extra } @@ -60,7 +54,7 @@ class EntityFormFinalizationProcessor : FormEntryFinalizationProcessor { saveTos: List, action: EntityAction, mainInstance: FormInstance - ): FormEntity? { + ): FormEntity { val entityGroupRef = elementRef.getParentRef().getParentRef() val fields = saveTos.mapNotNull { saveTo -> if (!entityGroupRef.genericize().equals(saveTo.entityGroupReference)) { @@ -79,10 +73,6 @@ class EntityFormFinalizationProcessor : FormEntryFinalizationProcessor { } } - return if (id.isV4UUID()) { - FormEntity(action, dataset, id, label, fields) - } else { - null - } + return FormEntity(action, dataset, id, label, fields) } } diff --git a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/FormEntity.kt b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/FormEntity.kt index fc9aef9ba40..5538ed3f4af 100644 --- a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/FormEntity.kt +++ b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/FormEntity.kt @@ -5,7 +5,7 @@ import org.odk.collect.entities.javarosa.spec.EntityAction data class FormEntity( @JvmField val action: EntityAction, @JvmField val dataset: String, - @JvmField val id: String, + @JvmField val id: String?, @JvmField val label: String, @JvmField val properties: List> ) diff --git a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/InvalidEntity.kt b/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/InvalidEntity.kt deleted file mode 100644 index 2ab7356a371..00000000000 --- a/entities/src/main/java/org/odk/collect/entities/javarosa/finalization/InvalidEntity.kt +++ /dev/null @@ -1,7 +0,0 @@ -package org.odk.collect.entities.javarosa.finalization - -data class InvalidEntity( - val dataset: String, - val id: String?, - val label: String? -) diff --git a/entities/src/test/java/org/odk/collect/entities/LocalEntityUseCasesTest.kt b/entities/src/test/java/org/odk/collect/entities/LocalEntityUseCasesTest.kt index b35af6bd5d8..9aef0c838b3 100644 --- a/entities/src/test/java/org/odk/collect/entities/LocalEntityUseCasesTest.kt +++ b/entities/src/test/java/org/odk/collect/entities/LocalEntityUseCasesTest.kt @@ -12,7 +12,6 @@ import org.mockito.kotlin.mock import org.mockito.kotlin.verify import org.odk.collect.entities.javarosa.finalization.EntitiesExtra import org.odk.collect.entities.javarosa.finalization.FormEntity -import org.odk.collect.entities.javarosa.finalization.InvalidEntity import org.odk.collect.entities.javarosa.parse.EntitySchema import org.odk.collect.entities.javarosa.spec.EntityAction import org.odk.collect.entities.server.EntitySource @@ -37,7 +36,7 @@ class LocalEntityUseCasesTest { entitiesRepository.addList("things") val formEntity = - FormEntity(EntityAction.CREATE, "things", "id", "label", listOf("property" to "value")) + FormEntity(EntityAction.CREATE, "things", UUID.randomUUID().toString(), "label", listOf("property" to "value")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -49,12 +48,25 @@ class LocalEntityUseCasesTest { assertThat(entities[0].branchId, not(blankOrNullString())) } + @Test + fun `#updateLocalEntitiesFromForm does not save a new entity on create if id is not a valid UUID`() { + entitiesRepository.addList("things") + + val formEntity = + FormEntity(EntityAction.CREATE, "things", "id", "label", listOf("property" to "value")) + val formEntities = EntitiesExtra(listOf(formEntity)) + LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) + + val entities = entitiesRepository.query("things") + assertThat(entities.size, equalTo(0)) + } + @Test fun `#updateLocalEntitiesFromForm does not save a new entity on create if label is blank`() { entitiesRepository.addList("things") val formEntity = - FormEntity(EntityAction.CREATE, "things", "id", "", listOf("property" to "value")) + FormEntity(EntityAction.CREATE, "things", UUID.randomUUID().toString(), "", listOf("property" to "value")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -65,7 +77,7 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm does not save a new entity on create if the list doesn't already exist`() { val formEntity = - FormEntity(EntityAction.CREATE, "things", "id", "label", listOf("property" to "value")) + FormEntity(EntityAction.CREATE, "things", UUID.randomUUID().toString(), "label", listOf("property" to "value")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -74,7 +86,7 @@ class LocalEntityUseCasesTest { } @Test - fun `#updateLocalEntitiesFromForm increments version on update`() { + fun `#updateLocalEntitiesFromForm does not update an entity if id in not a valid UUID`() { entitiesRepository.save( "things", Entity.New( @@ -85,7 +97,30 @@ class LocalEntityUseCasesTest { ) val formEntity = - FormEntity(EntityAction.UPDATE, "things", "id", "label", emptyList()) + FormEntity(EntityAction.UPDATE, "things", "id", "new_label", emptyList()) + val formEntities = EntitiesExtra(listOf(formEntity)) + + LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) + val entities = entitiesRepository.query("things") + assertThat(entities.size, equalTo(1)) + assertThat(entities[0].label, equalTo("label")) + assertThat(entities[0].version, equalTo(1)) + } + + @Test + fun `#updateLocalEntitiesFromForm increments version on update`() { + val id = UUID.randomUUID().toString() + entitiesRepository.save( + "things", + Entity.New( + id, + "label", + version = 1 + ) + ) + + val formEntity = + FormEntity(EntityAction.UPDATE, "things", id, "label", emptyList()) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -96,10 +131,11 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm updates properties on update`() { + val id = UUID.randomUUID().toString() entitiesRepository.save( "things", Entity.New( - "id", + id, "label", version = 1, properties = listOf("prop" to "value") @@ -107,7 +143,7 @@ class LocalEntityUseCasesTest { ) val formEntity = - FormEntity(EntityAction.UPDATE, "things", "id", "label", listOf("prop" to "value 2")) + FormEntity(EntityAction.UPDATE, "things", id, "label", listOf("prop" to "value 2")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -119,10 +155,11 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm updates properties and does not change label on update if label is blank`() { + val id = UUID.randomUUID().toString() entitiesRepository.save( "things", Entity.New( - "id", + id, "label", version = 1, properties = listOf("prop" to "value") @@ -130,7 +167,7 @@ class LocalEntityUseCasesTest { ) val formEntity = - FormEntity(EntityAction.UPDATE, "things", "id", " ", listOf("prop" to "value 2")) + FormEntity(EntityAction.UPDATE, "things", id, " ", listOf("prop" to "value 2")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -146,7 +183,7 @@ class LocalEntityUseCasesTest { entitiesRepository.addList("things") val formEntity = - FormEntity(EntityAction.UPSERT, "things", "id", "label", listOf("property" to "value")) + FormEntity(EntityAction.UPSERT, "things", UUID.randomUUID().toString(), "label", listOf("property" to "value")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -163,7 +200,7 @@ class LocalEntityUseCasesTest { entitiesRepository.addList("things") val formEntity = - FormEntity(EntityAction.UPSERT, "things", "id", "", listOf("property" to "value")) + FormEntity(EntityAction.UPSERT, "things", UUID.randomUUID().toString(), "", listOf("property" to "value")) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -173,17 +210,18 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm updates an existing entity on upsert if it exists`() { + val id = UUID.randomUUID().toString() entitiesRepository.save( "things", Entity.New( - "id", + id, "label", version = 1 ) ) val formEntity = - FormEntity(EntityAction.UPSERT, "things", "id", "new label", emptyList()) + FormEntity(EntityAction.UPSERT, "things", id, "new label", emptyList()) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -207,7 +245,7 @@ class LocalEntityUseCasesTest { ) val formEntity = - FormEntity(EntityAction.UPDATE, "things", "id", "label", emptyList()) + FormEntity(EntityAction.UPDATE, "things", UUID.randomUUID().toString(), "label", emptyList()) val formEntities = EntitiesExtra(listOf(formEntity)) LocalEntityUseCases.updateLocalEntitiesFromForm(formEntities, entitiesRepository) @@ -220,7 +258,7 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm does not save updated entity that doesn't already exist`() { val formEntity = - FormEntity(EntityAction.UPDATE, "things", "1", "1", emptyList()) + FormEntity(EntityAction.UPDATE, "things", UUID.randomUUID().toString(), "1", emptyList()) val formEntities = EntitiesExtra(listOf(formEntity)) entitiesRepository.addList("things") @@ -231,8 +269,17 @@ class LocalEntityUseCasesTest { @Test fun `#updateLocalEntitiesFromForm logs invalid entities`() { val debugLogger = mock() + val id = UUID.randomUUID().toString() + val formEntity1 = + FormEntity(EntityAction.CREATE, "things", "", "label", emptyList()) + val formEntity2 = + FormEntity(EntityAction.CREATE, "things", "id", "label", emptyList()) + val formEntity3 = + FormEntity(EntityAction.CREATE, "things", id, "", emptyList()) + val formEntity4 = + FormEntity(EntityAction.UPDATE, "things", id, "", emptyList()) val formEntities = - EntitiesExtra(emptyList(), listOf(InvalidEntity("things", "id", "label"))) + EntitiesExtra(listOf(formEntity1, formEntity2, formEntity3, formEntity4)) LocalEntityUseCases.updateLocalEntitiesFromForm( formEntities, @@ -240,9 +287,23 @@ class LocalEntityUseCasesTest { debugLogger ) - verify(debugLogger).log( + verify(debugLogger).logWithAnalytics( + "Entities", + "Failed to create/update dataset=things, id=, label=label", + "EntityWithNoId", + "form" + ) + verify(debugLogger).logWithAnalytics( + "Entities", + "Failed to create/update dataset=things, id=id, label=label", + "EntityWithInvalidId", + "form" + ) + verify(debugLogger).logWithAnalytics( "Entities", - "Failed to create/update dataset=things, id=id, label=label" + "Failed to update dataset=things, id=$id, label=", + "EntityUpdateNoMatch", + "form" ) } diff --git a/entities/src/test/java/org/odk/collect/entities/javarosa/EntitiesTest.kt b/entities/src/test/java/org/odk/collect/entities/javarosa/EntitiesTest.kt index fe6ba668821..d2287358353 100644 --- a/entities/src/test/java/org/odk/collect/entities/javarosa/EntitiesTest.kt +++ b/entities/src/test/java/org/odk/collect/entities/javarosa/EntitiesTest.kt @@ -678,128 +678,6 @@ class EntitiesTest { ) } - @Test - fun `filling form with create without an id makes invalid entity available`() { - val scenario = Scenario.init( - html( - listOf(Pair("entities", "http://www.opendatakit.org/xforms/entities")), - head( - title("Create entity form"), - model( - listOf(Pair("entities:entities-version", "2024.1.0")), - mainInstance( - t( - "data id=\"create-entity-form\"", - t("id"), - t("name"), - t( - "meta", - t("entity dataset=\"people\" create=\"1\" id=\"\"", - t("label") - ) - ) - ) - ), - bind("/data/id").type("string"), - bind("/data/meta/entity/@id").type("string").calculate("/data/id"), - bind("/data/meta/entity/label").type("string").calculate("/data/name") - ) - ), - body( - input("/data/id"), - input("/data/name") - ) - ) - ) - - scenario.formEntryController.addPostProcessor(EntityFormFinalizationProcessor()) - scenario.finalizeInstance() - - val entitiesExtra = scenario.formEntryController.model.extras.get(EntitiesExtra::class.java) - val (entities, invalidEntities) = entitiesExtra - assertThat(entities.size, equalTo(0)) - assertThat(invalidEntities.size, equalTo(1)) - assertThat(invalidEntities[0].dataset, equalTo("people")) - assertThat(invalidEntities[0].id, equalTo(null)) - } - - @Test - fun `filling form with blank label makes invalid entity available`() { - val scenario = Scenario.init( - html( - listOf(Pair("entities", "http://www.opendatakit.org/xforms/entities")), - head( - title("Create entity form"), - model( - listOf(Pair("entities:entities-version", "2024.1.0")), - mainInstance( - t( - "data id=\"create-entity-form\"", - t("name"), - t("meta", entityNode("people", CREATE)) - ) - ), - bind("/data/name").type("string").withSaveTo("name"), - entityLabelBind("/data/name"), - ) - ), - body( - input("/data/name") - ) - ) - ) - - scenario.formEntryController.addPostProcessor(EntityFormFinalizationProcessor()) - scenario.answer("/data/name", " ") - scenario.finalizeInstance() - - val entitiesExtra = scenario.formEntryController.model.extras.get(EntitiesExtra::class.java) - val (entities, invalidEntities) = entitiesExtra - assertThat(entities.size, equalTo(0)) - assertThat(invalidEntities.size, equalTo(1)) - assertThat(invalidEntities[0].dataset, equalTo("people")) - assertThat(invalidEntities[0].label, equalTo(" ")) - } - - @Test - fun `filling fom with non-UUID id makes invalid entity available`() { - val scenario = Scenario.init( - html( - listOf(Pair("entities", "http://www.opendatakit.org/xforms/entities")), - head( - title("Create entity form"), - model( - listOf(Pair("entities:entities-version", "2024.1.0")), - mainInstance( - t( - "data id=\"create-entity-form\"", - t("name"), - t("meta", entityNode("people", CREATE)) - ) - ), - bind("/data/name").type("string").withSaveTo("name"), - entityLabelBind("/data/name"), - setvalue("odk-instance-first-load", "/data/meta/entity/@id", "1") - ) - ), - body( - input("/data/name") - ) - ) - ) - - scenario.formEntryController.addPostProcessor(EntityFormFinalizationProcessor()) - scenario.answer("/data/name", "Dylan") - scenario.finalizeInstance() - - val entitiesExtra = scenario.formEntryController.model.extras.get(EntitiesExtra::class.java) - val (entities, invalidEntities) = entitiesExtra - assertThat(entities.size, equalTo(0)) - assertThat(invalidEntities.size, equalTo(1)) - assertThat(invalidEntities[0].dataset, equalTo("people")) - assertThat(invalidEntities[0].id, equalTo("1")) - } - @Test fun `filling form with update makes entity available`() { val scenario = Scenario.init( @@ -846,42 +724,6 @@ class EntitiesTest { assertThat(entities[0].action, equalTo(EntityAction.UPDATE)) } - @Test - fun `filling form with update without an id does not make entity available`() { - val scenario = Scenario.init( - html( - listOf(Pair("entities", "http://www.opendatakit.org/xforms/entities")), - head( - title("Update entity form"), - model( - listOf(Pair("entities:entities-version", "2024.1.0")), - mainInstance( - t( - "data id=\"update-entity-form\"", - t("id"), - t( - "meta", - t("entity dataset=\"people\" update=\"1\" id=\"\" baseVersion=\"\"") - ) - ) - ), - bind("/data/id").type("string"), - bind("/data/meta/entity/@id").type("string").calculate("/data/id").readonly() - ) - ), - body( - input("/data/id") - ) - ) - ) - - scenario.formEntryController.addPostProcessor(EntityFormFinalizationProcessor()) - scenario.finalizeInstance() - - val entities = scenario.formEntryController.model.extras.get(EntitiesExtra::class.java).entities - assertThat(entities.size, equalTo(0)) - } - @Test fun `filling form with create and update makes entity available with upsert action`() { val scenario = Scenario.init( diff --git a/shared/src/main/java/org/odk/collect/shared/DebugLogger.kt b/shared/src/main/java/org/odk/collect/shared/DebugLogger.kt index 45a1643fd5d..c9b24104705 100644 --- a/shared/src/main/java/org/odk/collect/shared/DebugLogger.kt +++ b/shared/src/main/java/org/odk/collect/shared/DebugLogger.kt @@ -2,4 +2,6 @@ package org.odk.collect.shared interface DebugLogger { fun log(tag: String, message: String) + + fun logWithAnalytics(tag: String, message: String, analyticsEvent: String, analyticsKey: String) }