Repository: OpenRefine/OpenRefine (java)

Issue:
Respect "Batch size" if specified by reconciliation manifest
Reconciliation manifests can sometimes have a [ `batchSize` property set in their manifest](https://reconciliation-api.github.io/specs/0.2/#service-manifest) indicating the maximum number of reconciliation queries in a reconciliation query batch.

OpenRefine currently sends 10 reconciliation queries per batch rather than the value specified by the `batchSize` property. By respecting the `batchSize` property many users would likely see less HTTP-requests and faster reconciliation.

Subset of #2549

Pull request:
#5603 changes the batch size as per condition
Fixes #5603 

Changes proposed in this pull request:
- This tweaks the batchsize to 1% of the dataset if batchsize in service manifest allows for it 
- if there is no batchsize given in the service manifest or 1% of dataset is already less than the 10(the default batch size) we keep the batch size to 10.



Patch:
diff --git a/.github/workflows/snapshot_release.yml b/.github/workflows/snapshot_release.yml
index 91a0fccbc..739af93f8 100644
--- a/.github/workflows/snapshot_release.yml
+++ b/.github/workflows/snapshot_release.yml
@@ -21,7 +21,7 @@ jobs:
           ref: ${{ github.event.pull_request.head.ref }}
           repository: ${{ github.event.pull_request.head.repo.full_name }}
       - name: Setup Node
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           node-version: '14'
       - id: set-matrix
@@ -56,7 +56,7 @@ jobs:
         run: ./refine build
 
       - name: Setup Node
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           node-version: '14'
 
@@ -254,7 +254,7 @@ jobs:
     - name: Upload release asset for Windows (without Java)
       id: upload-release-asset-win
       if: github.event_name == 'release'
-      uses: shogo82148/actions-upload-release-asset@v1.6.4
+      uses: shogo82148/actions-upload-release-asset@v1.6.5
       with:
         upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }}
         asset_path: ./packaging/target/openrefine-win-${{ steps.version_string_variable.outputs.output_value }}.zip
@@ -271,7 +271,7 @@ jobs:
     - name: Upload release asset for Windows (with Java)
       id: upload-release-asset-win-with-java
       if: github.event_name == 'release'
-      uses: shogo82148/actions-upload-release-asset@v1.6.4
+      uses: shogo82148/actions-upload-release-asset@v1.6.5
       with:
         upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }}
         asset_path: ./packaging/target/openrefine-win-with-java-${{ steps.version_string_variable.outputs.output_value }}.zip
@@ -299,7 +299,7 @@ jobs:
     - name: Upload Release Asset Linux
       id: upload-release-asset-linux
       if: github.event_name == 'release'
-      uses: shogo82148/actions-upload-release-asset@v1.6.4
+      uses: shogo82148/actions-upload-release-asset@v1.6.5
       with:
         upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }}
         asset_path: ./packaging/target/openrefine-linux-${{ steps.version_string_variable.outputs.output_value }}.tar.gz
@@ -316,7 +316,7 @@ jobs:
     - name: Upload Release Asset Mac
       id: upload-release-asset-mac
       if: github.event_name == 'release'
-      uses: shogo82148/actions-upload-release-asset@v1.6.4
+      uses: shogo82148/actions-upload-release-asset@v1.6.5
       with:
         upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }}
         asset_path: ./packaging/target/openrefine-mac-${{ steps.version_string_variable.outputs.output_value }}.dmg
@@ -403,7 +403,7 @@ jobs:
     - name: Upload release asset for Windows (with Java)
       id: upload-release-asset-win-with-java-installer
       if: github.event_name == 'release'
-      uses: shogo82148/actions-upload-release-asset@v1.6.4
+      uses: shogo82148/actions-upload-release-asset@v1.6.5
       with:
         upload_url: ${{ steps.get_release_upload_url.outputs.upload_url }}
         asset_path: ./packaging/target/openrefine-win-with-java-installer-${{ needs.mac_test_and_deploy.outputs.version_number_string }}.exe
diff --git a/benchmark/pom.xml b/benchmark/pom.xml
index c9f3cd13d..f2515e161 100644
--- a/benchmark/pom.xml
+++ b/benchmark/pom.xml
@@ -48,7 +48,7 @@
 
   <properties>
     <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
-    <jmh.version>1.36</jmh.version>
+    <jmh.version>1.37</jmh.version>
     <javac.target>${java.minversion}</javac.target>
     <uberjar.name>openrefine-benchmarks</uberjar.name>
   </properties>
diff --git a/extensions/wikibase/module/scripts/dialogs/schema-list-item.html b/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
index 8ce783b1e..334f625b5 100644
--- a/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
+++ b/extensions/wikibase/module/scripts/dialogs/schema-list-item.html
@@ -2,7 +2,7 @@
     <div class="wikibase-template-info">
         <span class="wikibase-name" bind="templateName"></span>
     </div>
-    <div class="wikibase-template-actions">
+    <div class="wikibase-template-actions" style="float: right;">
         <a bind="renameTemplate"></a> | 
         <a bind="downloadTemplate"></a> |
         <a bind="deleteTemplate"></a>
diff --git a/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css b/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
index 01f4618e0..890e3fec2 100644
--- a/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
+++ b/extensions/wikibase/module/styles/dialogs/schema-management-dialog.css
@@ -1,5 +1,6 @@
 .schema-template-list {
-  list-style: none;
+  list-style: decimal;
+  list-style-position: inside;
   max-height: 450px;
   overflow-y: auto;
 }
@@ -10,8 +11,7 @@
 
 .schema-template-list li {
   padding: .5em;
-  display: flex;
-  justify-content: space-between;
+  display: list-item;
 }
 
 .schema-template-list li:nth-child(odd) {
diff --git a/main/src/com/google/refine/expr/functions/Length.java b/main/src/com/google/refine/expr/functions/Length.java
index d7fde5db9..6ca73c740 100644
--- a/main/src/com/google/refine/expr/functions/Length.java
+++ b/main/src/com/google/refine/expr/functions/Length.java
@@ -37,6 +37,8 @@ import java.util.Collection;
 import java.util.Properties;
 
 import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
 import com.google.refine.expr.EvalError;
 import com.google.refine.expr.HasFieldsList;
 import com.google.refine.grel.ControlFunctionRegistry;
@@ -61,6 +63,8 @@ public class Length implements Function {
                     return ((HasFieldsList) v).length();
                 } else if (v instanceof ArrayNode) {
                     return ((ArrayNode) v).size();
+                } else if (v instanceof ObjectNode) {
+                    return ((ObjectNode) v).size();
                 } else {
                     String s = (v instanceof String ? (String) v : v.toString());
                     return s.length();
diff --git a/main/src/com/google/refine/importers/WikitextImporter.java b/main/src/com/google/refine/importers/WikitextImporter.java
index c230d796a..f87d95dcd 100644
--- a/main/src/com/google/refine/importers/WikitextImporter.java
+++ b/main/src/com/google/refine/importers/WikitextImporter.java
@@ -775,6 +775,7 @@ public class WikitextImporter extends TabularImportingParserBase {
                 "",
                 "entity",
                 true,
+                10,
                 new ArrayList<ColumnDetail>(),
                 1);
         return cfg;
diff --git a/main/src/com/google/refine/model/Row.java b/main/src/com/google/refine/model/Row.java
index b1d2f295f..9eaf291a1 100644
--- a/main/src/com/google/refine/model/Row.java
+++ b/main/src/com/google/refine/model/Row.java
@@ -80,7 +80,7 @@ public class Row implements HasFields {
 
     /**
      * Copy a row and return the copy. Note that this is a shallow copy, so if the contents of cells are changed in the
-     * original, they will be be changed in the duplicate.
+     * original, they will be changed in the duplicate.
      * 
      * @return the duplicated row
      */
diff --git a/main/src/com/google/refine/model/changes/MassRowChange.java b/main/src/com/google/refine/model/changes/MassRowChange.java
index 860495485..d79ac1f0e 100644
--- a/main/src/com/google/refine/model/changes/MassRowChange.java
+++ b/main/src/com/google/refine/model/changes/MassRowChange.java
@@ -46,6 +46,8 @@ import com.google.refine.model.Project;
 import com.google.refine.model.Row;
 import com.google.refine.util.Pool;
 
+// TODO: This replaces all rows in the project with a new set of rows, but if only a small percentage of the rows
+// are being changed, it'd be much more efficient to apply a change list of new/modified rows.
 public class MassRowChange implements Change {
 
     final protected List<Row> _newRows;
diff --git a/main/src/com/google/refine/model/recon/DataExtensionReconConfig.java b/main/src/com/google/refine/model/recon/DataExtensionReconConfig.java
index da68bc946..f961d0119 100644
--- a/main/src/com/google/refine/model/recon/DataExtensionReconConfig.java
+++ b/main/src/com/google/refine/model/recon/DataExtensionReconConfig.java
@@ -53,13 +53,7 @@ public class DataExtensionReconConfig extends StandardReconConfig {
             String identifierSpace,
             String schemaSpace,
             ReconType type) {
-        super(
-                service,
-                identifierSpace,
-                schemaSpace,
-                type != null ? type.id : null,
-                type != null ? type.name : null,
-                true,
+        super(service, identifierSpace, schemaSpace, type != null ? type.id : null, type != null ? type.name : null, true, 10,
                 new ArrayList<ColumnDetail>());
         this.type = type;
     }
@@ -71,7 +65,7 @@ public class DataExtensionReconConfig extends StandardReconConfig {
     }
 
     @Override
-    public int getBatchSize() {
+    public int getBatchSize(int rowCount) {
         throw new RuntimeException(WARN);
     }
 
diff --git a/main/src/com/google/refine/model/recon/ReconConfig.java b/main/src/com/google/refine/model/recon/ReconConfig.java
index 2d14fde3a..0bc18dd4b 100644
--- a/main/src/com/google/refine/model/recon/ReconConfig.java
+++ b/main/src/com/google/refine/model/recon/ReconConfig.java
@@ -99,7 +99,8 @@ abstract public class ReconConfig {
         return ParsingUtilities.mapper.readValue(json, ReconConfig.class);
     }
 
-    abstract public int getBatchSize();
+    @JsonIgnore
+    public abstract int getBatchSize(int rowCount);
 
     abstract public String getBriefDescription(Project project, String columnName);
 
diff --git a/main/src/com/google/refine/model/recon/StandardReconConfig.java b/main/src/com/google/refine/model/recon/StandardReconConfig.java
index 35cd7c4c5..88354f623 100644
--- a/main/src/com/google/refine/model/recon/StandardReconConfig.java
+++ b/main/src/com/google/refine/model/recon/StandardReconConfig.java
@@ -143,6 +143,8 @@ public class StandardReconConfig extends ReconConfig {
     final public String typeName;
     @JsonProperty("autoMatch")
     final public boolean autoMatch;
+    @JsonProperty("batchSize")
+    final public int batchSize;
     @JsonProperty("columnDetails")
     final public List<ColumnDetail> columnDetails;
     @JsonProperty("limit")
@@ -158,12 +160,13 @@ public class StandardReconConfig extends ReconConfig {
             @JsonProperty("schemaSpace") String schemaSpace,
             @JsonProperty("type") ReconType type,
             @JsonProperty("autoMatch") boolean autoMatch,
+            @JsonProperty("batchSize") int batchSize,
             @JsonProperty("columnDetails") List<ColumnDetail> columnDetails,
             @JsonProperty("limit") int limit) {
         this(service, identifierSpace, schemaSpace,
                 type != null ? type.id : null,
                 type != null ? type.name : null,
-                autoMatch, columnDetails, limit);
+                autoMatch, batchSize, columnDetails, limit);
     }
 
     public StandardReconConfig(
@@ -174,8 +177,9 @@ public class StandardReconConfig extends ReconConfig {
             String typeID,
             String typeName,
             boolean autoMatch,
+            int batchSize,
             List<ColumnDetail> columnDetails) {
-        this(service, identifierSpace, schemaSpace, typeID, typeName, autoMatch, columnDetails, 0);
+        this(service, identifierSpace, schemaSpace, typeID, typeName, autoMatch, batchSize, columnDetails, 0);
     }
 
     /**
@@ -196,6 +200,7 @@ public class StandardReconConfig extends ReconConfig {
             String typeID,
             String typeName,
             boolean autoMatch,
+            int batchSize,
             List<ColumnDetail> columnDetails,
             int limit) {
         this.service = service;
@@ -205,6 +210,7 @@ public class StandardReconConfig extends ReconConfig {
         this.typeID = typeID;
         this.typeName = typeName;
         this.autoMatch = autoMatch;
+        this.batchSize = batchSize;
         this.columnDetails = columnDetails;
         this.limit = limit;
     }
@@ -220,8 +226,8 @@ public class StandardReconConfig extends ReconConfig {
 
     @Override
     @JsonIgnore
-    public int getBatchSize() {
-        return 10;
+    public int getBatchSize(int rowCount) {
+        return Math.min(Math.max(rowCount / 10, 10), batchSize);
     }
 
     @Override
diff --git a/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java b/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
index da0d93cb3..f80d9bec8 100644
--- a/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
+++ b/main/src/com/google/refine/operations/cell/MultiValuedCellSplitOperation.java
@@ -1,6 +1,6 @@
 /*
 
-Copyright 2010, Google Inc.
+Copyright 2010, 2023 Google Inc. & OpenRefine contributors
 All rights reserved.
 
 Redistribution and use in source and binary forms, with or without
@@ -59,6 +59,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
     final protected String _mode;
     final protected String _separator;
     final protected Boolean _regex;
+    private final Pattern _pattern;
 
     final protected int[] _fieldLengths;
 
@@ -94,6 +95,11 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
         _separator = separator;
         _mode = "separator";
         _regex = regex;
+        if (_regex) {
+            _pattern = Pattern.compile(_separator, Pattern.UNICODE_CHARACTER_CLASS);
+        } else {
+            _pattern = null;
+        }
 
         _fieldLengths = null;
     }
@@ -108,7 +114,14 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
         _mode = "lengths";
         _separator = null;
         _regex = null;
+        _pattern = null;
 
+        // Make sure all of our lengths are non-negative
+        for (int i = 0; i < fieldLengths.length; i++) {
+            if (fieldLengths[i] < 0) {
+                fieldLengths[i] = 0;
+            }
+        }
         _fieldLengths = fieldLengths;
     }
 
@@ -176,45 +189,39 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
 
             Object value = oldRow.getCellValue(cellIndex);
             String s = value instanceof String ? ((String) value) : value.toString();
-            String[] values = null;
+            String[] values;
             if ("lengths".equals(_mode)) {
                 if (_fieldLengths.length > 0 && _fieldLengths[0] > 0) {
                     values = new String[_fieldLengths.length];
 
-                    int lastIndex = 0;
-
+                    int from = 0;
+                    int end = s.length();
                     for (int i = 0; i < _fieldLengths.length; i++) {
-                        int thisIndex = lastIndex;
-
-                        Object o = _fieldLengths[i];
-                        if (o instanceof Number) {
-                            thisIndex = Math.min(s.length(), lastIndex + Math.max(0, ((Number) o).intValue()));
-                        }
-
-                        values[i] = s.substring(lastIndex, thisIndex);
-                        lastIndex = thisIndex;
+                        int to = Math.min(end, from + _fieldLengths[i]);
+                        values[i] = s.substring(from, to);
+                        from = to;
                     }
+                } else {
+                    values = new String[] { s };
                 }
             } else if (_regex) {
-                Pattern pattern = Pattern.compile(_separator, Pattern.UNICODE_CHARACTER_CLASS);
-                values = pattern.split(s);
+                values = _pattern.split(s);
             } else {
                 values = StringUtils.splitByWholeSeparatorPreserveAllTokens(s, _separator);
             }
 
+            // Split didn't change anything. Just copy the row
             if (values.length < 2) {
                 newRows.add(oldRow.dup());
                 continue;
             }
 
-            // First value goes into the same row
-            {
-                Row firstNewRow = oldRow.dup();
-                firstNewRow.setCell(cellIndex, new Cell(values[0], null));
-
-                newRows.add(firstNewRow);
-            }
+            // First newly split value goes into the original cell in the existing row
+            Row firstNewRow = oldRow.dup();
+            firstNewRow.setCell(cellIndex, new Cell(values[0], null));
+            newRows.add(firstNewRow);
 
+            // For remaining values, use an empty cell, if one exists in the row, otherwise allocate a new row
             int r2 = r + 1;
             for (int v = 1; v < values.length; v++) {
                 Cell newCell = new Cell(values[v], null);
@@ -222,6 +229,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
                 if (r2 < project.rows.size()) {
                     Row oldRow2 = project.rows.get(r2);
                     if (oldRow2.isCellBlank(cellIndex) &&
+                    // key cell not blank means we are on next record
                             oldRow2.isCellBlank(keyCellIndex)) {
 
                         Row newRow = oldRow2.dup();
@@ -234,6 +242,7 @@ public class MultiValuedCellSplitOperation extends AbstractOperation {
                     }
                 }
 
+                // We need a new (empty) row
                 Row newRow = new Row(cellIndex + 1);
                 newRow.setCell(cellIndex, newCell);
 
diff --git a/main/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperation.java b/main/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperation.java
index f6c434c3c..3b22766ba 100644
--- a/main/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperation.java
+++ b/main/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperation.java
@@ -129,6 +129,7 @@ public class ReconMarkNewTopicsOperation extends EngineDependentMassCellOperatio
                         _schemaSpace,
                         null,
                         false,
+                        10,
                         Collections.emptyList(),
                         0);
     }
diff --git a/main/src/com/google/refine/operations/recon/ReconOperation.java b/main/src/com/google/refine/operations/recon/ReconOperation.java
index 6214c741a..023682588 100644
--- a/main/src/com/google/refine/operations/recon/ReconOperation.java
+++ b/main/src/com/google/refine/operations/recon/ReconOperation.java
@@ -261,7 +261,7 @@ public class ReconOperation extends EngineDependentOperation {
                 group.entries.add(entry);
             }
 
-            int batchSize = _reconConfig.getBatchSize();
+            int batchSize = _reconConfig.getBatchSize(_project.rows.size());
             int done = 0;
 
             List<CellChange> cellChanges = new ArrayList<CellChange>(_entries.size());
diff --git a/main/src/com/google/refine/operations/recon/ReconUseValuesAsIdentifiersOperation.java b/main/src/com/google/refine/operations/recon/ReconUseValuesAsIdentifiersOperation.java
index 2471351e2..46051d74e 100644
--- a/main/src/com/google/refine/operations/recon/ReconUseValuesAsIdentifiersOperation.java
+++ b/main/src/com/google/refine/operations/recon/ReconUseValuesAsIdentifiersOperation.java
@@ -73,7 +73,7 @@ public class ReconUseValuesAsIdentifiersOperation extends EngineDependentMassCel
         this.service = service;
         this.identifierSpace = identifierSpace;
         this.schemaSpace = schemaSpace;
-        this.reconConfig = new StandardReconConfig(service, identifierSpace, schemaSpace, null, null, true, Collections.emptyList());
+        this.reconConfig = new StandardReconConfig(service, identifierSpace, schemaSpace, null, null, true, 10, Collections.emptyList());
     }
 
     @Override
diff --git a/main/webapp/modules/core/scripts/reconciliation/standard-service-panel.js b/main/webapp/modules/core/scripts/reconciliation/standard-service-panel.js
index 0ea4d6d42..d26a9ec16 100644
--- a/main/webapp/modules/core/scripts/reconciliation/standard-service-panel.js
+++ b/main/webapp/modules/core/scripts/reconciliation/standard-service-panel.js
@@ -345,6 +345,7 @@ ReconStandardServicePanel.prototype.start = function() {
         schemaSpace: this._service.schemaSpace,
         type: (type) ? { id: type.id, name: type.name } : null,
         autoMatch: this._elmts.automatchCheck[0].checked,
+        batchSize: (Number.isInteger(this._service.batchSize) && this._service.batchSize > 0) ? this._service.batchSize : 10,
         columnDetails: columnDetails,
         limit: parseInt(this._elmts.maxCandidates[0].value) || 0
       })
@@ -362,4 +363,4 @@ ReconStandardServicePanel.prototype.showBusyReconciling = function(message) {
   return function() {
     $(body).remove()
   };
-};
\ No newline at end of file
+};
diff --git a/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js b/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
index f2c2625ce..087b0922a 100644
--- a/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
+++ b/main/webapp/modules/core/scripts/views/data-table/menu-edit-cells.js
@@ -362,14 +362,14 @@ DataTableColumnHeaderUI.extendMenu(function(column, columnHeaderUI, menu) {
           return;
         }
       } else if (mode === "cases") {
-        if(elmts.reversTranistionCases[0].checked) {
+        if(elmts.reverseTransitionCases[0].checked) {
           config.separator = "(?<=\\p{Upper}|[\\p{Upper}][\\s])(?=\\p{Lower})";
         } else {
           config.separator = "(?<=\\p{Lower}|[\\p{Lower}][\\s])(?=\\p{Upper})";
         }
         config.regex = true;
       } else if (mode === "number") {
-        if(elmts.reversTranistionNumbers[0].checked) {
+        if(elmts.reverseTransitionNumbers[0].checked) {
           config.separator = "(?<=\\p{L}|[\\p{L}][\\s])(?=\\p{Digit})";
         } else {
           config.separator = "(?<=\\p{Digit}|[\\p{Digit}][\\s])(?=\\p{L})";
diff --git a/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html b/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
index 72942f18a..f544d3bd5 100644
--- a/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
+++ b/main/webapp/modules/core/scripts/views/data-table/split-multi-valued-cells-dialog.html
@@ -31,7 +31,7 @@
             <tr>
               <td width="1%"><input type="radio" name="split-by-mode" value="cases" id="$split-multi-valued-cells-by-case" /></td>
               <td colspan="2"><label for="$split-multi-valued-cells-by-case" bind="or_views_byCase"></label></td>
-              <td><input type="checkbox" bind="reversTranistionCases" id="$split-cell-transition-order-case" />
+              <td><input type="checkbox" bind="reverseTransitionCases" id="$split-cell-transition-order-case" />
               <label for="$split-cell-transition-order-case" bind="or_views_revCase"></label></td>
             </tr>
             <tr>
@@ -42,7 +42,7 @@
             <tr>
               <td width="1%"><input type="radio" name="split-by-mode" value="number" id="$split-multi-valued-cells-by-number" /></td>
               <td colspan="2"><label for="$split-multi-valued-cells-by-number" bind="or_views_byNumber"></label></td>
-              <td><input type="checkbox" bind="reversTranistionNumbers" id="$split-cell-transition-order-number" />
+              <td><input type="checkbox" bind="reverseTransitionNumbers" id="$split-cell-transition-order-number" />
               <label for="$split-cell-transition-order-number" bind="or_views_revNum"></label></td>
             </tr>
             <tr>
@@ -59,4 +59,4 @@
       <button class="button" bind="cancelButton"></button>
     </div>
   </div>
-</div>
\ No newline at end of file
+</div>
diff --git a/pom.xml b/pom.xml
index 7522c027c..4f5ef1c2d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -81,7 +81,7 @@
     <okhttp.version>4.10.0</okhttp.version>
     <jena.version>4.8.0</jena.version>
     <poi.version>5.2.3</poi.version>
-    <commons-lang3.version>3.12.0</commons-lang3.version>
+    <commons-lang3.version>3.13.0</commons-lang3.version>
     <commons-fileupload.version>1.5</commons-fileupload.version>
     <commons-collections.version>3.2.2</commons-collections.version>
     <commons-text.version>1.10.0</commons-text.version>


Test patch:
diff --git a/.github/workflows/pull_request_e2e.yml b/.github/workflows/pull_request_e2e.yml
index 3ef38c771..1a466cc41 100644
--- a/.github/workflows/pull_request_e2e.yml
+++ b/.github/workflows/pull_request_e2e.yml
@@ -24,7 +24,7 @@ jobs:
     steps:
       - uses: actions/checkout@v3
       - name: Setup Node
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           node-version: '14'
       - id: set-matrix
@@ -56,7 +56,7 @@ jobs:
         run: ./refine build
 
       - name: Setup Node
-        uses: actions/setup-node@v3.6.0
+        uses: actions/setup-node@v3.7.0
         with:
           node-version: '14'
 
diff --git a/extensions/wikibase/tests/src/org/openrefine/wikibase/editing/NewEntityLibraryTest.java b/extensions/wikibase/tests/src/org/openrefine/wikibase/editing/NewEntityLibraryTest.java
index 00d61004d..debf7e35a 100644
--- a/extensions/wikibase/tests/src/org/openrefine/wikibase/editing/NewEntityLibraryTest.java
+++ b/extensions/wikibase/tests/src/org/openrefine/wikibase/editing/NewEntityLibraryTest.java
@@ -60,7 +60,7 @@ public class NewEntityLibraryTest extends WikidataRefineTest {
     public void testUpdateReconciledCells() {
         Project project = createCSVProject(TestingData.inceptionWithNewCsv);
         StandardReconConfig config = new StandardReconConfig("http://my.endpoint",
-                "http://my.schema", "http://my.schema", "Q5", "human", true, Collections.emptyList());
+                "http://my.schema", "http://my.schema", "Q5", "human", true, 10, Collections.emptyList());
         project.columnModel.columns.get(0).setReconConfig(config);
 
         project.rows.get(0).cells.set(0, TestingData.makeNewItemCell(3289L, "University of Ljubljana"));
diff --git a/main/tests/cypress/package.json b/main/tests/cypress/package.json
index c7c278064..4894ef37c 100644
--- a/main/tests/cypress/package.json
+++ b/main/tests/cypress/package.json
@@ -16,14 +16,14 @@
         "lint": "prettier --check . && eslint ."
     },
     "dependencies": {
-        "cypress": "12.17.2",
+        "cypress": "12.17.3",
         "cypress-file-upload": "^5.0.8"
     },
     "devDependencies": {
-        "eslint": "^8.45.0",
+        "eslint": "^8.46.0",
         "eslint-config-google": "^0.14.0",
-        "eslint-config-prettier": "^8.8.0",
+        "eslint-config-prettier": "^8.10.0",
         "eslint-plugin-cypress": "^2.13.3",
-        "prettier": "3.0.0"
+        "prettier": "3.0.1"
     }
 }
diff --git a/main/tests/cypress/yarn.lock b/main/tests/cypress/yarn.lock
index 41d013ac6..3e5967306 100644
--- a/main/tests/cypress/yarn.lock
+++ b/main/tests/cypress/yarn.lock
@@ -46,15 +46,15 @@
   dependencies:
     eslint-visitor-keys "^3.3.0"
 
-"@eslint-community/regexpp@^4.4.0":
-  version "4.4.0"
-  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.4.0.tgz#3e61c564fcd6b921cb789838631c5ee44df09403"
-  integrity sha512-A9983Q0LnDGdLPjxyXQ00sbV+K+O+ko2Dr+CZigbHWtX9pNfxlaBkMR8X1CztI73zuEyEBXTVjx7CE+/VSwDiQ==
+"@eslint-community/regexpp@^4.6.1":
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.6.2.tgz#1816b5f6948029c5eaacb0703b850ee0cb37d8f8"
+  integrity sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==
 
-"@eslint/eslintrc@^2.1.0":
-  version "2.1.0"
-  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d"
-  integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==
+"@eslint/eslintrc@^2.1.1":
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.1.tgz#18d635e24ad35f7276e8a49d135c7d3ca6a46f93"
+  integrity sha512-9t7ZA7NGGK8ckelF0PQCfcxIUzs1Md5rrO6U/c+FIQNanea5UZC0wqKXH4vHBccmu4ZJgZ2idtPeW7+Q2npOEA==
   dependencies:
     ajv "^6.12.4"
     debug "^4.3.2"
@@ -66,10 +66,10 @@
     minimatch "^3.1.2"
     strip-json-comments "^3.1.1"
 
-"@eslint/js@8.44.0":
-  version "8.44.0"
-  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
-  integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
+"@eslint/js@^8.46.0":
+  version "8.46.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.46.0.tgz#3f7802972e8b6fe3f88ed1aabc74ec596c456db6"
+  integrity sha512-a8TLtmPi8xzPkCbp/OGFUo5yhRkHM2Ko9kOWP4znJr0WAhWyThaw3PnwX4vOTWOAMsV2uRt32PPDcEz63esSaA==
 
 "@humanwhocodes/config-array@^0.11.10":
   version "0.11.10"
@@ -116,15 +116,10 @@
   resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz"
   integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==
 
-"@types/node@*":
-  version "15.12.4"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-15.12.4.tgz#e1cf817d70a1e118e81922c4ff6683ce9d422e26"
-  integrity sha512-zrNj1+yqYF4WskCMOHwN+w9iuD12+dGm0rQ35HLl9/Ouuq52cEtd0CH9qMgrdNmi5ejC1/V7vKEXYubB+65DkA==
-
-"@types/node@^14.14.31":
-  version "14.14.37"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-14.14.37.tgz#a3dd8da4eb84a996c36e331df98d82abd76b516e"
-  integrity sha512-XYmBiy+ohOR4Lh5jE379fV2IU+6Jn4g5qASinhitfyO71b/sCo6MKsMLF5tc7Zf2CE8hViVQyYSobJNke8OvUw==
+"@types/node@*", "@types/node@^16.18.39":
+  version "16.18.39"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.39.tgz#aa39a1a87a40ef6098ee69689a1acb0c1b034832"
+  integrity sha512-8q9ZexmdYYyc5/cfujaXb4YOucpQxAV4RMG0himLyDUOEr8Mr79VrqsFI+cQ2M2h89YIuy95lbxuYjxT4Hk4kQ==
 
 "@types/sinonjs__fake-timers@8.1.1":
   version "8.1.1"
@@ -161,7 +156,7 @@ aggregate-error@^3.0.0:
     clean-stack "^2.0.0"
     indent-string "^4.0.0"
 
-ajv@^6.10.0, ajv@^6.12.4:
+ajv@^6.12.4:
   version "6.12.6"
   resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz"
   integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -436,14 +431,14 @@ cypress-file-upload@^5.0.8:
   resolved "https://registry.yarnpkg.com/cypress-file-upload/-/cypress-file-upload-5.0.8.tgz#d8824cbeaab798e44be8009769f9a6c9daa1b4a1"
   integrity sha512-+8VzNabRk3zG6x8f8BWArF/xA/W0VK4IZNx3MV0jFWrJS/qKn8eHfa5nU73P9fOQAgwHFJx7zjg4lwOnljMO8g==
 
-cypress@12.17.2:
-  version "12.17.2"
-  resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.2.tgz#040ac55de1e811f6e037d231a2869d5ab8c29c85"
-  integrity sha512-hxWAaWbqQBzzMuadSGSuQg5PDvIGOovm6xm0hIfpCVcORsCAj/gF2p0EvfnJ4f+jK2PCiDgP6D2eeE9/FK4Mjg==
+cypress@12.17.3:
+  version "12.17.3"
+  resolved "https://registry.yarnpkg.com/cypress/-/cypress-12.17.3.tgz#1e2b19037236fc60e4a4b3a14f0846be17a1fc0e"
+  integrity sha512-/R4+xdIDjUSLYkiQfwJd630S81KIgicmQOLXotFxVXkl+eTeVO+3bHXxdi5KBh/OgC33HWN33kHX+0tQR/ZWpg==
   dependencies:
     "@cypress/request" "^2.88.11"
     "@cypress/xvfb" "^1.2.4"
-    "@types/node" "^14.14.31"
+    "@types/node" "^16.18.39"
     "@types/sinonjs__fake-timers" "8.1.1"
     "@types/sizzle" "^2.3.2"
     arch "^2.2.0"
@@ -569,10 +564,10 @@ eslint-config-google@^0.14.0:
   resolved "https://registry.yarnpkg.com/eslint-config-google/-/eslint-config-google-0.14.0.tgz"
   integrity sha512-WsbX4WbjuMvTdeVL6+J3rK1RGhCTqjsFjX7UMSMgZiyxxaNLkoJENbrGExzERFeoTpGw3F3FypTiWAP9ZXzkEw==
 
-eslint-config-prettier@^8.8.0:
-  version "8.8.0"
-  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.8.0.tgz#bfda738d412adc917fd7b038857110efe98c9348"
-  integrity sha512-wLbQiFre3tdGgpDv67NQKnJuTlcUVYHas3k+DZCc2U2BadthoEY4B7hLPvAxaqdyOGCzuLfii2fqGph10va7oA==
+eslint-config-prettier@^8.10.0:
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.10.0.tgz#3a06a662130807e2502fc3ff8b4143d8a0658e11"
+  integrity sha512-SM8AMJdeQqRYT9O9zguiruQZaN7+z+E4eAP9oiLNGKMtomwaB1E9dcgUD6ZAn/eQAb52USbvezbiljfZUhbJcg==
 
 eslint-plugin-cypress@^2.13.3:
   version "2.13.3"
@@ -581,40 +576,40 @@ eslint-plugin-cypress@^2.13.3:
   dependencies:
     globals "^11.12.0"
 
-eslint-scope@^7.2.0:
-  version "7.2.0"
-  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
-  integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
+eslint-scope@^7.2.2:
+  version "7.2.2"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f"
+  integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==
   dependencies:
     esrecurse "^4.3.0"
     estraverse "^5.2.0"
 
-eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
-  version "3.4.1"
-  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
-  integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.2:
+  version "3.4.2"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.2.tgz#8c2095440eca8c933bedcadf16fefa44dbe9ba5f"
+  integrity sha512-8drBzUEyZ2llkpCA67iYrgEssKDUu68V8ChqqOfFupIaG/LCVPUT+CoGJpT77zJprs4T/W7p07LP7zAIMuweVw==
 
-eslint@^8.45.0:
-  version "8.45.0"
-  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.45.0.tgz#bab660f90d18e1364352c0a6b7c6db8edb458b78"
-  integrity sha512-pd8KSxiQpdYRfYa9Wufvdoct3ZPQQuVuU5O6scNgMuOMYuxvH0IGaYK0wUFjo4UYYQQCUndlXiMbnxopwvvTiw==
+eslint@^8.46.0:
+  version "8.46.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.46.0.tgz#a06a0ff6974e53e643acc42d1dcf2e7f797b3552"
+  integrity sha512-cIO74PvbW0qU8e0mIvk5IV3ToWdCq5FYG6gWPHHkx6gNdjlbAYvtfHmlCMXxjcoVaIdwy/IAt3+mDkZkfvb2Dg==
   dependencies:
     "@eslint-community/eslint-utils" "^4.2.0"
-    "@eslint-community/regexpp" "^4.4.0"
-    "@eslint/eslintrc" "^2.1.0"
-    "@eslint/js" "8.44.0"
+    "@eslint-community/regexpp" "^4.6.1"
+    "@eslint/eslintrc" "^2.1.1"
+    "@eslint/js" "^8.46.0"
     "@humanwhocodes/config-array" "^0.11.10"
     "@humanwhocodes/module-importer" "^1.0.1"
     "@nodelib/fs.walk" "^1.2.8"
-    ajv "^6.10.0"
+    ajv "^6.12.4"
     chalk "^4.0.0"
     cross-spawn "^7.0.2"
     debug "^4.3.2"
     doctrine "^3.0.0"
     escape-string-regexp "^4.0.0"
-    eslint-scope "^7.2.0"
-    eslint-visitor-keys "^3.4.1"
-    espree "^9.6.0"
+    eslint-scope "^7.2.2"
+    eslint-visitor-keys "^3.4.2"
+    espree "^9.6.1"
     esquery "^1.4.2"
     esutils "^2.0.2"
     fast-deep-equal "^3.1.3"
@@ -637,10 +632,10 @@ eslint@^8.45.0:
     strip-ansi "^6.0.1"
     text-table "^0.2.0"
 
-espree@^9.6.0:
-  version "9.6.0"
-  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"
-  integrity sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==
+espree@^9.6.0, espree@^9.6.1:
+  version "9.6.1"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f"
+  integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==
   dependencies:
     acorn "^8.9.0"
     acorn-jsx "^5.3.2"
@@ -1312,10 +1307,10 @@ prelude-ls@^1.2.1:
   resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz"
   integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
 
-prettier@3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.0.tgz#e7b19f691245a21d618c68bc54dc06122f6105ae"
-  integrity sha512-zBf5eHpwHOGPC47h0zrPyNn+eAEIdEzfywMoYn2XPi0P44Zp0tSq64rq0xAREh4auw2cJZHo9QUob+NqCQky4g==
+prettier@3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.0.1.tgz#65271fc9320ce4913c57747a70ce635b30beaa40"
+  integrity sha512-fcOWSnnpCrovBsmFZIGIy9UqK2FaI7Hqax+DIO0A9UxeVoY4iweyaFjS5TavZN97Hfehph0nhsZnjlVKzEQSrQ==
 
 pretty-bytes@^5.6.0:
   version "5.6.0"
diff --git a/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java b/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java
index 0504d922d..722dd1897 100644
--- a/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java
+++ b/main/tests/server/src/com/google/refine/commands/recon/ReconJudgeOneCellCommandTest.java
@@ -74,7 +74,7 @@ public class ReconJudgeOneCellCommandTest extends RefineTest {
                 "http://my.recon.service/rdf/schema",
                 "type3894",
                 "octopus",
-                true,
+                true, 10,
                 Collections.emptyList(),
                 5);
         reconciled.setReconConfig(config);
diff --git a/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java b/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
index 3e04d14d6..14bc957bd 100644
--- a/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
+++ b/main/tests/server/src/com/google/refine/expr/functions/LengthTests.java
@@ -27,9 +27,35 @@
 
 package com.google.refine.expr.functions;
 
+import static org.testng.Assert.assertEquals;
+
+import java.util.Properties;
+
 import org.testng.annotations.Test;
 
-import com.google.refine.util.TestUtils;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
+import com.google.refine.RefineTest;
+import com.google.refine.grel.Function;
+import com.google.refine.util.ParsingUtilities;
+
+public class LengthTests extends RefineTest {
+
+    static Properties bindings = new Properties();
+
+    @Test
+    public void testLength() throws JsonProcessingException {
+        Function f = new Length();
+        assertEquals(f.call(bindings, new Object[] { Long.valueOf(123) }), 3);
+        assertEquals(f.call(bindings, new Object[] { Double.valueOf(123.1) }), 5);
+        assertEquals(f.call(bindings, new Object[] { "12345" }), 5);
+
+        ArrayNode array = (ArrayNode) ParsingUtilities.mapper.readTree("[1,2,3]");
+        assertEquals(f.call(bindings, new Object[] { array }), 3);
 
-public class LengthTests {
+        ObjectNode dict = (ObjectNode) ParsingUtilities.mapper.readTree("{\"a\": \"b\", \"c\": \"d\"}");
+        assertEquals(f.call(bindings, new Object[] { dict }), 2);
+    }
 }
diff --git a/main/tests/server/src/com/google/refine/model/ColumnTests.java b/main/tests/server/src/com/google/refine/model/ColumnTests.java
index 624c592c6..f4a134cbe 100644
--- a/main/tests/server/src/com/google/refine/model/ColumnTests.java
+++ b/main/tests/server/src/com/google/refine/model/ColumnTests.java
@@ -55,6 +55,7 @@ public class ColumnTests {
                 + "   \"schemaSpace\":\"http://www.wikidata.org/prop/direct/\","
                 + "   \"type\":{\"id\":\"Q43229\",\"name\":\"organization\"},"
                 + "   \"autoMatch\":true,"
+                + "   \"batchSize\":10,"
                 + "   \"columnDetails\":["
                 + "      {\"column\":\"organization_country\",\"propertyName\":\"SPARQL: P17/P297\",\"propertyID\":\"P17/P297\"},"
                 + "      {\"column\":\"organization_id\",\"propertyName\":\"SPARQL: P3500|P2427\",\"propertyID\":\"P3500|P2427\"}"
diff --git a/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java b/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
index 3c5ec9973..41fd3843c 100644
--- a/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
+++ b/main/tests/server/src/com/google/refine/model/recon/StandardReconConfigTests.java
@@ -83,7 +83,7 @@ public class StandardReconConfigTests extends RefineTest {
     private class StandardReconConfigStub extends StandardReconConfig {
 
         public StandardReconConfigStub() {
-            super("", "", "", "", "", false, new ArrayList<ColumnDetail>());
+            super("", "", "", "", "", false, 10, new ArrayList<ColumnDetail>());
         }
 
         public double wordDistanceTest(String s1, String s2) {
@@ -124,6 +124,7 @@ public class StandardReconConfigTests extends RefineTest {
                 "                \"name\": \"scientific article\"\n" +
                 "        },\n" +
                 "        \"autoMatch\": true,\n" +
+                "        \"batchSize\": 10,\n" +
                 "        \"columnDetails\": [\n" +
                 "           {\n" +
                 "             \"column\": \"organization_country\",\n" +
@@ -164,6 +165,28 @@ public class StandardReconConfigTests extends RefineTest {
         assertNull(config.typeName);
     }
 
+    @Test
+    public void testGetBatchSize() throws IOException {
+
+        String json = "{\"mode\":\"standard-service\","
+                + "\"service\":\"https://tools.wmflabs.org/openrefine-wikidata/en/api\","
+                + "\"identifierSpace\":\"http://www.wikidata.org/entity/\","
+                + "\"schemaSpace\":\"http://www.wikidata.org/prop/direct/\","
+                + "\"type\":null,"
+                + "\"autoMatch\":true,"
+                + "\"batchSize\":50,"
+                + "\"columnDetails\":["
+                + "    {\"column\":\"_ - id\","
+                + "     \"property\":{\"id\":\"P3153\",\"name\":\"Crossref funder ID\"}}"
+                + "],"
+                + "\"limit\":0}";
+        StandardReconConfig c = StandardReconConfig.reconstruct(json);
+        assertEquals(c.getBatchSize(10), 10);
+        assertEquals(c.getBatchSize(120), 12);
+        assertEquals(c.getBatchSize(1200), 50);
+        assertEquals(c.getBatchSize(10000), 50);
+    }
+
     @Test
     public void testReconstructNoIdentifierSchemaSpaces() throws IOException {
         String json = "{\"mode\":\"standard-service\","
@@ -243,6 +266,7 @@ public class StandardReconConfigTests extends RefineTest {
                     "                \"name\": \"film\"\n" +
                     "        },\n" +
                     "        \"autoMatch\": true,\n" +
+                    "        \"batchSize\": 10,\n" +
                     "        \"columnDetails\": [\n" +
                     "           {\n" +
                     "             \"column\": \"director\",\n" +
@@ -349,6 +373,7 @@ public class StandardReconConfigTests extends RefineTest {
                     "                \"name\": \"film\"\n" +
                     "        },\n" +
                     "        \"autoMatch\": true,\n" +
+                    "        \"batchSize\": 10,\n" +
                     "        \"columnDetails\": [\n" +
                     "           {\n" +
                     "             \"column\": \"director\",\n" +
diff --git a/main/tests/server/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsTests.java b/main/tests/server/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsTests.java
index b651c8938..4177c016c 100644
--- a/main/tests/server/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsTests.java
+++ b/main/tests/server/src/com/google/refine/operations/recon/ReconJudgeSimilarCellsTests.java
@@ -104,7 +104,7 @@ public class ReconJudgeSimilarCellsTests extends RefineTest {
                 "http://my.database/schema/",
                 null,
                 null,
-                true, Collections.emptyList());
+                true, 10, Collections.emptyList());
         column.setReconConfig(config);
 
         AbstractOperation op = new ReconJudgeSimilarCellsOperation(
diff --git a/main/tests/server/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperationTests.java b/main/tests/server/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperationTests.java
index 089022128..0a1e4f5de 100644
--- a/main/tests/server/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperationTests.java
+++ b/main/tests/server/src/com/google/refine/operations/recon/ReconMarkNewTopicsOperationTests.java
@@ -108,7 +108,7 @@ public class ReconMarkNewTopicsOperationTests extends RefineTest {
                 "http://foo.com/identifierSpace",
                 "http://foo.com/schemaSpace",
                 null,
-                false,
+                false, 10,
                 Collections.emptyList(),
                 0);
 
diff --git a/main/tests/server/src/com/google/refine/operations/recon/ReconOperationTests.java b/main/tests/server/src/com/google/refine/operations/recon/ReconOperationTests.java
index 91987413a..2d2e62c53 100644
--- a/main/tests/server/src/com/google/refine/operations/recon/ReconOperationTests.java
+++ b/main/tests/server/src/com/google/refine/operations/recon/ReconOperationTests.java
@@ -65,6 +65,7 @@ public class ReconOperationTests extends RefineTest {
             + "   \"schemaSpace\":\"http://www.wikidata.org/prop/direct/\","
             + "   \"type\":{\"id\":\"Q5\",\"name\":\"human\"},"
             + "   \"autoMatch\":true,"
+            + "   \"batchSize\":10,"
             + "   \"columnDetails\":[],"
             + "   \"limit\":0"
             + "},"
@@ -131,7 +132,7 @@ public class ReconOperationTests extends RefineTest {
         List<Recon> reconList = Arrays.asList((Recon) null, (Recon) null, (Recon) null);
         ReconJob reconJob = mock(ReconJob.class);
         when(reconConfig.batchRecon(Mockito.any(), Mockito.anyLong())).thenReturn(reconList);
-        when(reconConfig.getBatchSize()).thenReturn(10);
+        when(reconConfig.getBatchSize(project.rows.size())).thenReturn(10);
         when(reconConfig.createJob(Mockito.eq(project), Mockito.anyInt(), Mockito.any(), Mockito.any(), Mockito.any()))
                 .thenReturn(reconJob);