feat: add bulk annotation of traces and spans from the selection bar#6945
feat: add bulk annotation of traces and spans from the selection bar#6945bisgeario wants to merge 2 commits into
Conversation
Add an Annotate button to the TracesActionsPanel that opens a dialog allowing users to apply feedback scores to multiple selected traces or spans at once. - Add useTraceFeedbackScoreBatchSetMutation hook (PUT /traces/feedback-scores) - Add useSpanFeedbackScoreBatchSetMutation hook (PUT /spans/feedback-scores) - Add AddAnnotationDialog component using FeedbackScoresEditor - Wire Annotate button into TracesActionsPanel with canAnnotateTraceSpanThread permission guard
| onSettled: (data, error, variables) => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: [SPANS_KEY, { projectId: variables.projectId }], | ||
| }); |
There was a problem hiding this comment.
Bulk span annotation only invalidates [SPANS_KEY, { projectId }], so ['spans-columns', params], ['spans-statistic', params], and [TRACE_KEY, { traceId }] can stay stale after the score dialog — should we mirror the single-score mutation and invalidate those caches too?
Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
apps/opik-frontend/src/api/traces/useSpanFeedbackScoreBatchSetMutation.ts around lines
50-53 inside the `onSettled` handler, fix the cache invalidation to cover all UI reads
of span feedback scores. Currently it only invalidates `[SPANS_KEY, { projectId:
variables.projectId }]`, but the span columns/statistics query keys are
`['spans-columns', params]` and `['spans-statistic', params]` and the trace details
panel reads aggregated `traceData.span_feedback_scores` from `[TRACE_KEY, { traceId }]`,
so those stale caches persist after the selection-bar dialog updates. Refactor
`onSettled` (or `onSuccess`) to mirror the single-score mutation behavior: invalidate
the spans-columns and spans-statistic queries for the same projectId/params, and also
invalidate the trace-detail queries for the affected traceId(s) (either infer traceId(s)
from variables if available or from the mutation response). Ensure you use the exact
same queryKey shapes/param objects as the consumers so the invalidation matches
correctly.
| const useTraceFeedbackScoreBatchSetMutation = () => { | ||
| const queryClient = useQueryClient(); | ||
| const { toast } = useToast(); | ||
|
|
||
| return useMutation({ | ||
| mutationFn: async ({ | ||
| scores, | ||
| }: UseTraceFeedbackScoreBatchSetMutationParams) => { | ||
| const { data } = await api.put(`${TRACES_REST_ENDPOINT}feedback-scores`, { | ||
| scores: scores.map((score) => ({ | ||
| id: score.id, | ||
| name: score.name, |
There was a problem hiding this comment.
useTraceFeedbackScoreBatchSetMutation and useSpanFeedbackScoreBatchSetMutation are copy/paste twins; should we factor out a shared factory/helper for the endpoint and query key?
Want Baz to fix this for you? Activate Fixer
| onSettled: (data, error, variables) => { | ||
| queryClient.invalidateQueries({ | ||
| queryKey: [TRACES_KEY, { projectId: variables.projectId }], | ||
| }); |
There was a problem hiding this comment.
bulk trace annotation only invalidates [TRACES_KEY, { projectId }]; should we also invalidate ['traces-columns', params], ['traces-statistic', params], and [TRACE_KEY, { traceId }] like the single-score mutation?
Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
apps/opik-frontend/src/api/traces/useTraceFeedbackScoreBatchSetMutation.ts around lines
50-53 inside the `onSettled` handler, the mutation only invalidates the query key
`[TRACES_KEY, { projectId }]`, but the UI also reads `['traces-columns', params]`,
`['traces-statistic', params]` in `TraceLogsSidebar`, and individual trace data from
`[TRACE_KEY, { traceId }]` in `TraceDetailsPanel`. Refactor `onSettled` to mirror the
single-score mutation by invalidating all relevant trace-related query keys after the
batch update, using the available `variables.projectId` and (if needed) deriving
traceIds from `variables.scores` or invalidating trace-detail keys for those traces.
Ensure the invalidations happen on both success and error (or at least success) so the
selection-bar dialog updates all visible trace columns/statistics/cards/drawers without
requiring a manual refresh.
| toast({ | ||
| title: "Feedback scores applied", | ||
| description: `Applied ${feedbackScores.length} score${ | ||
| feedbackScores.length === 1 ? "" : "s" | ||
| } to ${rows.length} ${ | ||
| rows.length === 1 | ||
| ? type === TRACE_DATA_TYPE.traces | ||
| ? "trace" | ||
| : "span" | ||
| : type === TRACE_DATA_TYPE.traces | ||
| ? "traces" | ||
| : "spans" |
There was a problem hiding this comment.
entityLabel is recomputed in the toast with the same nested ternary, should we reuse entityLabel or a small helper here so the singular/plural logic stays declared once?
Want Baz to fix this for you? Activate Fixer
Other fix methods
Prompt for AI Agents
Before applying, verify this suggestion against the current code. In
apps/opik-frontend/src/v2/pages-shared/traces/AddAnnotationDialog/AddAnnotationDialog.tsx
around lines 102-114 inside `handleApply`, the toast `description` string rebuilds the
same nested ternary logic for trace vs span and singular vs plural that is already
computed as `entityLabel` (lines 123-130). Refactor by reusing the existing
`entityLabel` (or by extracting a small helper like `getEntityLabel(count, type)` that
both `entityLabel` and the toast can call) so the label logic is declared once and the
template literal becomes straightforward. Ensure the toast still correctly pluralizes
and references `trace(s)` vs `span(s)` based on `rows.length` and `type`.
…nnotation The batch feedback-score mutations only invalidated the top-level traces/spans list query, so the columns, statistics, and trace-details panel kept showing stale scores until a manual refresh. Mirror the single-score mutations and invalidate those caches too.
| await queryClient.invalidateQueries({ queryKey: [TRACES_KEY] }); | ||
| await queryClient.invalidateQueries({ queryKey: ["traces-columns"] }); | ||
| await queryClient.invalidateQueries({ queryKey: ["traces-statistic"] }); | ||
|
|
||
| const traceIds = [...new Set(variables.scores.map((score) => score.id))]; |
There was a problem hiding this comment.
Lines 54-58 duplicate the trace cache invalidation logic from useTraceFeedbackScoreSetMutation.ts and useTraceFeedbackScoreDeleteMutation.ts — should we extract a shared helper so the three mutations stay in lockstep?
Want Baz to fix this for you? Activate Fixer
| await queryClient.invalidateQueries({ queryKey: [SPANS_KEY] }); | ||
| await queryClient.invalidateQueries({ queryKey: ["spans-columns"] }); | ||
| await queryClient.invalidateQueries({ queryKey: ["spans-statistic"] }); | ||
| await queryClient.invalidateQueries({ queryKey: [TRACE_KEY] }); |
There was a problem hiding this comment.
Lines 55-58 duplicate the span cache invalidation branch from useTraceFeedbackScoreSetMutation.ts and useTraceFeedbackScoreDeleteMutation.ts; should we extract a shared helper so the span set/delete/batch hooks stay in sync?
Want Baz to fix this for you? Activate Fixer
Details
Adds an Annotate bulk action to the
TracesActionsPanel(the action bar shown when multiple traces or spans are selected). Clicking the button opens a dialog that renders the existingFeedbackScoresEditor, letting users set one or more feedback scores and apply them to all selected items in a single batch API call.Change checklist
Issues
AI-WATERMARK
AI-WATERMARK: yes
Testing
npx tsc --project tsconfig.json --noEmit— cleannpx eslinton all four changed/new files — cleanAddTagDialog/ManageTagsDialog; reusesFeedbackScoresEditorwithout modification; permission gated bycanAnnotateTraceSpanThreadDocumentation
No documentation changes needed — this is an incremental addition to an existing UI pattern.
Closes #1010
/claim #1010