feat(tag-search): expand children and surface ancestor path in search results

Modifies TagService.search() to enrich name-matches with tree relatives:
root matches expand descendants, child matches prepend ancestors.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Marcel
2026-04-17 11:27:41 +02:00
parent 59b7f7cddf
commit d075bf390a
2 changed files with 93 additions and 1 deletions

View File

@@ -39,7 +39,9 @@ public class TagService {
private final TagRepository tagRepository;
public List<Tag> search(String query) {
return tagRepository.findByNameContainingIgnoreCase(query);
List<Tag> matched = tagRepository.findByNameContainingIgnoreCase(query);
if (matched.isEmpty()) return matched;
return enrichWithRelatives(matched);
}
public Tag getById(UUID id) {
@@ -161,6 +163,29 @@ public class TagService {
// ─── private helpers ─────────────────────────────────────────────────────
// Each matched tag issues 1 CTE query (findDescendantIds or findAncestorIds) + 1 batch
// fetch for extras. Typical queries match 13 tags at depth ≤ 4, so 35 queries total.
private List<Tag> enrichWithRelatives(List<Tag> matched) {
Set<UUID> matchedIds = matched.stream().map(Tag::getId).collect(Collectors.toSet());
Set<UUID> extraIds = new HashSet<>();
for (Tag tag : matched) {
if (tag.getParentId() == null) {
extraIds.addAll(tagRepository.findDescendantIds(tag.getId()));
} else {
extraIds.addAll(tagRepository.findAncestorIds(tag.getId()));
}
}
extraIds.removeAll(matchedIds);
List<Tag> result = new ArrayList<>(matched);
if (!extraIds.isEmpty()) {
result.addAll(tagRepository.findAllById(extraIds));
}
resolveEffectiveColors(result);
return result;
}
private void validateNotSelf(UUID sourceId, UUID targetId) {
if (sourceId.equals(targetId)) {
throw DomainException.badRequest(ErrorCode.TAG_MERGE_SELF,