Skip to content

Commit 4603e67

Browse files
authored
Merge pull request #6451 from seadowg/entities-pulldata
Add support for pulldata with local entities
2 parents c5659d5 + 78b30c6 commit 4603e67

File tree

9 files changed

+342
-40
lines changed

9 files changed

+342
-40
lines changed

collect_app/src/androidTest/java/org/odk/collect/android/feature/formentry/dynamicpreload/DynamicPreLoadedDataPullTest.kt

Lines changed: 30 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,11 @@ package org.odk.collect.android.feature.formentry.dynamicpreload
33
import org.junit.Rule
44
import org.junit.Test
55
import org.junit.rules.RuleChain
6-
import org.odk.collect.android.support.rules.FormEntryActivityTestRule
6+
import org.odk.collect.android.support.StubOpenRosaServer.EntityListItem
7+
import org.odk.collect.android.support.StubOpenRosaServer.MediaFileItem
8+
import org.odk.collect.android.support.TestDependencies
9+
import org.odk.collect.android.support.pages.FormEntryPage
10+
import org.odk.collect.android.support.rules.CollectTestRule
711
import org.odk.collect.android.support.rules.TestRuleChain.chain
812

913
/**
@@ -12,16 +16,36 @@ import org.odk.collect.android.support.rules.TestRuleChain.chain
1216
*/
1317
class DynamicPreLoadedDataPullTest {
1418

15-
private val rule = FormEntryActivityTestRule()
19+
private val rule = CollectTestRule(useDemoProject = false)
20+
private val testDependencies = TestDependencies()
1621

1722
@get:Rule
18-
val copyFormChain: RuleChain = chain()
19-
.around(rule)
23+
val chain: RuleChain = chain(testDependencies).around(rule)
2024

2125
@Test
2226
fun canUsePullDataFunctionToPullDataFromCSV() {
23-
rule.setUpProjectAndCopyForm("pull_data.xml", listOf("fruits.csv"))
24-
.fillNewForm("pull_data.xml", "pull_data")
27+
testDependencies.server.addForm("pull_data.xml", listOf(MediaFileItem("fruits.csv")))
28+
29+
rule.withMatchExactlyProject(testDependencies.server.url)
30+
.startBlankForm("pull_data")
2531
.assertText("The fruit Mango is pulled csv data.")
2632
}
33+
34+
@Test
35+
fun canUsePullDataFunctionToPullDataFromLocalEntities() {
36+
testDependencies.server.addForm("one-question-entity-registration.xml")
37+
testDependencies.server.addForm(
38+
"entity-update-pulldata.xml",
39+
listOf(EntityListItem("people.csv"))
40+
)
41+
42+
rule.withMatchExactlyProject(testDependencies.server.url)
43+
.startBlankForm("One Question Entity Registration")
44+
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))
45+
46+
.startBlankForm("Entity Update Pull Data")
47+
.clickOnText("Logan Roy")
48+
.swipeToNextQuestion("Name")
49+
.assertText("Logan Roy")
50+
}
2751
}

collect_app/src/main/java/org/odk/collect/android/formmanagement/CollectFormEntryControllerFactory.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import org.odk.collect.android.dynamicpreload.ExternalDataManagerImpl
88
import org.odk.collect.android.dynamicpreload.handler.ExternalDataHandlerPull
99
import org.odk.collect.android.tasks.FormLoaderTask.FormEntryControllerFactory
1010
import org.odk.collect.entities.javarosa.filter.LocalEntitiesFilterStrategy
11+
import org.odk.collect.entities.javarosa.filter.PullDataFunctionHandler
1112
import org.odk.collect.entities.javarosa.finalization.EntityFormFinalizationProcessor
1213
import org.odk.collect.entities.storage.EntitiesRepository
1314
import org.odk.collect.settings.keys.ProjectKeys
@@ -25,7 +26,8 @@ class CollectFormEntryControllerFactory(
2526
}
2627

2728
return FormEntryController(FormEntryModel(formDef)).also {
28-
it.addFunctionHandler(ExternalDataHandlerPull(externalDataManager))
29+
val externalDataHandlerPull = ExternalDataHandlerPull(externalDataManager)
30+
it.addFunctionHandler(PullDataFunctionHandler(entitiesRepository, externalDataHandlerPull))
2931
it.addPostProcessor(EntityFormFinalizationProcessor())
3032

3133
if (settings.getBoolean(ProjectKeys.KEY_LOCAL_ENTITIES)) {

entities/src/main/java/org/odk/collect/entities/javarosa/filter/LocalEntitiesFilterStrategy.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ import java.util.function.Supplier
2222
class LocalEntitiesFilterStrategy(entitiesRepository: EntitiesRepository) :
2323
FilterStrategy {
2424

25-
private val dataAdapter = LocalEntitiesInstanceAdapter(entitiesRepository)
25+
private val instanceAdapter = LocalEntitiesInstanceAdapter(entitiesRepository)
2626

2727
override fun filter(
2828
sourceInstance: DataInstance<*>,
@@ -32,7 +32,7 @@ class LocalEntitiesFilterStrategy(entitiesRepository: EntitiesRepository) :
3232
evaluationContext: EvaluationContext,
3333
next: Supplier<MutableList<TreeReference>>
3434
): List<TreeReference> {
35-
if (sourceInstance.instanceId == null || !dataAdapter.supportsInstance(sourceInstance.instanceId)) {
35+
if (sourceInstance.instanceId == null || !instanceAdapter.supportsInstance(sourceInstance.instanceId)) {
3636
return next.get()
3737
}
3838

@@ -43,7 +43,7 @@ class LocalEntitiesFilterStrategy(entitiesRepository: EntitiesRepository) :
4343
val child = candidate.nodeSide.steps[0].name.name
4444
val value = candidate.evalContextSide(sourceInstance, evaluationContext)
4545

46-
val results = dataAdapter.queryEq(
46+
val results = instanceAdapter.queryEq(
4747
sourceInstance.instanceId,
4848
child,
4949
value as String
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package org.odk.collect.entities.javarosa.filter
2+
3+
import org.javarosa.core.model.condition.EvaluationContext
4+
import org.javarosa.core.model.condition.IFunctionHandler
5+
import org.javarosa.xpath.expr.XPathFuncExpr
6+
import org.odk.collect.entities.javarosa.intance.LocalEntitiesInstanceAdapter
7+
import org.odk.collect.entities.storage.EntitiesRepository
8+
9+
class PullDataFunctionHandler(
10+
entitiesRepository: EntitiesRepository,
11+
private val fallback: IFunctionHandler? = null
12+
) : IFunctionHandler {
13+
14+
private val instanceAdapter = LocalEntitiesInstanceAdapter(entitiesRepository)
15+
16+
override fun getName(): String {
17+
return NAME
18+
}
19+
20+
override fun getPrototypes(): List<Array<Class<Any>>> {
21+
return emptyList()
22+
}
23+
24+
override fun rawArgs(): Boolean {
25+
return true
26+
}
27+
28+
override fun realTime(): Boolean {
29+
return false
30+
}
31+
32+
override fun eval(args: Array<Any>, ec: EvaluationContext): Any {
33+
val instanceId = XPathFuncExpr.toString(args[0])
34+
val child = XPathFuncExpr.toString(args[1])
35+
val filterChild = XPathFuncExpr.toString(args[2])
36+
val filterValue = XPathFuncExpr.toString(args[3])
37+
38+
return if (instanceAdapter.supportsInstance(instanceId)) {
39+
instanceAdapter.queryEq(instanceId, filterChild, filterValue)!!.firstOrNull()
40+
?.getFirstChild(child)?.value?.value ?: ""
41+
} else {
42+
fallback?.eval(args, ec) ?: ""
43+
}
44+
}
45+
46+
companion object {
47+
private const val NAME = "pulldata"
48+
}
49+
}

entities/src/main/java/org/odk/collect/entities/javarosa/intance/LocalEntitiesInstanceAdapter.kt

Lines changed: 41 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class LocalEntitiesInstanceAdapter(private val entitiesRepository: EntitiesRepos
2323

2424
0.until(count).map {
2525
if (it == 0) {
26-
convertToElement(first, true)
26+
convertToElement(first)
2727
} else {
2828
TreeElement("item", it, true)
2929
}
@@ -33,62 +33,82 @@ class LocalEntitiesInstanceAdapter(private val entitiesRepository: EntitiesRepos
3333
}
3434
} else {
3535
entitiesRepository.getEntities(instanceId).map { entity ->
36-
convertToElement(entity, false)
36+
convertToElement(entity)
3737
}
3838
}
3939
}
4040

4141
fun queryEq(instanceId: String, child: String, value: String): List<TreeElement>? {
4242
return when {
43-
child == "name" -> {
43+
child == EntityItemElement.ID -> {
4444
val entity = entitiesRepository.getById(
4545
instanceId,
4646
value
4747
)
4848

4949
if (entity != null) {
50-
listOf(convertToElement(entity, false))
50+
listOf(convertToElement(entity))
5151
} else {
5252
emptyList()
5353
}
5454
}
5555

56-
!listOf(EntityItemElement.LABEL, EntityItemElement.VERSION).contains(child) -> {
56+
child == EntityItemElement.LABEL -> {
57+
filterAndConvertEntities(instanceId) { it.label == value }
58+
}
59+
60+
child == EntityItemElement.VERSION -> {
61+
filterAndConvertEntities(instanceId) { it.version == value.toInt() }
62+
}
63+
64+
child == EntityItemElement.TRUNK_VERSION -> {
65+
filterAndConvertEntities(instanceId) { it.trunkVersion == value.toInt() }
66+
}
67+
68+
child == EntityItemElement.BRANCH_ID -> {
69+
filterAndConvertEntities(instanceId) { it.branchId == value }
70+
}
71+
72+
else -> {
5773
val entities = entitiesRepository.getAllByProperty(
5874
instanceId,
5975
child,
6076
value
6177
)
6278

63-
entities.map { convertToElement(it, false) }
79+
entities.map { convertToElement(it) }
6480
}
65-
66-
else -> null
6781
}
6882
}
6983

70-
private fun convertToElement(entity: Entity.Saved, partial: Boolean): TreeElement {
84+
private fun filterAndConvertEntities(
85+
list: String,
86+
filter: (Entity.Saved) -> Boolean
87+
): List<TreeElement> {
88+
val entities = entitiesRepository.getEntities(list)
89+
return entities.filter(filter).map { convertToElement(it) }
90+
}
91+
92+
private fun convertToElement(entity: Entity.Saved): TreeElement {
7193
val name = TreeElement(EntityItemElement.ID)
7294
val label = TreeElement(EntityItemElement.LABEL)
7395
val version = TreeElement(EntityItemElement.VERSION)
7496
val trunkVersion = TreeElement(EntityItemElement.TRUNK_VERSION)
7597
val branchId = TreeElement(EntityItemElement.BRANCH_ID)
7698

77-
if (!partial) {
78-
name.value = StringData(entity.id)
79-
version.value = StringData(entity.version.toString())
80-
branchId.value = StringData(entity.branchId)
99+
name.value = StringData(entity.id)
100+
version.value = StringData(entity.version.toString())
101+
branchId.value = StringData(entity.branchId)
81102

82-
if (entity.label != null) {
83-
label.value = StringData(entity.label)
84-
}
103+
if (entity.label != null) {
104+
label.value = StringData(entity.label)
105+
}
85106

86-
if (entity.trunkVersion != null) {
87-
trunkVersion.value = StringData(entity.trunkVersion.toString())
88-
}
107+
if (entity.trunkVersion != null) {
108+
trunkVersion.value = StringData(entity.trunkVersion.toString())
89109
}
90110

91-
val item = TreeElement("item", entity.index, partial)
111+
val item = TreeElement("item", entity.index, false)
92112
item.addChild(name)
93113
item.addChild(label)
94114
item.addChild(version)
@@ -97,11 +117,7 @@ class LocalEntitiesInstanceAdapter(private val entitiesRepository: EntitiesRepos
97117

98118
entity.properties.forEach { property ->
99119
val propertyElement = TreeElement(property.first)
100-
101-
if (!partial) {
102-
propertyElement.value = StringData(property.second)
103-
}
104-
120+
propertyElement.value = StringData(property.second)
105121
item.addChild(propertyElement)
106122
}
107123

entities/src/test/java/org/odk/collect/entities/javarosa/LocalEntitiesInstanceProviderTest.kt

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class LocalEntitiesInstanceProviderTest {
111111
}
112112

113113
@Test
114-
fun `partial parse returns elements without values for first item and just item for others`() {
114+
fun `partial parse returns the full first item and just item for others`() {
115115
val entity = arrayOf(
116116
Entity.New(
117117
"1",
@@ -133,11 +133,9 @@ class LocalEntitiesInstanceProviderTest {
133133
assertThat(instance.numChildren, equalTo(2))
134134

135135
val item1 = instance.getChildAt(0)!!
136-
assertThat(item1.isPartial, equalTo(true))
136+
assertThat(item1.isPartial, equalTo(false))
137137
assertThat(item1.numChildren, equalTo(6))
138-
0.until(item1.numChildren).forEach {
139-
assertThat(item1.getChildAt(it).value?.value, equalTo(null))
140-
}
138+
assertThat(item1.getFirstChild("name")!!.value!!.value, equalTo("1"))
141139

142140
val item2 = instance.getChildAt(1)!!
143141
assertThat(item2.isPartial, equalTo(true))

0 commit comments

Comments
 (0)