Skip to content

feat: add bulk annotation of traces and spans from the selection bar#6945

Open
bisgeario wants to merge 2 commits into
comet-ml:mainfrom
bisgeario:fix/issue-1010
Open

feat: add bulk annotation of traces and spans from the selection bar#6945
bisgeario wants to merge 2 commits into
comet-ml:mainfrom
bisgeario:fix/issue-1010

Conversation

@bisgeario

Copy link
Copy Markdown

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 existing FeedbackScoresEditor, letting users set one or more feedback scores and apply them to all selected items in a single batch API call.

Change checklist

  • User facing
  • Documentation update

Issues

AI-WATERMARK

AI-WATERMARK: yes

  • If yes:
    • Tools: Claude
    • Model(s): claude-opus-4-5
    • Scope: Full implementation of the feature (new API hooks, dialog component, TracesActionsPanel wiring)
    • Human verification: TypeScript and ESLint pass; logic verified against existing batch tag and delete patterns

Testing

  • npx tsc --project tsconfig.json --noEmit — clean
  • npx eslint on all four changed/new files — clean
  • Manual review: follows same pattern as AddTagDialog / ManageTagsDialog; reuses FeedbackScoresEditor without modification; permission gated by canAnnotateTraceSpanThread

Documentation

No documentation changes needed — this is an incremental addition to an existing UI pattern.

Closes #1010
/claim #1010

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
Comment on lines +50 to +53
onSettled: (data, error, variables) => {
queryClient.invalidateQueries({
queryKey: [SPANS_KEY, { projectId: variables.projectId }],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

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.

Comment on lines +22 to +33
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,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

useTraceFeedbackScoreBatchSetMutation and useSpanFeedbackScoreBatchSetMutation are copy/paste twins; should we factor out a shared factory/helper for the endpoint and query key?

Severity

Want Baz to fix this for you? Activate Fixer

Comment on lines +50 to +53
onSettled: (data, error, variables) => {
queryClient.invalidateQueries({
queryKey: [TRACES_KEY, { projectId: variables.projectId }],
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

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.

Comment on lines +102 to +113
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"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Severity

Want Baz to fix this for you? Activate Fixer

Other fix methods

Fix in Cursor

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.
Comment on lines +54 to +58
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))];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Severity

Want Baz to fix this for you? Activate Fixer

Comment on lines +55 to +58
await queryClient.invalidateQueries({ queryKey: [SPANS_KEY] });
await queryClient.invalidateQueries({ queryKey: ["spans-columns"] });
await queryClient.invalidateQueries({ queryKey: ["spans-statistic"] });
await queryClient.invalidateQueries({ queryKey: [TRACE_KEY] });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

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?

Severity

Want Baz to fix this for you? Activate Fixer

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FR]: UI - Ability to Annotate multiple traces at once within a Project

1 participant