From 39a61eaefbaca189984ddc583dc073e69c8bf8c7 Mon Sep 17 00:00:00 2001
From: Jack <jack20220723@gmail.com>
Date: Fri, 15 Nov 2024 10:48:00 +0800
Subject: [PATCH] Update label property from 'name' to 'label'

---
 README.md                                       |  1 +
 src/main/java/org/qubitpi/wilhelm/Link.java     | 16 +++++++++++-----
 src/main/java/org/qubitpi/wilhelm/Node.java     | 16 +++++++++++-----
 .../wilhelm/web/endpoints/Neo4JServlet.java     | 17 ++++++++++++-----
 .../groovy/org/qubitpi/wilhelm/LinkSpec.groovy  |  4 ++--
 .../groovy/org/qubitpi/wilhelm/NodeSpec.groovy  |  4 ++--
 .../web/endpoints/Neo4JServletITSpec.groovy     |  2 +-
 .../web/endpoints/Neo4JServletSpec.groovy       | 12 ++++++------
 8 files changed, 46 insertions(+), 26 deletions(-)

diff --git a/README.md b/README.md
index 2807ad2..c968bbc 100644
--- a/README.md
+++ b/README.md
@@ -81,6 +81,7 @@ We should see `HTTP/1.1 201 Created` as signs of success.
 - vocabulary count: https://api.paion-data.dev/wilhelm/languages/german?perPage=100&page=1
 - query vocabulary paged: https://api.paion-data.dev/wilhelm/languages/german/count
 - expand: https://api.paion-data.dev/wilhelm/expand/nämlich
+- search: https://api.paion-data.dev/wilhelm/search/das
 
 License
 -------
diff --git a/src/main/java/org/qubitpi/wilhelm/Link.java b/src/main/java/org/qubitpi/wilhelm/Link.java
index 3f69838..0e45f5e 100644
--- a/src/main/java/org/qubitpi/wilhelm/Link.java
+++ b/src/main/java/org/qubitpi/wilhelm/Link.java
@@ -47,6 +47,13 @@
 @JsonIncludeProperties({ "label", "sourceNodeId", "targetNodeId", "attributes" })
 public class Link {
 
+    /**
+     * The database node attribute name whose value is used for displaying the relationship caption on UI.
+     * <p>
+     * For example, for Neo4J database, this would correspond to a relationship property.
+     */
+    public static final String LABEL_ATTRIBUTE = "label";
+
     private static final Logger LOG = LoggerFactory.getLogger(Link.class);
 
     private final String label;
@@ -93,18 +100,17 @@ private Link(
      * @throws IllegalStateException if {@code relationship} is missing a "name" property
      */
     public static Link valueOf(final Relationship relationship) {
-        final String labelKey = "name";
-        if (!Objects.requireNonNull(relationship).asMap().containsKey(labelKey)) {
-            LOG.error("Neo4J relationship does not contain '{}' attribute: {}", labelKey, relationship.asMap());
+        if (!Objects.requireNonNull(relationship).asMap().containsKey(LABEL_ATTRIBUTE)) {
+            LOG.error("Neo4J relationship does not contain '{}' attribute: {}", LABEL_ATTRIBUTE, relationship.asMap());
             throw new IllegalStateException(
                     "There seems to be a data format mismatch between Wilhelm webservice and Neo4J database. " +
                             "Please file an issue at https://github.com/QubitPi/wilhelm-ws/issues for a fix"
             );
         }
 
-        final String label = relationship.asMap().get(labelKey).toString();
+        final String label = relationship.asMap().get(LABEL_ATTRIBUTE).toString();
         final Map<String, Object> attributes = relationship.asMap().entrySet().stream()
-                .filter(entry -> !labelKey.equals(entry.getKey()))
+                .filter(entry -> !LABEL_ATTRIBUTE.equals(entry.getKey()))
                 .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
 
         return new Link(label, relationship.startNodeElementId(), relationship.endNodeElementId(), attributes);
diff --git a/src/main/java/org/qubitpi/wilhelm/Node.java b/src/main/java/org/qubitpi/wilhelm/Node.java
index 82554ce..ad99242 100644
--- a/src/main/java/org/qubitpi/wilhelm/Node.java
+++ b/src/main/java/org/qubitpi/wilhelm/Node.java
@@ -45,6 +45,13 @@
 @JsonIncludeProperties({ "id", "label", "attributes" })
 public class Node {
 
+    /**
+     * The database node attribute name whose value is used for displaying the node caption on UI.
+     * <p>
+     * For example, for Neo4J database, this would correspond to a node property.
+     */
+    public static final String LABEL_ATTRIBUTE = "label";
+
     private static final Logger LOG = LoggerFactory.getLogger(Node.class);
 
     private final String id;
@@ -82,18 +89,17 @@ private Node(@NotNull final String id, @NotNull final String label, @NotNull fin
      * @throws IllegalStateException if {@code node} is missing a "name" property
      */
     public static Node valueOf(@NotNull final org.neo4j.driver.types.Node node) {
-        final String labelKey = "name";
-        if (!Objects.requireNonNull(node).asMap().containsKey(labelKey)) {
-            LOG.error("Neo4J node does not contain '{}' attribute: {}", labelKey, node.asMap());
+        if (!Objects.requireNonNull(node).asMap().containsKey(LABEL_ATTRIBUTE)) {
+            LOG.error("Neo4J node does not contain '{}' attribute: {}", LABEL_ATTRIBUTE, node.asMap());
             throw new IllegalStateException(
                     "There seems to be a data format mismatch between Wilhelm webservice and Neo4J database. " +
                             "Please file an issue at https://github.com/QubitPi/wilhelm-ws/issues for a fix"
             );
         }
 
-        final String label = node.asMap().get(labelKey).toString();
+        final String label = node.asMap().get(LABEL_ATTRIBUTE).toString();
         final Map<String, Object> attributes = node.asMap().entrySet().stream()
-                .filter(entry -> !labelKey.equals(entry.getKey()))
+                .filter(entry -> !LABEL_ATTRIBUTE.equals(entry.getKey()))
                 .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, Map.Entry::getValue));
 
         return new Node(node.elementId(), label, attributes);
diff --git a/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java b/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
index cb7a7f4..7308cf7 100644
--- a/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
+++ b/src/main/java/org/qubitpi/wilhelm/web/endpoints/Neo4JServlet.java
@@ -126,9 +126,12 @@ public Response getVocabularyByLanguagePaged(
 
         final String query = String.format(
                 "MATCH (t:Term WHERE t.language = '%s')-[r]->(d:Definition) " +
-                "RETURN t.name AS term, d.name AS definition " +
+                "RETURN t.%s AS term, d.%s AS definition " +
                 "SKIP %s LIMIT %s",
-                requestedLanguage.getDatabaseName(), (Integer.parseInt(page) - 1) * Integer.parseInt(perPage), perPage
+                requestedLanguage.getDatabaseName(),
+                Node.LABEL_ATTRIBUTE,
+                Node.LABEL_ATTRIBUTE,
+                (Integer.parseInt(page) - 1) * Integer.parseInt(perPage), perPage
         );
 
         return Response
@@ -142,14 +145,17 @@ public Response getVocabularyByLanguagePaged(
      *
      * @param keyword  The provided keyword
      *
-     * @return all nodes whose "name" attribute contains the search keyword
+     * @return all nodes whose {@link Node#LABEL_ATTRIBUTE "label"} attribute contains the search keyword
      */
     @GET
     @Path("/search/{keyword}")
     @Produces(MediaType.APPLICATION_JSON)
     @SuppressWarnings("MultipleStringLiterals")
     public Response search(@NotNull @PathParam("keyword") final String keyword) {
-        final String query = String.format("MATCH (node) WHERE node.name =~ '.*%s.*' RETURN node", keyword);
+        final String query = String.format(
+                "MATCH (node) WHERE node.%s =~ '.*%s.*' RETURN node",
+                Node.LABEL_ATTRIBUTE, keyword
+        );
 
         return Response
                 .status(Response.Status.OK)
@@ -313,12 +319,13 @@ public Response expandApoc(
 
         final String query = String.format(
                 """
-                        MATCH (node{name:'%s'})
+                        MATCH (node{%s:'%s'})
                         CALL apoc.path.expand(node, "LINK", null, 1, %s)
                         YIELD path
                         RETURN path, length(path) AS hops
                         ORDER BY hops;
                 """,
+                Node.LABEL_ATTRIBUTE,
                 word.replace("'", "\\'"), maxHops
         );
 
diff --git a/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
index 0b85ed8..945bfa0 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/LinkSpec.groovy
@@ -47,7 +47,7 @@ class LinkSpec extends Specification {
         _ | "attributes"
     }
 
-    def "when a Neo4J relationship does not contain 'name' property, an error is thrown"() {
+    def "when a Neo4J relationship does not contain 'label' property, an error is thrown"() {
         when: "a Neo4J node has no properties"
         Link.valueOf(Mock(Relationship) {asMap() >> [:]})
 
@@ -62,7 +62,7 @@ class LinkSpec extends Specification {
         when: "a happy path Neo4J relationship is being converted to a transparent link"
         Link actual = Link.valueOf(Mock(Relationship) {
             asMap() >> [
-                    name: "my node",
+                    label: "my node",
                     type: "follows"
             ]
             startNodeElementId() >> "node1"
diff --git a/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
index 9a1d3d2..e73e898 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/NodeSpec.groovy
@@ -44,7 +44,7 @@ class NodeSpec extends Specification {
         _ | "attributes"
     }
 
-    def "when a Neo4J node does not contain 'name' property, an error is thrown"() {
+    def "when a Neo4J node does not contain 'label' property, an error is thrown"() {
         when: "a Neo4J node has no properties"
         Node.valueOf(Mock(org.neo4j.driver.types.Node) {asMap() >> [:]})
 
@@ -59,7 +59,7 @@ class NodeSpec extends Specification {
         when: "a happy path Neo4J node is being converted to a transparent node"
         Node actual = Node.valueOf(Mock(org.neo4j.driver.types.Node) {
             asMap() >> [
-                    name: "my node",
+                    label: "my node",
                     color: "blue",
                     size: "medium"
             ]
diff --git a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
index 7a94b98..4b40387 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletITSpec.groovy
@@ -140,7 +140,7 @@ class Neo4JServletITSpec extends Specification {
                 .contentType(MediaType.APPLICATION_JSON)
                 .accept(MediaType.APPLICATION_JSON)
                 .when()
-                .get("/neo4j/expand/nämlich")
+                .get("/neo4j/expand/dreißig")
                 .then()
                 .statusCode(200)
                 .body("", hasKey("nodes"))
diff --git a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
index 3dbb781..d6a48bf 100644
--- a/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
+++ b/src/test/groovy/org/qubitpi/wilhelm/web/endpoints/Neo4JServletSpec.groovy
@@ -32,8 +32,8 @@ class Neo4JServletSpec extends Specification {
             keys() >> ["term", "definition"]
             get("term") >> Mock(Value) {
                 type() >> InternalTypeSystem.TYPE_SYSTEM.NODE()
-                keys() >> ["name", "language"]
-                get("name") >> Mock(Value) {
+                keys() >> ["label", "language"]
+                get("label") >> Mock(Value) {
                     type() >> InternalTypeSystem.TYPE_SYSTEM.STRING()
                     asString() >> "Hallo"
                 }
@@ -44,8 +44,8 @@ class Neo4JServletSpec extends Specification {
             }
             get("definition") >> Mock(Value) {
                 type() >> InternalTypeSystem.TYPE_SYSTEM.NODE()
-                keys() >> ["name"]
-                get("name") >> Mock(Value) {
+                keys() >> ["label"]
+                get("label") >> Mock(Value) {
                     type() >> InternalTypeSystem.TYPE_SYSTEM.STRING()
                     asString() >> "Hello"
                 }
@@ -55,11 +55,11 @@ class Neo4JServletSpec extends Specification {
         expect:
         Neo4JServlet.expand(value) == [
                 term: [
-                    name: "Hallo",
+                    label: "Hallo",
                     language: "German"
                 ],
                 definition: [
-                    name: "Hello"
+                    label: "Hello"
                 ]
         ]
     }