Skip to content

fix: preserve deep taxonomy tree children#86

Open
BhagyaAmarasinghe wants to merge 1 commit into
mainfrom
fix-taxonomy-tree-nesting
Open

fix: preserve deep taxonomy tree children#86
BhagyaAmarasinghe wants to merge 1 commit into
mainfrom
fix-taxonomy-tree-nesting

Conversation

@BhagyaAmarasinghe

Copy link
Copy Markdown
Contributor

Summary

  • Fix taxonomy tree reconstruction so deep descendants are attached recursively instead of losing grandchildren when nodes are copied by value.
  • Add a regression test that builds a 5-level taxonomy path and verifies sorted level-4 leaves are preserved.

Why

The Formbricks taxonomy explorer depends on Hub returning the full tree. The previous reconstruction appended value copies before descendants were attached, so only shallow levels were visible in the UI.

Validation

  • go test ./internal/repository

@coderabbitai

coderabbitai Bot commented Jun 12, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

This PR refactors the taxonomy tree-building algorithm in buildTaxonomyTree. Previously, the function built a parent-pointer map and called a separate sorting helper. The new implementation constructs a childrenByParent lookup map once, locates the root node, and uses a new recursive attachTaxonomyChildren helper to populate the tree by assigning children from the map and sorting them stably by SortOrder and ID. A new test validates that the refactored logic correctly preserves deep multi-level child relationships.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description includes a summary of changes and validation steps, but lacks the required 'How should this be tested?' section and most checklist items from the template. Add a detailed 'How should this be tested?' section with test instructions and check off the required checklist items, including build/test/lint commands and self-review confirmations.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: preserve deep taxonomy tree children' clearly and concisely summarizes the main change: fixing taxonomy tree reconstruction to preserve nested children.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/repository/taxonomy_repository.go`:
- Around line 1056-1062: The code in buildTaxonomyTree silently drops any root
beyond the first by only setting root when root == nil and continuing, which
masks unexpected multi-root rows; instead keep the existing behavior of using
the first root but add a defensive log when another root is encountered: when
copyNode.ParentID == nil and root != nil, emit a warning (e.g.,
logger.Warn/Warnf) indicating multiple roots for the run and include identifying
info (run id / node id) rather than silently continuing; leave the initial set
root = &copyNode path unchanged.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: fc5037a7-dfca-44e8-9440-602602a6b1ed

📥 Commits

Reviewing files that changed from the base of the PR and between 4d165fb and a66adbf.

📒 Files selected for processing (2)
  • internal/repository/taxonomy_repository.go
  • internal/repository/taxonomy_repository_test.go

Comment on lines +1056 to 1062
if copyNode.ParentID == nil {
if root == nil {
root = &copyNode
}

continue
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check for database constraints or application logic that enforces single-root invariant.

# Search for unique constraints or CHECK constraints on parent_id in taxonomy_nodes table
rg -n -A5 -B2 'CREATE TABLE.*taxonomy_nodes' --type=sql

# Search for validation logic that prevents multiple roots
rg -n -C3 'ParentID.*nil.*root' --type=go
rg -n -C3 'multiple.*root' --type=go

# Check if StoreResultAndActivate validates single-root
ast-grep --pattern $'func $_StoreResultAndActivate($$$) {
  $$$
}'

Repository: formbricks/hub

Length of output: 670


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== 1) taxonomy_nodes schema (migration) ==="
nl -ba migrations/010_taxonomy_schema.sql | sed -n '90,170p'

echo
echo "=== 2) buildTaxonomyTree implementation ==="
rg -n "func buildTaxonomyTree" internal -S --glob='*.go'
BUILD_FILE="$(rg -n "func buildTaxonomyTree" internal -S --glob='*.go' --files-with-matches | head -n1 || true)"
if [ -n "$BUILD_FILE" ]; then
  echo "--- file: $BUILD_FILE ---"
  nl -ba "$BUILD_FILE" | sed -n '1000,1120p'
fi

echo
echo "=== 3) buildTaxonomyTree call sites ==="
rg -n "buildTaxonomyTree\\(" internal -S --glob='*.go'

echo
echo "=== 4) StoreResultAndActivate definition ==="
rg -n "StoreResultAndActivate" internal -S --glob='*.go'
STORE_FILE="$(rg -n "StoreResultAndActivate" internal -S --glob='*.go' --files-with-matches | head -n1 || true)"
if [ -n "$STORE_FILE" ]; then
  echo "--- file: $STORE_FILE ---"
  # best-effort window around the first match
  MATCH_LINE="$(rg -n "StoreResultAndActivate" "$STORE_FILE" -S --glob='*.go' | head -n1 | cut -d: -f1)"
  START=$((MATCH_LINE-80)); END=$((MATCH_LINE+120))
  nl -ba "$STORE_FILE" | sed -n "${START},${END}p"
fi

echo
echo "=== 5) Any single-root / multiple-root / validation logic ==="
rg -n -S "(multiple.*root|single.*root|rootCount|parent_id.*IS NULL|ParentID.*nil|ParentID.*== *nil)" internal --glob='*.go' || true

Repository: formbricks/hub

Length of output: 164


🏁 Script executed:

#!/bin/bash
set -euo pipefail

number_lines() { awk '{printf "%6d\t%s\n", NR, $0}'; }

echo "=== 1) taxonomy_nodes schema (migration) ==="
MIG="migrations/010_taxonomy_schema.sql"
if [ -f "$MIG" ]; then
  sed -n '90,170p' "$MIG" | number_lines
else
  echo "Missing: $MIG"
fi

echo
echo "=== 2) buildTaxonomyTree implementation ==="
BUILD_FILE="$(rg -n "func buildTaxonomyTree" internal -S --glob='*.go' --files-with-matches | head -n1 || true)"
if [ -n "$BUILD_FILE" ]; then
  echo "--- file: $BUILD_FILE ---"
  # show around where it likely lives; adjust window if needed
  MATCH_LINE="$(rg -n "func buildTaxonomyTree" "$BUILD_FILE" -S --glob='*.go' | head -n1 | cut -d: -f1)"
  START=$((MATCH_LINE-40)); END=$((MATCH_LINE+160))
  sed -n "${START},${END}p" "$BUILD_FILE" | number_lines
else
  echo "buildTaxonomyTree not found under internal/"
fi

echo
echo "=== 3) buildTaxonomyTree call sites ==="
rg -n "buildTaxonomyTree\\(" internal -S --glob='*.go' || true

echo
echo "=== 4) StoreResultAndActivate definition ==="
STORE_FILE="$(rg -n "StoreResultAndActivate" internal -S --glob='*.go' --files-with-matches | head -n1 || true)"
if [ -n "$STORE_FILE" ]; then
  echo "--- file: $STORE_FILE ---"
  MATCH_LINE="$(rg -n "StoreResultAndActivate" "$STORE_FILE" -S --glob='*.go' | head -n1 | cut -d: -f1)"
  START=$((MATCH_LINE-80)); END=$((MATCH_LINE+160))
  sed -n "${START},${END}p" "$STORE_FILE" | number_lines
else
  echo "StoreResultAndActivate not found under internal/"
fi

echo
echo "=== 5) Any single-root / multiple-root / validation logic ==="
rg -n -S "(multiple.*root|single.*root|rootCount|parent_id.*IS NULL|ParentID.*nil|ParentID.*== *nil)" internal --glob='*.go' || true

Repository: formbricks/hub

Length of output: 15809


🏁 Script executed:

#!/bin/bash
set -euo pipefail

number_lines() { awk '{printf "%6d\t%s\n", NR, $0}'; }

echo "=== buildTaxonomyTree call site context (taxonomy_repository.go around line 600-700) ==="
F="internal/repository/taxonomy_repository.go"
if [ -f "$F" ]; then
  sed -n '600,720p' "$F" | number_lines
fi

echo
echo "=== buildTaxonomyTree tests (taxonomy_repository_test.go around line 1-140) ==="
T="internal/repository/taxonomy_repository_test.go"
if [ -f "$T" ]; then
  sed -n '1,140p' "$T" | number_lines
fi

Repository: formbricks/hub

Length of output: 7196


🏁 Script executed:

#!/bin/bash
set -euo pipefail

F="internal/repository/taxonomy_repository.go"
rg -n "func \\(r \\*TaxonomyRepository\\) listVisibleNodes" "$F" --glob='*.go'
LINE="$(rg -n "func \\(r \\*TaxonomyRepository\\) listVisibleNodes" "$F" --glob='*.go' | head -n1 | cut -d: -f1)"
START=$((LINE-40)); END=$((LINE+120))
sed -n "${START},${END}p" "$F" | head -n 200

Repository: formbricks/hub

Length of output: 4486


Drop the silent multiple-root data-loss concern—DB enforces single root per run

The migration creates idx_taxonomy_nodes_one_root_per_run as a UNIQUE INDEX on taxonomy_nodes (run_id) with WHERE parent_id IS NULL, so for any valid run there can’t be more than one root node (ParentID == nil). The buildTaxonomyTree logic that only keeps the first root therefore shouldn’t be reachable in normal operation.

Optional: add a defensive sanity check/log if multiple roots ever appear due to legacy/corrupt data.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/repository/taxonomy_repository.go` around lines 1056 - 1062, The
code in buildTaxonomyTree silently drops any root beyond the first by only
setting root when root == nil and continuing, which masks unexpected multi-root
rows; instead keep the existing behavior of using the first root but add a
defensive log when another root is encountered: when copyNode.ParentID == nil
and root != nil, emit a warning (e.g., logger.Warn/Warnf) indicating multiple
roots for the run and include identifying info (run id / node id) rather than
silently continuing; leave the initial set root = &copyNode path unchanged.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant