Skip to content

[OPIK-5599] [BE] Fix experiment items not displayed after evaluation suite deletion#6053

Merged
thiagohora merged 6 commits intomainfrom
thiaghora/OPIK-5599-fix-experiment-items-display-after-suite-deletion
Apr 2, 2026
Merged

[OPIK-5599] [BE] Fix experiment items not displayed after evaluation suite deletion#6053
thiagohora merged 6 commits intomainfrom
thiaghora/OPIK-5599-fix-experiment-items-display-after-suite-deletion

Conversation

@thiagohora
Copy link
Copy Markdown
Contributor

Details

When an evaluation suite (dataset with type = 'evaluation_suite') was deleted, any subsequent request to retrieve dataset items with experiment items for that suite returned 404. This happened because DatasetItemService.getItems() unconditionally called datasetService.findById(), which throws NotFoundException when the dataset no longer exists.

Root causes fixed:

  • DatasetItemService.getItems(): Removed the blocking findById() guard and replaced it with a reactive Mono.fromCallable wrapping a new verifyVisibilityIfExists() method. The new method only throws 404 if the dataset exists but fails the visibility check — it silently succeeds when the dataset is already deleted, allowing the request to proceed.
  • DatasetItemService.getItemsWithExperimentItems(): When getFallbackVersionId() returns empty (because all MySQL dataset versions were deleted alongside the suite), the code now falls back to the legacy dao.getItems() query so that experiment items stored in ClickHouse are still returned.
  • DatasetService: Added verifyVisibilityIfExists(UUID id, String workspaceId, Visibility visibility) — a visibility-only check that skips deleted datasets.

Change checklist

  • User facing
  • Documentation update

Issues

  • Resolves #
  • OPIK-5599

AI-WATERMARK

AI-WATERMARK: yes

  • Tools: Claude Code
  • Model(s): claude-sonnet-4-6
  • Scope: Bug fix implementation and tests
  • Human verification: Reviewed and tested

Testing

Regression test added: DatasetsResourceTest#find__whenEvaluationSuiteIsDeleted__thenExperimentItemsShouldStillBeRetrievable

Test scenario:

  1. Create an evaluation suite
  2. Create a trace and a dataset item linked to it
  3. Create an experiment + experiment item linking the dataset item to the trace
  4. Delete the evaluation suite
  5. Assert that GET /datasets/{id}/items/experiments still returns the experiment item (HTTP 200)
mvn test -Dtest="DatasetsResourceTest#find__whenEvaluationSuiteIsDeleted__thenExperimentItemsShouldStillBeRetrievable"

All 6 tests in FindDatasetItemsWithExperimentItems pass.

Note on returned data: After deletion the dataset_items ClickHouse data is gone (items were stored in dataset_item_versions via the versioning feature). The legacy fallback query returns synthetic items built from experiment_items only, so traceId, spanId, source, description, evaluators, and executionPolicy are null on the returned dataset item. The experiment items themselves remain fully populated.

Documentation

No documentation changes required — this is a bug fix restoring previously expected behaviour.

…suite deletion

When an evaluation suite (dataset) was deleted, subsequent requests for
dataset items with experiment items returned 404 because DatasetItemService
unconditionally called datasetService.findById(), which throws when the
dataset no longer exists.

Changes:
- DatasetService: add verifyVisibilityIfExists() — only throws 404 when the
  dataset exists but fails the visibility check; silently succeeds when deleted
- DatasetItemService.getItems(): replace the blocking findById() guard with a
  reactive Mono.fromCallable wrapping verifyVisibilityIfExists(), allowing
  requests to proceed after deletion
- DatasetItemService.getItemsWithExperimentItems(): when getFallbackVersionId()
  returns empty (all versions deleted), fall back to the legacy dao.getItems()
  so experiment items are still returned from ClickHouse
- DatasetsResourceTest: add regression test covering the deleted-suite scenario
@thiagohora thiagohora requested a review from a team as a code owner April 2, 2026 10:41
@github-actions github-actions bot added java Pull requests that update Java code Backend tests Including test files, or tests related like configuration. labels Apr 2, 2026
@thiagohora thiagohora added the test-environment Deploy Opik adhoc environment label Apr 2, 2026
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

🔄 Test environment deployment process has started

Phase 1: Deploying base version 1.10.59-4794 (from main branch) if environment doesn't exist
Phase 2: Building new images from PR branch thiaghora/OPIK-5599-fix-experiment-items-display-after-suite-deletion
Phase 3: Will deploy newly built version after build completes

You can monitor the progress here.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Backend Tests - Integration Group 15

 25 files   25 suites   4m 53s ⏱️
243 tests 242 ✅ 0 💤 0 ❌ 1 🔥
242 runs  242 ✅ 0 💤 0 ❌

For more details on these errors, see this check.

Results for commit b37bb47.

♻️ This comment has been updated with latest results.

Comment on lines 1164 to +1166
return Mono.deferContextual(ctx -> {
String workspaceId = ctx.get(RequestContext.WORKSPACE_ID);
Visibility visibility = ctx.get(RequestContext.VISIBILITY);
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.

getItems reads RequestContext.VISIBILITY from the Reactor context but callers like DatasetExpansionService don't set it — should we default to a visibility (e.g. PRIVATE) when missing or ensure non-HTTP callers set VISIBILITY before calling getItems?

Finding type: Logical Bugs | Severity: 🔴 High


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-backend/src/main/java/com/comet/opik/domain/DatasetItemService.java around
lines 1164-1169, the getItems method unconditionally calls
ctx.get(RequestContext.VISIBILITY) which throws when VISIBILITY is not present in the
Reactor context. Change this to safely obtain a visibility with a default (for example,
Visibility.PRIVATE) when the key is missing (e.g., use
ctx.getOrDefault(RequestContext.VISIBILITY, Visibility.PRIVATE) or check containsKey and
default). Ensure the rest of the method uses this defaulted visibility. Optionally,
add/update unit tests and document that non-HTTP callers should also set VISIBILITY if a
non-default behavior is required.

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.

I guess our tests are just mocking this service, this wasn't noticed in the local testing. Worth to check, Thiago.

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.

Commit aefebe6 addressed this comment by updating DatasetExpansionService to write the entire RequestContext (via AsyncUtils.setRequestContext) before calling DatasetItemService.getItems. That helper copies VISIBILITY along with workspace/user info, so non-HTTP callers now supply the visibility required by getItems.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch! Fixed in commit aefebe6DatasetExpansionService now uses AsyncUtils.setRequestContext(ctx, requestContext) which copies all context keys including VISIBILITY, so non-HTTP callers always supply the required visibility before calling getItems.

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.

Thanks for the update—per DatasetExpansionService (around the new .contextWrite(ctx -> AsyncUtils.setRequestContext(ctx, requestContext)) in getItems), the full RequestContext, including VISIBILITY, is now propagated before calling DatasetItemService#getItems, so non-HTTP callers no longer miss that value.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Apr 2, 2026

Backend Tests - Integration Group 1

411 tests   411 ✅  13m 40s ⏱️
 23 suites    0 💤
 23 files      0 ❌

Results for commit f9f70fb.

♻️ This comment has been updated with latest results.

@CometActions
Copy link
Copy Markdown
Collaborator

Test environment is now available!

To configure additional Environment variables for your environment, run [Deploy Opik AdHoc Environment workflow] (https://github.com/comet-ml/comet-deployment/actions/workflows/deploy_opik_adhoc_env.yaml)

Access Information

The deployment has completed successfully and the version has been verified.

Copy link
Copy Markdown
Contributor

@ldaugusto ldaugusto left a comment

Choose a reason for hiding this comment

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

Some points to check before merge

Comment on lines 1164 to +1166
return Mono.deferContextual(ctx -> {
String workspaceId = ctx.get(RequestContext.WORKSPACE_ID);
Visibility visibility = ctx.get(RequestContext.VISIBILITY);
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.

I guess our tests are just mocking this service, this wasn't noticed in the local testing. Worth to check, Thiago.

- DatasetExpansionService: use AsyncUtils.setRequestContext to ensure VISIBILITY is set in Reactor context
- DatasetService.verifyVisibilityIfExists: add debug log documenting the silent-pass when dataset not found
- DatasetsResourceTest: fix pre-existing typos; use assertPageAndContent to also assert traceId on returned experiment items
@thiagohora thiagohora requested a review from ldaugusto April 2, 2026 12:09
ldaugusto
ldaugusto previously approved these changes Apr 2, 2026
Copy link
Copy Markdown
Contributor

@ldaugusto ldaugusto left a comment

Choose a reason for hiding this comment

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

Thanks for addressing those points!

…ing to null

When dataset items are queried via LEFT JOIN after the dataset is deleted,
ClickHouse returns the epoch (1970-01-01T00:00:00Z) for DateTime fields where
no row matched. Map epoch instants to null in DatasetItemResultMapper so
callers receive null instead of the sentinel epoch value.

Update assertPageAndContent to handle null timestamps in expected items,
branching between asserting null vs asserting isAfter.
Make nullIfEpoch package-private in DatasetItemResultMapper and use it
in DatasetItemVersionDAO, which has the same LEFT JOIN queries against
deleted dataset tables that can return epoch timestamps.
@thiagohora thiagohora requested a review from ldaugusto April 2, 2026 12:53
@thiagohora thiagohora merged commit 21ae163 into main Apr 2, 2026
70 of 73 checks passed
@thiagohora thiagohora deleted the thiaghora/OPIK-5599-fix-experiment-items-display-after-suite-deletion branch April 2, 2026 13:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Backend java Pull requests that update Java code test-environment Deploy Opik adhoc environment tests Including test files, or tests related like configuration.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants