Skip to content

[OPIK-7791] [QA] Proposed e2e specs from the 2.2.42 → 2.2.43 release exploration - #8045

Draft
CometActions wants to merge 1 commit into
mainfrom
AndreiCautisanu/OPIK-7791/dataset-item-filter-scope-versioned
Draft

[OPIK-7791] [QA] Proposed e2e specs from the 2.2.42 → 2.2.43 release exploration#8045
CometActions wants to merge 1 commit into
mainfrom
AndreiCautisanu/OPIK-7791/dataset-item-filter-scope-versioned

Conversation

@CometActions

Copy link
Copy Markdown
Collaborator

Where these came from

The QA release side flow explored the 2.2.42 → 2.2.43 release by hand on
staging.dev.comet.com/opik (workspace opik-testing). Three of the flows a
human verified there are worth a permanent test; this PR is those three.

Generated by the release QA side flow, and it needs review before merge.
It is a draft on purpose. Nothing here has been reviewed by a person.

  • Written and run against 2.2.43 (the exploration's ref — the branch is
    cut from that tag). main has moved on since; please re-run before merging.
  • Base is main only because a PR base must be a branch and 2.2.43 is a tag.

Two of these specs are RED, and that is the finding

dataset-items-versioned-filter-scope.spec.ts and
dataset-items-select-all-delete.spec.ts fail against 2.2.43, and they fail for
the reason they were written: a filter-scoped dataset-item mutation does not
touch the set the same filter lists
, once the dataset has been versioned more
than once.

dataset_item_versions carries two column sets — the version snapshot row's
(id, created_at, created_by, …) and the item's (dataset_item_id,
item_created_at, …). The read paths alias the item-level ones; the delete and
batch-update paths still filter the raw table. A snapshot row gets a fresh id
and a fresh created_at on every version commit, so from the second version on
the two disagree and the read and the write stop describing the same rows.

Each failing test contains its own proof: the read step passes (the filter lists
exactly what it should) and the write step with the identical filter
contradicts it. No test here asserts which column is the correct one — only that
the two agree.

The control test in the same file passes. It drives the same endpoint over
the same doubly-versioned dataset with a data.<key> filter and deletes exactly
the right rows, which is what makes the failures a statement about column
binding rather than about the endpoint, the fixture, or the environment.

If the team would rather not carry red specs in the estate, the right move is to
hold this PR against the fix, not to weaken the assertions. None of them are
.skipped.

Per spec

1. tests/datasets/dataset-items-versioned-filter-scope.spec.ts@t3-nightly @area:datasets

API-level, four tests. Extends dataset-items-filter-scope.spec.ts, which
covers these endpoints only over data.<key> on a single-version dataset —
both of which keep the two column sets in agreement, so it passes without ever
distinguishing them.

Test Capability Result
CONTROL: a data.<key> filter still deletes exactly the items it lists @cap:datasets.bulk-delete-items PASS
a delete filtered by item id removes exactly the item that filter lists @cap:datasets.bulk-delete-items FAIL — under-delete
a delete filtered by a created_at window that lists no rows removes no rows @cap:datasets.bulk-delete-items FAIL — over-delete
a batch update filtered by item id tags exactly the item that filter lists @cap:datasets.filter-scoped-batch-update FAIL — no-op

What the failures actually printed:

  • Under-delete. GET items?filters=[id = <item>] returns that item.
    POST /v1/private/datasets/items/delete with the identical filter answers
    204 and removes nothing — all 15 seeded items are still there, target
    included.
  • Over-delete — the data-loss one. The threshold sits strictly after every
    item was authored and strictly before the version was committed, so
    GET items?filters=[created_at > T] returns total: 0: the page a user would
    read says the filter matches nothing. The delete with that same filter
    removed all 15 items, including the 3 bystanders the filter could not have
    matched
    . Irreversible, 204, no error.
  • Batch update. PATCH /v1/private/datasets/items/batch filtered by
    id = <item> answers 204 and tags nothing, while GET with that filter
    still returns the row.

2. tests/datasets/dataset-items-select-all-delete.spec.ts@t2-cuj @area:datasets

UI, one test, @cap:datasets.view-items + @cap:datasets.bulk-delete-items.
FAIL, and this is the half that makes the defect user-reachable.

Every existing dataset-items spec ticks rows one at a time, which stages a draft
the user reviews. DatasetItemsActionsPanel sends an id list unless
isAllItemsSelected is set, and the only control that sets it is the
"Select all N items?" banner — which only renders when the filtered set spans
more than one page. Every other spec in the estate stays under the default
10-row page, so nothing has ever reached this path. Once set, the request is the
filter-scoped one and commits its own version immediately, with no draft to
review.

The run reached the delete with everything before it green: the grid rendered
Showing 1-10 of 12, the banner escalated to All 12 items are selected, and
the delete request was accepted with 204. Then 10 rows were still on
screen
, the API still held all 15 items, and the new version recorded
items_deleted: 0 — over a success toast. The spec deliberately does not assert
on that toast: rendering a success toast over an unchanged table is the failure.

Three of the fifteen seeded items are outside the filtered prefix, so
"the 12 rows went" cannot be satisfied by a delete that emptied the dataset.

3. tests/experiments/compare-item-timestamps.spec.ts@t2-cuj @area:experiments

API-level, one test, @cap:experiments.compare-side-by-side. PASS — a
regression guard on behaviour that is correct today, over exactly the column
aliasing that is wrong above.

experiments-compare.spec.ts covers scores, outputs, sort, search, the Feedback
scores tab and the row detail panel, and asserts nothing about timestamps. This
asserts that after a second version the comparison read reports each item
authored when the item was, not when its version was cut.

Deliberately API-level, and this narrows the candidate rather than shortcuts
it.
The compare grid renders no created-at column at all — neither
getFilterColumns() nor the dataset/output column builders in
ExperimentItemsTab define one, and the row detail panel does not show it
either. There is no UI proxy for this value to assert on, so asserting the
endpoint the grid reads from is the honest form of the check. If a created-at
column is ever added to the compare view, the UI half belongs here alongside it.

How they were run

From tests_end_to_end/e2e/, against $OPIK_BASE_URL
(https://staging.dev.comet.com/opik, workspace opik-testing):

npx playwright test \
  tests/datasets/dataset-items-versioned-filter-scope.spec.ts \
  tests/datasets/dataset-items-select-all-delete.spec.ts \
  tests/experiments/compare-item-timestamps.spec.ts \
  --reporter=list --retries=0

Because this PR touches a shared page object and the backend client, the full
tests/datasets/ + tests/experiments/ directories were run too: 23 tests,
19 passed, 4 failed — and the 4 are exactly the ones described above.
No
pre-existing spec broke. tag_lint.py reports 0 problem(s).

npx tsc --noEmit cannot run in this checkout: tsconfig.json still sets
baseUrl, which the pinned TypeScript 7 removed (TS5102), so the command
fails before compiling anything. That is pre-existing on the release ref and not
something this PR touches. Type-checking the same project with TypeScript 5.9
reports only the pre-existing duplicate deleteDashboard member in
core/backend/client.ts, and nothing from these files.

Supporting changes

  • fixtures/versioned-dataset.fixture.ts (new) — a dataset versioned twice
    without its items being re-authored, which is the only state in which the
    two column sets hold different values. The version is committed by a grouped
    batch update
    (the request the UI's select-all "Add tag" sends); an SDK
    re-insert would not do, because rewriting an item moves item_created_at
    forward with the snapshot and the two agree again. 12 items share an aged
    UUIDv7 id prefix, 3 deliberately do not.
  • fixtures/versioned-comparison.fixture.ts (new) — the same version
    commit chained onto the existing comparison seed, so the two experiments,
    their traces and their teardown stay owned by the fixture that already does
    that properly. It creates nothing, so it tears nothing down.
  • Both fixtures verify their own precondition server-side before any test
    reads them
    — two versions, every seeded id present, the prefix carried by
    exactly the intended items, and the version stamped strictly after the last
    item was authored — and throw naming what was wrong. A test asserting on a
    divergence its fixture silently failed to create could not fail.
  • core/backend/client.tscreatedAt on DatasetVersionRef and on
    DatasetItemWithTagsRef; listDatasetItemsPage (the ids and the reported
    total, so a response whose total disagrees with the rows it carried fails);
    commitDatasetItemVersionByTagging; listCompareItems. Plus
    requireTimestamp, which throws rather than defaulting an absent timestamp —
    the generated REST types mark every createdAt optional, and a default here
    would turn a field the endpoint stopped returning into a comparison that
    quietly still passes.
  • pom/dataset-items.page.tsgoto({ filters }), selectAllOnPage,
    selectAllMatching, allItemsSelectedBanner, paginationSummary,
    emptyState, bulkDeleteAllSelected. goto()'s existing no-argument callers
    are unaffected.
  • coverage/taxonomy.yaml — the three specs added to their areas' specs:
    lists. No capability is flipped: all four keys used were already
    covered: true, and nothing here claims new coverage.

One thing worth a data-testid

The items grid's Filters trigger is FiltersButton with layout="icon": an
icon-only button with no accessible name and no data-testid, so the only
handle on it today is a structural selector. The POM therefore writes the
filters query param directly — the same serialised value the popover writes,
read back by the same useQueryParam the popover feeds, so the grid takes the
identical code path. This follows the precedent CompareExperimentsPage
already sets for its own unclickable controls.

A data-testid on that shared trigger would be worth adding, but adding it here
would not help: these specs run against a deployed environment, which serves
the released frontend bundle, so a new attribute could not reach them.

What was deliberately not written

The exploration offered a fourth candidate, marked weak:
"a filtered dataset items page reports a total that agrees with the rows you
can page to"
— the read/paging half, verified healthy on staging. Skipped: it
is a narrow API assertion over behaviour that currently works, search-items
and version-history-view already cover the neighbourhood, and reviewer
attention is better spent on the three above.

The exploration itself had already set aside three more before proposing
anything: the Dashboards Description filter operators (already covered by
dashboards-list-filters.spec.ts; extending it is a one-line parameterisation
for that spec's author), the list-error-state defect (the frontend fix was
deliberately reverted and deferred, so a test now would either encode the
misleading behaviour as correct or ship red for a reason nobody chose), and
redaction plus the LangChain/Bedrock token extractors (never driven — flag off,
and no provider credentials — and the bar is not to propose a test for a flow
nobody verified).

4 candidates proposed → 3 written, 1 dropped. 0 dropped for being unrunnable.

…sions

From the 2.2.42 -> 2.2.43 release exploration on staging. Three specs for
flows a human verified there by hand.

Two of them are RED against 2.2.43, and that is the finding: once a dataset
has been versioned more than once, a filter-scoped delete or batch update
does not touch the set the same filter lists. The read paths alias the
item-level columns; the mutation paths still filter the raw table, whose
snapshot rows get a fresh id and created_at on every version commit. Both
directions reproduce - a delete by item id that removes nothing, and a
created_at window the page says matches nothing that removes every row. The
control test in the same file (a data.<key> filter over the same dataset)
passes, which isolates the failure to column binding.

The third is green: a regression guard that the experiment-comparison read
reports each item authored when the item was, not when its version was cut.

- tests/datasets/dataset-items-versioned-filter-scope.spec.ts (API, t3)
- tests/datasets/dataset-items-select-all-delete.spec.ts (UI, t2)
- tests/experiments/compare-item-timestamps.spec.ts (API, t2)
- fixtures/versioned-dataset, fixtures/versioned-comparison: a dataset
  versioned twice without its items being re-authored, each verifying that
  precondition server-side before a test reads it
- core/backend/client: createdAt on version and item refs,
  listDatasetItemsPage, commitDatasetItemVersionByTagging, listCompareItems
- pom/dataset-items: filtered goto, the select-all banner, the select-all
  delete
- coverage/taxonomy.yaml: the three specs added to their areas' specs lists;
  no capability flipped

Generated by the release QA side flow. Needs review before merge.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added tests Including test files, or tests related like configuration. typescript *.ts *.tsx labels Aug 27, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📋 PR Linter Failed

Missing Section. The description is missing the ## Details section.


Missing Section. The description is missing the ## Change checklist section.


Missing Section. The description is missing the ## Issues section.


Missing Section. The description is missing the ## Testing section.


Missing Section. The description is missing the ## Documentation section.

@github-actions

Copy link
Copy Markdown
Contributor

⏱️ pre-commit per-hook timing

Hook Description Result Duration
⚓ helm-docs Regenerate Helm chart README 3.49s
Total (1 ran) 3.49s
⏭️ 43 skipped (no matching files changed)
Hook Description Result
🐍 trim trailing whitespace — python sdk Strip trailing whitespace ⏭️
🐍 fix end of files — python sdk Ensure files end in a newline ⏭️
🐍 ruff — python sdk Lint + autofix Python (ruff) ⏭️
🐍 ruff-format — python sdk Format Python code (ruff) ⏭️
🐍 mypy — python sdk Static type check ⏭️
🤖 trim trailing whitespace — optimizer Strip trailing whitespace ⏭️
🤖 fix end of files — optimizer Ensure files end in a newline ⏭️
🤖 check yaml — optimizer Validate YAML syntax ⏭️
🤖 check json — optimizer Validate JSON syntax ⏭️
🤖 check toml — optimizer Validate TOML syntax ⏭️
🤖 check for added large files — optimizer Block large files (>1MB) ⏭️
🔐 detect private key — optimizer Block committed private keys ⏭️
🤖 check for merge conflicts — optimizer Block merge-conflict markers ⏭️
🤖 check for case conflicts — optimizer Block case-only name clashes ⏭️
🤖 pyupgrade — optimizer Modernize Python syntax ⏭️
🤖 ruff — optimizer Lint + autofix Python (ruff) ⏭️
🤖 ruff-format — optimizer Format Python code (ruff) ⏭️
🤖 mypy — optimizer Static type check ⏭️
📓 nbstripout — optimizer notebooks Strip notebook output ⏭️
📝 markdownlint — optimizer Lint Markdown ⏭️
🔤 codespell — optimizer Fix common misspellings ⏭️
📊 radon cc — optimizer Cyclomatic-complexity gate ⏭️
📊 radon raw — optimizer Raw size metrics gate ⏭️
📊 xenon — optimizer Fail on complexity thresholds ⏭️
📊 lizard — optimizer Cyclomatic-complexity gate ⏭️
🧹 vulture — optimizer Find dead code ⏭️
🛡️ trim trailing whitespace — guardrails Strip trailing whitespace ⏭️
🛡️ fix end of files — guardrails Ensure files end in a newline ⏭️
🛡️ ruff — guardrails Lint + autofix Python (ruff) ⏭️
🛡️ ruff-format — guardrails Format Python code (ruff) ⏭️
🛡️ mypy — guardrails Static type check ⏭️
block non-public FE plugins Block non-public FE plugins ⏭️
☕ spotless — java backend Format Java code ⏭️
🧪 pre-commit wrapper smoke tests Self-test the wrapper scripts ⏭️
🧪 rebaseline script tests Self-test the changelog re-baseline script ⏭️
🌐 eslint — frontend Lint + autofix JS/TS ⏭️
🌐 typecheck — frontend Whole-project tsc type check ⏭️
📘 eslint — typescript sdk Lint + autofix JS/TS ⏭️
📘 typecheck — typescript sdk Whole-project tsc type check ⏭️
⚙️ actionlint — github workflows Lint GitHub Actions workflows ⏭️
🐳 hadolint — dockerfiles Lint Dockerfiles ⏭️
🌈 zizmor — github workflows security Security-scan GitHub Actions workflows ⏭️
🛡️ semgrep — java backend sql Block SQL injection-prone string formatting ⏭️

Comment on lines +6 to +12
export interface DatasetItemsFilter {
field: string;
operator: string;
value: string;
type?: string;
key?: string;
}

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.

Filter popover loses row identity

DatasetItemsFilter omits the required Filter.id, and goto() carries those objects into FiltersContent unchanged, so decoded deep-linked filters have undefined identities and cannot be independently edited or removed — should we serialize complete Filter objects with unique ids and frontend type/operator domains, or normalize URL filters before passing them to the editor?

Severity web_search

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
`tests_end_to_end/e2e/pom/dataset-items.page.ts` around lines 6-12, update
`DatasetItemsFilter` and the `goto()` URL-filter serialization so deep-linked filters
remain valid editor filters. Include a unique `id` and the frontend’s accepted `type`
and `operator` domains, or normalize decoded URL filters before passing them to
`initialFilters`; ensure each filter has a distinct identity so `FiltersContent` can
render, update, and remove rows independently.

Comment on lines +38 to +40
const query = opts.filters?.length
? `?filters=${encodeURIComponent(JSON.stringify(opts.filters))}`
: '';

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.

Persisted page size breaks select-all spec

goto() serializes only filters, so the prior dataset-items-pagination-size=25 remains active when the URL omits size; all 12 matching rows render on page one, countItems() returns 12 instead of PAGE_SIZE 10, and showSelectAllBanner is false because selectedRows.length === totalCount, so selectAllMatching() cannot find the escalation button. Could we include size=10 in the navigation URL or explicitly reset the page-size state so this spec exercises cross-page selection?

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
`tests_end_to_end/e2e/pom/dataset-items.page.ts` around lines 38-40, update
`DatasetItemsPage.goto()` so navigation explicitly sets the page size to 10 instead of
relying on persisted pagination state. Include `dataset-items-pagination-size=10` in the
URL (or otherwise reset the page-size state) while preserving the optional `filters`
parameter, ensuring the fixture uses multiple pages and the select-all escalation banner
appears.

Comment on lines +124 to +135
const created = await sdkClient.python.createDataset({
project_name: project.name,
name,
description: 'filter scope across dataset versions',
items: seededIds.map((id, index) => ({
id,
[VERSION_GROUP_COLUMN]: index % 2 === 0 ? VERSION_TARGET_GROUP : 'alpha',
input: `question ${index}`,
expected_output: `answer ${index}`,
})) as unknown as Array<Record<string, unknown>>,
});

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.

Setup failures leak remote datasets

tests_end_to_end/e2e/fixtures/versioned-dataset.fixture.ts:124-135 creates the remote dataset, but cleanup in tests_end_to_end/e2e/fixtures/versioned-dataset.fixture.ts:206-214 runs only after await use(ref), so any setup failure before that point leaves the dataset behind — should we register cleanup immediately after creation or wrap setup and use in try/finally, honoring shouldLeaveArtifacts and preserving the original failure if cleanup also fails?

Severity web_search

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
tests_end_to_end/e2e/fixtures/versioned-dataset.fixture.ts around lines 124-135, update
the `versionedDataset` fixture setup so the dataset created by `createDataset` is
cleaned up even if commit, validation, attachment, or `use` fails. Register cleanup
immediately after creation or wrap the entire setup and `await use(ref)` flow in a
`try/finally`, honoring `shouldLeaveArtifacts`; catch and warn on cleanup failures so
they do not replace the original setup or test failure.

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

Labels

baz: pending 🔴 size/XL tests Including test files, or tests related like configuration. typescript *.ts *.tsx

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant