Skip to content

Add all-tags item filter option#1493

Open
sohocine wants to merge 7 commits into
sysadminsmedia:mainfrom
sohocine:copilot/match-all-tags-filter
Open

Add all-tags item filter option#1493
sohocine wants to merge 7 commits into
sysadminsmedia:mainfrom
sohocine:copilot/match-all-tags-filter

Conversation

@sohocine
Copy link
Copy Markdown

@sohocine sohocine commented May 13, 2026

Summary

  • add an optional item filter mode that requires all selected tag groups
  • keep the existing match-any tag filter as the default behavior
  • include descendant tags when matching each selected tag group

Relates to #436.

Validation

  • pnpm exec eslint pages/items.vue lib/api/classes/items.ts
  • go test ./internal/data/repo -run TestEntityRepository_QueryByGroup_MatchAllTags -count=1
  • go test ./internal/data/repo -count=1
  • git diff --check

Summary by CodeRabbit

  • New Features

    • Added "Match All Selected Tags" toggle on the items page with URL/query integration and client support.
    • Added English label "Match All Selected Tags".
  • Refactor

    • Backend tag-matching enhanced to support match-all vs match-any semantics and more robust descendant resolution.
    • Client API typing for location responses tightened.
    • Minor frontend cleanup removing unused location-store wiring.
  • Tests

    • Added repository tests for match-all, match-any, and negation tag behaviors.
  • Chores

    • CI node setup pinned; moved legacy attachment-path migration into a helper.

Review Change Stack

Adds an optional match-all mode for item tag filters so users can require every selected tag group while preserving the existing match-any behavior by default.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Adds a MatchAllTags boolean to queries, implements per-root descendant resolution and AND/OR predicate composition in QueryByGroup, exposes the flag through API types, UI toggle, routing, and localization, and adds repository tests plus a tag-descendants helper.

Changes

Match All Tags Filter Implementation

Layer / File(s) Summary
Backend: query contract and predicate logic
backend/internal/data/repo/repo_entities.go
EntityQuery adds MatchAllTags. QueryByGroup delegates descendant resolution and composes tag predicates for match-all (AND across per-root descendant groups) or match-any (OR across combined descendants), keeping NegateTags semantics and tracing counts.
Backend: tag descendant helper
backend/internal/data/repo/repo_tags.go
Adds GetDescendantTagIDsByRoot and descendantsFromChildren to compute per-root descendant ID maps using an in-memory children adjacency map and BFS.
Backend: repository test for MatchAllTags
backend/internal/data/repo/repo_entities_test.go
Adds TestEntityRepository_QueryByGroup_MatchAllTags that seeds parent/child tags and entities, verifies default any-tag results, AND results with MatchAllTags=true, negation with NegateTags=true, and cleans up.
Frontend: API types and return typing
frontend/lib/api/classes/items.ts
ItemsQuery adds optional matchAllTags?: boolean. ItemsApi.getLocations narrows the unwrapped response error typing from any to unknown.
Frontend: UI, routing, and i18n
frontend/pages/items.vue, frontend/locales/en.json
Items page adds matchAllTags route query ref, watcher, URL sync, passes the flag to API calls, adds a filter Switch bound to matchAllTags, and adds the items.match_all_tags localization string "Match All Selected Tags".
Misc: CI, app migration, and frontend cleanups
.github/workflows/partial-frontend.yaml, backend/app/api/main.go, various frontend components/pages
Pins actions/setup-node in CI, extracts migrateLegacyAttachmentPaths(app) helper, and removes unused imports/variables and tightens type-only imports across several frontend files.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Security Recommendations

  • Validate UUID inputs: Ensure tag ID arrays in QueryByGroup are validated/parsed as UUIDs before use in SQL predicate builders.
  • Authorization: Re-run entity-level ACL checks for query results; predicate changes must not bypass existing access controls.
  • Tracing/logs: Traces now include descendant counts; avoid emitting raw UUIDs or sensitive metadata in production telemetry.
  • Frontend error handling: After narrowing error to unknown, ensure callers refine the error type before property access to avoid runtime exceptions.

Possibly related PRs

Suggested labels

⬆️ enhancement, go, review needed

Suggested reviewers

  • tankerkiller125
  • tonyaellie

🏷️ Tags now match in harmony,
Toggle set, the queries refine,
Descendants traced and counted,
Frontend switch and tests appointed,
Small change, results align.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is missing required sections including PR type, detailed file-level change summaries, and explicit issue reference format per template (should be 'Fixes #436' not 'Relates to #436'). Complete the PR description by adding: (1) PR type section with 'feature' selected, (2) detailed 'What this PR does' section with bullet-pointed file summaries, (3) explicit 'Fixes #436' in the issue section instead of 'Relates to'.
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% 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 'Add all-tags item filter option' directly and clearly describes the main feature addition: a new optional filter mode for items that matches all selected tags.
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.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
backend/internal/data/repo/repo_entities.go (1)

639-658: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Both NegateTags and MatchAllTags flags can be set independently; the backend silently ignores MatchAllTags when NegateTags is true.

The frontend UI presents both as independent toggles (lines 436-441 in frontend/pages/items.vue) with no mutual-exclusivity guard, and the handler extracts both flags without validation. However, in the backend (line 611), the per-tag descendant collection only runs when q.MatchAllTags && !q.NegateTags. When NegateTags is true, the condition is false, descendantGroups falls back to a single combined slice, and the negate predicate branch at line 653 constructs AND(NOT HasTag(...)) — effectively "entity has none of the tags" — regardless of MatchAllTags.

A user toggling both flags would reasonably expect independent behavior, but the flag combination becomes a no-op. Choose one:

  • Enforce mutual exclusivity at the handler layer to reject the combination as invalid, ensuring the API contract is unambiguous.
  • Support both flags: when NegateTags && MatchAllTags, implement negated-all semantics (e.g., Not(And(HasAny(group1), HasAny(group2), ...))) so the combination is not silently broken.
🤖 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 `@backend/internal/data/repo/repo_entities.go` around lines 639 - 658, The
current logic silently ignores q.MatchAllTags when q.NegateTags is true by
building tagPredicates from only descendantGroups[0] and appending
entity.And(entity.Not(...)) which yields "has none of the tags" instead of
honoring both flags; fix by either validating and rejecting the combination
early in the request handler (return a 400 if q.NegateTags && q.MatchAllTags) or
by changing the predicate construction in this block to compute per-group
predicates over descendantGroups and then wrap them in a negated AND (e.g.,
build per-group entity.Or(...) for each descendants slice, combine with
entity.And(...), then wrap with entity.Not(...)) so q.NegateTags &&
q.MatchAllTags produces Not(And(HasAny(group1), HasAny(group2), ...)).
🤖 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 `@backend/internal/data/repo/repo_entities.go`:
- Around line 609-635: The loop in repo_entities.go causes N calls to
tagRepo.GetDescendantTagIDs when MatchAllTags && !NegateTags, enabling an N+1 DB
round-trip and potential DoS via large q.TagIDs; fix by (1) adding API-level
input validation in v1_ctrl_entities.go to cap len(q.TagIDs) (e.g., max 50) and
return a 400 when exceeded, and (2) add a batched repository method on
TagRepository (e.g., GetAllDescendantsByTagID(ctx, tagIDs)
map[uuid.UUID][]uuid.UUID) and change the MatchAllTags branch in the function
using q.TagIDs to call that single batched method and build descendantGroups
from the returned map instead of looping per tag.

---

Outside diff comments:
In `@backend/internal/data/repo/repo_entities.go`:
- Around line 639-658: The current logic silently ignores q.MatchAllTags when
q.NegateTags is true by building tagPredicates from only descendantGroups[0] and
appending entity.And(entity.Not(...)) which yields "has none of the tags"
instead of honoring both flags; fix by either validating and rejecting the
combination early in the request handler (return a 400 if q.NegateTags &&
q.MatchAllTags) or by changing the predicate construction in this block to
compute per-group predicates over descendantGroups and then wrap them in a
negated AND (e.g., build per-group entity.Or(...) for each descendants slice,
combine with entity.And(...), then wrap with entity.Not(...)) so q.NegateTags &&
q.MatchAllTags produces Not(And(HasAny(group1), HasAny(group2), ...)).
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 085b99b5-f564-4499-bc5e-b80981aec755

📥 Commits

Reviewing files that changed from the base of the PR and between e5b0fe2 and c99922c.

📒 Files selected for processing (5)
  • backend/internal/data/repo/repo_entities.go
  • backend/internal/data/repo/repo_entities_test.go
  • frontend/lib/api/classes/items.ts
  • frontend/locales/en.json
  • frontend/pages/items.vue

Comment thread backend/internal/data/repo/repo_entities.go Outdated
Allow negateTags to combine with matchAllTags and batch descendant tag lookup for match-all queries.

Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

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 `@backend/internal/data/repo/repo_tags.go`:
- Around line 214-236: The GetDescendantTagIDsByRoot helper currently queries
all tags and must be scoped by group; change its signature to accept a gid
(uuid.UUID) and update its query r.db.Tag.Query().WithParent().All(ctx) to
include a group predicate (e.g., .Where(tag.GroupIDEQ(gid)) or use the
repository's QueryByGroup helper) so childrenByParent is built only from tags in
that group; also update the caller in QueryByGroup (the MatchAllTags path) to
pass the existing gid through into GetDescendantTagIDsByRoot and ensure
descendantsFromChildren continues to operate unchanged on the filtered
childrenByParent map.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 7df9022a-6da6-4df7-b0c4-180f00d1f7f2

📥 Commits

Reviewing files that changed from the base of the PR and between c99922c and 1defcf6.

📒 Files selected for processing (3)
  • backend/internal/data/repo/repo_entities.go
  • backend/internal/data/repo/repo_entities_test.go
  • backend/internal/data/repo/repo_tags.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/internal/data/repo/repo_entities_test.go

Comment thread backend/internal/data/repo/repo_tags.go Outdated
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
@sohocine
Copy link
Copy Markdown
Author

Scoped the descendant tag lookup to the current group and pushed the fix. The match-all/negate tag logic remains supported.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
@sohocine
Copy link
Copy Markdown
Author

Pushed fbe8c9b to address the latest review findings.

The tag descendant lookup is now batched and group-scoped through GetDescendantTagIDsByRoot(ctx, gid, tagIDs), MatchAllTags + NegateTags keeps negated-all semantics, and the tag predicate construction is extracted out of QueryByGroup so the PR-introduced gocyclo issue is gone.

Local checks passed:

  • go test ./internal/data/repo -run 'TestEntityRepository_QueryByGroup_MatchAllTags' -count=1\n- go test ./internal/data/repo -count=1\n- git diff --check\n- gocyclo -over 30 internal/data/repo/repo_entities.go\n\nCurrent CI failures appear unrelated to this PR: backend gocyclo is now backend/app/api/main.go:108 func run, and frontend lint fails before linting with TypeError: Object.groupBy is not a function.

@sohocine
Copy link
Copy Markdown
Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
@coderabbitai coderabbitai Bot added ⬆️ enhancement New feature or request review needed A review is needed on this PR or Issue go Pull requests that update Go code labels May 13, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
frontend/pages/location/[id]/index/index.vue (1)

51-60: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix reactive key for useAsyncData to prevent stale location data on route param changes.

At Line 51, locationId.value captures the route param as a snapshot string key. On in-app navigation between /location/:id routes, this prevents useAsyncData from refetching, leaving the location data stale in the same component instance.

Proposed fix
-  const { data: location } = useAsyncData(locationId.value, async () => {
-    const { data, error } = await api.items.getLocation(locationId.value);
-    if (error) {
-      toast.error(t("locations.toast.failed_load_location"));
-      navigateTo("/home");
-      return;
-    }
-
-    return data;
-  });
+  const { data: location } = useAsyncData(
+    () => `location-${locationId.value}`,
+    async () => {
+      const { data, error } = await api.items.getLocation(locationId.value);
+      if (error) {
+        toast.error(t("locations.toast.failed_load_location"));
+        navigateTo("/home");
+        return;
+      }
+      return data;
+    },
+    { watch: [locationId] }
+  );

Use a reactive key (getter function) or the watch option to trigger refetches when the route param changes.

🤖 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 `@frontend/pages/location/`[id]/index/index.vue around lines 51 - 60,
useAsyncData is using the snapshot key locationId.value which prevents refetch
when the route param changes; change the key to a reactive getter (e.g. pass ()
=> locationId.value as the first argument) or use the watch option so the fetch
block that calls api.items.getLocation(locationId.value) re-runs on param
changes, preserving the existing error handling (toast.error, navigateTo) and
return value semantics.
🧹 Nitpick comments (1)
.github/workflows/partial-frontend.yaml (1)

22-25: ⚡ Quick win

Pin the Node.js runtime version instead of using lts/* for supply-chain consistency.

While actions/setup-node is securely pinned to a commit hash, the node-version: lts/* parameter remains a floating target that can drift between CI runs, weakening reproducibility and creating non-deterministic behavior. Other workflows in this repository (e.g., copilot-setup-steps.yml) correctly use explicit versions like "24". Consider pinning to a specific LTS version (e.g., "20" or "22") to maintain consistency with your hardened action pinning strategy.

🤖 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 @.github/workflows/partial-frontend.yaml around lines 22 - 25, Replace the
floating node-version specifier used with the actions/setup-node step by pinning
to an explicit LTS major version string (e.g., "20" or "22") instead of "lts/*";
locate the setup step that references actions/setup-node@395ad326... and update
the node-version: value to the chosen explicit version to ensure reproducible CI
runs and match the repository’s hardened pinning strategy.
🤖 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.

Outside diff comments:
In `@frontend/pages/location/`[id]/index/index.vue:
- Around line 51-60: useAsyncData is using the snapshot key locationId.value
which prevents refetch when the route param changes; change the key to a
reactive getter (e.g. pass () => locationId.value as the first argument) or use
the watch option so the fetch block that calls
api.items.getLocation(locationId.value) re-runs on param changes, preserving the
existing error handling (toast.error, navigateTo) and return value semantics.

---

Nitpick comments:
In @.github/workflows/partial-frontend.yaml:
- Around line 22-25: Replace the floating node-version specifier used with the
actions/setup-node step by pinning to an explicit LTS major version string
(e.g., "20" or "22") instead of "lts/*"; locate the setup step that references
actions/setup-node@395ad326... and update the node-version: value to the chosen
explicit version to ensure reproducible CI runs and match the repository’s
hardened pinning strategy.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 8a46578f-8734-4029-bf22-28d553102265

📥 Commits

Reviewing files that changed from the base of the PR and between fbe8c9b and 75e6b41.

📒 Files selected for processing (7)
  • .github/workflows/partial-frontend.yaml
  • backend/app/api/main.go
  • frontend/components/Location/CreateModal.vue
  • frontend/pages/collection/index/entity-types.vue
  • frontend/pages/item/[id]/index/edit.vue
  • frontend/pages/location/[id]/index/edit.vue
  • frontend/pages/location/[id]/index/index.vue
💤 Files with no reviewable changes (3)
  • frontend/pages/collection/index/entity-types.vue
  • frontend/pages/item/[id]/index/edit.vue
  • frontend/pages/location/[id]/index/edit.vue
✅ Files skipped from review due to trivial changes (1)
  • frontend/components/Location/CreateModal.vue

sohocine and others added 2 commits May 13, 2026 06:06
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Signed-off-by: sofiane <131229158+sohocine@users.noreply.github.com>
@sohocine
Copy link
Copy Markdown
Author

Pushed the remaining backend CI fixes; Backend Server Tests / Go is now green at 07f29bdb. I also addressed CodeRabbit’s stale-location-data note by making the location detail useAsyncData key reactive to route changes.

The remaining red Frontend Tests / Lint check is now past ESLint and failing in the workflow’s pnpm run typecheck step. I reproduced the frontend workflow locally with Node lts/* and pnpm 10, then compared upstream main (e5b0fe22) with this branch: both report the same 217 TypeScript errors, with 0 normalized differences. pnpm run lint:ci passes locally with the existing single Tailwind warning.

I did not disable or relax typechecking in this PR because that would hide existing frontend type debt rather than fix this feature. Happy to split that baseline cleanup separately if desired.

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

Labels

⬆️ enhancement New feature or request go Pull requests that update Go code review needed A review is needed on this PR or Issue

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant