Skip to content

VPS-96/Allowed renaming of resources in edit view#413

Open
kmck133 wants to merge 8 commits into
masterfrom
VPS-96/change-resource-name
Open

VPS-96/Allowed renaming of resources in edit view#413
kmck133 wants to merge 8 commits into
masterfrom
VPS-96/change-resource-name

Conversation

@kmck133

@kmck133 kmck133 commented May 26, 2026

Copy link
Copy Markdown
Contributor

Issue

After uploading a resource, there was no way to rename it. Users were stuck with the original filename even if it was unclear or incorrect.

Solution

Added the ability to rename uploaded resources from the collections sidebar in the manage resources view. A pencil icon appears next to each file; clicking it replaces the filename with an inline input field. The user can confirm with Enter or the checkmark button, or cancel with Escape or the X button.
This required a new PATCH /api/files/:fileId endpoint on the backend that validates and updates the name field on StoredFile. The frontend calls this endpoint, then updates both the sidebar list and the active preview panel (if the renamed file is currently selected) without a page reload.

Risk

  • Low. Renaming a resource updates the display name in StoredFile but not the filename stored in GridFS. This is intentional, as the application always resolves filenames through StoredFile.name (including when downloading the resource), so there is no functional impact. The divergence is only visible when inspecting the raw resources.files collection in MongoDB directly.
  • The new PATCH /:fileId route sits alongside the existing DELETE /:fileId route with no ambiguity since they use different HTTP methods.

Summary by CodeRabbit

  • New Features
    • Added inline file renaming in the Resources management UI with Save/Cancel controls and keyboard shortcuts (Enter to save, Escape to cancel), including auto-focus and trimmed names.
  • Usability
    • Added filename validation (max length) and prevents no-op renames.
    • Improved layout/styling for clearer file selection and download/preview alignment.
  • Reliability
    • Enhanced rename update flow with appropriate “not found” and error handling when changes can’t be saved.

@linear

linear Bot commented May 26, 2026

Copy link
Copy Markdown

VPS-96

@leowla leowla left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

There should be a character limit

Image

@coderabbitai

coderabbitai Bot commented Jun 3, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@kmck133, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 15 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2fbde875-022e-4969-8058-1c5fb56fa7c7

📥 Commits

Reviewing files that changed from the base of the PR and between 9b122ac and f9a7e1b.

📒 Files selected for processing (3)
  • backend/src/routes/api/__tests__/filesApi.test.js
  • backend/src/routes/api/files.js
  • frontend/src/features/resources/ManageResourcesPage.jsx
📝 Walkthrough

Walkthrough

Adds an authenticated file-rename API endpoint and inline renaming in ManageResourcesPage, including validation, persistence, state synchronization, keyboard controls, and layout adjustments.

Changes

File Renaming Feature

Layer / File(s) Summary
Backend file rename endpoint
backend/src/routes/api/files.js
Adds PATCH /api/files/:fileId, validates and trims the name, enforces a 255-character limit, handles missing records, persists via meta.save(), and omits gridFsId from the response.
Frontend rename state and API integration
frontend/src/features/resources/ManageResourcesPage.jsx
Adds rename state and icons, sends the PATCH request, synchronizes groups and selectedFile, and validates submissions before saving changed names.
Frontend rename UI and layout
frontend/src/features/resources/ManageResourcesPage.jsx
Adds inline editing with autofocus, Enter/Escape handling, Save/Cancel controls, and collection, group, preview, and download layout updates.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant ManageResourcesPage
  participant FilesRoute
  participant StoredFile
  User->>ManageResourcesPage: Open inline rename editor
  ManageResourcesPage->>ManageResourcesPage: Validate and trim new name
  ManageResourcesPage->>FilesRoute: PATCH /api/files/:fileId
  FilesRoute->>StoredFile: Update and save file metadata
  StoredFile-->>FilesRoute: Return updated file
  FilesRoute-->>ManageResourcesPage: Return renamed file
  ManageResourcesPage->>ManageResourcesPage: Update groups and selectedFile
Loading

Possibly related PRs

  • UoaWDCC/VPS#446: Refactors the same files router and changes the stored-file model and response contract used by the rename endpoint.

Suggested reviewers: hazikchaudhry

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: allowing resource renaming in the edit/manage resources view.
Description check ✅ Passed The description covers Issue, Solution, and Risk and is mostly complete, though the Checklist section is missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch VPS-96/change-resource-name

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🧹 Nitpick comments (1)
frontend/src/features/resources/ManageResourcesPage.jsx (1)

390-404: 💤 Low value

Prefer the truncate utility and a focusable control for the file name.

Two small refinements on the default file row:

  • The inline overflow/textOverflow/whiteSpace styles duplicate Tailwind's truncate (already used for the group name on Line 329).
  • The clickable <a> has no href, so it isn't keyboard-focusable/operable. A <button> (or adding role="button" + tabIndex={0} and key handling) makes selection accessible.
♻️ Optional
-                                    <a
-                                      className="text--1"
-                                      title={f.name}
-                                      style={{ overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
-                                      onClick={() =>
-                                        setSelectedFile({
-                                          ...f,
-                                          groupId: group.id,
-                                          groupName: group.name,
-                                        })
-                                      }
-                                    >
-                                      {f.name}
-                                    </a>
+                                    <button
+                                      type="button"
+                                      className="text--1 truncate text-left"
+                                      title={f.name}
+                                      onClick={() =>
+                                        setSelectedFile({
+                                          ...f,
+                                          groupId: group.id,
+                                          groupName: group.name,
+                                        })
+                                      }
+                                    >
+                                      {f.name}
+                                    </button>
🤖 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 `@frontend/src/features/resources/ManageResourcesPage.jsx` around lines 390 -
404, Replace the inline truncation styles on the file name link with the
existing truncate utility and make the clickable element keyboard-focusable:
remove overflow/textOverflow/whiteSpace inline styles and use the truncate class
(e.g., className="truncate text--1") for the element that displays f.name, and
change the non-href <a> to a focusable control that calls setSelectedFile
(either convert to a <button type="button"> that invokes setSelectedFile({...f,
groupId: group.id, groupName: group.name}) or keep the element but add
role="button" tabIndex={0} and key handlers for Enter/Space to call
setSelectedFile). Ensure the handler calls the same setSelectedFile signature
used in the diff.
🤖 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 `@backend/src/routes/api/files.js`:
- Around line 159-161: The if-check using name.trim().length > 255 returns a
JSON error response that exceeds the project's Prettier print width; run
prettier --write on the file or reflow the call so the res.status(400).json(...)
is wrapped across lines (preserving the same message string) to satisfy
formatting. Locate the snippet containing name.trim().length and the
res.status(400).json({ error: "Name must be 255 characters or fewer" }) call and
apply Prettier formatting (or manually break the .json call into multiple lines)
so the linter/formatter CI passes.
- Around line 155-158: Validate that req.body.name is a string before calling
name.trim(): change the guard around "const { name } = req.body" to check typeof
name !== 'string' || !name.trim() and return res.status(400).json({ error: "Name
is required" }) for non-string or empty values so name.trim() cannot throw;
reference the existing "name" variable, the name.trim() call, and the
res.status(...) return to locate and update the check.
- Around line 152-172: The PATCH handler is missing an authorization check and
allows IDOR; after fetching meta via StoredFile.findById(fileId) (and similarly
in DELETE /:fileId and state-conditionals handlers), verify the caller has
access to the file's scenarioId/groupId using the existing authorization helpers
(e.g., call scenarioAuth or dashboardAuth or the same access-check logic used
elsewhere) using the authenticated principal from req (uid or user on req set by
router.use(auth)); if the check fails return 403 and do not modify/save the
StoredFile, otherwise proceed to set meta.name and save. Ensure the same pattern
is applied to delete and other mutation routes that touch StoredFile.

---

Nitpick comments:
In `@frontend/src/features/resources/ManageResourcesPage.jsx`:
- Around line 390-404: Replace the inline truncation styles on the file name
link with the existing truncate utility and make the clickable element
keyboard-focusable: remove overflow/textOverflow/whiteSpace inline styles and
use the truncate class (e.g., className="truncate text--1") for the element that
displays f.name, and change the non-href <a> to a focusable control that calls
setSelectedFile (either convert to a <button type="button"> that invokes
setSelectedFile({...f, groupId: group.id, groupName: group.name}) or keep the
element but add role="button" tabIndex={0} and key handlers for Enter/Space to
call setSelectedFile). Ensure the handler calls the same setSelectedFile
signature used in the diff.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4d4a9819-71a0-46f8-9d55-0cf623aaa679

📥 Commits

Reviewing files that changed from the base of the PR and between 736892a and 121e598.

📒 Files selected for processing (2)
  • backend/src/routes/api/files.js
  • frontend/src/features/resources/ManageResourcesPage.jsx

Comment thread backend/src/routes/api/files.js
Comment thread backend/src/routes/api/files.js Outdated
Comment thread backend/src/routes/api/files.js

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/features/resources/ManageResourcesPage.jsx (1)

163-165: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fix stale-state overwrite when rename resolves asynchronously.

Line 163 uses a stale selectedFile snapshot after await, so selecting another file mid-request can incorrectly overwrite that file’s name in Line 164.

Suggested fix
-      if (selectedFile?.id === fileId) {
-        setSelectedFile((prev) => ({ ...prev, name: data.name }));
-      }
+      setSelectedFile((prev) =>
+        prev?.id === fileId ? { ...prev, name: data.name } : prev
+      );
🤖 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 `@frontend/src/features/resources/ManageResourcesPage.jsx` around lines 163 -
165, The code reads the stale selectedFile snapshot before an async rename
completes and may overwrite a newly selected file; replace the external if
(selectedFile?.id === fileId) check with a functional state update that compares
prev.id inside setSelectedFile so the comparison is done against the latest
state, e.g. call setSelectedFile(prev => prev?.id === fileId ? { ...prev, name:
data.name } : prev) so only the currently selected file with matching id is
updated after the async response.
🤖 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 `@frontend/src/features/resources/ManageResourcesPage.jsx`:
- Around line 580-582: In the ManageResourcesPage component, update the anchor
element that renders the download link (the <a> with href={downloadUrl}) to
replace the deprecated Tailwind utility class "flex-shrink-0" with the v4
canonical "shrink-0" in its className string so the download button uses the
correct shrinking behavior.

---

Outside diff comments:
In `@frontend/src/features/resources/ManageResourcesPage.jsx`:
- Around line 163-165: The code reads the stale selectedFile snapshot before an
async rename completes and may overwrite a newly selected file; replace the
external if (selectedFile?.id === fileId) check with a functional state update
that compares prev.id inside setSelectedFile so the comparison is done against
the latest state, e.g. call setSelectedFile(prev => prev?.id === fileId ? {
...prev, name: data.name } : prev) so only the currently selected file with
matching id is updated after the async response.
🪄 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: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 4cee5d3e-a027-4c3a-b0cf-1b1a6d52b3b2

📥 Commits

Reviewing files that changed from the base of the PR and between 121e598 and bab4def.

📒 Files selected for processing (2)
  • backend/src/routes/api/files.js
  • frontend/src/features/resources/ManageResourcesPage.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/routes/api/files.js

Comment thread frontend/src/features/resources/ManageResourcesPage.jsx

@harbassan harbassan left a comment

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.

just this one + the coderabbit stuff which is valid.

Comment thread frontend/src/features/resources/ManageResourcesPage.jsx Outdated
…rce-name

# Conflicts:
#	frontend/src/features/resources/ManageResourcesPage.jsx

@coderabbitai coderabbitai Bot left a comment

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.

Caution

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

⚠️ Outside diff range comments (1)
frontend/src/features/resources/ManageResourcesPage.jsx (1)

154-201: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Rename failures silently discard the user's edit, and empty input fails without feedback.

renameFile swallows all errors internally and never reports success/failure back to the caller. handleRenameSubmit then unconditionally calls setRenamingFileId(null) after awaiting renameFile, so if the PATCH fails (duplicate name, invalid characters, network error, backend validation reject), the inline editor closes anyway and the typed name is lost — the user must reopen the editor and retype from scratch. Separately, when trimmed is empty, the function silently cancels the edit with no toast/error feedback, unlike every other mutation path in this file which surfaces failures via toast.error.

🛠️ Proposed fix: propagate rename outcome and surface empty-name feedback
   async function renameFile(fileId, newName) {
     try {
       const user = getAuth().currentUser;
       if (!user) {
         toast.error("You must be logged in.");
-        return;
+        return false;
       }
       const idToken = await user.getIdToken();
       const { data } = await axios.patch(
         `/api/files/${fileId}`,
         { name: newName },
         { headers: { Authorization: `Bearer ${idToken}` } }
       );

       setGroups((prev) =>
         prev.map((g) => ({
           ...g,
           files: (g.files || []).map((f) =>
             f.id === fileId ? { ...f, name: data.name } : f
           ),
         }))
       );

       if (selectedFile?.id === fileId) {
         setSelectedFile((prev) => ({ ...prev, name: data.name }));
       }

       toast.success("Renamed");
+      return true;
     } catch (err) {
       console.error(err);
       toast.error(err?.response?.data?.error || "Rename failed");
+      return false;
     }
   }

   async function handleRenameSubmit() {
     const trimmed = renameInput.trim();
     if (!trimmed) {
-      setRenamingFileId(null);
+      toast.error("File name cannot be empty");
       return;
     }
     const current = groups
       .flatMap((g) => g.files)
       .find((f) => f.id === renamingFileId);
     if (current && trimmed !== current.name) {
-      await renameFile(renamingFileId, trimmed);
+      const ok = await renameFile(renamingFileId, trimmed);
+      if (!ok) return;
     }
     setRenamingFileId(null);
   }
🤖 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 `@frontend/src/features/resources/ManageResourcesPage.jsx` around lines 154 -
201, Update renameFile to return a success/failure outcome after handling the
PATCH error, and change handleRenameSubmit to clear renamingFileId only when the
rename succeeds or no change is needed; keep the editor open after failures so
the typed value is preserved. In the empty trimmed-input branch, display a
toast.error validation message before returning instead of silently cancelling
the edit.
🤖 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 `@frontend/src/features/resources/ManageResourcesPage.jsx`:
- Around line 154-201: Update renameFile to return a success/failure outcome
after handling the PATCH error, and change handleRenameSubmit to clear
renamingFileId only when the rename succeeds or no change is needed; keep the
editor open after failures so the typed value is preserved. In the empty
trimmed-input branch, display a toast.error validation message before returning
instead of silently cancelling the edit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 95950db5-54c3-4e6c-90a2-fc2d86d26bdb

📥 Commits

Reviewing files that changed from the base of the PR and between bab4def and 9b122ac.

📒 Files selected for processing (2)
  • backend/src/routes/api/files.js
  • frontend/src/features/resources/ManageResourcesPage.jsx
🚧 Files skipped from review as they are similar to previous changes (1)
  • backend/src/routes/api/files.js

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants