Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Organization users management #575

Merged
merged 6 commits into from
Nov 30, 2024
Merged

feat: Organization users management #575

merged 6 commits into from
Nov 30, 2024

Conversation

daryllimyt
Copy link
Contributor

@daryllimyt daryllimyt commented Nov 30, 2024

Changes

  • Add base service class (eventually have services inherit from this)
  • Add org service for members management under /organization/members. Will also support managing settings under /organization/settings in the future

PS

Most of the LOC added here are from copying and pasting frontend table code

Screens

In this clip i show:

  • Cannot delete/change role of superuser
  • New user logging in and becomes visible in this page. Not yet part of any workspace.
  • We log into an admin account and promote this user to an admin.
  • On the next signin, that user is now confirmed to be an admin and can see any workspace
Screen.Recording.2024-11-30.at.13.09.15.mov

In this second clip I start in [email protected] (1), then log into [email protected] and delete (1). Then i try to log into (1) whose password is password1234, and am no longer able to login.

Screen.Recording.2024-11-30.at.15.29.59.mov

Security

Registering and trying to list org members is blocked by default beacuse of the admin level requirement

image

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a MembersPage component for managing organization members.
    • Added OrgMembersTable component to display member details with management options.
    • Enhanced sidebar navigation with a new "Users" section for member management.
    • Implemented API endpoints for listing, deleting, and updating organization members.
  • Bug Fixes

    • Improved error handling for member management operations.
  • Documentation

    • Updated API documentation to reflect new endpoints and functionalities.

Copy link

coderabbitai bot commented Nov 30, 2024

Walkthrough

This pull request introduces several components and functionalities for managing organization members within a React application and its backend. Key changes include the addition of a MembersPage component for displaying organization members, a new OrgMembersTable component for detailed member management, and the implementation of various API endpoints for listing, updating, and deleting members. Additionally, new data types and schemas have been defined to support these features, along with modifications to existing service and router files to accommodate the new organizational functionalities.

Changes

File Path Change Summary
frontend/src/app/organization/members/page.tsx New component MembersPage added for managing organization members.
frontend/src/app/organization/sidebar-nav.tsx Renamed navItems to secretNavItems, added userNavItems for "Members" navigation.
frontend/src/client/schemas.gen.ts New schema $OrgMemberRead added for organization member representation.
frontend/src/client/services.gen.ts Added functions: organizationListOrgMembers, organizationDeleteOrgMember, organizationUpdateOrgMember.
frontend/src/client/types.gen.ts New types added: OrgMemberRead, OrganizationDeleteOrgMemberData, OrganizationUpdateOrgMemberData, etc.
frontend/src/components/organization/org-members-table.tsx New component OrgMembersTable added for displaying and managing organization members.
frontend/src/components/table/table.tsx Added optional property initialSortingState to DataTableProps interface.
frontend/src/lib/auth.ts New function userIsPrivileged added to check user access level.
frontend/src/lib/hooks.tsx New hook useOrgMembers added for fetching and managing organization members.
tracecat/api/app.py Updated routing structure to include new org_router for organization features.
tracecat/auth/router.py Updated search_user function to use stricter email type validation.
tracecat/organization/models.py New Pydantic models OrgMemberRead and OrgRead added for organization data representation.
tracecat/organization/router.py New endpoints for managing organization members: get_organization, list_org_members, delete_org_member, update_org_member.
tracecat/organization/service.py New OrgService class added for handling organization member management.
tracecat/service.py New base class Service added for structured session management and role handling.

Possibly related PRs

🐰 In the land of code, we hop and play,
New members to manage, hip-hip-hooray!
With tables and pages, all set to go,
A rabbit's delight in the data flow!
Roles and permissions, all in a row,
Let's celebrate changes, let the good times flow! 🐇✨


Thank you for using CodeRabbit. We offer it for free to the OSS community and would appreciate your support in helping us grow. If you find it useful, would you consider giving us a shout-out on your favorite social media?

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Generate unit testing code for this file.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai generate unit testing code for this file.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and generate unit testing code.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@github-actions github-actions bot added engine Improvements or additions to the workflow engine enhancement New feature or request frontend Improvements or additions to the frontend labels Nov 30, 2024
Copy link

@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

🧹 Outside diff range and nitpick comments (19)
tracecat/organization/models.py (2)

9-17: Consider adding docstrings to the OrgMemberRead class.

Adding docstrings will improve code readability and help other developers understand the purpose and usage of this model.


23-25: Add docstrings to the OrgRead class for better documentation.

Including a brief description of the OrgRead model enhances maintainability and provides clarity on its role within the application.

frontend/src/lib/auth.ts (1)

22-27: Use an enumeration or constants for user roles for type safety.

Currently, the role comparison uses a string literal "admin". Defining an enum or constants for user roles can prevent typos and enhance code maintainability.

Apply this change:

// Define a UserRole enum
export enum UserRole {
  Admin = "admin",
  User = "user",
  // Add other roles as needed
}

// Update the function
export function userIsPrivileged(user: UserRead | null): boolean {
  if (!user) {
    return false
  }
  return user.is_superuser || user.role === UserRole.Admin
}
frontend/src/app/organization/members/page.tsx (3)

18-18: Remove empty div to clean up the markup

There is an empty div with class names but no content. If it's not required for layout purposes, consider removing it to simplify the code.

Apply this diff to remove the unnecessary div:

-          <div className="ml-auto flex items-center space-x-2"></div>

21-24: Remove unnecessary React fragment

The fragment <>...</> is unnecessary here since the elements are already wrapped within a parent div. Removing it can simplify the code.

Apply this diff to remove the fragment:

-          <>
             <h6 className="text-sm font-semibold">Manage Members</h6>
             <OrgMembersTable />
-          </>

22-22: Use appropriate heading levels for accessibility

Consider using a heading level that follows the hierarchical structure. Since the main heading is h2, using h3 here would be more appropriate than h6.

Apply this diff to adjust the heading level:

-            <h6 className="text-sm font-semibold">Manage Members</h6>
+            <h3 className="text-sm font-semibold">Manage Members</h3>
tracecat/organization/router.py (2)

16-21: Implement or remove the get_organization endpoint

The get_organization endpoint currently raises a NotImplementedError. If this endpoint is intended to provide organization details, consider implementing it. Otherwise, remove it to avoid exposing an unimplemented endpoint.

Would you like assistance in implementing this endpoint or opening a GitHub issue to track this task?


65-74: Refactor duplicated exception handling into a decorator

The exception handling for NoResultFound and TracecatAuthorizationError is duplicated in both delete_org_member and update_org_member endpoints. Refactoring this into a decorator can reduce code duplication and improve maintainability.

Apply this diff to create a handle_member_exceptions decorator:

+def handle_member_exceptions(func):
+    async def wrapper(*args, **kwargs):
+        try:
+            return await func(*args, **kwargs)
+        except NoResultFound as e:
+            raise HTTPException(
+                status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
+            ) from e
+        except TracecatAuthorizationError as e:
+            raise HTTPException(
+                status_code=status.HTTP_403_FORBIDDEN, detail="Forbidden"
+            ) from e
+    return wrapper

 @router.delete("/members/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
+@handle_member_exceptions
 async def delete_org_member(
     *,
     role: Role = RoleACL(
         allow_user=True,
         allow_service=False,
         require_workspace="no",
         min_access_level=AccessLevel.ADMIN,
     ),
     session: AsyncDBSession,
     user_id: UserID,
 ):
     service = OrgService(session, role=role)
     await service.delete_member(user_id)

 @router.patch("/members/{user_id}", response_model=OrgMemberRead)
+@handle_member_exceptions
 async def update_org_member(
     *,
     role: Role = RoleACL(
         allow_user=True,
         allow_service=False,
         require_workspace="no",
         min_access_level=AccessLevel.ADMIN,
     ),
     session: AsyncDBSession,
     user_id: UserID,
     params: UserUpdate,
 ):
     service = OrgService(session, role=role)
     user = await service.update_member(user_id, params)
     return OrgMemberRead(
         user_id=user.id,
         first_name=user.first_name,
         last_name=user.last_name,
         email=user.email,
         role=user.role,
         is_active=user.is_active,
         is_superuser=user.is_superuser,
         is_verified=user.is_verified,
     )

Also applies to: 103-110

tracecat/organization/service.py (1)

84-89: Extract superuser check to a helper method to reduce duplication

The check for user.is_superuser is duplicated in both delete_member and update_member methods. Consider extracting this logic into a private helper method to improve code readability and maintainability.

Apply this diff to implement the change:

+    async def _ensure_not_superuser(self, user: User) -> None:
+        if user.is_superuser:
+            raise TracecatAuthorizationError("Cannot modify superuser")

     @require_access_level(AccessLevel.ADMIN)
     async def delete_member(self, user_id: UserID) -> None:
         user = await self.get_member(user_id)
-        if user.is_superuser:
-            raise TracecatAuthorizationError("Cannot delete superuser")
+        await self._ensure_not_superuser(user)
         async with self._manager() as user_manager:
             await user_manager.delete(user)

     @require_access_level(AccessLevel.ADMIN)
     async def update_member(self, user_id: UserID, params: UserUpdate) -> User:
         user = await self.get_member(user_id)
-        if user.is_superuser:
-            raise TracecatAuthorizationError("Cannot update superuser")
+        await self._ensure_not_superuser(user)
         async with self._manager() as user_manager:
             updated_user = await user_manager.update(
                 user_update=params, user=user, safe=True
             )
         return updated_user

Also applies to: 108-115

frontend/src/components/table/table.tsx (1)

53-53: Ensure consistent naming for initialSortingState prop

The prop is defined as initialSortingState, but it's destructured and renamed to initialSorting in the function parameters. For clarity and consistency, consider using the same name throughout.

Apply this diff to align the naming:

 export function DataTable<TData, TValue>({
     columns,
     data,
     onClickRow,
     toolbarProps,
     tableHeaderAuxOptions,
     isLoading,
     error,
     emptyMessage,
     errorMessage,
     showSelectedRows = false,
-    initialSortingState: initialSorting = [],
+    initialSortingState = [],
 }: DataTableProps<TData, TValue>) {
     // ...
-    const [sorting, setSorting] = React.useState<SortingState>(initialSorting)
+    const [sorting, setSorting] = React.useState<SortingState>(initialSortingState)

Also applies to: 67-67

frontend/src/components/organization/org-members-table.tsx (5)

71-71: Remove debugging console statements from production code

The console.log and console.debug statements used for debugging should be removed or replaced with proper logging mechanisms before deploying to production. This helps maintain clean code and prevents unintended exposure of sensitive information.

Apply this diff:

- console.log("Changing role", selectedMember, role)
- console.log("Removing member", selectedMember)
- console.debug("Selected user", row.original)

Also applies to: 87-87, 262-262


77-79: Provide user feedback on role change failure

Currently, if an error occurs during the role change, the user is not notified. Consider adding a toast notification to inform the user about the failure, enhancing the user experience.

Example:

 } catch (error) {
   console.error("Failed to change role", error)
+  toast({
+    title: "Error changing role",
+    description: "An error occurred while changing the user's role.",
+    variant: "destructive",
+  })
 } finally {

92-94: Notify user on member removal failure

If an error occurs while removing a member, the user is not informed. Adding a toast notification will provide immediate feedback to the user about the failure.

Example:

 } catch (error) {
   console.error("Failed to remove member", error)
+  toast({
+    title: "Error removing member",
+    description: "An error occurred while removing the member.",
+    variant: "destructive",
+  })
 } finally {

265-265: Update action label to "Remove from organization" for consistency

The action label is currently "Remove from workspace," which may cause confusion since this component manages organization members. Updating it to "Remove from organization" aligns with the context and improves clarity.

Apply this diff:

-           Remove from workspace
+           Remove from organization

315-315: Initialize newRole state with the selected user's current role

When opening the change role dialog, the default selected role is always set to "basic," which may not reflect the user's actual current role. Initializing newRole with the selected user's current role enhances usability.

Apply this diff:

- const [newRole, setNewRole] = useState<UserRole>("basic")
+ const [newRole, setNewRole] = useState<UserRole>(
+   selectedUser?.role ?? "basic"
+ )
frontend/src/lib/hooks.tsx (2)

1070-1073: Expose loading and error states from useOrgMembers hook

The useOrgMembers hook currently returns only orgMembers. To handle loading and error states effectively in components that use this hook, consider exposing isLoading and error from the useQuery.

Update the code as follows:

-export function useOrgMembers() {
+export function useOrgMembers(): {
+  orgMembers?: OrgMemberRead[]
+  isLoading: boolean
+  error: unknown
+  // other properties
+} {
   const queryClient = useQueryClient()
-  const { data: orgMembers } = useQuery<OrgMemberRead[]>({
+  const {
+    data: orgMembers,
+    isLoading,
+    error,
+  } = useQuery<OrgMemberRead[]>({
     queryKey: ["org-members"],
     queryFn: async () => await organizationListOrgMembers(),
   })

And return the new variables:

   return {
     orgMembers,
+    isLoading,
+    error,
     updateOrgMember,
     updateOrgMemberIsPending,
     updateOrgMemberError,

1090-1105: Refactor error handling to reduce code duplication

The onError handlers in both updateOrgMember and deleteOrgMember mutations have similar structures. Extracting the common error handling logic into a shared function enhances maintainability and reduces repetition.

Example refactor:

+function handleOrgMemberError(
+  error: TracecatApiError,
+  action: "update" | "delete"
+) {
+  const apiError = error
+  switch (apiError.status) {
+    case 403:
+      toast({
+        title: "You cannot perform this action",
+        description: `${apiError.message}: ${apiError.body.detail}`,
+      })
+      break
+    default:
+      toast({
+        title: `Failed to ${action} organization member`,
+        description: `An unexpected error occurred while trying to ${action} the organization member. ${apiError.message}: ${apiError.body.detail}`,
+        variant: "destructive",
+      })
+  }
+}

 // In updateOrgMember mutation
 onError: (error: TracecatApiError) => {
-  const apiError = error as TracecatApiError
-  switch (apiError.status) {
-    case 403:
-      toast({
-        title: "You cannot perform this action",
-        description: `${apiError.message}: ${apiError.body.detail}`,
-      })
-      break
-    default:
-      toast({
-        title: "Failed to update organization member",
-        description: `An unexpected error occurred while updating the organization member. ${apiError.message}: ${apiError.body.detail}`,
-      })
-  }
+  handleOrgMemberError(error, "update")
 },

 // In deleteOrgMember mutation
 onError: (error: TracecatApiError) => {
-  const apiError = error as TracecatApiError
-  switch (apiError.status) {
-    case 403:
-      toast({
-        title: "You cannot perform this action",
-        description: `${apiError.message}: ${apiError.body.detail}`,
-      })
-      break
-    default:
-      toast({
-        title: "Failed to delete organization member",
-        description: `An unexpected error occurred while deleting the organization member. ${apiError.message}: ${apiError.body.detail}`,
-      })
-  }
+  handleOrgMemberError(error, "delete")
 },

Also applies to: 1122-1137

frontend/src/client/services.gen.ts (2)

Line range hint 1685-1685: Replace 'void' with 'undefined' in type declaration

Using void outside of return types or parameters can be confusing in TypeScript. Consider using undefined instead.

Apply this diff to fix the issue:

-export type OrganizationDeleteOrgMemberResponse = void;
+export type OrganizationDeleteOrgMemberResponse = undefined;

Line range hint 2654-2654: Replace 'void' with 'undefined' in response type

Using void outside of return types or parameters can be confusing in TypeScript. Replace void with undefined to correctly represent the absence of a value.

Apply this diff to fix the issue:

-                    204: void;
+                    204: undefined;
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between ce5f227 and c8560b5.

📒 Files selected for processing (15)
  • frontend/src/app/organization/members/page.tsx (1 hunks)
  • frontend/src/app/organization/sidebar-nav.tsx (4 hunks)
  • frontend/src/client/schemas.gen.ts (1 hunks)
  • frontend/src/client/services.gen.ts (2 hunks)
  • frontend/src/client/types.gen.ts (3 hunks)
  • frontend/src/components/organization/org-members-table.tsx (1 hunks)
  • frontend/src/components/table/table.tsx (2 hunks)
  • frontend/src/lib/auth.ts (1 hunks)
  • frontend/src/lib/hooks.tsx (2 hunks)
  • tracecat/api/app.py (3 hunks)
  • tracecat/auth/router.py (2 hunks)
  • tracecat/organization/models.py (1 hunks)
  • tracecat/organization/router.py (1 hunks)
  • tracecat/organization/service.py (1 hunks)
  • tracecat/service.py (1 hunks)
🧰 Additional context used
🪛 Biome (1.9.4)
frontend/src/client/types.gen.ts

[error] 1685-1685: void is confusing outside a return type or a type parameter.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)


[error] 2654-2654: void is confusing outside a return type or a type parameter.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

🔇 Additional comments (7)
tracecat/service.py (1)

3-3: Ensure compatibility of Self type annotation with Python version.

The Self type hint is available in Python 3.11 and later. If the project supports earlier Python versions, consider importing Self from typing_extensions or adjusting the type annotations to maintain compatibility.

frontend/src/app/organization/sidebar-nav.tsx (1)

Line range hint 82-83: Review active link highlighting logic

The current logic for highlighting the active navigation link may not work as intended for nested routes or dynamic segments. Consider revising the condition to ensure accurate highlighting.

Run the following script to check how the active link is determined:

✅ Verification successful

The active link highlighting logic is implemented correctly

The current implementation is appropriate for the navigation structure used in this application. Here's why:

  • The navigation items are flat (not nested), with simple routes like /organization/credentials and /organization/members
  • The leafRoute extraction using pathname.split("/").pop() correctly matches the last segment of the URL
  • The fallback to defaultRoute handles the root organization path
  • The highlighting is applied using the endsWith check which works well for this flat route structure
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Verify the active link highlighting in the sidebar navigation.

# Test: Find all instances where `item.href` is compared to `leafRoute`. Expect: Correct handling of routes.
rg 'item\.href\.endsWith\(leafRoute.*\)' frontend/src/app/organization/sidebar-nav.tsx

Length of output: 149


Script:

#!/bin/bash
# Let's get more context around the active link highlighting logic
rg -B 5 -A 5 'item\.href\.endsWith\(leafRoute.*\)' frontend/src/app/organization/sidebar-nav.tsx

# Also check how leafRoute and defaultRoute are determined
rg 'const.*(?:leafRoute|defaultRoute)' frontend/src/app/organization/sidebar-nav.tsx

# Check the structure of navigation items
ast-grep --pattern 'const $_ = [
  $$$
] as const'

Length of output: 1661


Script:

#!/bin/bash
# Let's check the navigation items structure and how they're defined
rg -B 2 -A 10 'const.*items.*=.*\[' frontend/src/app/organization/sidebar-nav.tsx

# Also check if there are any nested routes in the codebase
fd -e tsx -e ts -t f . frontend/src/app/organization | xargs rg 'href.*=.*/'

Length of output: 162


Script:

#!/bin/bash
# Let's try a different approach to find the navigation items and route structure
cat frontend/src/app/organization/sidebar-nav.tsx

# Also check for any route definitions or patterns
rg -l 'href.*=.*["\']/' frontend/src/app/organization/

Length of output: 2319

tracecat/api/app.py (1)

16-16: Ensure consistency after router import changes

The import for users_router has been updated, and a new org_router has been added. Verify that these changes maintain the expected functionality and that all routes are correctly registered. Ensure that any dependencies or configurations affected by the import changes are also updated accordingly.

Also applies to: 27-27, 255-255

frontend/src/client/services.gen.ts (1)

1354-1402: New organization member API methods implemented correctly

The methods for listing, deleting, and updating organization members are correctly defined and properly implement the intended functionality.

frontend/src/client/types.gen.ts (2)

439-449: Type OrgMemberRead is correctly defined

The OrgMemberRead type includes all necessary fields to represent an organization member.


1679-1693: Organization member types are correctly defined

The types supporting organization member operations are appropriately defined and align with the expected data structures.

🧰 Tools
🪛 Biome (1.9.4)

[error] 1685-1685: void is confusing outside a return type or a type parameter.

Unsafe fix: Use undefined instead.

(lint/suspicious/noConfusingVoidType)

frontend/src/client/schemas.gen.ts (1)

1316-1369: Schema $OrgMemberRead added correctly

The new schema $OrgMemberRead is properly defined and matches the expected structure for organization members, including all required properties with appropriate types.

tracecat/organization/models.py Show resolved Hide resolved
tracecat/service.py Show resolved Hide resolved
tracecat/auth/router.py Show resolved Hide resolved
@daryllimyt daryllimyt merged commit 95c70da into main Nov 30, 2024
20 checks passed
@daryllimyt daryllimyt deleted the feat/users-management branch November 30, 2024 23:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
engine Improvements or additions to the workflow engine enhancement New feature or request frontend Improvements or additions to the frontend
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants