Skip to content

fix(admin): validate group combos, fix checkbox UX, remove stale bulk checkboxes - #1427

Merged
cristim merged 2 commits into
mainfrom
fix/qa-admin-user-mgmt
Jul 17, 2026
Merged

fix(admin): validate group combos, fix checkbox UX, remove stale bulk checkboxes#1427
cristim merged 2 commits into
mainfrom
fix/qa-admin-user-mgmt

Conversation

@cristim

@cristim cristim commented Jul 16, 2026

Copy link
Copy Markdown
Member

Summary

Files changed

  • frontend/src/users/utils.ts -- new exported validateGroupCombination(groupIds, allGroups) function
  • frontend/src/users/userList.ts -- import validateGroupCombination; add pre-API validation in toggleUserGroup(); replace renderUsers(filteredUsers) calls in both checkbox handlers with in-place DOM patching
  • frontend/src/users/userModals.ts -- import validateGroupCombination; add pre-API validation in saveUser() after the existing >= 1 group check
  • frontend/src/__tests__/users.test.ts -- 33 new test cases: validateGroupCombination unit tests, checkbox-does-not-collapse-panel (individual + select-all), bulk-actions-bar show/hide, contradictory-combo rejection in expand panel and modal

Test plan

  • All 234 tests pass (npx jest --testPathPattern=users.test --no-coverage)
  • Build succeeds (webpack --mode production)
  • Select a user's row-selection checkbox while another user's expand panel is open -- panel stays open
  • Click select-all while an expand panel is open -- panel stays open
  • In expand panel: add a write-capable group to a view-only user -- error toast appears, checkbox reverts, no API call
  • In create/edit modal: select a view-only group and a write-capable group simultaneously -- error toast appears, no API call
  • Compatible combinations (only view-only groups, or only write-capable groups) still save correctly

closes #1404 closes #1405 closes #1408

Summary by CodeRabbit

  • New Features

    • Added validation to prevent contradictory view-only and write-capable group combinations.
    • Added clear error messages when invalid group selections are attempted.
    • Bulk updates now skip conflicting selections while applying valid changes and preserving skipped selections.
  • Bug Fixes

    • Group assignment errors no longer trigger unnecessary API updates.
    • Open expand panels remain expanded when selecting individual users or all users.
    • Selection styling and bulk-action counts now update accurately.

… checkboxes

Closes #1404 closes #1405 closes #1408.

#1404 - user-selection and select-all checkboxes no longer call
renderUsers(), which was rebuilding the entire table and collapsing any
open expand panels. Both handlers now patch the affected rows in-place
(row-selected class, individual checkbox state, bulk-actions-bar
visibility) without touching the expand rows.

#1405 - contradictory group combinations (view-only group paired with a
write-capable group) are now blocked at two sites:
- toggleUserGroup() in the expand panel, before the updateUser API call.
  The checkbox is reverted immediately on rejection.
- saveUser() in the create/edit modal, before the createUser/updateUser
  API call.
The shared validateGroupCombination() utility in users/utils.ts
encapsulates the detection logic and returns a human-readable message.

#1408 - the user-list checkboxes were already wired to real bulk actions
(bulk-delete and bulk-add-to-group) in PR #977; this PR retains that
wiring and the UX fix in #1404 removes the last friction point (panel
collapse on selection) that made the feature feel broken.

Tests: 33 new test cases covering validateGroupCombination unit
behaviour, checkbox-does-not-collapse-panel for both individual and
select-all, bulk-actions-bar show/hide, and group-combo validation in
both the expand panel and the modal.
@cristim cristim added triaged Item has been triaged priority/p2 Backlog-worthy severity/medium Moderate harm urgency/this-sprint Within the current sprint impact/many Affects most users effort/s Hours type/bug Defect labels Jul 16, 2026
@cristim

cristim commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

The #1405 validation added in this branch guarded the inline expand-panel
toggle and the create/edit modal, but bulkAddToGroup (wired up by the
#1408 bulk-actions bar) computed the resulting group set and called
updateUser with no validation. Selecting a view-only-group user and
bulk-adding a write group silently created the exact contradictory
combination #1405 is meant to prevent.

bulkAddToGroup now partitions the selected users: each user's resulting
group set is run through validateGroupCombination before any API call.
Contradictory users are skipped (never silently written) and named in an
error toast so the admin knows which were left unchanged; they stay
selected so the offending membership can be fixed and retried. Compatible
users are applied and deselected as before.

Regression tests (fail-before/pass-after, confirmed against pre-fix
code): bulk-adding a write group to a view-only user does not call
updateUser and surfaces the error naming the user; the skipped user stays
selected; a mixed selection applies to the compatible user and skips only
the contradictory one.
@cristim

cristim commented Jul 16, 2026

Copy link
Copy Markdown
Member Author

Addressed the BLOCK on the bulk-add bypass.

bulkAddToGroup (frontend/src/users/userActions.ts) previously computed updatedGroups = [...user.groups, groupId] and called updateUser with no validation, so bulk-adding a write group to a Viewer-group user silently created the contradictory combination #1405 is meant to prevent. It now runs validateGroupCombination per selected user on their resulting group set before any API call:

  • Contradictory users are skipped (never silently written) and named in an error toast so the admin knows which were left unchanged.
  • Skipped users stay selected so the offending membership can be fixed and retried.
  • Compatible users are applied and deselected as before.

Regression tests added in contradictory group combination guard (issue #1405) under the bulkAddToGroup describe block. Verified fail-before/pass-after: the 3 guard-specific assertions fail on the pre-fix code (updateUser gets called with the contradictory set) and pass after the fix.

Gates (true exit codes): jest full suite 78 suites / 2598 passed / 1 skipped (exit 0); webpack --mode production exit 0 (only the pre-existing entrypoint-size warning); eslint on changed files 0 errors (only pre-existing-style no-explicit-any warnings on test as any casts). No Go touched.

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

No new commits to review since the last review.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 413fc45d-2a84-4008-8dee-d99c2c8ad865

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds shared contradictory-group validation to bulk, inline, and modal user flows. Bulk actions now skip invalid users selectively, while checkbox updates patch rows in place so expanded panels remain open.

Changes

User group controls

Layer / File(s) Summary
Group combination validator
frontend/src/users/utils.ts, frontend/src/__tests__/users.test.ts
Adds validateGroupCombination to reject view-only and write-capable group combinations, with coverage for valid cases and error wording.
Assignment-flow validation
frontend/src/users/userList.ts, frontend/src/users/userModals.ts, frontend/src/users/userActions.ts, frontend/src/__tests__/users.test.ts
Validates inline, modal, and bulk assignments before API calls; invalid inline selections revert, modal submissions abort, and bulk operations retain skipped users selected.
Selection-state UI updates
frontend/src/users/userList.ts, frontend/src/__tests__/users.test.ts
Updates selection classes, checkboxes, and bulk-action counts in place without rerendering the table or collapsing expanded panels.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant AdminUI
  participant userList
  participant userModals
  participant validateGroupCombination
  participant UserAPI
  participant Toast
  AdminUI->>userList: Toggle inline group assignment
  userList->>validateGroupCombination: Validate prospective group set
  validateGroupCombination-->>userList: Conflict message or null
  userList->>Toast: Show error and revert checkbox
  userList->>UserAPI: updateUser when valid
  AdminUI->>userModals: Save selected groups
  userModals->>validateGroupCombination: Validate selected groups
  validateGroupCombination-->>userModals: Conflict message or null
  userModals->>UserAPI: createUser or updateUser when valid
Loading

Possibly related PRs

  • LeanerCloud/CUDly#1002: Modifies the user-list group-checkbox and rerender flow for stable expanded-panel behavior.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately summarizes the main user-management fixes in the PR.
Linked Issues check ✅ Passed The changes address #1404, #1405, and #1408 by preserving panel state, preventing contradictory groups, and keeping bulk actions functional.
Out of Scope Changes check ✅ Passed The modified code and tests are all directly tied to the stated admin user-management validation and checkbox UX objectives.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/qa-admin-user-mgmt

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

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for addressing the bulk-add bypass. I’ll review the updated validation flow and regression coverage.

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@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

🤖 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/users/userActions.ts`:
- Around line 238-247: Update the bulk update flow around the applicable.map
calls to use Promise.allSettled instead of Promise.all, track each result
against its userId, deselect only fulfilled updates, and reload users whenever
at least one update succeeds. Report rejected entries separately while
preserving selection for failed users, and retain the existing success behavior
for fulfilled updates.

In `@frontend/src/users/userList.ts`:
- Around line 232-244: Update the expand-panel guidance near the group
assignment UI to state that view-only groups cannot coexist with write-capable
groups. Replace the outdated “has no effect” wording while preserving the
existing validation behavior in the handler around validateGroupCombination.
- Around line 351-356: Update the select-all synchronization logic in the
user-list selection handler to determine checked state from the currently
filtered users, rather than comparing the global selectedUserIds.size with
filteredUsers.length. Ensure every visible filtered user is selected before
checking the checkbox, while preserving the requirement that at least one user
is selected.
🪄 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

Run ID: 0eea891b-0590-4588-b7f5-c8612d2ad386

📥 Commits

Reviewing files that changed from the base of the PR and between cd67485 and 3c0fe9f.

📒 Files selected for processing (5)
  • frontend/src/__tests__/users.test.ts
  • frontend/src/users/userActions.ts
  • frontend/src/users/userList.ts
  • frontend/src/users/userModals.ts
  • frontend/src/users/utils.ts

Comment on lines +238 to +247
try {
await Promise.all(
applicable.map(({ userId, groups }) => api.updateUser(userId, { groups })),
);

clearSelectedUserIds();
// Deselect only the successfully-applied users so any skipped
// (contradictory) users stay selected for the admin to fix.
applicable.forEach(({ userId }) => removeSelectedUserId(userId));
await loadUsers();
showSuccess(`Successfully added ${count} user(s) to group`);
showSuccess(`Successfully added ${applicable.length} user(s) to group`);

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.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Reconcile partial bulk successes individually.

Promise.all rejects when one request fails even though other updates may have succeeded. Those successful users remain selected and the UI is not reloaded, so retrying operates on stale state. Use Promise.allSettled, deselect fulfilled entries, reload after any success, and report failed entries separately.

🤖 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/users/userActions.ts` around lines 238 - 247, Update the bulk
update flow around the applicable.map calls to use Promise.allSettled instead of
Promise.all, track each result against its userId, deselect only fulfilled
updates, and reload users whenever at least one update succeeds. Report rejected
entries separately while preserving selection for failed users, and retain the
existing success behavior for fulfilled updates.

Comment on lines +232 to +244
// Validate the resulting group combination before hitting the API.
// A view-only group (all permissions are view:*) combined with a
// write-capable group is semantically contradictory (issue #1405).
const combinationError = validateGroupCombination(next, availableGroups);
if (combinationError) {
showError(combinationError);
// Revert the checkbox immediately so the DOM reflects reality.
const cb = document.querySelector<HTMLInputElement>(
`.group-assign-checkbox[data-user-id="${userId}"][data-group-id="${groupId}"]`,
);
if (cb) cb.checked = !checked;
return;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the expand-panel guidance to match this validation.

Line 185 says combining lower- and higher-privilege groups “has no effect,” but this handler now rejects that combination. Tell admins that view-only groups cannot coexist with write-capable groups.

🤖 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/users/userList.ts` around lines 232 - 244, Update the
expand-panel guidance near the group assignment UI to state that view-only
groups cannot coexist with write-capable groups. Replace the outdated “has no
effect” wording while preserving the existing validation behavior in the handler
around validateGroupCombination.

Comment on lines +351 to +356
// Keep the select-all checkbox in sync.
const selectAll = document.getElementById('select-all-users') as HTMLInputElement | null;
if (selectAll) {
selectAll.checked =
selectedUserIds.size > 0 && selectedUserIds.size === filteredUsers.length;
}

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Determine select-all state from the filtered users, not the global count.

Hidden selected users can make these counts equal even when some visible users are unchecked, incorrectly checking select-all.

Proposed fix
-        selectAll.checked =
-          selectedUserIds.size > 0 && selectedUserIds.size === filteredUsers.length;
+        selectAll.checked =
+          filteredUsers.length > 0 &&
+          filteredUsers.every(user => selectedUserIds.has(user.id));
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// Keep the select-all checkbox in sync.
const selectAll = document.getElementById('select-all-users') as HTMLInputElement | null;
if (selectAll) {
selectAll.checked =
selectedUserIds.size > 0 && selectedUserIds.size === filteredUsers.length;
}
// Keep the select-all checkbox in sync.
const selectAll = document.getElementById('select-all-users') as HTMLInputElement | null;
if (selectAll) {
selectAll.checked =
filteredUsers.length > 0 &&
filteredUsers.every(user => selectedUserIds.has(user.id));
}
🤖 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/users/userList.ts` around lines 351 - 356, Update the select-all
synchronization logic in the user-list selection handler to determine checked
state from the currently filtered users, rather than comparing the global
selectedUserIds.size with filteredUsers.length. Ensure every visible filtered
user is selected before checking the checkbox, while preserving the requirement
that at least one user is selected.

@cristim
cristim merged commit 473f69b into main Jul 17, 2026
20 checks passed
@cristim
cristim deleted the fix/qa-admin-user-mgmt branch July 17, 2026 05:37
@cristim

cristim commented Jul 17, 2026

Copy link
Copy Markdown
Member Author

Merged to main (all CI green after the tflint-503 outage cleared). admin user-mgmt - contradictory group-combo validation (incl the bulkAddToGroup path), no-collapse checkbox UX, bulk-actions wiring; review BLOCK on the bulk bypass fixed + regression test (closes #1404 #1405 #1408).

cristim added a commit that referenced this pull request Jul 17, 2026
fix(admin): validate group combos, fix checkbox UX, remove stale bulk checkboxes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/s Hours impact/many Affects most users priority/p2 Backlog-worthy severity/medium Moderate harm triaged Item has been triaged type/bug Defect urgency/this-sprint Within the current sprint

Projects

None yet

1 participant