Skip to content

[NA] [QA] Proposed e2e specs from the #8113 exploration — dataset/test-suite insert deduplication flag - #8114

Draft
CometActions wants to merge 1 commit into
mainfrom
comet-qa-bot/NA/dataset-insert-deduplication-e2e
Draft

[NA] [QA] Proposed e2e specs from the #8113 exploration — dataset/test-suite insert deduplication flag#8114
CometActions wants to merge 1 commit into
mainfrom
comet-qa-bot/NA/dataset-insert-deduplication-e2e

Conversation

@CometActions

Copy link
Copy Markdown
Collaborator

Generated by the release QA side flow (release-test-proposal). It is a draft on
purpose and needs a human review before merge — nothing here has been reviewed by a person.

Where these came from

#8113[SDK] feat: allow dataset item insertion without deduplication. A human-driven exploration worked the change on the PR's
own deployed environment (pr-8113.dev.comet.com, backend 2.2.47-8113-merge-3176) and
verified each flow below by hand before any spec was written.

Which base this targets, and why

The run that produced this was told #8113 was still open with its head branch alive, and to
base on aliaksandrk/NA-dataset-insert-without-deduplication. That is no longer true:
#8113 is merged (merge commit 976d1ca) and the head branch returns 404, so this PR is
based on main instead, which already carries the change.

The specs were written against the PR's head commit
c684b655c30932b0f0fe54ba8155826a388f480a and then re-run on main (this branch's
base) before this PR was opened — see the run below. The app they ran against is the PR
environment, which is a merge build of #8113 into main, so both the SDK under test and the
e2e estate the specs live in are the post-merge code.

What is in here

Three things, all in tests_end_to_end/:

1. The bridge can express the flag (it could not before — the exploration flagged this as
a blocker for writing any spec at all).

  • deduplication: bool = True on DatasetInsertItemsRequest and
    TestSuiteInsertItemsRequest (both are extra="forbid"), threaded into
    Dataset.insert(...) / TestSuite.insert(...), and surfaced on insertDatasetItems /
    insertTestSuiteItems in core/sdk/python-sdk-client.ts.
  • A new POST /datasets/insert-items-session route + insertDatasetItemsSession client
    method. /datasets/insert-items builds a fresh client — and therefore a fresh Dataset
    per request, and a backend-fetched Dataset always starts with its local hash cache
    marked unsynced. That makes the second spec below impossible to fail across separate
    calls: the third insert would re-sync regardless of what the second one did. The new route
    keeps one Dataset alive across a sequence of inserts, which is the only shape where
    "the cache was invalidated and re-synced" is distinguishable from "a new object synced
    because it always does."

2. Two POM additions, no new selectors invented:

  • DatasetItemsPage.versionChangeSummary(versionName) — the Version history "Changes" cell
    (+ N / ~ N / − N), addressed by data-cell-id exactly like the existing
    versionItemCount. This is the only place a user sees the per-version added/modified
    split; "Item count" alone cannot tell a version that added one item from one that added
    two and deleted one.
  • TestSuiteItemsPage.itemRowIds() (mirroring the dataset page) and itemRows() on both,
    so row counts use a retrying toHaveCount(...) rather than a one-shot countItems().

3. Two specs. Each pairs the flag against its own default on a second entity seeded
identically, so the flag is the only variable and the test cannot pass by coincidence — not
against a backend that stored everything, nor one that collapsed everything.

tests/datasets/dataset-insert-deduplication.spec.ts

Test Tags Asserts
deduplication=false stores every duplicate and counts it; the default collapses duplicates into one item and one version @t2-cuj @cap:datasets.sdk-round-trip Same item inserted twice into two datasets, one flag apart. Dedup-off holds 2 items under distinct ids and cuts 2 versions (items_total 1→2, items_added 1 each, items_modified 0); the control holds 1 item and one version — the second call deduplicated to zero, so no batch was sent and none was cut. UI: 2 rows on the Records tab matched by data-row-id (the rows are content-identical, so only identity shows both stored items are rendering rather than one twice), Item count 1 / 2 and Changes + 1 / + 1 on Version history; 1 row for the control.
A deduplication=false insert invalidates the local hash cache, so the next deduplicated insert still drops the duplicate @t2-cuj @cap:datasets.version-history-view Three inserts in one SDK session: [ITEM] deduplicated, [ITEM, OTHER] not deduplicated, [OTHER, THIRD] deduplicated. Result is 4 items (2×ITEM, 1×OTHER, 1×THIRD) and versions +1 / +2 / +1 over totals 1 / 3 / 4, asserted in the API and rendered on Version history.

One deliberate deviation from the verified flow, and it matters. The candidate's flow was
two deduplication=False inserts followed by a deduplicated [duplicate, new]. Run through
the bridge that sequence passes whether or not the SDK invalidates its cache, because the
session's Dataset starts unsynced and insert 3 would sync anyway. The order above starts
with a deduplicated insert, which forces the one-shot sync and leaves the cache marked
in-sync holding only ITEM's hash — so insert 3 can only be right if the non-deduplicated
insert 2 invalidated it. Without that invalidation, OTHER is sent again under a fresh id and
v3 reads + 2 over 6 items instead of + 1 over 4. Same behaviour the exploration verified,
sharpened so the test can actually fail.

tests/test-suites/test-suite-insert-deduplication.spec.ts

Test Tags Asserts
deduplication=false stores both identical test cases and the Test cases tab renders both; the default stores one @t2-cuj @cap:test-suites.view-suite-items TestSuite.insert is a separate public entry point routing into the same internal insert. Dedup-off suite holds 2 items under distinct ids and renders 2 rows; the control holds 1 and renders 1. Rows matched by data-row-id — the Test cases table renders only ID / Tags / Created, so the item content is not visible there and identity is the only discriminator. The content assertion is API-side for that reason.

Taxonomy

Both specs added to their areas' specs: lists. All three capabilities were already
covered: true, so no flip was needed; each note: was extended to scope what is now
asserted (tag_lint.py does not check either of those, so they are worth reading).
No new capability keys were invented.

Verification — passed

Run against the PR environment (OPIK_BASE_URL=https://pr-8113.dev.comet.com, OSS install,
workspace default, no auth), from tests_end_to_end/e2e/ on this branch:

OPIK_DEPLOYMENT=oss npx playwright test \
  tests/datasets/dataset-insert-deduplication.spec.ts \
  tests/test-suites/test-suite-insert-deduplication.spec.ts \
  --reporter=list --workers=2
✓ 1 dataset-insert-deduplication.spec.ts:183 › …invalidates the local hash cache… (5.0s)
✓ 2 dataset-insert-deduplication.spec.ts:51  › …stores every duplicate and counts it… (7.7s)
✓ 3 test-suite-insert-deduplication.spec.ts:23 › …stores both identical test cases… (7.3s)

3 passed (18.5s)

python3 tests_end_to_end/coverage/tag_lint.py --taxonomy tests_end_to_end/coverage/taxonomy.yaml --estate tests_end_to_end58 specs checked, 1 exempt, 0 problem(s).

npx tsc --noEmit could not be used as a gate here, and not because of this change:
tsconfig.json still sets baseUrl, which TypeScript 7 (the pinned ^7.0.2) has removed,
so it fails identically on an untouched checkout. Type-checking with baseUrl dropped
reports the same 94 pre-existing errors before and after this change and none in the files
added or edited here
. Worth fixing separately.

What was deliberately not written

  • Nothing was dropped. All three verified candidates became specs; none failed.
  • The exploration had already set aside a fourth item — a 1200-duplicate multi-batch
    deduplication=False insert on the new num_threads=4 default. dataset-version-counters.spec.ts
    already covers the parallel-upload-collapses-to-one-version axis at num_threads=8 and
    asserts on the result rather than the transport, so a new spec would add cost without
    coverage.
  • Relatedly, the exploration suggested changing the bridge's DatasetInsertItemsRequest
    default from num_threads=1 to 4 so existing specs exercise the SDK's new default.
    Not done here — it silently changes how every existing dataset spec uploads, which is a
    call for the estate's owners, not a side effect of a test proposal.

Reviewer notes

  • The new bridge route is the one piece of added surface that is not a spec. If you would
    rather not have it, the second dataset test cannot be written honestly — say so and it can
    be dropped rather than weakened into a test that always passes.
  • Both specs clean up through registerDatasetCleanup (datasets and test suites share
    storage and do not cascade with their project); no cleanup lives in a test body.

…lag (#8113)

Turns the verified exploration candidates for #8113 into permanent specs.

- Bridge: thread `deduplication` through the dataset and test-suite
  insert-items routes, and add `/datasets/insert-items-session` so a spec can
  run several inserts against ONE `Dataset` object — without it, the SDK's
  hash-cache invalidation is unobservable and the spec asserting it could
  never fail.
- POM: `versionChangeSummary` on the dataset items page (the Version history
  "Changes" cell), `itemRowIds` on the test-suite items page, `itemRows` on
  both.
- Specs: `deduplication=False` stores every duplicate and counts it in the
  version counters while the default collapses it and cuts no second version;
  a non-deduplicated insert invalidates the local hash cache so the next
  deduplicated insert still drops its duplicate; and the same flag on
  `TestSuite.insert`, rendered on the Test cases tab.
- Taxonomy: both specs listed, capability notes scoped to what is now
  asserted.

Generated by the release QA side flow. Draft — needs human review.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@github-actions github-actions Bot added python Pull requests that update Python code tests Including test files, or tests related like configuration. typescript *.ts *.tsx labels Sep 2, 2026
@github-actions

github-actions Bot commented Sep 2, 2026

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

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

⏱️ pre-commit per-hook timing

No linted files changed — nothing to run.

⏭️ 44 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 ⏭️
⚓ helm-docs Regenerate Helm chart README ⏭️
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 +148 to +151
# Mirrors Dataset.insert's own default. False bypasses the content-hash
# dedup path entirely: every item is sent as-is, so identical content
# inserted twice is stored twice.
deduplication: bool = True

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.

Initial-item deduplication remains unavailable

Only DatasetInsertItemsRequest exposes deduplication, so createDataset({items, deduplication:false}) is rejected by DatasetCreate's extra="forbid", while createTestSuite and the live experiment seed routes always use the default — should we forward the option through all insertion paths or explicitly document and enforce default-only create/seed operations?

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/services/opik-sdk-driver/src/opik_sdk_driver/schemas.py` around
lines 148-151, the `deduplication` option is exposed only by
`DatasetInsertItemsRequest`, so `createDataset`, `createTestSuite`, and the live
experiment seed routes cannot represent non-default insertion behavior and may be
rejected by `extra="forbid"`. Add `deduplication: bool = True` to the corresponding
create/seed request models and update their handlers/bridge calls to forward it to
`Dataset.insert` or `TestSuite.insert`; add or update coverage for `False` in each path.

model_config = ConfigDict(extra="forbid")

items: list[dict[str, Any]]
num_threads: int = 1

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.

Invalid thread booleans reach dataset writes

DatasetInsertCall.num_threads accepts booleans as integers, so FastAPI coerces true into 1 before Dataset.insert can reject it, letting invalid requests write data — should we enforce strict positive-integer validation on this field while keeping the default of 1?

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/services/opik-sdk-driver/src/opik_sdk_driver/schemas.py` around
lines 160-167, update `DatasetInsertCall.num_threads`, which currently coerces `true`
into `1` and permits invalid non-positive values before `Dataset.insert` runs. Use
strict integer validation with a positive constraint (while preserving the default of
1), so FastAPI rejects booleans and values below 1 during request validation before
dataset lookup or insertion.

Comment on lines +160 to +171
class DatasetInsertCall(BaseModel):
"""One `Dataset.insert(...)` inside an insert-items-session request."""

model_config = ConfigDict(extra="forbid")

items: list[dict[str, Any]]
num_threads: int = 1
deduplication: bool = True


class DatasetInsertItemsSessionRequest(BaseModel):
"""Several inserts against ONE `Dataset` object, in one client session.

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.

Missing unit coverage for request validation

The new DatasetInsertCall/DatasetInsertItemsSessionRequest boundary lacks tests for unknown fields at either nesting level and for inserts: [], so validation/status regressions can go unnoticed — should we add focused Pydantic/FastAPI tests asserting the intended behavior, as AGENTS.md requires?

Severity

Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by apply pr to [branch name]

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/services/opik-sdk-driver/src/opik_sdk_driver/schemas.py` around
lines 160-171, the new `DatasetInsertCall` and `DatasetInsertItemsSessionRequest`
contract is not covered at its validation boundary. Add focused Pydantic/FastAPI tests
in the corresponding test module that submit unknown fields at both the nested-call and
top-level request levels, and exercise an empty `inserts` list, asserting the intended
validation or HTTP status behavior defined by the route. Keep the existing
`extra="forbid"` contract and update tests according to the conventions in `AGENTS.md`
and nearby endpoint tests.

Comment on lines +61 to +62
}) => {
const offName = `${testNamespace}-dedup-off`;

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.

Add required test description annotations

The three new test callbacks begin without the required description annotation; should we add test.info().annotations.push({ type: 'description', description: '...' }) as the first statement in each body, per .agents/skills/playwright-e2e/test-conventions.md?

Severity

Want Baz to fix this for you? Activate Fixer You can also update your AI coding guidelines based on this comment by apply pr to [branch name]

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/tests/datasets/dataset-insert-deduplication.spec.ts` around lines
61-62, update the new dataset test callback so its first statement is the required
`test.info().annotations` description annotation, before declaring dataset names. Apply
the same convention to the other new test callback around lines 193-194 (and any
affected suite/test callback in this spec), using concise descriptions that explain each
test’s purpose and following `.agents/skills/playwright-e2e/test-conventions.md`.

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

Labels

python Pull requests that update Python code 🟠 size/L 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