Skip to content

Commit

Permalink
Merge pull request #5982 from seadowg/entity-create
Browse files Browse the repository at this point in the history
Include locally created entities in follow up forms
  • Loading branch information
grzesiek2010 committed Mar 29, 2024
2 parents e819e12 + 9f4e7cc commit 4429ad9
Show file tree
Hide file tree
Showing 55 changed files with 1,252 additions and 160 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ open class CoroutineScheduler(private val foregroundContext: CoroutineContext, p
}
}

override fun immediate(background: Boolean, runnable: Runnable) {
val context = if (background) {
override fun immediate(foreground: Boolean, runnable: Runnable) {
val context = if (!foreground) {
backgroundContext
} else {
foregroundContext
Expand Down
2 changes: 1 addition & 1 deletion async/src/main/java/org/odk/collect/async/Scheduler.kt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ interface Scheduler {
/**
* Run work in the foreground or background. Cancelled if application closed.
*/
fun immediate(background: Boolean = false, runnable: Runnable)
fun immediate(foreground: Boolean = false, runnable: Runnable)

/**
* Schedule a task to run in the background even if the app isn't running. The task
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,8 @@ abstract class SchedulerAsyncTaskMimic<Params, Progress, Result>(private val sch
}

protected fun publishProgress(vararg values: Progress) {
scheduler.immediate(
runnable = { onProgressUpdate(*values) }
)
scheduler.immediate(foreground = true) {
onProgressUpdate(*values)
}
}
}
2 changes: 1 addition & 1 deletion buildSrc/src/main/java/dependencies/Dependencies.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ object Dependencies {
const val rarepebble_colorpicker = "com.github.martin-stone:hsv-alpha-color-picker-android:3.1.0"
const val commons_io = "commons-io:commons-io:2.5" // Commons 2.6+ introduce java.nio usage that we can't access until our minSdkVersion >= 26 (https://developer.android.com/reference/java/io/File#toPath())
const val opencsv = "com.opencsv:opencsv:5.9"
const val javarosa_online = "org.getodk:javarosa:4.3.2"
const val javarosa_online = "org.getodk:javarosa:4.4.0-SNAPSHOT"
const val javarosa_local = "org.getodk:javarosa:local"
const val javarosa = javarosa_online
const val karumi_dexter = "com.karumi:dexter:6.2.3"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package org.odk.collect.android.feature.entitymanagement

import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.FormEntryPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain
import org.odk.collect.strings.R.string

class DeleteEntitiesTest {

private val rule = CollectTestRule(useDemoProject = false)
private val testDependencies = TestDependencies()

@get:Rule
val ruleChain: RuleChain = TestRuleChain.chain(testDependencies)
.around(rule)

@Test
fun canClearAllEntities() {
testDependencies.server.addForm("one-question-entity-registration.xml")

rule.withMatchExactlyProject(testDependencies.server.url)
.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))
.openEntityBrowser()
.clickOptionsIcon(string.clear_entities)
.clickOnString(string.clear_entities)
.assertTextDoesNotExist("people")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,27 +5,139 @@ import org.junit.Rule
import org.junit.Test
import org.junit.rules.RuleChain
import org.junit.runner.RunWith
import org.odk.collect.android.support.TestDependencies
import org.odk.collect.android.support.pages.FormEntryPage
import org.odk.collect.android.support.rules.CollectTestRule
import org.odk.collect.android.support.rules.TestRuleChain

@RunWith(AndroidJUnit4::class)
class EntityFormTest {

private val rule = CollectTestRule()
private val rule = CollectTestRule(useDemoProject = false)
private val testDependencies = TestDependencies()

@get:Rule
val ruleChain: RuleChain = TestRuleChain.chain()
val ruleChain: RuleChain = TestRuleChain.chain(testDependencies)
.around(rule)

@Test
fun fillingFormWithEntityCreateElement_createsAnEntity() {
rule.startAtMainMenu()
.copyForm("one-question-entity.xml")
.startBlankForm("One Question Entity")
fun fillingEntityRegistrationForm_createsEntityInTheBrowser() {
testDependencies.server.addForm("one-question-entity-registration.xml")

rule.withMatchExactlyProject(testDependencies.server.url)
.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))
.openEntityBrowser()
.clickOnDataset("people")
.assertEntity("full_name: Logan Roy")
.assertEntity("Logan Roy", "full_name: Logan Roy")
}

@Test
fun fillingEntityRegistrationForm_createsEntityForFollowUpForms() {
testDependencies.server.addForm("one-question-entity-registration.xml")
testDependencies.server.addForm("one-question-entity-update.xml", listOf("people.csv"))

rule.withMatchExactlyProject(testDependencies.server.url)
.enableLocalEntitiesInForms()

.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))

.startBlankForm("One Question Entity Update")
.assertQuestion("Select person")
.assertText("Roman Roy")
.assertText("Logan Roy")
}

@Test
fun fillingEntityRegistrationForm_createsEntityForFollowUpFormsWithCachedFormDefs() {
testDependencies.server.addForm("one-question-entity-registration.xml")
testDependencies.server.addForm("one-question-entity-update.xml", listOf("people.csv"))

rule.withMatchExactlyProject(testDependencies.server.url)
.enableLocalEntitiesInForms()

.startBlankForm("One Question Entity Update") // Open to create cached form def
.pressBackAndDiscardForm()

.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Logan Roy"))

.startBlankForm("One Question Entity Update")
.assertQuestion("Select person")
.assertText("Roman Roy")
.assertText("Logan Roy")
}

@Test
fun fillingEntityUpdateForm_updatesEntityForFollowUpForms() {
testDependencies.server.addForm("one-question-entity-update.xml", listOf("people.csv"))

rule.withMatchExactlyProject(testDependencies.server.url)
.enableLocalEntitiesInForms()

.startBlankForm("One Question Entity Update")
.assertQuestion("Select person")
.clickOnText("Roman Roy")
.swipeToNextQuestion("Name")
.answerQuestion("Name", "Romulus Roy")
.swipeToEndScreen()
.clickFinalize()

.startBlankForm("One Question Entity Update")
.assertText("Romulus Roy")
.assertTextDoesNotExist("Roman Roy")
}

@Test
fun fillingEntityFollowUpForm_whenOnlineDuplicateHasHigherVersion_showsOnlineVersion() {
testDependencies.server.addForm("one-question-entity-update.xml", listOf("people.csv"))

val mainMenuPage = rule.withMatchExactlyProject(testDependencies.server.url)
.enableLocalEntitiesInForms()

.startBlankForm("One Question Entity Update")
.assertQuestion("Select person")
.clickOnText("Roman Roy")
.swipeToNextQuestion("Name")
.answerQuestion("Name", "Romulus Roy")
.swipeToEndScreen()
.clickFinalize()

testDependencies.server.updateMediaFile(
"one-question-entity-update.xml",
"people.csv",
"updated-people.csv"
)

mainMenuPage.clickFillBlankForm()
.clickRefresh()
.clickOnForm("One Question Entity Update")
.assertText("Ro-Ro Roy")
.assertTextDoesNotExist("Romulus Roy")
.assertTextDoesNotExist("Roman Roy")
}

@Test
fun fillingEntityFollowUpForm_whenOfflineDuplicateHasHigherVersion_showsOfflineVersion() {
testDependencies.server.addForm(
"one-question-entity-update.xml",
mapOf("people.csv" to "updated-people.csv")
)

rule.withMatchExactlyProject(testDependencies.server.url)
.enableLocalEntitiesInForms()

.startBlankForm("One Question Entity Update")
.assertQuestion("Select person")
.clickOnText("Ro-Ro Roy")
.swipeToNextQuestion("Name")
.answerQuestion("Name", "Romulus Roy")
.swipeToEndScreen()
.clickFinalize()

.startBlankForm("One Question Entity Update")
.assertText("Romulus Roy")
.assertTextDoesNotExist("Ro-Ro Roy")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ class SwitchProjectTest {

@Test
fun switchingProject_switchesSettingsFormsInstancesAndEntities() {
testDependencies.server.addForm("One Question Entity", "one-question-entity", "1", "one-question-entity.xml")
testDependencies.server.addForm("One Question Entity Registration", "one-question-entity", "1", "one-question-entity-registration.xml")

rule.startAtMainMenu()
// Copy and fill form
Expand Down Expand Up @@ -76,15 +76,15 @@ class SwitchProjectTest {
.clickOKOnDialog(MainMenuPage())

// Fill form
.startBlankForm("One Question Entity")
.startBlankForm("One Question Entity Registration")
.fillOutAndFinalize(FormEntryPage.QuestionAndAnswer("Name", "Alice"))
.clickSendFinalizedForm(1)
.assertText("One Question Entity")
.assertText("One Question Entity Registration")
.pressBack(MainMenuPage())

.openEntityBrowser()
.clickOnDataset("people")
.assertEntity("full_name: Alice")
.assertEntity("Alice", "full_name: Alice")
.pressBack(EntitiesPage())
.pressBack(ExperimentalPage())
.pressBack(ProjectSettingsPage())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

import static org.odk.collect.android.utilities.FileUtils.getResourceAsStream;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
Expand Down Expand Up @@ -31,6 +30,7 @@
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

public class StubOpenRosaServer implements OpenRosaHttpInterface {
Expand Down Expand Up @@ -75,7 +75,7 @@ public HttpGetResult executeGetRequest(@NonNull URI uri, @Nullable String conten
} else {
return new HttpGetResult(null, new HashMap<>(), "", 404);
}
} else if (uri.getPath().equals("/mediaFile")) {
} else if (uri.getPath().startsWith("/mediaFile")) {
return new HttpGetResult(getMediaFile(uri), new HashMap<>(), "", 200);
} else {
return new HttpGetResult(null, new HashMap<>(), "", 404);
Expand Down Expand Up @@ -136,6 +136,23 @@ public void addForm(String formLabel, String id, String version, String formXML,
forms.add(new XFormItem(formLabel, formXML, id, version, mediaFiles));
}

public void addForm(String formXML, List<String> mediaFiles) {
forms.add(new XFormItem(formXML, formXML, formXML, "1", mediaFiles));
}

public void addForm(String formXML, Map<String, String> mediaFiles) {
forms.add(new XFormItem(formXML, formXML, formXML, "1", mediaFiles));
}

public void addForm(String formXML) {
forms.add(new XFormItem(formXML, formXML, formXML, "1"));
}

public void updateMediaFile(String formXML, String name, String newFile) {
Optional<XFormItem> formToUpdate = forms.stream().filter((form) -> form.formXML.equals(formXML)).findFirst();
formToUpdate.get().getMediaFiles().put(name, newFile);
}

public void removeForm(String formLabel) {
forms.removeIf(xFormItem -> xFormItem.getFormLabel().equals(formLabel));
}
Expand Down Expand Up @@ -245,18 +262,18 @@ private InputStream getManifestResponse(@NonNull URI uri) throws IOException {
.append("<?xml version='1.0' encoding='UTF-8' ?>\n")
.append("<manifest xmlns=\"http://openrosa.org/xforms/xformsManifest\">\n");

for (String mediaFile : xformItem.getMediaFiles()) {
for (Map.Entry<String, String> mediaFile : xformItem.getMediaFiles().entrySet()) {
String mediaFileHash;

if (randomHash) {
mediaFileHash = RandomString.randomString(8);
} else {
mediaFileHash = Md5.getMd5Hash(getResourceAsStream("media/" + mediaFile));
mediaFileHash = Md5.getMd5Hash(getResourceAsStream("media/" + mediaFile.getValue()));
}

stringBuilder
.append("<mediaFile>")
.append("<filename>" + mediaFile + "</filename>\n");
.append("<filename>" + mediaFile.getKey() + "</filename>\n");

if (noHashPrefixInMediaFiles) {
stringBuilder.append("<hash>" + mediaFileHash + " </hash>\n");
Expand All @@ -265,7 +282,7 @@ private InputStream getManifestResponse(@NonNull URI uri) throws IOException {
}

stringBuilder
.append("<downloadUrl>" + getURL() + "/mediaFile?name=" + mediaFile + "</downloadUrl>\n")
.append("<downloadUrl>" + getURL() + "/mediaFile/" + formID + "/" + mediaFile.getKey() + "</downloadUrl>\n")
.append("</mediaFile>\n");
}

Expand All @@ -282,8 +299,11 @@ private InputStream getFormXML(String formID) throws IOException {

@NotNull
private InputStream getMediaFile(URI uri) throws IOException {
String mediaFileName = uri.getQuery().split("=")[1];
return getResourceAsStream("media/" + mediaFileName);
String formID = uri.getPath().split("/mediaFile/")[1].split("/")[0];
String mediaFileName = uri.getPath().split("/mediaFile/")[1].split("/")[1];
XFormItem xformItem = forms.get(Integer.parseInt(formID));
String actualFileName = xformItem.getMediaFiles().get(mediaFileName);
return getResourceAsStream("media/" + actualFileName);
}

private static class XFormItem {
Expand All @@ -292,13 +312,20 @@ private static class XFormItem {
private final String formXML;
private final String id;
private final String version;
private final List<String> mediaFiles;
private final Map<String, String> mediaFiles;

XFormItem(String formLabel, String formXML, String id, String version) {
this(formLabel, formXML, id, version, emptyList());
this(formLabel, formXML, id, version, new HashMap<>());
}

XFormItem(String formLabel, String formXML, String id, String version, List<String> mediaFiles) {
this(formLabel, formXML, id, version);
for (String mediaFile : mediaFiles) {
this.mediaFiles.put(mediaFile, mediaFile);
}
}

XFormItem(String formLabel, String formXML, String id, String version, Map<String, String> mediaFiles) {
this.formLabel = formLabel;
this.formXML = formXML;
this.id = id;
Expand All @@ -322,7 +349,7 @@ public String getID() {
return id;
}

public List<String> getMediaFiles() {
public Map<String, String> getMediaFiles() {
return mediaFiles;
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@ class TestScheduler : Scheduler, CoroutineDispatcher() {
}
}

override fun immediate(background: Boolean, runnable: Runnable) {
override fun immediate(foreground: Boolean, runnable: Runnable) {
increment()
wrappedScheduler.immediate(background) {
wrappedScheduler.immediate(foreground) {
runnable.run()
decrement()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ class DatasetPage(private val datasetName: String) : Page<DatasetPage>() {
return this
}

fun assertEntity(fields: String): DatasetPage {
assertText(fields)
fun assertEntity(label: String, properties: String): DatasetPage {
assertText(label)
assertText(properties)
return this
}
}
Loading

0 comments on commit 4429ad9

Please sign in to comment.