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

Handle entities that have been removed on the server #6132

Merged
merged 26 commits into from
May 21, 2024
Merged
Show file tree
Hide file tree
Changes from 23 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
07116b6
Update naming for local entities
seadowg May 8, 2024
ad52bc1
Add test for updating entities with missing online one
seadowg May 8, 2024
a306ce5
Remove online entities that no longer appear in list
seadowg May 8, 2024
1ec593f
Make sure online entities updated offline remain online
seadowg May 8, 2024
abb95b7
Show pill on offline entities
seadowg May 8, 2024
03a332a
Move custom theme attribute
seadowg May 8, 2024
e3d2ba9
Style offline pill
seadowg May 8, 2024
31f747b
Emphasise entity label
seadowg May 9, 2024
34fbed2
Fix entity delete in cases where there are no updates
seadowg May 9, 2024
31f8402
Simplify local entity logic
seadowg May 9, 2024
9591834
Fix error pill
seadowg May 9, 2024
cbed198
Extract entity parse logic
seadowg May 10, 2024
62f4047
Make entity naming consistent
seadowg May 10, 2024
a685876
Update test naming guidelines
seadowg May 13, 2024
3bac60f
Update tests to reflect that entities have unique IDs across lists
seadowg May 13, 2024
e8ce9dc
Clear entities if they fail to parse
seadowg May 13, 2024
9edea05
Update test for JSON format changes
seadowg May 20, 2024
164022a
Remove other references to dataset
seadowg May 20, 2024
fc680cd
Simpplify file recreation
seadowg May 20, 2024
fefae32
Remove unneeded param
seadowg May 20, 2024
3f19a8b
Make method intention clearer
seadowg May 20, 2024
be3fb7e
Specify behaviour when updating entities with same ID in different lists
seadowg May 20, 2024
1dff3be
Convert boolean to enum
seadowg May 20, 2024
9d6f0e8
Rename parameter
seadowg May 21, 2024
301f765
Fix test assertion
seadowg May 21, 2024
106105b
Remove unneeded file deletion
seadowg May 21, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ class ViewEntitiesTest {
.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))
.openEntityBrowser()
.clickOnDataset("people")
.clickOnList("people")
.assertEntity("Logan Roy", "full_name: Logan Roy")
}

Expand All @@ -42,7 +42,7 @@ class ViewEntitiesTest {
.addEntityListInBrowser("people")
.refreshForms()
.openEntityBrowser()
.clickOnDataset("people")
.clickOnList("people")
.assertEntity("Roman Roy", "full_name: Roman Roy")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ class SwitchProjectTest {
.pressBack(MainMenuPage())

.openEntityBrowser()
.clickOnDataset("people")
.clickOnList("people")
.assertEntity("Alice", "full_name: Alice")
.pressBack(EntitiesPage())
.pressBack(ExperimentalPage())
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,16 +1,14 @@
package org.odk.collect.android.support.pages

import org.odk.collect.android.R

class EntitiesPage : Page<EntitiesPage>() {

override fun assertOnPage(): EntitiesPage {
assertToolbarTitle(org.odk.collect.strings.R.string.entities_title)
return this
}

fun clickOnDataset(datasetName: String): DatasetPage {
clickOnText(datasetName)
return DatasetPage(datasetName)
fun clickOnList(list: String): EntityListPage {
clickOnText(list)
return EntityListPage(list)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.odk.collect.android.support.pages

class EntityListPage(private val datasetName: String) : Page<EntityListPage>() {
seadowg marked this conversation as resolved.
Show resolved Hide resolved

override fun assertOnPage(): EntityListPage {
assertToolbarTitle(datasetName)
return this
}

fun assertEntity(label: String, properties: String): EntityListPage {
assertText(label)
assertText(properties)
return this
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,35 @@ class JsonFileEntitiesRepository(directory: File) : EntitiesRepository {

private val entitiesFile = File(directory, "entities.json")

override fun getDatasets(): Set<String> {
seadowg marked this conversation as resolved.
Show resolved Hide resolved
override fun getLists(): Set<String> {
return readJson().keys
}

override fun getEntities(dataset: String): List<Entity> {
return readEntities().filter { it.dataset == dataset }
override fun getEntities(list: String): List<Entity> {
return readEntities().filter { it.list == list }
}

override fun save(vararg entities: Entity) {
val storedEntities = readEntities()

entities.forEach { entity ->
val existing = storedEntities.find { it.id == entity.id && it.dataset == entity.dataset }
val existing = storedEntities.find { it.id == entity.id }

if (existing != null) {
val state = when (existing.state) {
Entity.State.OFFLINE -> entity.state
Entity.State.ONLINE -> Entity.State.ONLINE
}

storedEntities.remove(existing)
storedEntities.add(
Entity(
entity.dataset,
entity.list,
entity.id,
entity.label ?: existing.label,
version = entity.version,
properties = mergeProperties(existing, entity)
properties = mergeProperties(existing, entity),
state = state
)
)
} else {
Expand All @@ -49,27 +55,33 @@ class JsonFileEntitiesRepository(directory: File) : EntitiesRepository {
entitiesFile.delete()
}

override fun addDataset(dataset: String) {
override fun addList(list: String) {
val existing = readJson()
if (!existing.containsKey(dataset)) {
existing[dataset] = mutableListOf()
if (!existing.containsKey(list)) {
existing[list] = mutableListOf()
}

writeJson(existing)
}

override fun delete(id: String) {
val existing = readEntities()
existing.removeIf { it.id == id }
writeEntities(existing)
}

private fun writeEntities(entities: MutableList<Entity>) {
val map = mutableMapOf<String, MutableList<JsonEntity>>()
entities.forEach {
map.getOrPut(it.dataset) { mutableListOf() }.add(it.toJson())
map.getOrPut(it.list) { mutableListOf() }.add(it.toJson())
}

writeJson(map)
}

private fun readEntities(): MutableList<Entity> {
return readJson().entries.flatMap { (dataset, entities) ->
entities.map { it.toEntity(dataset) }
return readJson().entries.flatMap { (list, entities) ->
entities.map { it.toEntity(list) }
}.toMutableList()
}

Expand All @@ -81,20 +93,26 @@ class JsonFileEntitiesRepository(directory: File) : EntitiesRepository {
entitiesFile.createNewFile()
}

val typeToken = TypeToken.getParameterized(
MutableMap::class.java,
String::class.java,
TypeToken.getParameterized(MutableList::class.java, JsonEntity::class.java).type
)
try {
val typeToken = TypeToken.getParameterized(
MutableMap::class.java,
String::class.java,
TypeToken.getParameterized(MutableList::class.java, JsonEntity::class.java).type
)

val json = entitiesFile.readText()
return if (json.isNotBlank()) {
val parsedJson =
Gson().fromJson<MutableMap<String, MutableList<JsonEntity>>>(json, typeToken.type)
val json = entitiesFile.readText()
return if (json.isNotBlank()) {
val parsedJson =
Gson().fromJson<MutableMap<String, MutableList<JsonEntity>>>(json, typeToken.type)

parsedJson
} else {
mutableMapOf()
parsedJson
} else {
mutableMapOf()
}
} catch (e: Exception) {
entitiesFile.delete()
seadowg marked this conversation as resolved.
Show resolved Hide resolved
entitiesFile.createNewFile()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah good spot! We don't actually need to delete the file itself as we just treat the file as empty and then override next time we mutate (as we always do anyway). I can remove the delete/createNewFile call.

return mutableMapOf()
}
}

Expand All @@ -120,17 +138,25 @@ class JsonFileEntitiesRepository(directory: File) : EntitiesRepository {
private data class JsonEntity(
val id: String,
val label: String?,
val version: Int = 1,
val properties: Map<String, String>
val version: Int,
val properties: Map<String, String>,
val offline: Boolean
)

private fun JsonEntity.toEntity(dataset: String): Entity {
private fun JsonEntity.toEntity(list: String): Entity {
val state = if (this.offline) {
Entity.State.OFFLINE
} else {
Entity.State.ONLINE
}

return Entity(
dataset,
list,
this.id,
this.label,
this.version,
this.properties.entries.map { Pair(it.key, it.value) }
this.properties.entries.map { Pair(it.key, it.value) },
state
)
}

Expand All @@ -139,7 +165,8 @@ class JsonFileEntitiesRepository(directory: File) : EntitiesRepository {
this.id,
this.label,
this.version,
this.properties.toMap()
this.properties.toMap(),
this.state == Entity.State.OFFLINE
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ object FormEntryUseCases {
) {
formController.finalizeForm()
formController.getEntities().forEach { entity ->
if (entitiesRepository.getDatasets().contains(entity.dataset)) {
if (entitiesRepository.getLists().contains(entity.list)) {
entitiesRepository.save(entity)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import android.widget.Button
import androidx.recyclerview.widget.RecyclerView
import org.odk.collect.android.R
import org.odk.collect.androidshared.ui.multiclicksafe.MultiClickGuard
import org.odk.collect.lists.RecyclerViewUtils.matchParentWidth

class BlankFormListAdapter(
val listener: OnFormItemClickListener
Expand Down Expand Up @@ -63,10 +64,7 @@ class BlankFormListAdapter(
}

init {
itemView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
matchParentWidth()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import org.odk.collect.android.R
import org.odk.collect.androidshared.ui.FragmentFactoryBuilder
import org.odk.collect.lists.RecyclerViewUtils
import org.odk.collect.lists.RecyclerViewUtils.matchParentWidth
import org.odk.collect.lists.multiselect.MultiSelectAdapter
import org.odk.collect.lists.multiselect.MultiSelectControlsFragment
import org.odk.collect.lists.multiselect.MultiSelectItem
Expand Down Expand Up @@ -110,10 +111,7 @@ private class SelectableBlankFormListItemViewHolder(parent: ViewGroup) :
) {

init {
itemView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
matchParentWidth()
}

override fun setItem(item: BlankFormListItem) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import android.view.ViewGroup
import android.widget.CheckBox
import org.odk.collect.android.R
import org.odk.collect.forms.instances.Instance
import org.odk.collect.lists.RecyclerViewUtils.matchParentWidth
import org.odk.collect.lists.multiselect.MultiSelectAdapter

class SelectableSavedFormListItemViewHolder(parent: ViewGroup) :
Expand All @@ -14,10 +15,7 @@ class SelectableSavedFormListItemViewHolder(parent: ViewGroup) :
private var selectView = itemView

init {
itemView.layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT
)
matchParentWidth()
}

override fun setItem(item: Instance) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ object ServerFormUseCases {
}

val dataset = mediaFile.filename.substringBefore(".csv")
if (entitiesRepository.getDatasets().contains(dataset)) {
if (entitiesRepository.getLists().contains(dataset)) {
LocalEntityUseCases.updateLocalEntities(dataset, tempMediaFile, entitiesRepository)
}
}
Expand Down
4 changes: 4 additions & 0 deletions collect_app/src/main/res/values/attrs.xml
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,8 @@
<attr name="name" format="string" />
<attr name="highlightable" format="boolean" />
</declare-styleable>

<declare-styleable name="CollectCustomAttributes">
<attr name="widgetButtonIconQuestionWidgetStyle" format="reference" />
</declare-styleable>
</resources>
Loading