Skip to content

feat(admin): User Management tab for admins#9379

Open
bbonenfant wants to merge 4 commits into
makeplane:previewfrom
bbonenfant:feat/god-mode-user-management
Open

feat(admin): User Management tab for admins#9379
bbonenfant wants to merge 4 commits into
makeplane:previewfrom
bbonenfant:feat/god-mode-user-management

Conversation

@bbonenfant

@bbonenfant bbonenfant commented Jul 9, 2026

Copy link
Copy Markdown

Description

Adds a user management tab to the god-mode admin screen. I was surprised upon setting up Plane that this didn't exist. This is currently a simple interface that displays basic user information and allows the admin to deactivate/reactivate user accounts. Hopefully this can act as a launching pad to build out some more substantial user management functionality!

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • Feature (non-breaking change which adds functionality)
  • Improvement (change that would cause existing functionality to not work as expected)
  • Code refactoring
  • Performance improvements
  • Documentation update

Screenshots and Media (if applicable)

Screenshot 2026-07-08 at 7 09 13 PM

Test Scenarios

  • Wrote unit tests to exercise this functionality with RBAC checks.
  • Loaded the locally built images into my running service.

References

Related to #3373

Summary by CodeRabbit

  • New Features
    • Added an admin Users on this instance page with search, total count, and paginated “Load more”.
    • Added instance-user activation/deactivation from the admin interface with clear loading and error states.
    • Updated navigation with a new sidebar entry and page title.
  • Bug Fixes
    • Prevented deactivating the currently signed-in user.
    • Added validation for missing/invalid is_active values and improved handling of bot/unknown users.
  • Tests
    • Added contract tests for listing (with pagination/search/bot exclusion) and updating active status.

Adds a paginated user list to the god-mode admin screen with
deactivate/reactivate support and instance admin badges.

- Backend: new GET /api/instances/users/ (paginated, searchable) and
  PATCH /api/instances/users/<uuid>/ endpoints behind InstanceAdminPermission;
  is_instance_admin annotated via EXISTS subquery
- Frontend: InstanceUserService, InstanceUserStore (MobX), useInstanceUser
  hook, UserListItem component, and /users page following the workspace
  screen pattern
- Sidebar entry and breadcrumb label added for the new route
…ints

13 tests covering GET /api/instances/users/ and PATCH /api/instances/users/<pk>/ — auth guards, bot exclusion, is_instance_admin annotation, search, deactivate/reactivate, self-deactivation guard, and 404 handling.
- PATCH endpoint now annotates the queryset with is_instance_admin so the
  response accurately reflects admin status; previously serialized as false
  for all users, stripping the badge in the UI after deactivate/reactivate
- Reject non-boolean is_active values with 400 to prevent bool("false") => True
  coercion; drop redundant bool() cast
- Replace raw <button> in UserListItem with <Button> from @plane/propel/button
  using error-outline/secondary variants to match the design system
- Remove getUserById from makeObservable (pure read, not a state mutation);
  relabel interface section to helpers
- Wrap loader = "loaded" in finally blocks with runInAction for correct
  MobX 6 async action semantics
- Add contract tests for non-boolean rejection and is_instance_admin
  preservation after PATCH (15 tests total, all passing)
@CLAassistant

CLAassistant commented Jul 9, 2026

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: d140a9f8-b883-454d-8778-14f83b97bde9

📥 Commits

Reviewing files that changed from the base of the PR and between f2a180b and a767453.

📒 Files selected for processing (2)
  • apps/admin/app/(all)/(dashboard)/users/page.tsx
  • apps/admin/store/instance-user.store.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • apps/admin/store/instance-user.store.ts
  • apps/admin/app/(all)/(dashboard)/users/page.tsx

📝 Walkthrough

Walkthrough

This PR adds instance-user listing and activation management across the API, shared types, frontend service/store wiring, and admin UI, including a new users page, row action component, and navigation entries.

Changes

Instance User Management

Layer / File(s) Summary
Types and serializer contract
packages/types/src/users.ts, apps/api/plane/license/api/serializers/user.py, apps/api/plane/license/api/serializers/__init__.py
Adds the instance-user pagination type, makes is_instance_admin optional on IUser, and adds the serializer that exposes the admin flag.
Instance user endpoint and routes
apps/api/plane/license/api/views/user.py, apps/api/plane/license/api/views/__init__.py, apps/api/plane/license/urls.py, apps/api/plane/tests/contract/app/test_instance_user.py
Adds the admin-only users endpoint with list and patch handlers, wires it into URL/package exports, and covers list/update behaviors with contract tests.
Instance user service client
packages/services/src/instance/instance-user.service.ts, packages/services/src/instance/index.ts
Adds the client service for listing and updating instance users and re-exports it from the instance service entrypoint.
Instance user store wiring
apps/admin/store/instance-user.store.ts, apps/admin/store/root.store.ts, apps/admin/hooks/store/use-instance-user.tsx, apps/admin/hooks/store/index.ts
Adds the MobX instance-user store, wires it into the root store, and exports the hook used by admin pages.
Users page and navigation
apps/admin/app/(all)/(dashboard)/users/page.tsx, apps/admin/components/users/list-item.tsx, apps/admin/app/routes.ts, apps/admin/components/common/header/core.ts, apps/admin/hooks/use-sidebar-menu/core.ts, apps/admin/hooks/use-sidebar-menu/index.ts
Adds the users admin page, row action component, route registration, and sidebar/header labels for the new section.

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

Sequence Diagram(s)

sequenceDiagram
  participant Admin
  participant UserManagementPage
  participant InstanceUserStore
  participant InstanceUserService
  participant InstanceUserEndpoint
  Admin->>UserManagementPage: open /users/
  UserManagementPage->>InstanceUserStore: fetchUsers()
  InstanceUserStore->>InstanceUserService: list()
  InstanceUserService->>InstanceUserEndpoint: GET /api/instances/users/
  InstanceUserEndpoint-->>InstanceUserService: paginated users
  InstanceUserService-->>InstanceUserStore: results and pagination
  InstanceUserStore-->>UserManagementPage: userIds and loader state
Loading
sequenceDiagram
  participant Admin
  participant UserListItem
  participant InstanceUserStore
  participant InstanceUserService
  participant InstanceUserEndpoint
  Admin->>UserListItem: click activate/deactivate
  UserListItem->>InstanceUserStore: updateUser(userId, is_active)
  InstanceUserStore->>InstanceUserService: update(userId, data)
  InstanceUserService->>InstanceUserEndpoint: PATCH /api/instances/users/pk/
  InstanceUserEndpoint-->>InstanceUserService: updated user
  InstanceUserService-->>InstanceUserStore: updated user
  InstanceUserStore-->>UserListItem: refreshed row state
Loading
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.70% 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 summarizes the main change: an admin user management tab for admins.
Description check ✅ Passed The description includes all required sections and covers the feature, tests, screenshots, and reference.
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

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: 1

🧹 Nitpick comments (2)
apps/admin/store/instance-user.store.ts (1)

70-73: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Prefer direct assignment over lodash.set for MobX observables inside runInAction.

Using set(this.users, [user.id], user) and set(this, "paginationInfo", paginationInfo) is unnecessarily indirect. Inside runInAction, direct assignment is idiomatic MobX and clearer. If this pattern is used elsewhere in the codebase for consistency, disregard.

♻️ Suggested refactor
   runInAction(() => {
     const { results, ...paginationInfo } = data;
     results.forEach((user: IUser) => {
-      set(this.users, [user.id], user);
+      this.users[user.id] = user;
     });
-    set(this, "paginationInfo", paginationInfo);
+    this.paginationInfo = paginationInfo;
   });
🤖 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 `@apps/admin/store/instance-user.store.ts` around lines 70 - 73, The
`instance-user.store` update logic in `runInAction` is using `lodash.set` for
MobX state writes, which is unnecessarily indirect. Update the `set(this.users,
[user.id], user)` and `set(this, "paginationInfo", paginationInfo)` assignments
to direct property assignment inside the same action, keeping the `users`
map/object and `paginationInfo` update explicit and consistent with MobX style.
apps/admin/app/(all)/(dashboard)/users/page.tsx (1)

28-28: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Surface fetch errors to the user.

useSWR captures fetch failures in its error field, but the page doesn't destructure or render it. On API errors, the store's finally block sets loader to "loaded", so the page shows an empty list ("• 0") with no items and no error message — users get zero feedback. As per coding guidelines, error handling should log errors appropriately and surface them to users.

♻️ Suggested error state
-  useSWR("INSTANCE_USERS", () => fetchUsers());
+  const { error } = useSWR("INSTANCE_USERS", () => fetchUsers());

And conditionally render an error state before the loader check:

       header={{
         title: "Users on this instance",
         description: "View all users and deactivate or reactivate their accounts.",
       }}
     >
+      {error ? (
+        <div className="flex items-center justify-center py-8 text-red-500">
+          Failed to load users. Please try again.
+        </div>
+      ) : loader !== "init-loader" ? (
       {loader !== "init-loader" ? (
🤖 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 `@apps/admin/app/`(all)/(dashboard)/users/page.tsx at line 28, The Users page
is ignoring the `useSWR("INSTANCE_USERS", () => fetchUsers())` error state, so
fetch failures are hidden behind the normal empty/loaded UI. Update the `useSWR`
usage in the users page component to destructure `error`, log it appropriately,
and render an explicit error state before the existing loader/empty-list checks
so users see a clear failure message instead of “0” items.

Source: Coding guidelines

🤖 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 `@apps/admin/store/instance-user.store.ts`:
- Around line 64-82: fetchUsers in instance-user.store.ts is merging each
refresh into this.users without removing old entries, so stale users remain
after revalidation. Update fetchUsers to clear the existing users map before
iterating over data.results and repopulating it, while keeping the current
paginationInfo update and loader handling in place.

---

Nitpick comments:
In `@apps/admin/app/`(all)/(dashboard)/users/page.tsx:
- Line 28: The Users page is ignoring the `useSWR("INSTANCE_USERS", () =>
fetchUsers())` error state, so fetch failures are hidden behind the normal
empty/loaded UI. Update the `useSWR` usage in the users page component to
destructure `error`, log it appropriately, and render an explicit error state
before the existing loader/empty-list checks so users see a clear failure
message instead of “0” items.

In `@apps/admin/store/instance-user.store.ts`:
- Around line 70-73: The `instance-user.store` update logic in `runInAction` is
using `lodash.set` for MobX state writes, which is unnecessarily indirect.
Update the `set(this.users, [user.id], user)` and `set(this, "paginationInfo",
paginationInfo)` assignments to direct property assignment inside the same
action, keeping the `users` map/object and `paginationInfo` update explicit and
consistent with MobX style.
🪄 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: 76e1d9d9-ec8d-4b4f-b9f9-673e2d299fa1

📥 Commits

Reviewing files that changed from the base of the PR and between 4fc79a2 and f2a180b.

📒 Files selected for processing (19)
  • apps/admin/app/(all)/(dashboard)/users/page.tsx
  • apps/admin/app/routes.ts
  • apps/admin/components/common/header/core.ts
  • apps/admin/components/users/list-item.tsx
  • apps/admin/hooks/store/index.ts
  • apps/admin/hooks/store/use-instance-user.tsx
  • apps/admin/hooks/use-sidebar-menu/core.ts
  • apps/admin/hooks/use-sidebar-menu/index.ts
  • apps/admin/store/instance-user.store.ts
  • apps/admin/store/root.store.ts
  • apps/api/plane/license/api/serializers/__init__.py
  • apps/api/plane/license/api/serializers/user.py
  • apps/api/plane/license/api/views/__init__.py
  • apps/api/plane/license/api/views/user.py
  • apps/api/plane/license/urls.py
  • apps/api/plane/tests/contract/app/test_instance_user.py
  • packages/services/src/instance/index.ts
  • packages/services/src/instance/instance-user.service.ts
  • packages/types/src/users.ts

Comment thread apps/admin/store/instance-user.store.ts
- Replace lodash.set calls with direct property assignment throughout
  instance-user.store; lodash-es import removed
- Clear this.users before repopulating in fetchUsers so deleted or
  renamed users do not survive SWR revalidation (fetchNextUsers
  intentionally keeps its merge behaviour for pagination)
- Destructure error from useSWR and render an explicit error message
  instead of showing an empty list when the fetch fails
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.

2 participants