[WIKI-419] chore: new asset duplicate endpoint added
Description
This PR introduces a new endpoint to duplicate assets and attach them to an entiyt without the need to re-upload them.
Type of Change
- [x] Improvement (change that would cause existing functionality to not work as expected)
Summary by CodeRabbit
-
New Features
- Duplicate file assets via API and receive a new asset ID.
- Per-asset rate limiting to reduce excessive duplicate requests.
- Editor support for duplicating embedded assets (rich text/images) with retry UI on failure.
- Paste handling now processes and deduplicates embedded assets; editor paste sanitization that removed img tags has been removed.
-
Bug Fixes
- None.
[!NOTE]
Other AI code review bot(s) detected
CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.
Walkthrough
Adds a workspace-scoped POST endpoint to duplicate uploaded assets (per-asset throttled), server logic to copy storage and create a new FileAsset, client-side service/store/hook wiring to call it, editor file-handler duplicate support, paste-time duplication handlers, and custom-image duplication lifecycle and UI retry controls.
Changes
| Cohort / File(s) | Change Summary |
|---|---|
API route & export apps/api/plane/app/urls/asset.py, apps/api/plane/app/views/__init__.py |
Register DuplicateAssetEndpoint route at assets/v2/workspaces/<str:slug>/duplicate-assets/<uuid:asset_id>/ and export the endpoint from views. |
API implementation & throttling apps/api/plane/app/views/asset/v2.py, apps/api/plane/throttles/asset.py, apps/api/plane/settings/common.py |
Add DuplicateAssetEndpoint (POST) that validates inputs, maps entity context to FK, copies storage object to a new key, creates a new FileAsset (uploaded=true) and returns new asset id; add AssetRateThrottle keyed by asset_id and register asset_id = 5/minute (and anon = 30/minute). |
Web client — service & store apps/web/core/services/file.service.ts, apps/web/core/store/editor/asset.store.ts, apps/web/core/hooks/store/use-editor-asset |
Add fileService.duplicateAsset(...); expose duplicateEditorAsset in store/hook that calls the service and returns { asset_id }. |
Web UI integrations apps/web/core/components/**, apps/web/core/components/editor/rich-text/description-input/root.tsx, apps/web/core/components/inbox/modals/create-modal/issue-description.tsx, apps/web/core/components/issues/issue-detail/issue-activity/helper.tsx, apps/web/core/components/issues/issue-modal/components/description-editor.tsx, apps/web/app/.../page.tsx |
Wire duplicateEditorAsset into editor/file handlers and RichTextEditor via new duplicateFile prop; handlers call duplication with workspace/project/entity params, propagate returned asset id, and surface consistent error messages on failure. |
Editor paste plugin & helpers packages/editor/src/ce/helpers/asset-duplication.ts, packages/editor/src/core/plugins/paste-asset.ts, packages/editor/src/core/extensions/utility.ts, packages/editor/src/core/props.ts |
Add asset-duplication handlers (image handler injecting new UUID and STATUS=DUPLICATING), add PasteAssetPlugin to process pasted HTML via handlers and re-dispatch modified paste, register plugin in utility extension, and remove prior transformPastedHTML hook. |
Custom-image types, utils, extension packages/editor/src/core/extensions/custom-image/types.ts, .../utils.ts, .../extension.tsx |
Add ECustomImageStatus enum (PENDING, UPLOADING, UPLOADED, DUPLICATING, DUPLICATION_FAILED), include STATUS in defaults, add helpers (isImageDuplicating, hasImageDuplicationFailed, etc.), and expose optional duplicateImage option from fileHandler. |
Custom-image components (node-view / uploader / block) packages/editor/src/core/extensions/custom-image/components/node-view.tsx, .../uploader.tsx, .../block.tsx |
Integrate duplication lifecycle: node-view triggers duplicateImage when status is DUPLICATING and updates attrs or sets DUPLICATION_FAILED; uploader accepts hasDuplicationFailed, shows Retry UI and blocks picker while failed; block component adjusts loader/toolbar/resizer visibility during duplication. |
Editor types & props packages/editor/src/core/types/config.ts, packages/editor/src/core/props.ts, packages/editor/src/core/extensions/utility.ts, packages/editor/src/core/plugins/paste-asset.ts |
Add duplicate(assetId: string) => Promise<string> to TFileHandler; remove transformPastedHTML from CoreEditorProps; register PasteAssetPlugin() in utility plugins. |
Site app fallback apps/space/helpers/editor.helper.ts |
Add a no-op duplicate(assetId) handler returning the same id as fallback (marked unsupported for sites/space). |
Sequence Diagram(s)
sequenceDiagram
autonumber
participant Editor as Editor UI
participant Store as EditorStore / FileService
participant API as DuplicateAssetEndpoint
participant Throttle as AssetRateThrottle
participant DB as FileAsset DB
participant Storage as ObjectStorage
Editor->>Store: duplicateEditorAsset(assetId, entityType, ...)
Store->>API: POST /assets/v2/workspaces/{slug}/duplicate-assets/{assetId}/ { entity_type, entity_id?, project_id? }
API->>Throttle: get_cache_key(view.kwargs) -> "throttle_asset_{assetId}"
Throttle-->>API: allow/deny
API->>DB: GET original FileAsset(assetId)
API->>Storage: copy original_key -> new_dest_key
API->>DB: CREATE FileAsset (uploaded=true, linked FK)
API-->>Store: 201 { asset_id: new_asset_id }
Store-->>Editor: return { asset_id: new_asset_id }
Estimated code review effort
🎯 4 (Complex) | ⏱️ ~45 minutes
- Areas needing extra attention:
- Storage copy semantics and chosen destination key naming in
DuplicateAssetEndpoint.post. - Correctness of entity-type → FK mapping in
get_entity_id_field. - Throttle key logic in
AssetRateThrottle.get_cache_keyand matchingDEFAULT_THROTTLE_RATESkey. - Paste plugin HTML mutation, idempotency marking (data-uploaded) and re-dispatch behavior.
- Concurrency guards and retry transitions in custom-image node-view/uploader (DUPLICATING → UPLOADED / DUPLICATION_FAILED).
- Client-side error propagation and consistent UI messages across duplicate flows.
- Storage copy semantics and chosen destination key naming in
Poem
I’m a rabbit with a tiny hat,
I clone pixels where they sat.
Keys get twins and paste gets wise,
Throttle hums and small retries.
Hop again — the duplicate flies! 🐇✨
Pre-merge checks and finishing touches
❌ Failed checks (2 warnings)
| Check name | Status | Explanation | Resolution |
|---|---|---|---|
| Description check | ⚠️ Warning | The description is incomplete. While it mentions the feature (duplicating assets), it lacks required sections like Test Scenarios, Screenshots/Media, and References, and contains a typo ('entiyt'). | Add Test Scenarios section describing how the duplication feature was tested, include any relevant Screenshots/Media, and add References linking to the related issue (WIKI-419). Fix the typo 'entiyt' to 'entity'. |
| Docstring Coverage | ⚠️ Warning | Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. | You can run @coderabbitai generate docstrings to improve docstring coverage. |
✅ Passed checks (1 passed)
| Check name | Status | Explanation |
|---|---|---|
| Title check | ✅ Passed | The title accurately describes the main change: adding a new asset duplicate endpoint, which is the core feature across all modified files. |
✨ Finishing touches
- [ ] 📝 Generate docstrings
🧪 Generate unit tests (beta)
- [ ] Create PR with unit tests
- [ ] Post copyable unit tests in a comment
- [ ] Commit unit tests in branch
chore/asset-duplicate-endpoint
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
Comment @coderabbitai help to get the list of available commands and usage tips.