Skip to content

Add attachments to existing row in workbench#7068

Open
alesan99 wants to merge 253 commits into
mainfrom
issue-6078
Open

Add attachments to existing row in workbench#7068
alesan99 wants to merge 253 commits into
mainfrom
issue-6078

Conversation

@alesan99
Copy link
Copy Markdown
Contributor

@alesan99 alesan99 commented Jul 14, 2025

Fixes #6078

image

Adds the ability to upload additional attachments to a workbench dataset with attachments. You can also delete attachments that belong to a row.

Not implemented (future updates):

  • Edit attachment ordering / Deleting any attachment in a row

Checklist

  • Self-review the PR after opening it to make sure the changes look good and
    self-explanatory (or properly documented)
  • Add relevant issue to release milestone
  • Add pr to documentation list
  • Add automated tests
  • Add a reverse migration if a migration is present in the PR

Testing instructions

  • Go to Workbench -> Import Attachments
  • Upload multiple attachments.
  • Select an existing row and add a new attachment
  • Make sure the dataset was saved once the attachment was uploaded.
  • Select an existing row and add multiple new attachments.
  • Add an attachment to a new row
  • Move the "Attachment" column to a different place by dragging it by the header. Make sure adding and deleting attachments still works and updates the text in the column correctly
  • Open up the attachment preview for a row with multiple attachments
    • Make sure you can scroll through all the attachments with the form slider
    • Make sure the preview updates correctly when you click on a different row in the workbench
    • Make sure you can still scroll through images when you pop out the preview dialog.
  • Make sure there are no new errors in your browser's console when compared to main.
  • Make sure you cannot delete any rows that contain attachments. If your selection of rows contains any rows with attachments, no rows should be deleted. You should still be able to delete rows without any attachments.
  • Validate
  • Upload the dataset successfully
  • General Testing
  • You should still be able to upload attachments through data entry
  • You should still be able to upload attachments to bulk attachment upload (in the attachments page, not workbench)

Summary by CodeRabbit

  • New Features

    • Attachment viewer slider for browsing multiple attachments
    • Attachment upload with per-file progress tracking
    • Save flow now shows progress and reliably refreshes the spreadsheet after saving
  • Improvements

    • Per-attachment deletion directly from the spreadsheet
    • Prevent accidental row deletion when a dataset contains attachments
  • Documentation

    • Minor formatting updates to localization README

Review Change Stack

Triggered by b8d1889 on branch refs/heads/issue-6075
Triggered by bf9a7eb on branch refs/heads/issue-6075
Triggered by fd2c42c on branch refs/heads/issue-6075
Triggered by d93a3ac on branch refs/heads/issue-6075
Triggered by af95077 on branch refs/heads/issue-6075
Triggered by e7f5281 on branch refs/heads/issue-6075
Triggered by 7af5f3e on branch refs/heads/issue-6075
Triggered by 30ff2dc on branch refs/heads/issue-6075
Use promise chaining for attachment uploading
@alesan99 alesan99 removed this from the 7.12.0 milestone Jan 13, 2026
@CarolineDenis
Copy link
Copy Markdown
Contributor

@alesan99 could you:

  • merge main into this pr and resolve any conflicts
  • write a complete summary of what has been done
  • add a detailed description of what is left to do, along with guidance on how to proceed

@alesan99
Copy link
Copy Markdown
Contributor Author

  • WIP
  • This PR adds the ability to add new attachments to an existing row in a workbench dataset. This is a heavily requested feature.
  • The code needs to be cleaned up, and the ability to delete attachments needs to be implemented (It currently only removes the attachment from the dataset, leaving an orphan file). Deletion technically doesn't need to be added in this PR, though.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 20, 2026

📝 Walkthrough

Walkthrough

Adds end-to-end attachment support to the workbench: shared upload/delete helpers, an extracted save helper, expanded attachments preview with upload/progress and per-row deletion, viewer slider navigation, wiring to WbView, and row-deletion safety checks. Minor test and README formatting adjusted.

Changes

Workbench Attachments V2

Layer / File(s) Summary
Core attachment operation helpers
specifyweb/frontend/js_src/lib/components/WorkBench/attachmentHelpers.tsx
New exported functions uploadFiles, createDataSetAttachments, saveDataSetAttachments, uploadAttachmentsToRow, and deleteAttachmentFromRow provide reusable workflows for uploading files with progress reporting, creating and persisting dataset attachment records, and deleting attachments with backend synchronization.
Extract save workflow helper
specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx
New exported handleWorkbenchSave function encapsulates the full save flow: validation management, PUT request, deletion-failure callback, and spreadsheet state updates. The component's handleSave is refactored to an async function that opens/closes the progress bar around this helper.
Import attachments refactoring
specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx
Moves upload and dataset-attachment helpers to shared attachmentHelpers, removing local implementations and importing uploadFiles, createDataSetAttachments, and saveDataSetAttachments instead.
Attachment preview panel with uploads and deletion
specifyweb/frontend/js_src/lib/components/WorkBench/WbAttachmentsPreview.tsx
Expands the component to support workbench-level operations: upload progress tracking and modal, per-attachment DatasetAttachmentPreview with delete, fetches attachment metadata alongside resources, integrates with save and spreadsheet refresh workflows, manages a tuple-based attachment selector for the viewer, and updates cache/unload behavior for multi-attachment support. Props signature expanded to include workbench, save callbacks, upload state, and error handlers.
Wire expanded attachments preview
specifyweb/frontend/js_src/lib/components/WorkBench/WbView.tsx
Passes expanded prop set (workbench, isUploaded, searchRef, checkDeletedFail, onSpreadsheetUpToDate) to WbAttachmentsPreview to enable save integration and error handling.
Attachment viewer slider navigation
specifyweb/frontend/js_src/lib/components/WbAttachmentViewer/index.tsx
Adds Slider import and renders a slider in the dialog header bound to selectedAttachment, enabling users to navigate between multiple attachments by index.
Prevent deletion of rows with attachments
specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts
Imports getVisualAttachmentsColumn and usesAttachments, then updates beforeRemoveRow to check the attachments column for each row scheduled for deletion and return false to prevent removal when any row has a defined attachment.
Test and documentation formatting
specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts, specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts, specifyweb/frontend/js_src/lib/localization/README.md
Adjusts dynamic import statement formatting in two test files and corrects markdown spacing in the localization README without changing behavior or assertions.

Suggested reviewers

  • CarolineDenis
  • emenslin
  • Iwantexpresso
🚥 Pre-merge checks | ✅ 4 | ❌ 2

❌ Failed checks (2 inconclusive)

Check name Status Explanation Resolution
Automatic Tests ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
Testing Instructions ❓ Inconclusive Repository clone failed, so this custom check could not run with code access. Retry the review run. If this persists, inspect pre-merge custom-check logs for infrastructure or agent runtime failures.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the primary change: enabling attachment uploads to existing workbench rows, which is the core feature of this PR.
Linked Issues check ✅ Passed All coding requirements from #6078 are implemented: attachment upload UI in preview sidebar [6078], attachment addition to existing rows [6078], automatic dataset saving [6078], and deletion prevention for rows with attachments [6078].
Out of Scope Changes check ✅ Passed Changes are focused on implementing attachment functionality. Minor formatting updates to test imports and markdown documentation are supporting changes that don't introduce unrelated features.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-6078

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Triggered by 535c58a on branch refs/heads/issue-6078
@alesan99 alesan99 marked this pull request as ready for review May 20, 2026 19:03
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx`:
- Around line 15-40: handleWorkbenchSave returns immediately because the
ping(...) promise is neither awaited nor returned; change handleWorkbenchSave to
await or return the ping call so callers wait for the PUT to finish.
Specifically, update the body of handleWorkbenchSave to either "return await
ping(...)" or "return ping(...)" (keeping the existing then(...) chain that
calls checkDeletedFail, handleSpreadsheetUpToDate, sets
workbench.cells.cellMeta, calls workbench.utils?.searchCells and
workbench.hot?.render) so the async function properly resolves only after the
network save completes.

In `@specifyweb/frontend/js_src/lib/components/WorkBench/attachmentHelpers.tsx`:
- Around line 248-253: The current flow calls hot.setDataAtCell(row,
attachmentColumn, data) before awaiting the DELETE request, which can leave the
UI out-of-sync if the ping(`/api/specify/spdatasetattachment/${idToDelete}/`, {
method: 'DELETE' }) fails; change the order so you await the DELETE (using ping
and checking its success/response) first and only call hot.setDataAtCell(...)
after a successful deletion (handle and surface errors from ping so you do not
update the cell on failure), referencing the existing attachmentsToCell,
hot.setDataAtCell, ping, and idToDelete symbols when locating the code to
change.

In `@specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts`:
- Around line 515-521: The delete-guard currently reads renderer metadata
(cellMeta.formattedValue) which is unreliable; instead, for each entry in
removedRows use the dataset model on workbench.dataset to locate the
corresponding row object and inspect its attachment field/link list. Use
getVisualAttachmentsColumn(workbench.dataset, workbench.hot) to identify the
attachments column, map that column to the dataset's column key (or index) and
then check the dataset row's attachment value (e.g., non-empty array or
non-empty link string) to decide whether to return false; remove the dependence
on workbench.hot.getCellMeta(...) and cellMeta.formattedValue.

In
`@specifyweb/frontend/js_src/lib/components/WorkBench/WbAttachmentsPreview.tsx`:
- Around line 303-307: The delete button is incorrectly disabled for every
attachment except the last because the disabled check compares dataSetAttachment
to attachments.at(-1)?.spDataSetAttachment; update the guard so each
attachment's delete button is enabled when it corresponds to the current
dataSetAttachment (or meets the intended permission/ownership rule) by comparing
against the specific attachment instance in the mapping (use the local
attachment variable used when rendering Button.Small rather than
attachments.at(-1)), and ensure the remove handler (the delete flow that updates
ordinals/indexes) reindexes remaining attachments after removal (adjust
whichever function performs deletion to recompute ordinals and update attachment
list state).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: f98d5b4c-e0fa-4aa0-93f8-589709d3dc11

📥 Commits

Reviewing files that changed from the base of the PR and between 66b1266 and 3d973be.

📒 Files selected for processing (10)
  • specifyweb/frontend/js_src/lib/components/DataModel/__tests__/businessRules.test.ts
  • specifyweb/frontend/js_src/lib/components/PickLists/__tests__/fetch.test.ts
  • specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx
  • specifyweb/frontend/js_src/lib/components/WbAttachmentViewer/index.tsx
  • specifyweb/frontend/js_src/lib/components/WbImportAttachments/index.tsx
  • specifyweb/frontend/js_src/lib/components/WorkBench/WbAttachmentsPreview.tsx
  • specifyweb/frontend/js_src/lib/components/WorkBench/WbView.tsx
  • specifyweb/frontend/js_src/lib/components/WorkBench/attachmentHelpers.tsx
  • specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts
  • specifyweb/frontend/js_src/lib/localization/README.md
💤 Files with no reviewable changes (1)
  • specifyweb/frontend/js_src/lib/localization/README.md

Comment thread specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx
Comment thread specifyweb/frontend/js_src/lib/components/WorkBench/attachmentHelpers.tsx Outdated
Comment thread specifyweb/frontend/js_src/lib/components/WorkBench/hooks.ts
@github-project-automation github-project-automation Bot moved this from 📋Back Log to Dev Attention Needed in General Tester Board May 20, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx (1)

59-71: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Wrap in try/finally to ensure progress bar closes on error.

If handleWorkbenchSave throws, closeProgressBar() won't execute and the dialog remains visible indefinitely.

Suggested fix
   const handleSave = async (): Promise<void> => {
     // Show saving progress bar
     openProgressBar();
-
-    await handleWorkbenchSave(
-      workbench,
-      searchRef,
-      checkDeletedFail,
-      handleSpreadsheetUpToDate,
-    );
-
-    closeProgressBar();
+    try {
+      await handleWorkbenchSave(
+        workbench,
+        searchRef,
+        checkDeletedFail,
+        handleSpreadsheetUpToDate,
+      );
+    } finally {
+      closeProgressBar();
+    }
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx` around lines
59 - 71, The progress bar open/close should be made exception-safe: in
handleSave wrap the call to handleWorkbenchSave(...) with a try/finally so
openProgressBar() is always paired with closeProgressBar(); call
openProgressBar() before the try, await handleWorkbenchSave(workbench,
searchRef, checkDeletedFail, handleSpreadsheetUpToDate) inside the try, and call
closeProgressBar() in the finally block (rethrow or let errors propagate after
finally).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx`:
- Around line 59-71: The progress bar open/close should be made exception-safe:
in handleSave wrap the call to handleWorkbenchSave(...) with a try/finally so
openProgressBar() is always paired with closeProgressBar(); call
openProgressBar() before the try, await handleWorkbenchSave(workbench,
searchRef, checkDeletedFail, handleSpreadsheetUpToDate) inside the try, and call
closeProgressBar() in the finally block (rethrow or let errors propagate after
finally).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a10a8442-067e-408b-b4b5-d99a3da01432

📥 Commits

Reviewing files that changed from the base of the PR and between 3d973be and cd4dcd1.

📒 Files selected for processing (2)
  • specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx
  • specifyweb/frontend/js_src/lib/components/WorkBench/attachmentHelpers.tsx

@alesan99 alesan99 added this to the 7.12.1 milestone May 20, 2026
@alesan99 alesan99 requested review from a team May 20, 2026 20:20
@grantfitzsimmons
Copy link
Copy Markdown
Member

@alesan99 Are all the TODOs finished?

@alesan99
Copy link
Copy Markdown
Contributor Author

alesan99 commented May 21, 2026

@alesan99 Are all the TODOs finished?

Yes, I think the improvements can be left for future PRs, so this PR should be ready to test 👍

Well, after this PR is merged #8073

@alesan99 alesan99 removed request for a team May 21, 2026 13:50
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx (1)

59-71: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Progress bar can get stuck open if save fails.

closeProgressBar() runs only on the success path. If handleWorkbenchSave rejects (network error, unexpected status, etc.), the "Saving…" dialog will remain open until the user dismisses it manually. Wrapping the await in try/finally keeps the UI in a sane state on errors.

🛡️ Suggested fix
   const handleSave = async (): Promise<void> => {
     // Show saving progress bar
     openProgressBar();
-
-    await handleWorkbenchSave(
-      workbench,
-      searchRef,
-      checkDeletedFail,
-      handleSpreadsheetUpToDate,
-    );
-
-    closeProgressBar();
-  }
+    try {
+      await handleWorkbenchSave(
+        workbench,
+        searchRef,
+        checkDeletedFail,
+        handleSpreadsheetUpToDate,
+      );
+    } finally {
+      closeProgressBar();
+    }
+  };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx` around lines
59 - 71, handleSave currently calls openProgressBar(), awaits
handleWorkbenchSave(...), then calls closeProgressBar(), so if
handleWorkbenchSave throws the progress bar stays open; wrap the await in a
try/finally inside handleSave so closeProgressBar() is always called (and
rethrow or let the error propagate after finally), keeping
openProgressBar()/closeProgressBar() paired even on network or unexpected
failures in handleWorkbenchSave.
🧹 Nitpick comments (1)
specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx (1)

26-40: 💤 Low value

Consider using async/await consistently inside handleWorkbenchSave.

The helper is declared async but uses a .then() chain. Converting to straight await would make the control flow easier to read and would let future error handling/logging hook into a single try block.

♻️ Suggested refactor
-  // Send data
-  return ping(`/api/workbench/rows/${workbench.dataset.id}/`, {
-    method: 'PUT',
-    body: workbench.data,
-    expectedErrors: [Http.NO_CONTENT, Http.NOT_FOUND],
-  })
-    .then((status) => checkDeletedFail(status))
-    .then(() => {
-      handleSpreadsheetUpToDate();
-      workbench.cells.cellMeta = [];
-      workbench.utils?.searchCells(
-        { key: 'SettingsChange' },
-        searchRef.current
-      );
-      workbench.hot?.render();
-    });
+  // Send data
+  const status = await ping(`/api/workbench/rows/${workbench.dataset.id}/`, {
+    method: 'PUT',
+    body: workbench.data,
+    expectedErrors: [Http.NO_CONTENT, Http.NOT_FOUND],
+  });
+  checkDeletedFail(status);
+  handleSpreadsheetUpToDate();
+  workbench.cells.cellMeta = [];
+  workbench.utils?.searchCells({ key: 'SettingsChange' }, searchRef.current);
+  workbench.hot?.render();
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx` around lines
26 - 40, The function handleWorkbenchSave currently returns
ping(...).then(...).then(...)—refactor it to use async/await: await the ping
call to /api/workbench/rows/${workbench.dataset.id}/ with method 'PUT' and
expectedErrors, then call checkDeletedFail(status) (await if it returns a
promise), and finally run the post-save steps (handleSpreadsheetUpToDate(),
reset workbench.cells.cellMeta = [], call
workbench.utils?.searchCells(...searchRef.current) and workbench.hot?.render())
inside the same try block; wrap the await flow in a try/catch so errors can be
handled/logged uniformly. Ensure you update references to ping,
checkDeletedFail, handleSpreadsheetUpToDate, workbench.cells.cellMeta,
workbench.utils?.searchCells and workbench.hot?.render when replacing the .then
chain.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx`:
- Around line 59-71: handleSave currently calls openProgressBar(), awaits
handleWorkbenchSave(...), then calls closeProgressBar(), so if
handleWorkbenchSave throws the progress bar stays open; wrap the await in a
try/finally inside handleSave so closeProgressBar() is always called (and
rethrow or let the error propagate after finally), keeping
openProgressBar()/closeProgressBar() paired even on network or unexpected
failures in handleWorkbenchSave.

---

Nitpick comments:
In `@specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx`:
- Around line 26-40: The function handleWorkbenchSave currently returns
ping(...).then(...).then(...)—refactor it to use async/await: await the ping
call to /api/workbench/rows/${workbench.dataset.id}/ with method 'PUT' and
expectedErrors, then call checkDeletedFail(status) (await if it returns a
promise), and finally run the post-save steps (handleSpreadsheetUpToDate(),
reset workbench.cells.cellMeta = [], call
workbench.utils?.searchCells(...searchRef.current) and workbench.hot?.render())
inside the same try block; wrap the await flow in a try/catch so errors can be
handled/logged uniformly. Ensure you update references to ping,
checkDeletedFail, handleSpreadsheetUpToDate, workbench.cells.cellMeta,
workbench.utils?.searchCells and workbench.hot?.render when replacing the .then
chain.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 426e78a8-751e-4f09-a7c6-f2a7b8e8d3c3

📥 Commits

Reviewing files that changed from the base of the PR and between cd4dcd1 and 23daa52.

📒 Files selected for processing (1)
  • specifyweb/frontend/js_src/lib/components/WbActions/WbSave.tsx

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

Labels

None yet

Projects

Status: Dev Attention Needed

Development

Successfully merging this pull request may close these issues.

Add attachments to rows in existing workbench data sets. (WB Attachments V2)

4 participants