-
[OPIK-3137][OPIK-3161] [BE][FE] Add bulk tag adding and removing operations (#5122)
发布于
2026-02-23 15:05:37 +00:00 - [OPIK-3137][OPIK-3161] [BE][FE] Add bulk tag adding and removing operations
Implement single-call batch operations for adding and removing tags across multiple entities:
Backend:
- Add tagsToAdd/tagsToRemove fields to API models (Experiments, Traces, Spans, Threads, Dataset Items)
- Update ClickHouse DAOs with array operations (arrayConcat, arrayFilter, arrayDistinct)
- Add backwards compatibility with existing tags/mergeTags parameters
- Add validation (100 char max per tag, 50 max tags for experiments)
- Add integration tests for batch tag operations
Frontend:
- Update mutation hooks to support tagsToAdd/tagsToRemove
- Simplify AddTagDialog components to use single API call
- Add shared ManageTagsDialog component with client-side validation
- Add maxEntities limit (1000) with improved UX
- Add comprehensive unit tests for ManageTagsDialog
Implements OPIK-3137: Allow multiple tags to be added in bulk
Implements OPIK-3161: Allow tag removal from multiple items at once- Revision 1: Address PR feedback - code quality improvements
Backend:
- Extract duplicated SQL tags logic into SqlFragments.tagUpdateFragment()
- Refactor test naming from test* to batchUpdateWhen* for clarity
- Parameterize duplicate validation tests using @MethodSource
- Fix import order in TraceThreadDAO
Frontend:
- Optimize tag operations from O(n²) to O(n) using Set data structures
- Fix React state update bug by moving setOpen to useEffect
- Prevent adding tags that already exist on all selected entities
- Add deduplication in draft mode tag additions
- Fix import ordering violations
- Revision 2: Cleanup tags/mergeTags approach and improve tag limits
Backend:
- Add comprehensive documentation explaining dual tag update strategy
(NEW: tagsToAdd/tagsToRemove for frontend vs OLD: tags+mergeTags for SDK) - Clarify backwards compatibility requirements in all DAOs
- Document mutual exclusivity and precedence rules
Frontend:
- Remove dead code: eliminate tags/mergeTags from mutation hooks
(experiments, traces, spans, threads, dataset items) - Add comment to usePromptVersionsUpdateMutation explaining why it still
uses the legacy approach (SQL complexity for bulk removals) - Convert maxTags, maxEntities, maxTagLength from props to constants
(never overridden in production code) - Update tests to reflect validation on submit instead of on add
- Revision 3: Add validation limits for tag fields in Update DTOs
- Add @Valid and @Size annotations to tags, tagsToAdd, and tagsToRemove fields
- Limit tags to max 50 per operation and 100 characters per tag
- Applied to TraceUpdate, SpanUpdate, ExperimentUpdate, DatasetItemUpdate, TraceThreadUpdate
- Provides per-operation validation at API boundary level
- Revision 4: Enforce 50-tag limit via SQL throwIf and refactor tag operations
- Rename SqlFragments to TagOperations; add TagUpdatable interface, shared
configureTagTemplate/bindTagParams helpers, and mapTagLimitError for 422 mapping - Add ClickHouse throwIf validation in tagUpdateFragment with short_circuit_function_evaluation
- Add @Valid @Size annotations on all Update DTOs (tags, tagsToAdd, tagsToRemove)
- Add server-side tag limit check in DatasetItemService.applyDeltaChanges for versioned inserts
- Wire mapTagLimitError into all service reactive chains (experiments, traces, spans, threads, dataset items)
- Update 6 frontend mutation hooks to parse ErrorMessage.errors[0] with message fallback
- Add integration test for sequential tag limit enforcement
- Revision 5: Redesign ManageTagsDialog with simpler and only common tags UI
- Rename to "Manage shared tags", show only tags common to all items
- Replace accordion layout with flat inline tag list
- Add inline "+ Add tag" editable tag (click to input, Enter to add)
- Removed tags shown as strikethrough with faded original color
- New tags distinguished with outline border
- RemovableTag: overlay close icon with gradient mask blend
- RemovableTag: tooltip on truncated tags, max-w-40 constraint
- Scrollable tag area for many tags
-
Revision 6: Extract shared TagUpdateFields type and buildTagUpdatePayload helper
-
Revision 7: Improve ManageTagsDialog UX - inline save/discard, Enter shortcut, Escape handling
-
Revision 8: Improve error handling - join multi-error responses and extract to shared utility
- Add extractErrorMessage utility to lib/tags.ts that handles multi-error responses
- Join multiple backend errors with comma separator instead of only showing first error
- Add fallback chain: errors array → message → error.message → generic message
- Update 6 mutation hooks to use shared extractErrorMessage utility
- Remove lodash/get imports where no longer needed
- Improve toast message in ManageTagsDialog to show item count
- Update tagUpdateFragment in TagOperations.java: avoid running throwIf when not needed
- Revision 9: Fix operator precedence issue in tagUpdateFragment
The .replace() method was binding to the last text block instead of the
concatenated result due to operator precedence (. has higher precedence than +).Added parentheses to force concatenation before calling .replace(), ensuring
TAGS_COL placeholder is properly replaced throughout the SQL fragment.Fixes: BatchTagOperationsTest
- Revision 10: Add more batch tag tests, simplify RemovableTag tooltip, fix removed tag truncation
[BE] Add 3 tests to BatchTagOperationsTest: tags with mergeTags=false, tagsToAdd
with tags simultaneously, and no tag fields provided. Format SQL in all DAOs for
consistent tagUpdateFragment concatenation.[FE] Simplify RemovableTag to always show tooltip instead of conditional truncation
detection. Fix long tags not truncating when marked for removal in ManageTagsDialog.- Revision 11: Reuse TAG_LIMIT_ERROR constant in DatasetItemService
下载附件