diff --git a/src/org/labkey/test/components/domain/DomainFieldRow.java b/src/org/labkey/test/components/domain/DomainFieldRow.java
index b5798c5e8c..dc1ea4f328 100644
--- a/src/org/labkey/test/components/domain/DomainFieldRow.java
+++ b/src/org/labkey/test/components/domain/DomainFieldRow.java
@@ -767,6 +767,11 @@ public DomainFieldRow clickRemoveOntologyConcept()
// behind the scenes. Because of that the validator aspect of the TextChoice field is hidden from the user (just like
// it is in the product).
+ public void setAllowMultipleSelections(Boolean allowMultipleSelections)
+ {
+ elementCache().allowMultipleSelectionsCheckbox.set(allowMultipleSelections);
+ }
+
/**
* Set the list of allowed values for a TextChoice field.
*
@@ -1702,6 +1707,10 @@ protected class ElementCache extends WebDriverComponent.ElementCache
public final WebElement domainWarningIcon = Locator.tagWithClass("span", "domain-warning-icon")
.findWhenNeeded(this);
+ // text choice field option
+ public final Checkbox allowMultipleSelectionsCheckbox = new Checkbox(Locator.tagWithClass("input", "domain-text-choice-multi")
+ .refindWhenNeeded(this).withTimeout(WAIT_FOR_JAVASCRIPT));
+
// lookup field options
public final Select lookupContainerSelect = SelectWrapper.Select(Locator.name("domainpropertiesrow-lookupContainer"))
.findWhenNeeded(this);
diff --git a/src/org/labkey/test/components/domain/DomainFormPanel.java b/src/org/labkey/test/components/domain/DomainFormPanel.java
index fb4b639f7c..f235c2391f 100644
--- a/src/org/labkey/test/components/domain/DomainFormPanel.java
+++ b/src/org/labkey/test/components/domain/DomainFormPanel.java
@@ -236,6 +236,10 @@ else if (validator instanceof FieldDefinition.TextChoiceValidator textChoiceVali
throw new IllegalArgumentException("TextChoice fields cannot have additional validators.");
}
fieldRow.setTextChoiceValues(textChoiceValidator.getValues());
+ if(fieldDefinition.getType() == FieldDefinition.ColumnType.MultiValueTextChoice)
+ {
+ fieldRow.setAllowMultipleSelections(true);
+ }
}
else
{
diff --git a/src/org/labkey/test/components/ui/FilterStatusValue.java b/src/org/labkey/test/components/ui/FilterStatusValue.java
index 79264d890e..3130ad1acb 100644
--- a/src/org/labkey/test/components/ui/FilterStatusValue.java
+++ b/src/org/labkey/test/components/ui/FilterStatusValue.java
@@ -50,9 +50,10 @@ public void remove()
{
String originalText = getText();
getWrapper().mouseOver(getComponentElement());
- getWrapper().mouseOver(elementCache().icon);
- WebDriverWrapper.waitFor(()-> isActive() && isClose(),
- "The filter status item with text ["+getText()+"] did not become active.", 500);
+ if(elementCache().popover.isDisplayed())
+ {
+ getWrapper().mouseOut();
+ }
elementCache().icon.click();
// If the item you're dismissing is not the rightmost, it won't become stale; instead, its text will
@@ -94,6 +95,8 @@ protected class ElementCache extends Component>.ElementCache
public final WebElement textSpan = Locator.tag("span").refindWhenNeeded(getComponentElement());
public final WebElement icon = Locator.tag("i").findWhenNeeded(getComponentElement());
+
+ public final WebElement popover = Locator.tag("div").withClass("lk-popover popover bottom").findWhenNeeded(_driver);
}
diff --git a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java
index a5edaae738..7c9309caba 100644
--- a/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java
+++ b/src/org/labkey/test/components/ui/entities/EntityBulkUpdateDialog.java
@@ -124,6 +124,19 @@ public EntityBulkUpdateDialog setSelectionField(CharSequence fieldIdentifier, Li
return this;
}
+ /**
+ * Clear the field (fieldIdentifier).
+ *
+ * @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
+ * @return this component
+ */
+ public EntityBulkUpdateDialog clearSelection(CharSequence fieldIdentifier)
+ {
+ FilteringReactSelect reactSelect = enableSelectionField(fieldIdentifier);
+ reactSelect.clearSelection();
+ return this;
+ }
+
/**
* @param fieldIdentifier Identifier for the field; name ({@link String}) or fieldKey ({@link FieldKey})
* @param selectValue value to select
diff --git a/src/org/labkey/test/components/ui/grids/EditableGrid.java b/src/org/labkey/test/components/ui/grids/EditableGrid.java
index 04aba55749..937db346c2 100644
--- a/src/org/labkey/test/components/ui/grids/EditableGrid.java
+++ b/src/org/labkey/test/components/ui/grids/EditableGrid.java
@@ -429,6 +429,12 @@ public void setCellValue(CharSequence columnToSearch, String valueToSearch, Char
setCellValue(getRowIndex(columnToSearch, valueToSearch), columnToSet, valueToSet);
}
+ public void overwriteCellValue(CharSequence columnToSearch, String valueToSearch, CharSequence columnToSet, Object valueToSet)
+ {
+ clearCellValue(getRowIndex(columnToSearch, valueToSearch), columnToSet);
+ setCellValue(getRowIndex(columnToSearch, valueToSearch), columnToSet, valueToSet);
+ }
+
/**
*
* For the identified row set the value in the identified column.
@@ -507,6 +513,7 @@ public WebElement setCellValue(int row, CharSequence columnIdentifier, Object va
if (value instanceof List)
{
+
// If this is a list assume that it will need a lookup.
List values = (List) value;
@@ -975,6 +982,7 @@ public String copyCurrentSelection() throws IOException, UnsupportedFlavorExcept
public void dragFill(WebElement startCell, WebElement endCell)
{
+ dismissPopover();
Locator.XPathLocator selectionHandleLoc = Locator.byClass("cell-selection-handle");
WebElement selectionHandle = selectionHandleLoc.findElement(startCell);
dragToCell(selectionHandle, endCell);
diff --git a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
index 643fd1e033..625e9d4909 100644
--- a/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
+++ b/src/org/labkey/test/components/ui/grids/ResponsiveGrid.java
@@ -17,6 +17,7 @@
import org.labkey.test.components.react.ReactCheckBox;
import org.labkey.test.components.ui.grids.FieldReferenceManager.FieldReference;
import org.labkey.test.components.ui.search.FilterExpressionPanel;
+import org.labkey.test.components.ui.search.FilterFacetedPanel;
import org.labkey.test.params.FieldKey;
import org.labkey.test.util.selenium.WebElementUtils;
import org.openqa.selenium.Keys;
@@ -40,6 +41,13 @@
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.junit.Assert.assertEquals;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_CONTAINS_ALL;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_CONTAINS_ANY;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_CONTAINS_EXACT;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_CONTAINS_NONE;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_CONTAINS_NOT_EXACT;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_ISEMPTY;
+import static org.labkey.remoteapi.query.Filter.Operator.ARRAY_ISNOTEMPTY;
import static org.labkey.test.WebDriverWrapper.waitFor;
public class ResponsiveGrid> extends WebDriverComponent.ElementCache> implements UpdatingComponent
@@ -232,20 +240,33 @@ public String filterColumnExpectingError(CharSequence columnIdentifier, Filter.O
return errorMsg;
}
+private static final List ARRAY_OPERATORS = List.of(ARRAY_CONTAINS_ALL, ARRAY_CONTAINS_ANY, ARRAY_CONTAINS_EXACT, ARRAY_CONTAINS_NONE,
+ ARRAY_CONTAINS_NOT_EXACT, ARRAY_ISEMPTY, ARRAY_ISNOTEMPTY);
+
private GridFilterModal initFilterColumn(CharSequence columnIdentifier, Filter.Operator operator, Object value)
{
clickColumnMenuItem(columnIdentifier, "Filter...", false);
GridFilterModal filterModal = new GridFilterModal(getDriver(), this);
if (operator != null)
{
- if (operator.equals(Filter.Operator.IN) && value instanceof List>)
+ if (operator.equals(Filter.Operator.IN) && value instanceof List> || ARRAY_OPERATORS.contains(operator))
{
- List values = (List) value;
- filterModal.selectFacetTab().selectValue(values.get(0));
- filterModal.selectFacetTab().checkValues(values.toArray(String[]::new));
+ FilterFacetedPanel filterPanel = filterModal.selectFacetTab();
+ if (ARRAY_OPERATORS.contains(operator))
+ {
+ filterPanel.selectArrayFilterOperator(operator);
+ }
+ if (value != null)
+ {
+ List values = (List) value;
+ filterPanel.selectValue(values.get(0));
+ filterPanel.checkValues(values.toArray(String[]::new));
+ }
}
else
+ {
filterModal.selectExpressionTab().setFilter(new FilterExpressionPanel.Expression(operator, value));
+ }
}
return filterModal;
}
@@ -385,15 +406,16 @@ public T selectRow(int index, boolean checked)
/**
* Finds the first row with the specified texts in the specified columns, and sets its checkbox
- * @param partialMap key-column (fieldKey, name, or label), value-text in that column
- * @param checked the desired checkbox state
+ *
+ * @param partialMap key-column (fieldKey, name, or label), value-text in that column
+ * @param checked the desired checkbox state
* @return this grid
*/
public T selectRow(Map partialMap, boolean checked)
{
GridRow row = getRow(partialMap);
selectRowAndVerifyCheckedCounts(row, checked);
- getWrapper().log("Row described by map ["+partialMap+"] selection state set to + ["+row.isSelected()+"]");
+ getWrapper().log("Row described by map [" + partialMap + "] selection state set to + [" + row.isSelected() + "]");
return getThis();
}
diff --git a/src/org/labkey/test/components/ui/search/FilterFacetedPanel.java b/src/org/labkey/test/components/ui/search/FilterFacetedPanel.java
index 90a8c78713..1ffbe27f88 100644
--- a/src/org/labkey/test/components/ui/search/FilterFacetedPanel.java
+++ b/src/org/labkey/test/components/ui/search/FilterFacetedPanel.java
@@ -1,11 +1,13 @@
package org.labkey.test.components.ui.search;
import org.apache.commons.lang3.StringUtils;
+import org.labkey.remoteapi.query.Filter;
import org.labkey.test.Locator;
import org.labkey.test.components.Component;
import org.labkey.test.components.WebDriverComponent;
import org.labkey.test.components.html.Checkbox;
import org.labkey.test.components.html.Input;
+import org.labkey.test.components.react.ReactSelect;
import org.labkey.test.components.ui.FilterStatusValue;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
@@ -48,6 +50,15 @@ public void selectValue(String value)
elementCache().findCheckboxLabel(value).click();
}
+ /**
+ * Select a filer by clicking its label. Right now this method relevant only for multi-value text choice.
+ * @param operator desired filter value
+ */
+ public void selectArrayFilterOperator(Filter.Operator operator)
+ {
+ elementCache().arrayFilterOperatorSelect.select(operator.getDisplayValue());
+ }
+
/**
* Check single facet value by label to see if it is checked or not.
* @param value desired value
@@ -123,6 +134,8 @@ protected class ElementCache extends Component>.ElementCache
{
protected final Input filterInput =
Input(Locator.id("filter-faceted__typeahead-input"), getDriver()).findWhenNeeded(this);
+ protected final ReactSelect arrayFilterOperatorSelect =
+ new ReactSelect.ReactSelectFinder(getDriver()).index(0).findWhenNeeded(this);
protected final WebElement checkboxSection =
Locator.byClass("labkey-wizard-pills").index(0).refindWhenNeeded(this);
protected final Locator.XPathLocator checkboxLabelLoc
diff --git a/src/org/labkey/test/pages/DatasetInsertPage.java b/src/org/labkey/test/pages/DatasetInsertPage.java
index 4651210bdf..6d61ce56de 100644
--- a/src/org/labkey/test/pages/DatasetInsertPage.java
+++ b/src/org/labkey/test/pages/DatasetInsertPage.java
@@ -22,6 +22,7 @@
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
+import java.util.List;
import java.util.Map;
import static org.labkey.test.util.EscapeUtil.FORM_FIELD_PREFIX;
@@ -45,26 +46,26 @@ protected void waitForReady()
waitForElement(Locator.tag("*").attributeStartsWith("name", FORM_FIELD_PREFIX));
}
- public void insert(Map values)
+ public void insert(Map values)
{
tryInsert(values);
assertElementNotPresent(Locators.labkeyError);
}
- public void insert(Map values, boolean b, String s)
+ public void insert(Map values, boolean b, String s)
{
tryInsert(values);
assertElementNotPresent(Locators.labkeyError);
}
- public void insertExpectingError(Map values)
+ public void insertExpectingError(Map values)
{
insertExpectingError(values, null);
}
- public void insertExpectingError(Map values, String errorMsg)
+ public void insertExpectingError(Map values, String errorMsg)
{
tryInsert(values);
@@ -78,20 +79,20 @@ public void insertExpectingError(Map values, String errorMsg)
}
}
- private void tryInsert(Map values)
+ private void tryInsert(Map values)
{
- for (Map.Entry entry : values.entrySet())
+ for (Map.Entry entry : values.entrySet())
{
- WebElement fieldInput = Locator.name(EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
+ WebElement fieldInput = Locator.tag("*").attributeEndsWith("name", EscapeUtil.getFormFieldName(entry.getKey())).findElement(getDriver());
String type = fieldInput.getAttribute("type");
switch (type)
{
case "text":
case "file":
- setFormElement(fieldInput, entry.getValue());
+ setFormElement(fieldInput, entry.getValue().toString());
break;
case "checkbox":
- if (Boolean.valueOf(entry.getValue()))
+ if (Boolean.valueOf(entry.getValue().toString()))
checkCheckbox(fieldInput);
else
uncheckCheckbox(fieldInput);
@@ -101,10 +102,19 @@ private void tryInsert(Map values)
switch (tag)
{
case "textarea":
- setFormElementJS(fieldInput, entry.getValue());
+ setFormElementJS(fieldInput, entry.getValue().toString());
break;
case "select":
- selectOptionByText(fieldInput, entry.getValue());
+ if (entry.getValue() instanceof List)
+ {
+ List options = (List) entry.getValue();
+ for (String option : options)
+ {
+ selectOptionByText(fieldInput, option);
+ }
+ break;
+ }
+ selectOptionByText(fieldInput, entry.getValue().toString());
break;
default:
throw new IllegalArgumentException("Update " + getClass().getSimpleName() + "#insert() to support field: " + entry.getKey() + ", tag = " + tag + ", type = " + type);
diff --git a/src/org/labkey/test/pages/query/UpdateQueryRowPage.java b/src/org/labkey/test/pages/query/UpdateQueryRowPage.java
index 7c265ebca7..98b87d52bf 100644
--- a/src/org/labkey/test/pages/query/UpdateQueryRowPage.java
+++ b/src/org/labkey/test/pages/query/UpdateQueryRowPage.java
@@ -11,13 +11,16 @@
import org.labkey.test.components.html.Checkbox;
import org.labkey.test.components.html.Input;
import org.labkey.test.components.html.OptionSelect;
+import org.labkey.test.components.html.SelectWrapper;
import org.labkey.test.pages.LabKeyPage;
import org.labkey.test.util.EscapeUtil;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
+import org.openqa.selenium.support.ui.Select;
import java.io.File;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
public class UpdateQueryRowPage extends LabKeyPage
@@ -52,7 +55,7 @@ public static UpdateQueryRowPage beginAt(WebDriverWrapper webDriverWrapper, Stri
public static UpdateQueryRowPage beginAtInsertRowPage(WebDriverWrapper webDriverWrapper, String containerPath, String schemaName, String queryName)
{
webDriverWrapper.beginAt(WebTestHelper.buildURL("query", containerPath, "insertQueryRow",
- Map.of("schemaName", schemaName, "query.queryName", queryName)));
+ Map.of("schemaName", schemaName, "query.queryName", queryName)));
return new UpdateQueryRowPage(webDriverWrapper.getDriver());
}
@@ -87,6 +90,10 @@ else if (value instanceof File f)
{
setField(entry.getKey(), f);
}
+ else if (value instanceof List l)
+ {
+ setField(entry.getKey(), l);
+ }
else
{
throw new IllegalArgumentException("Unsupported value type for '" + entry.getKey() + "': " + value.getClass().getName());
@@ -99,7 +106,7 @@ public UpdateQueryRowPage setField(String fieldName, String value)
WebElement field = elementCache().findField(fieldName);
if (field.getTagName().equals("select"))
{
- setField(fieldName, OptionSelect.SelectOption.textOption(value));
+ selectOptionByText(field, value);
}
else
{
@@ -108,6 +115,14 @@ public UpdateQueryRowPage setField(String fieldName, String value)
return this;
}
+ public UpdateQueryRowPage setField(String fieldName, List values)
+ {
+ Select field = elementCache().getMultiChoiceSelect(fieldName);
+ field.deselectAll();
+ values.forEach(field::selectByVisibleText);
+ return this;
+ }
+
public UpdateQueryRowPage setField(String fieldName, Boolean value)
{
new Checkbox(elementCache().findField(fieldName)).set(value);
@@ -136,12 +151,6 @@ public UpdateQueryRowPage setField(String fieldName, OptionSelect.SelectOption o
return this;
}
- public UpdateQueryRowPage setMultiValueField(String fieldName, OptionSelect.SelectOption option)
- {
- new OptionSelect<>(elementCache().findField(fieldName, true)).selectOption(option);
- return this;
- }
-
public String getTextInputValue(String fieldName)
{
var input = new Input(elementCache().findField(fieldName), getDriver());
@@ -203,5 +212,11 @@ WebElement findField(String name)
}
final WebElement submitButton = Locator.lkButton("Submit").findWhenNeeded(this);
+
+ Select getMultiChoiceSelect(String name)
+ {
+ return SelectWrapper.Select(Locator.name(EscapeUtil.getFormFieldName(name, true)))
+ .find(getDriver());
+ }
}
}
diff --git a/src/org/labkey/test/params/FieldDefinition.java b/src/org/labkey/test/params/FieldDefinition.java
index 9f2b5f0900..d679b19542 100644
--- a/src/org/labkey/test/params/FieldDefinition.java
+++ b/src/org/labkey/test/params/FieldDefinition.java
@@ -475,6 +475,13 @@ public FieldDefinition setTextChoiceValues(List values)
return this;
}
+ public FieldDefinition setMultiChoiceValues(List values)
+ {
+ Assert.assertEquals("Invalid field type for text choice values.", ColumnType.MultiValueTextChoice, getType());
+ setValidators(List.of(new FieldDefinition.TextChoiceValidator(values)));
+ return this;
+ }
+
public ExpSchema.DerivationDataScopeType getAliquotOption()
{
return _aliquotOption;
@@ -611,6 +618,7 @@ public boolean isMeasureByDefault()
ColumnType Sample = new ColumnTypeImpl("Sample", "int", "http://www.labkey.org/exp/xml#sample", new IntLookup( "exp", "Materials"));
ColumnType Barcode = new ColumnTypeImpl("Unique ID", "string", "http://www.labkey.org/types#storageUniqueId", null);
ColumnType TextChoice = new ColumnTypeImpl("Text Choice", "string", "http://www.labkey.org/types#textChoice", null);
+ ColumnType MultiValueTextChoice = new ColumnTypeImpl("Text Choice", "string", "http://cpas.fhcrc.org/exp/xml#multiChoice", null);
ColumnType SMILES = new ColumnTypeImpl("SMILES", "string", "http://www.labkey.org/exp/xml#smiles", null);
ColumnType Calculation = new ColumnTypeImpl("Calculation", null, "http://www.labkey.org/exp/xml#calculated", null);
/**
@@ -1144,7 +1152,6 @@ public List getValues()
}
}
-
}
class ColumnTypeImpl implements FieldDefinition.ColumnType
diff --git a/src/org/labkey/test/tests/list/ListTest.java b/src/org/labkey/test/tests/list/ListTest.java
index 41f8992f59..e581037208 100644
--- a/src/org/labkey/test/tests/list/ListTest.java
+++ b/src/org/labkey/test/tests/list/ListTest.java
@@ -18,6 +18,7 @@
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
+import org.junit.Assume;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
@@ -84,6 +85,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
@@ -1688,6 +1690,42 @@ public void testAutoIncrementKeyEncoded()
_listHelper.deleteList();
}
+ @Test
+ public void testMultiChoiceValues()
+ {
+ Assume.assumeTrue("Multi-choice text fields are only supported on PostgreSQL", WebTestHelper.getDatabaseType() == WebTestHelper.DatabaseType.PostgreSQL);
+ // setup a list with an auto-increment key and multi text choice field
+ String encodedListName = TestDataGenerator.randomDomainName("multiChoiceList", DomainUtils.DomainKind.IntList);
+ String keyName = TestDataGenerator.randomFieldName("'>'");
+ String columnName = TestDataGenerator.randomFieldName("MultiChoiceField");
+ List tcValues = List.of("~`!@#$%^&*()_+=[]{}\\|';:\"<>?,./", "1", "2");
+ _listHelper.createList(PROJECT_VERIFY, encodedListName, keyName, col(columnName, ColumnType.MultiValueTextChoice)
+ .setMultiChoiceValues(tcValues));
+ _listHelper.goToList(encodedListName);
+
+ DataRegionTable table = new DataRegionTable("query", getDriver());
+ UpdateQueryRowPage insertNewRow = table.clickInsertNewRow();
+ List valuesToChoose = tcValues.subList(1, 3);
+ insertNewRow.setField(columnName, valuesToChoose);
+ insertNewRow.submit();
+ String expectedList = valuesToChoose.stream()
+ .sorted()
+ .collect(Collectors.joining(" "));
+ checker().withScreenshot().verifyEquals("Multi choice value not as expected", expectedList, table.getDataAsText(0, columnName));
+
+ UpdateQueryRowPage editRow = table.clickEditRow(0);
+ valuesToChoose = tcValues.subList(1, 3);
+ editRow.setField(columnName, valuesToChoose);
+ editRow.submit();
+ expectedList = valuesToChoose.stream()
+ .sorted()
+ .collect(Collectors.joining(" "));
+ // verify the multi choice value is persisted
+ checker().withScreenshot().verifyEquals("Multi choice value not as expected", expectedList, table.getDataAsText(0, columnName));
+
+ _listHelper.deleteList();
+ }
+
private List getQueryFormFieldNames()
{
return Locator.tag("input").attributeStartsWith("name", "quf_")
diff --git a/src/org/labkey/test/util/TestDataGenerator.java b/src/org/labkey/test/util/TestDataGenerator.java
index 854b2d2952..8b9431a485 100644
--- a/src/org/labkey/test/util/TestDataGenerator.java
+++ b/src/org/labkey/test/util/TestDataGenerator.java
@@ -60,6 +60,7 @@
import java.util.List;
import java.util.Map;
import java.util.Objects;
+import java.util.Random;
import java.util.Set;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Function;
@@ -956,6 +957,12 @@ public ImportDataResponse importRows(Connection cn, List