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

Users admin tab #2672

Merged
merged 2 commits into from
Nov 4, 2024
Merged

Users admin tab #2672

merged 2 commits into from
Nov 4, 2024

Conversation

gaspergrom
Copy link
Contributor

@gaspergrom gaspergrom commented Nov 4, 2024

Changes proposed ✍️

What

copilot:summary

copilot:poem

Why

How

copilot:walkthrough

Checklist ✅

  • Label appropriately with Feature, Improvement, or Bug.
  • Add screenshots to the PR description for relevant FE changes
  • New backend functionality has been unit-tested.
  • API documentation has been updated (if necessary) (see docs on API documentation).
  • Quality standards are met.

Summary by CodeRabbit

Release Notes

  • New Features

    • Introduced a user management interface with enhanced search capabilities for filtering users by name or email.
    • Added a new search input component for improved user experience.
    • Implemented a new tab for user management accessible to admin users.
    • Enhanced autocomplete functionality for user searches, allowing searches by ID, full name, or email.
  • Bug Fixes

    • Improved error handling and loading states in user data retrieval.
  • Documentation

    • Added a clear structure for user data representation with the new UserModel interface.
  • Refactor

    • Enhanced type safety in user-related methods.

@gaspergrom gaspergrom added the Improvement Created by Linear-GitHub Sync label Nov 4, 2024
@gaspergrom gaspergrom self-assigned this Nov 4, 2024
Copy link

coderabbitai bot commented Nov 4, 2024

Caution

Review failed

The pull request is closed.

Walkthrough

The changes in this pull request introduce several enhancements to user management functionality across both the backend and frontend. The backend updates include improved user search capabilities and type safety in the UserRepository. On the frontend, a new user management interface is created, along with a dedicated service for fetching user data. Additionally, a reusable search component is added, and a new tab for user management is integrated into the admin panel.

Changes

File Path Change Summary
backend/src/database/repositories/userRepository.ts - Enhanced findAndCountAll method to support filtering by fullName or email using filter.query.
- Updated findAllAutocomplete method for searching by user ID, fullName, or email.
- Added type annotation for tenantId in findAllUsersOfTenant.
frontend/src/modules/admin/models/User.model.ts - Added new interface UserModel defining user object structure with properties: id, email, fullName, and roles.
frontend/src/modules/admin/pages/users.page.vue - Introduced users.page.vue for user management, including search input, user table, and pagination.
- Implemented fetchUsers, searchUsers, and loadMore functions for user data handling.
frontend/src/modules/admin/services/users.service.ts - Added UsersService class with static method list for retrieving user data based on a query.
frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue - Added "Users" tab to the admin panel, conditionally rendered for admin users, displaying the <lf-admin-users> component.
frontend/src/ui-kit/search/Search.vue - Introduced Search.vue component for search input with debounced updates and clear functionality.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant AdminPanel
    participant UsersService
    participant UserRepository

    User->>AdminPanel: Access User Management
    AdminPanel->>UsersService: list(query)
    UsersService->>UserRepository: findAndCountAll(filter.query)
    UserRepository-->>UsersService: Return user data
    UsersService-->>AdminPanel: Return user data
    AdminPanel->>User: Display user data
Loading

🐇 "In the admin panel, a new tab appears,
For managing users, it brings us cheers.
With search and filters, we find with ease,
A hop and a skip, our worries appease.
Data flows smoothly, all roles in sight,
Thanks to the changes, it feels just right!" 🐇✨


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.

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

🧹 Outside diff range and nitpick comments (13)
frontend/src/modules/admin/models/User.model.ts (1)

1-6: Consider enhancing type safety and documentation.

  1. Add JSDoc comments to document the interface and its properties
  2. Consider using a string literal union type for roles to restrict valid values

Here's a suggested improvement:

+/**
+ * Represents a user in the admin interface
+ */
 export interface UserModel {
+  /** Unique identifier for the user */
   id: string;
+  /** User's email address */
   email: string;
+  /** User's full name */
   fullName: string;
+  /** User's assigned roles */
-  roles: string[];
+  roles: Array<'admin' | 'user' | 'moderator'>; // adjust roles as per your needs
 }
frontend/src/ui-kit/search/Search.vue (4)

1-12: Consider extracting clear functionality to a method

The clear functionality is currently implemented directly in the template. For better maintainability and reusability, consider extracting it to a method.

 <template>
   <lf-input v-model="valueProxy">
     <template #prefix>
       <lf-icon name="search" />
     </template>
     <template v-if="valueProxy.length" #suffix>
-      <div @click="valueProxy = ''">
+      <div @click="clearSearch">
         <lf-icon name="xmark" />
       </div>
     </template>
   </lf-input>
 </template>

Add the method in the script section:

const clearSearch = () => {
  valueProxy.value = '';
};

20-25: Consider using more specific emit return type

The emit type currently uses any as the return type. Consider being more explicit about the return type for better type safety.

-const emit = defineEmits<{(e: 'update:modelValue', value: string | number): any }>();
+const emit = defineEmits<{(e: 'update:modelValue', value: string | number): void }>();

31-31: Consider making debounce delay configurable

The debounce delay is currently hardcoded to 300ms. Consider making it configurable through props to allow for different use cases.

+const props = defineProps<{
+  modelValue: string | number,
+  lazy?: boolean,
+  debounceDelay?: number,
+}>();

-const debouncedEmitValue = debounce(emitValue, 300);
+const debouncedEmitValue = debounce(emitValue, props.debounceDelay ?? 300);

42-46: Consider removing redundant script tag

When using <script setup>, the component name can be defined using a single script block with the defineOptions macro.

-<script lang="ts">
-export default {
-  name: 'LfSearch',
-};
-</script>

 <script setup lang="ts">
+defineOptions({
+  name: 'LfSearch',
+});
frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue (1)

59-59: Consider relocating the users component to maintain architectural consistency.

The LfAdminUsers component is imported from the pages directory but is being used as an embedded component within a tab. This might indicate a architectural inconsistency, as components in the pages directory typically represent route-level components.

Consider:

  1. Moving the component to a more appropriate location like @/modules/admin/components/users/
  2. Or, if this is intended to be a standalone page, consider using Vue Router to handle the tab navigation instead of embedding it.

This would help maintain a clearer separation between pages and components while improving maintainability.

frontend/src/modules/admin/pages/users.page.vue (4)

7-37: Enhance table accessibility.

The table lacks proper ARIA attributes and scope definitions for better screen reader support.

       <lf-table>
         <thead>
           <tr>
-            <th>User</th>
-            <th>Email</th>
-            <th>Role</th>
+            <th scope="col" role="columnheader">User</th>
+            <th scope="col" role="columnheader">Email</th>
+            <th scope="col" role="columnheader">Role</th>
           </tr>
         </thead>
-        <tbody>
+        <tbody role="rowgroup">

72-78: Enhance type safety for reactive state.

Consider adding more specific types for the reactive state variables.

-const search = ref('');
-const loading = ref<boolean>(false);
-const offset = ref(0);
-const limit = ref(20);
-const total = ref(0);
+const search = ref<string>('');
+const loading = ref<boolean>(false);
+const offset = ref<number>(0);
+const limit = ref<number>(20);
+const total = ref<number>(0);

110-118: Optimize pagination handling.

Consider adding a computed property for determining if more data is available and handling edge cases.

+const canLoadMore = computed(() => {
+  return !loading.value && users.value.length < total.value;
+});
+
 const loadMore = () => {
+  if (!canLoadMore.value) return;
   offset.value = users.value.length;
   fetchUsers();
 };

Then update the template:

-      <div v-if="users.length < total" class="pt-4">
+      <div v-if="canLoadMore" class="pt-4">

136-145: Consider adding cleanup on component unmount.

Add cleanup logic to prevent memory leaks and unnecessary API calls.

+import { onMounted, onUnmounted } from 'vue';
+
+const isComponentMounted = ref(true);
+
 onMounted(() => {
   searchUsers();
 });
+
+onUnmounted(() => {
+  isComponentMounted.value = false;
+});

Then update the fetchUsers function:

 const fetchUsers = () => {
-  if (loading.value) {
+  if (loading.value || !isComponentMounted.value) {
     return;
   }
backend/src/database/repositories/userRepository.ts (3)

Line range hint 39-39: Consider improving return type safety.

While adding the string type annotation for tenantId is a good improvement, the return type Promise<any[]> could be made more type-safe. Consider defining an interface for the user record structure and using it as the return type.

Example interface:

interface UserWithTenant {
  id: string;
  email: string;
  firstName?: string;
  lastName?: string;
  tenants: {
    tenantId: string;
    roles: string[];
    status: string;
  }[];
}

Then update the return type:

-static async findAllUsersOfTenant(tenantId: string): Promise<any[]> {
+static async findAllUsersOfTenant(tenantId: string): Promise<UserWithTenant[]> {

413-420: Consider performance optimization for search queries.

The implementation of the query filter is functionally correct, but there are potential performance considerations for large datasets:

  1. Using case-insensitive LIKE queries (ILIKE) on multiple columns without proper indexes could impact performance.
  2. Consider adding database indexes for frequently searched columns.

Consider these improvements:

  1. Add indexes to improve search performance:
CREATE INDEX idx_user_fullname_gin ON users USING gin (lower(full_name) gin_trgm_ops);
CREATE INDEX idx_user_email_gin ON users USING gin (lower(email) gin_trgm_ops);
  1. If full-text search capabilities are needed in the future, consider using PostgreSQL's full-text search features with tsvector columns and GiST indexes.

Line range hint 607-624: Enhance security measures for user data handling.

While the implementation handles basic security concerns, consider these security improvements:

  1. The raw SQL query could be vulnerable to SQL injection if the id parameter is not properly sanitized.
  2. The cleanup of sensitive data could be more systematic.

Consider these improvements:

  1. Replace raw SQL with Sequelize's query builder:
const record = await options.database.user.findOne({
  where: {
    id,
    deletedAt: null
  },
  transaction
});
  1. Create a systematic approach to remove sensitive data:
const SENSITIVE_FIELDS = [
  'password',
  'emailVerificationToken',
  'emailVerificationTokenExpiresAt',
  'providerId',
  'passwordResetToken',
  'passwordResetTokenExpiresAt',
  'jwtTokenInvalidBefore'
] as const;

// Use in cleanup
SENSITIVE_FIELDS.forEach(field => delete record[field]);
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL

📥 Commits

Reviewing files that changed from the base of the PR and between 66ef96a and cbe0250.

📒 Files selected for processing (6)
  • backend/src/database/repositories/userRepository.ts (1 hunks)
  • frontend/src/modules/admin/models/User.model.ts (1 hunks)
  • frontend/src/modules/admin/pages/users.page.vue (1 hunks)
  • frontend/src/modules/admin/services/users.service.ts (1 hunks)
  • frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue (2 hunks)
  • frontend/src/ui-kit/search/Search.vue (1 hunks)
🧰 Additional context used
🪛 Biome
frontend/src/modules/admin/services/users.service.ts

[error] 4-17: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

🔇 Additional comments (6)
frontend/src/modules/admin/models/User.model.ts (1)

1-6: LGTM! The interface provides a clear contract for user data.

The interface correctly defines the essential user properties with appropriate types.

frontend/src/modules/admin/services/users.service.ts (2)

1-2: LGTM! Imports are correctly defined.

The necessary dependencies for HTTP requests and authentication are properly imported.


4-17: Verify service usage across the application.

Let's ensure consistent implementation across the codebase.

✅ Verification successful

Let me check the usage in users.page.vue to understand how the service is being used and verify if the changes are compatible.


Service implementation and usage are consistent

The verification shows:

  • The service is used correctly in users.page.vue with the expected query parameters
  • Similar pattern is followed in DashboardApiService, confirming consistent implementation across services
  • No breaking changes or inconsistencies found in the implementation
🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check for similar patterns and usage of this service

# Check for components using this service
echo "Checking components using UsersService:"
rg -l "UsersService" frontend/src/modules

# Check for similar service implementations
echo "\nChecking for similar service patterns:"
ast-grep --pattern 'export class $SERVICE_NAME {
  static async $METHOD_NAME($$$) {
    $$$
  }
}' frontend/src/modules

Length of output: 3190


Script:

#!/bin/bash
# Check how UsersService is used in the page component
echo "Checking UsersService usage in users.page.vue:"
rg "UsersService" frontend/src/modules/admin/pages/users.page.vue -A 5

Length of output: 634

🧰 Tools
🪛 Biome

[error] 4-17: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

frontend/src/ui-kit/search/Search.vue (1)

14-19: LGTM! Well-structured imports and setup

The component uses modern Vue.js practices with Composition API and has all necessary imports properly organized.

frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue (2)

35-37: Verify backend API security for the new Users admin functionality.

While the frontend properly restricts access using isAdminUser, we should ensure corresponding backend API endpoints are properly secured.

Let's verify the backend security implementation:

#!/bin/bash
# Description: Check for proper admin-only middleware in related API routes
# Look for user-related route definitions
rg -A 5 "users.*router\.(get|post|put|delete)" 

# Look for admin middleware usage
rg "admin.*middleware" 

Also applies to: 59-59


35-37: LGTM! Implementation follows established patterns.

The new Users tab implementation correctly follows the existing pattern for admin-only tabs with proper conditional rendering.

Let's verify the tab order matches the intended UX design:

✅ Verification successful

Tab order follows logical grouping and is consistent with admin features

The new Users tab is being added to a well-structured set of admin tabs that follow a logical order:

  1. Project Groups (general access)
  2. Organizations (admin only)
  3. Automations (admin only)
  4. API Keys (admin only)
  5. Audit logs (admin only)

Adding the Users tab alongside other admin-only features is appropriate, and the implementation follows the established pattern correctly.

🏁 Scripts executed

The following scripts were executed for the analysis:

Script:

#!/bin/bash
# Description: Check if there are any UX/design documents specifying the tab order
rg -l "admin.*panel.*tab.*order" --type md

Length of output: 73


Script:

#!/bin/bash
# Let's check the existing tab order in the template
rg -A 10 '<el-tabs' frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue

Length of output: 549


Script:

#!/bin/bash
# Let's see the complete tabs structure to understand the full order
rg -B 2 -A 15 '<el-tabs' frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue

Length of output: 750


Script:

#!/bin/bash
# Let's see if there are any more tabs after the shown ones
rg -B 0 -A 10 'label="Automations"' frontend/src/modules/lf/segments/pages/lf-admin-panel-page.vue

Length of output: 533

import { AuthService } from '@/modules/auth/services/auth.service';

export class UsersService {
static async list(query: any) {
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type safety for query parameters.

The any type for the query parameter reduces type safety. Consider creating a proper interface for the query parameters.

Comment on lines +8 to +14
const response = await authAxios.get(
`/tenant/${tenantId}/user`,
{
params: query,
},
);

Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Add proper error handling.

The HTTP request lacks error handling. Consider implementing try-catch blocks and proper error handling for different scenarios (network errors, 404, etc.).

},
);

return response.data;
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add type definition for the response data.

The response data is returned without type information. Consider defining an interface for the response structure to improve type safety and documentation.

Comment on lines +4 to +17
export class UsersService {
static async list(query: any) {
const tenantId = AuthService.getTenantId();

const response = await authAxios.get(
`/tenant/${tenantId}/user`,
{
params: query,
},
);

return response.data;
}
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Convert class to a standalone function module.

The static analysis tool correctly identifies that this class only contains static methods. Following TypeScript best practices, we should convert this to a module with standalone functions.

Here's the suggested implementation:

-export class UsersService {
-  static async list(query: any) {
-    const tenantId = AuthService.getTenantId();
-
-    const response = await authAxios.get(
-      `/tenant/${tenantId}/user`,
-      {
-        params: query,
-      },
-    );
-
-    return response.data;
-  }
-}
+interface UserQuery {
+  search?: string;
+  page?: number;
+  limit?: number;
+  // add other query parameters as needed
+}
+
+interface UserResponse {
+  data: User[];
+  total: number;
+  // add other response fields as needed
+}
+
+export async function listUsers(query: UserQuery): Promise<UserResponse> {
+  const tenantId = AuthService.getTenantId();
+
+  try {
+    const response = await authAxios.get<UserResponse>(
+      `/tenant/${tenantId}/user`,
+      {
+        params: query,
+      },
+    );
+    return response.data;
+  } catch (error) {
+    // Handle specific error cases
+    if (error.response?.status === 404) {
+      throw new Error('Users not found');
+    }
+    throw new Error('Failed to fetch users');
+  }
+}

Committable suggestion skipped: line range outside the PR's diff.

🧰 Tools
🪛 Biome

[error] 4-17: Avoid classes that contain only static members.

Prefer using simple functions instead of classes with only static members.

(lint/complexity/noStaticOnlyClass)

Comment on lines +31 to +39
const debouncedEmitValue = debounce(emitValue, 300);

watch(valueProxy, (newVal) => {
if (props.lazy) {
debouncedEmitValue(newVal);
} else {
emitValue(newVal);
}
});
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add cleanup for debounced function

The debounced function should be cleaned up when the component is unmounted to prevent memory leaks and unexpected behavior.

+import { ref, watch, onUnmounted } from 'vue';

 const debouncedEmitValue = debounce(emitValue, 300);

+onUnmounted(() => {
+  debouncedEmitValue.cancel();
+});
📝 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
const debouncedEmitValue = debounce(emitValue, 300);
watch(valueProxy, (newVal) => {
if (props.lazy) {
debouncedEmitValue(newVal);
} else {
emitValue(newVal);
}
});
import { ref, watch, onUnmounted } from 'vue';
const debouncedEmitValue = debounce(emitValue, 300);
onUnmounted(() => {
debouncedEmitValue.cancel();
});
watch(valueProxy, (newVal) => {
if (props.lazy) {
debouncedEmitValue(newVal);
} else {
emitValue(newVal);
}
});

Comment on lines 80 to 108
const fetchUsers = () => {
if (loading.value) {
return;
}
loading.value = true;
UsersService.list({
filter: {
query: search.value,
},
offset: offset.value,
limit: limit.value,
})
.then((res) => {
if (offset.value > 0) {
users.value = [...users.value, ...res.rows];
} else {
users.value = res.rows;
}

if (res.rows.length > 0) {
total.value = res.count;
} else {
total.value = users.value.length;
}
})
.finally(() => {
loading.value = false;
});
};
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue

Improve error handling and edge cases in data fetching.

The current implementation has several areas for improvement:

  1. No error state management for failed API calls
  2. Edge case in total count logic could be misleading
 const fetchUsers = () => {
   if (loading.value) {
     return;
   }
   loading.value = true;
+  const errorMessage = ref<string>('');
   UsersService.list({
     filter: {
       query: search.value,
     },
     offset: offset.value,
     limit: limit.value,
   })
     .then((res) => {
+      errorMessage.value = '';
       if (offset.value > 0) {
         users.value = [...users.value, ...res.rows];
       } else {
         users.value = res.rows;
       }
 
-      if (res.rows.length > 0) {
-        total.value = res.count;
-      } else {
-        total.value = users.value.length;
-      }
+      total.value = res.count;
+    })
+    .catch((error) => {
+      errorMessage.value = 'Failed to fetch users. Please try again.';
+      console.error('Error fetching users:', error);
     })
     .finally(() => {
       loading.value = false;
     });
 };

Also, consider adding an error state in the template:

<div v-if="errorMessage" class="text-red-500 mt-2">
  {{ errorMessage }}
</div>

Comment on lines +120 to +134
const roleDisplay = (roles: string[]) => {
const role = roles?.[0];

if (role === 'admin') {
return 'Admin';
}
if (role === 'projectAdmin') {
return 'Project Admin';
}

if (role === 'readonly') {
return 'Read-only';
}
return role;
};
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Improve role mapping maintainability.

Consider using a constant map for role display values and adding type safety.

+const ROLE_DISPLAY_MAP: Record<string, string> = {
+  admin: 'Admin',
+  projectAdmin: 'Project Admin',
+  readonly: 'Read-only',
+} as const;
+
-const roleDisplay = (roles: string[]) => {
+const roleDisplay = (roles: string[] | undefined): string => {
   const role = roles?.[0];
-
-  if (role === 'admin') {
-    return 'Admin';
-  }
-  if (role === 'projectAdmin') {
-    return 'Project Admin';
-  }
-
-  if (role === 'readonly') {
-    return 'Read-only';
-  }
-  return role;
+  return role ? (ROLE_DISPLAY_MAP[role] ?? role) : 'No role';
 };
📝 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
const roleDisplay = (roles: string[]) => {
const role = roles?.[0];
if (role === 'admin') {
return 'Admin';
}
if (role === 'projectAdmin') {
return 'Project Admin';
}
if (role === 'readonly') {
return 'Read-only';
}
return role;
};
const ROLE_DISPLAY_MAP: Record<string, string> = {
admin: 'Admin',
projectAdmin: 'Project Admin',
readonly: 'Read-only',
} as const;
const roleDisplay = (roles: string[] | undefined): string => {
const role = roles?.[0];
return role ? (ROLE_DISPLAY_MAP[role] ?? role) : 'No role';
};

Comment on lines +3 to +5
<div class="pt-6 pb-6">
<lf-search v-model="search" class="h-9" :lazy="true" placeholder="Search users..." @update:model-value="searchUsers()" />
</div>
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add debounce to search input to prevent excessive API calls.

The search input's lazy prop might not be sufficient to prevent rapid API calls as users type.

-      <lf-search v-model="search" class="h-9" :lazy="true" placeholder="Search users..." @update:model-value="searchUsers()" />
+      <lf-search
+        v-model="search"
+        class="h-9"
+        :lazy="true"
+        :debounce="300"
+        placeholder="Search users..."
+        @update:model-value="searchUsers()"
+      />
📝 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
<div class="pt-6 pb-6">
<lf-search v-model="search" class="h-9" :lazy="true" placeholder="Search users..." @update:model-value="searchUsers()" />
</div>
<div class="pt-6 pb-6">
<lf-search
v-model="search"
class="h-9"
:lazy="true"
:debounce="300"
placeholder="Search users..."
@update:model-value="searchUsers()"
/>
</div>

@gaspergrom gaspergrom merged commit ca8c7ca into main Nov 4, 2024
7 of 8 checks passed
@gaspergrom gaspergrom deleted the improvement/users branch November 4, 2024 12:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Improvement Created by Linear-GitHub Sync
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants