Skip to content

feat(s3-explorer): copy objects and prefixes by dropping them on a prefix - #1090

Open
idemery wants to merge 2 commits into
InseeFrLab:mainfrom
idemery:feat/copy-objects
Open

idemery wants to merge 2 commits into
InseeFrLab:mainfrom
idemery:feat/copy-objects

Conversation

@idemery

@idemery idemery commented Sep 26, 2026

Copy link
Copy Markdown

Builds on #1089, which publishes what is being dragged. The diff here includes that commit; it will shrink to its own once #1089 lands.

What

Objects and prefixes dragged out of an explorer can be dropped onto a prefix row, or onto the surface to land in the listed prefix. It is CopyObject, server side.

Why

The explorer can list, upload, download, delete and share, but it cannot copy. There is no copy, no move and no rename anywhere in the app, and S3Client had no method for it — so getting a file from one prefix to another means downloading it and uploading it again, through the browser, at the size of the object.

Here no bytes reach the browser: a 40 GB object costs the client one request.

The key arithmetic

Most of what makes this feel like a file manager is path handling, so it lives in a pure module with tests rather than inside the thunk:

  • Dragging a folder keeps its name and its internal shape. The part of the key below the dragged item's parent is rebuilt under the destination. Taking the basename instead would flatten every object side by side and let same-named files in different folders overwrite each other.
  • A prefix dropped into itself or into its own descendant is refused — the crawl would keep finding the objects it had just written. The check is on path segments, so reports/ may still be copied into the sibling reports-archive/, which a string-prefix check would forbid.
  • Dropping something back where it already is is refused rather than performed; on a versioned bucket it would be a new version for nothing.

CopySource is encoded per segment, which the SDK does not do for this parameter — it is a header value, not a path parameter. An unencoded key containing a space, a + or a # either fails the request or copies a different object than the one asked for.

Deliberately not here

  • Move. It would be copy-then-delete with no transaction and no undo, so a partial failure loses data. It deserves its own design rather than riding in on a drag gesture.
  • Objects above 5 GiB, where S3 requires a multipart copy. The single-part limit is stated on the port so a caller keeps it rather than discovers it.

Happy to follow up with either if you'd like them.

Interaction details

  • An object row is not a destination: dragging across one on the way to the background falls through to the listed prefix rather than refusing the drop.
  • The destination highlight is an outline, not a fill — the row beneath may already be selected or striped, and a second background colour reads as a third state.
  • A drag of files from outside the browser remains an upload and stays visually distinct. Different gesture, different outcome.
  • onCopyObjects left undefined makes the explorer a drag source only.

Notes

  • New copyObject on the S3Client port, one adapter implementation.
  • The new overlay string is translated in all nine languages.
  • S3ExplorerMainView.spec.md updated.
  • 25 new tests; tsc, eslint and vite build clean.

The explorer accepts a drop of files from the desktop, but nothing can be
dragged out of it. Anyone who wants an object's URI somewhere else — a notebook
cell, a chat box, a form field, an application embedding Onyxia — has to open
the row menu, copy the URI, and paste it.

Rows are now draggable. A drag publishes the S3 URIs twice: as `text/plain`,
newline separated, which is what makes a drop onto an ordinary text field
useful without the target knowing anything about Onyxia; and as
`application/x-onyxia-s3-objects`, JSON, for a consumer that needs to tell
"these are S3 objects" from "the user dropped some text".

Nothing is read and nothing is modified — only the URIs travel.

Three details that are easy to get wrong, so they are pure functions with tests
rather than logic inside the component:

- Which rows a drag carries is not "the selection". Dragging an unselected row
  drags that row alone; returning the selection there would carry files the user
  cannot see they had selected, since this list is virtualized.
- Rows mid-upload or mid-delete are not draggable, and are dropped from a
  multi-row drag rather than refusing the whole gesture.
- Detection during dragover reads `types` and never `getData()`. The
  DataTransfer is in protected mode while a drag is in flight, so every value
  reads back empty — a check written against `getData()` passes its unit test
  and refuses every real drag.

A drag beginning on the row checkbox or a row action button is that control's
gesture and does not become a row drag.

Signed-off-by: Islam Eldemery <islam.eldemery@gmail.com>
…efix

The explorer can list, upload, download, delete and share, but it cannot copy.
There is no copy, no move and no rename anywhere in the app, and `S3Client` had
no method for it — so getting a file from one prefix to another means
downloading it and uploading it again, through the browser, at the size of the
object.

Objects and prefixes dragged out of an explorer can now be dropped onto a prefix
row, or onto the surface to land in the listed prefix. It is CopyObject, server
side: no bytes reach the browser, so a 40 GB object costs the client one request.

Most of what makes this feel like a file manager is key arithmetic, so it is in
a pure module with tests rather than inside the thunk:

- Dragging a folder keeps its name and its internal shape. The part of the key
  below the dragged item's parent is what gets rebuilt under the destination —
  taking the basename instead would flatten every object side by side and let
  same-named files in different folders overwrite each other.
- A prefix dropped into itself or into its own descendant is refused: the crawl
  would keep finding the objects it had just written. The check is on path
  segments, so `reports/` may still be copied into the sibling
  `reports-archive/`, which a string-prefix check would forbid.
- Dropping something back where it already is is refused rather than performed;
  on a versioned bucket it would be a new version for nothing.

`CopySource` is encoded per segment, which the AWS SDK does not do for this
parameter — it is a header value, not a path parameter. An unencoded key
containing a space, a `+` or a `#` either fails the request or copies a
different object than the one asked for.

Deliberately not here:

- MOVE. It would be copy-then-delete with no transaction and no undo, so a
  partial failure loses data. It deserves its own design rather than riding in
  on a drag gesture.
- Objects above 5 GiB, where S3 requires a multipart copy. The single-part limit
  is stated on the port so a caller keeps it rather than discovers it.

An object row is not a destination: dragging across one on the way to the
background falls through to the listed prefix rather than refusing the drop. The
destination highlight is an outline rather than a fill, because the row beneath
may already be selected or striped. A drag of files from outside the browser
remains an upload and stays visually distinct — different gesture, different
outcome.

Depends on the row-drag change that publishes what is being dragged.

Signed-off-by: Islam Eldemery <islam.eldemery@gmail.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Security Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

}) => {
const { sourceS3Uri, destinationS3Uri } = params;

const cmdId = Date.now() + Math.random();
@garronej

Copy link
Copy Markdown
Contributor

@idemery thank you very much for the PR.

We'll discuss it with the team soon.

On a scale of one to ten, how automated are the PR that you sumitted?
It's important for us to know if what you're offering is essencially some of your tokens that you've applied to contribute to Onyxia or if those are a human contribution.

I'm not saying that fully automated PR have no value, tokens are expensive, but I don't review human an LLM code in the same way.

Best,

This branch has not been deployed

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants