Summary
The build_tree method in concept_tree.rb produces a truncated/incorrect tree for ontologies where nodes have more than 99 direct children (e.g., SNOMEDCT). This is the root cause of ncbo/bioportal_web_ui#459, where using "Jump to" search results in the selected class missing from the tree and sibling nodes losing their subtree indicators.
Root Cause
In lib/ontologies_linked_data/concerns/concepts/concept_tree.rb, the tree method calls load_children(path, threshold: 99), which uses partially_load_children to load up to 99 children per node. For large ontologies like SNOMEDCT, nodes such as "Clinical finding" (404684003) can have well over 99 direct children.
When build_tree iterates through the loaded children looking for the next path node and doesn't find it (because it wasn't among the first 99 loaded), two bugs are triggered:
Bug 1: Double removal from path
# concept_tree.rb — build_tree method
if !path.empty? && next_tree_node.nil?
tree_node.children << path.shift # removes one element from path
end
tree_node = next_tree_node # nil
path.delete_at(0) # removes ANOTHER element from path
Both path.shift and path.delete_at(0) execute, removing two elements from the path instead of one. This causes an entire level of the hierarchy to be skipped.
Bug 2: Early loop termination
Since next_tree_node is nil, tree_node becomes nil and the while loop exits. The remaining path nodes are never processed, producing a truncated tree.
Bug 3: Missing hasChildren for sibling nodes
load_has_children is called inside build_tree only for levels that are processed. When the loop exits early, sibling nodes at deeper levels never get hasChildren computed. In the serialized API response, these nodes lack the hasChildren flag, so the web UI renders them without expand/collapse icons.
Steps to Reproduce
- Navigate to the SNOMEDCT Classes tab on BioPortal
- Use the "Jump to" search box to search for "Schizophrenia" (58214004)
- Select it from the results
Expected: The class tree shows the path from root to Schizophrenia, with correct subtree indicators on all sibling nodes.
Actual: Schizophrenia is missing from the tree. Many sibling nodes that should have subtrees (e.g., Adjustment disorder, Axis I diagnosis under Mental disorder) appear as leaf nodes.
Suggested Fix
-
Ensure path nodes are always present in their parent's children list. After load_children(path, threshold: 99), check that each path node exists in its parent's children. If not, add it. This guarantees build_tree will always find the matching child.
-
Fix the double-removal bug. When next_tree_node is nil, path.delete_at(0) should not execute because path.shift already consumed an element. Wrap path.delete_at(0) in a condition:
path.delete_at(0) unless next_tree_node.nil?
Or restructure the loop so that path.delete_at(0) only runs when the match was found.
-
Consider removing or raising the threshold for path nodes. The path is typically short (5–10 nodes), so ensuring all children are loaded along the path is feasible and avoids the problem entirely.
Related
Summary
The
build_treemethod inconcept_tree.rbproduces a truncated/incorrect tree for ontologies where nodes have more than 99 direct children (e.g., SNOMEDCT). This is the root cause of ncbo/bioportal_web_ui#459, where using "Jump to" search results in the selected class missing from the tree and sibling nodes losing their subtree indicators.Root Cause
In
lib/ontologies_linked_data/concerns/concepts/concept_tree.rb, thetreemethod callsload_children(path, threshold: 99), which usespartially_load_childrento load up to 99 children per node. For large ontologies like SNOMEDCT, nodes such as "Clinical finding" (404684003) can have well over 99 direct children.When
build_treeiterates through the loaded children looking for the next path node and doesn't find it (because it wasn't among the first 99 loaded), two bugs are triggered:Bug 1: Double removal from path
Both
path.shiftandpath.delete_at(0)execute, removing two elements from the path instead of one. This causes an entire level of the hierarchy to be skipped.Bug 2: Early loop termination
Since
next_tree_nodeis nil,tree_nodebecomes nil and thewhileloop exits. The remaining path nodes are never processed, producing a truncated tree.Bug 3: Missing
hasChildrenfor sibling nodesload_has_childrenis called insidebuild_treeonly for levels that are processed. When the loop exits early, sibling nodes at deeper levels never gethasChildrencomputed. In the serialized API response, these nodes lack thehasChildrenflag, so the web UI renders them without expand/collapse icons.Steps to Reproduce
Expected: The class tree shows the path from root to Schizophrenia, with correct subtree indicators on all sibling nodes.
Actual: Schizophrenia is missing from the tree. Many sibling nodes that should have subtrees (e.g., Adjustment disorder, Axis I diagnosis under Mental disorder) appear as leaf nodes.
Suggested Fix
Ensure path nodes are always present in their parent's children list. After
load_children(path, threshold: 99), check that each path node exists in its parent's children. If not, add it. This guaranteesbuild_treewill always find the matching child.Fix the double-removal bug. When
next_tree_nodeis nil,path.delete_at(0)should not execute becausepath.shiftalready consumed an element. Wrappath.delete_at(0)in a condition:Or restructure the loop so that
path.delete_at(0)only runs when the match was found.Consider removing or raising the threshold for path nodes. The path is typically short (5–10 nodes), so ensuring all children are loaded along the path is feasible and avoids the problem entirely.
Related