pvillard31 commented on code in PR #11355:
URL: https://github.com/apache/nifi/pull/11355#discussion_r3451092253


##########
nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-restapi-processors/src/test/java/org/apache/nifi/processors/elasticsearch/PutElasticsearchJsonTest.java:
##########
@@ -1110,4 +1110,170 @@ void testIndexFieldNullValueFallsBackConsistently() {
         runner.run();
         assertEquals("test_index", ops.getFirst().getIndex(), "NDJSON null -> 
fallback (not literal \"null\")");
     }
+
+    // 
-------------------------------------------------------------------------
+    // Nested field paths (e.g. @metadata/index)
+    // 
-------------------------------------------------------------------------
+
+    @Test
+    void testNestedIndexFieldNdjsonPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.NDJSON.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-2026\"},\"msg\":\"hello\"}\n");
+        runner.run();
+
+        assertEquals("docs-2026", ops.getFirst().getIndex());
+        final String content = docContent(ops.getFirst());
+        assertFalse(content.contains("@metadata"), "now-empty parent should be 
pruned");
+        assertTrue(content.contains("hello"));
+    }
+
+    @Test
+    void testNestedIndexFieldJsonArrayPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.JSON_ARRAY.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("[{\"@metadata\":{\"index\":\"docs-a\"},\"msg\":\"x\"}]");
+        runner.run();
+
+        assertEquals("docs-a", ops.getFirst().getIndex());
+        assertFalse(docContent(ops.getFirst()).contains("@metadata"));
+    }
+
+    @Test
+    void testNestedIndexFieldSingleJsonPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-single\"},\"msg\":\"x\"}");
+        runner.run();
+
+        assertEquals("docs-single", ops.getFirst().getIndex());
+        assertFalse(ops.getFirst().getFields().containsKey("@metadata"));
+    }
+
+    @Test
+    void testNestedIndexFieldKeepsParentWithRemainingSiblings() {
+        // @metadata still holds another field after the index leaf is 
removed, so it must NOT be pruned
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.NDJSON.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-2026\",\"keep\":\"yes\"},\"msg\":\"hello\"}\n");
+        runner.run();
+
+        assertEquals("docs-2026", ops.getFirst().getIndex());
+        final String content = docContent(ops.getFirst());
+        assertTrue(content.contains("@metadata"), "parent kept because it 
still has siblings");
+        assertTrue(content.contains("keep"));
+        assertFalse(content.contains("\"index\""));
+    }
+
+    @Test
+    void testNestedIndexFieldRetainedByDefault() {
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.NDJSON.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-2026\"},\"msg\":\"hello\"}\n");
+        runner.run();
+
+        assertEquals("docs-2026", ops.getFirst().getIndex());
+        assertTrue(docContent(ops.getFirst()).contains("@metadata"), "nested 
field retained by default");
+    }
+
+    @Test
+    void testNestedIdentifierFieldNdjsonPrunesEmptyParent() {

Review Comment:
   Should we add a nested identifier test for an Update, Delete, or Upsert 
operation, so the resolveId nested path is covered as well?



##########
nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-restapi-processors/src/test/java/org/apache/nifi/processors/elasticsearch/PutElasticsearchJsonTest.java:
##########
@@ -1110,4 +1110,170 @@ void testIndexFieldNullValueFallsBackConsistently() {
         runner.run();
         assertEquals("test_index", ops.getFirst().getIndex(), "NDJSON null -> 
fallback (not literal \"null\")");
     }
+
+    // 
-------------------------------------------------------------------------
+    // Nested field paths (e.g. @metadata/index)
+    // 
-------------------------------------------------------------------------
+
+    @Test
+    void testNestedIndexFieldNdjsonPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.NDJSON.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-2026\"},\"msg\":\"hello\"}\n");
+        runner.run();
+
+        assertEquals("docs-2026", ops.getFirst().getIndex());
+        final String content = docContent(ops.getFirst());
+        assertFalse(content.contains("@metadata"), "now-empty parent should be 
pruned");
+        assertTrue(content.contains("hello"));
+    }
+
+    @Test
+    void testNestedIndexFieldJsonArrayPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INPUT_FORMAT, 
InputFormat.JSON_ARRAY.getValue());
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("[{\"@metadata\":{\"index\":\"docs-a\"},\"msg\":\"x\"}]");
+        runner.run();
+
+        assertEquals("docs-a", ops.getFirst().getIndex());
+        assertFalse(docContent(ops.getFirst()).contains("@metadata"));
+    }
+
+    @Test
+    void testNestedIndexFieldSingleJsonPrunesEmptyParent() {
+        runner.setProperty(PutElasticsearchJson.INDEX_FIELD, 
"@metadata/index");
+        runner.setProperty(PutElasticsearchJson.RETAIN_INDEX_FIELD, "false");
+        runner.assertValid();
+
+        final List<IndexOperationRequest> ops = captureOperations();
+        
runner.enqueue("{\"@metadata\":{\"index\":\"docs-single\"},\"msg\":\"x\"}");
+        runner.run();
+
+        assertEquals("docs-single", ops.getFirst().getIndex());
+        assertFalse(ops.getFirst().getFields().containsKey("@metadata"));
+    }
+
+    @Test
+    void testNestedIndexFieldKeepsParentWithRemainingSiblings() {

Review Comment:
   Could we add a test with a 3 level path (for example a/b/c) to lock in the 
recursive pruning of multiple empty ancestor objects?



##########
nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-restapi-processors/src/main/java/org/apache/nifi/processors/elasticsearch/PutElasticsearchJson.java:
##########
@@ -1143,30 +1156,168 @@ private String resolveId(final Map<String, Object> 
contentMap, final String idAt
     /**
      * Extracts the index name from a pre-parsed {@link JsonNode}.
      * Used for JSON Array Index/Create operations where the node is already 
available.
+     * The field may be a {@code /}-delimited path into nested objects.
      * Falls back to {@code fallbackIndex} when the field is absent or blank.
      */
     private String extractIndex(final JsonNode node, final String indexField, 
final String fallbackIndex) {
         if (StringUtils.isBlank(indexField)) {
             return fallbackIndex;
         }
-        final String value = fieldNodeToString(node.get(indexField));
+        final String value = fieldNodeToString(nodeAtPath(node, indexField));
         return StringUtils.isNotBlank(value) ? value : fallbackIndex;
     }
 
     /**
      * Resolves the index name from an already-parsed content Map.
      * Used for Update/Delete/Upsert operations and suppression-enabled 
Index/Create paths
-     * where the Map is already available. Falls back to {@code fallbackIndex} 
when the
-     * field is absent or blank.
+     * where the Map is already available. The field may be a {@code 
/}-delimited path into
+     * nested objects. Falls back to {@code fallbackIndex} when the field is 
absent or blank.
      */
     private String resolveIndex(final Map<String, Object> contentMap, final 
String indexField, final String fallbackIndex) {
         if (StringUtils.isBlank(indexField)) {
             return fallbackIndex;
         }
-        final String value = fieldValueToString(contentMap.get(indexField));
+        final String value = fieldValueToString(valueAtPath(contentMap, 
indexField));
         return StringUtils.isNotBlank(value) ? value : fallbackIndex;
     }
 
+    /**
+     * Whether {@code field} is a {@code /}-delimited nested path rather than 
a flat field name.
+     */
+    private static boolean isNestedPath(final String field) {

Review Comment:
   A field key that literally contains a slash is now always read as a nested 
path. Should we note this limitation in the property descriptions so users with 
such field names are not surprised?



##########
nifi-extension-bundles/nifi-elasticsearch-bundle/nifi-elasticsearch-restapi-processors/src/main/java/org/apache/nifi/processors/elasticsearch/PutElasticsearchJson.java:
##########
@@ -241,6 +241,7 @@ public class PutElasticsearchJson extends 
AbstractPutElasticsearch {
             .name("Identifier Field")
             .description("""
                     The name of the field within each document to use as the 
Elasticsearch document ID. \
+                    A nested field can be referenced with a "/"-delimited path 
(e.g. "@metadata/id"). \

Review Comment:
   Could you rebase this branch on the latest main? The validation gate is 
failing on "branch not up to date", and because of that the full build and test 
matrix was skipped, so we do not have a green CI signal yet.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to