Skip to content

Conversation

AlexVelezLl
Copy link
Member

@AlexVelezLl AlexVelezLl commented Jul 8, 2025

Summary

  • Extracts users table to be its own component.
  • Adds date_joined__gt and date_joined__lt filters in the FacilityUserViewset.
  • Adds new users page.
  • Re-estructure users-related components to be all inside a users folder to have a better file organization.
  • Moves create user form to be a side panel.
  • Moves create user logic from vuex to the side panel itself.
  • Adds enroll to a class multi select field.
  • Allows enrolling users to classes on user create.
  • Prevents unnecessary users reload when naviating to a new page.
  • Fixes identifier info icon tooltip in users table.

Screencast

Screenshare.-.2025-07-08.10_15_35.AM.mp4

References

Closes #13410.

Reviewer guidance

  • Go to facilities > users.
  • Click on add user button.
  • Add users using both save and add another and save and continue actions
  • Check that users have been created correctly with their roles and classes.

@github-actions github-actions bot added DEV: backend Python, databases, networking, filesystem... APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) DEV: frontend SIZE: very large labels Jul 8, 2025
@AlexVelezLl AlexVelezLl force-pushed the improved-user-creation branch from 0c2a054 to b276fd7 Compare July 8, 2025 15:12
@AlexVelezLl AlexVelezLl force-pushed the improved-user-creation branch from b276fd7 to b4728e9 Compare July 8, 2025 15:19
Copy link
Contributor

github-actions bot commented Jul 8, 2025

@AlexVelezLl
Copy link
Member Author

@coderabbitai full review

Copy link
Contributor

coderabbitai bot commented Jul 8, 2025

✅ Actions performed

Full review triggered.

Copy link
Contributor

coderabbitai bot commented Jul 8, 2025

Walkthrough

This change restructures the user creation workflow in the facility user management module. It introduces a new "New Users" page and side panel for streamlined user creation and bulk actions, adds date-based filtering for recently added users, removes legacy user management components, and updates constants, routes, composables, and supporting utilities to support the new workflow and UI.

Changes

Files/Paths Change Summary
kolibri/core/auth/api.py Added date_joined__gt and date_joined__lt filters to FacilityUserFilter for backend date-based user filtering.
kolibri/plugins/facility/assets/src/views/UserCreatePage.vue, kolibri/plugins/facility/assets/src/views/UserPage/index.vue Removed legacy user creation and user management Vue components.
kolibri/plugins/facility/assets/src/views/users/UsersRootPage/index.vue, kolibri/plugins/facility/assets/src/views/users/NewUsersPage.vue, kolibri/plugins/facility/assets/src/views/users/common/UsersTable.vue, kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/index.vue, kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/ClassesSelect.vue Added new Vue components for the Users root page, New Users page, users table, user creation side panel, and class selection.
kolibri/plugins/facility/assets/src/composables/useUserManagement.js Updated composable to accept date-based filtering for new users and improved query watcher.
kolibri/plugins/facility/assets/src/constants.js Added new page and side panel constants for "New Users" flow.
kolibri/plugins/facility/assets/src/routes.js Updated routes to add the new "New Users" page, update user management routes, and integrate new side panels.
kolibri/plugins/facility/assets/src/modules/pluginModule.js Updated navigation logic for new user creation flow and facility links.
kolibri/plugins/facility/assets/src/utils.js Added route override utility, updated side panel route logic, and imported new side panel components.
kolibri/plugins/facility/assets/src/views/users/common/DeleteUserModal.vue, kolibri/plugins/facility/assets/src/views/users/common/ResetUserPasswordModal.vue Changed emitted events from "cancel" to "close" and added "change" event for state updates.
packages/kolibri-common/components/userAccounts/FullNameTextbox.vue, packages/kolibri-common/components/userAccounts/PasswordTextbox.vue, packages/kolibri-common/components/userAccounts/UsernameTextbox.vue Added public reset() methods to textbox components for form resetting.
packages/kolibri-common/components/PaginatedListContainerWithBackend.vue Only renders filter grid if relevant slots are present.
packages/kolibri-common/components/labels/CoreInfoIcon/index.vue Added appendToOverlay prop to tooltip.
packages/kolibri-common/strings/bulkUserManagementStrings.js, packages/kolibri-common/strings/enhancedQuizManagementStrings.js, packages/kolibri-common/uiText/userKinds.js, packages/kolibri/uiText/commonCoreStrings.js Added/updated translation strings and user kind display mapping utility.
kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue Changed string import source for saveAndClose$ to coreStrings.
kolibri/plugins/facility/assets/src/views/UserEditPage.vue, kolibri/plugins/facility/assets/src/views/users/UsersRootPage/test/UsersRootPage.spec.js Updated import paths for components and test utilities.

Assessment against linked issues

Objective (issue #13410) Addressed Explanation
Add sub-route to users page for new "UserCreation" (New Users) page
New page has two states: no recently added users (empty state + button), or immersive table filtered to last 30 days
Side panel form: includes "Enroll in class" select, opens on page load, supports "Save and close" and "Save and add another" actions
User creation: facility user creation, then assign role if not learner; code migrated from legacy actions

Assessment against linked issues: Out-of-scope changes

Code Change (file_path) Explanation
None found
✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 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.
    • Explain this complex logic.
    • 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 explain this code block.
    • @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 explain its main purpose.
    • @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.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

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 generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this 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
Contributor

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

🧹 Nitpick comments (4)
kolibri/plugins/facility/assets/src/views/users/NewUsersPage.vue (1)

126-127: Consider making the threshold configurable

The hardcoded MAX_NEW_USER_DAYS = 30 might be better as a configurable constant, potentially moved to a configuration file or made into a facility setting, to allow administrators to adjust the "new user" period.

-// Constant for the maximum number of days to consider a user as a "new user"
-const MAX_NEW_USER_DAYS = 30;
+// TODO: Consider making this configurable via facility settings
+const MAX_NEW_USER_DAYS = 30;
kolibri/plugins/facility/assets/src/utils.js (1)

33-48: Consider adding defensive checks to overrideRoute function.

The function correctly merges route parameters and queries. However, consider adding validation to ensure route and newRoute are valid objects.

 export function overrideRoute(route, newRoute) {
   // Override the route with a new one, preserving the params and query
+  if (!route || !newRoute) {
+    logging.error('Invalid route objects provided to overrideRoute');
+    return newRoute || route || {};
+  }
   const { params, query } = route;
   return {
     ...newRoute,
     params: {
       ...params,
       ...newRoute.params,
     },
     query: {
       ...query,
       ...newRoute.query,
     },
   };
 }
kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/ClassesSelect.vue (1)

119-123: Remove unused CSS class.

The .selected-chips class is defined but not used in the template.

-  .selected-chips {
-    display: flex;
-    flex-wrap: wrap;
-    gap: 8px;
-  }
kolibri/plugins/facility/assets/src/views/users/common/UsersTable.vue (1)

1-655: Consider splitting this large component for better maintainability.

At 655 lines, this component handles many responsibilities. Consider extracting:

  • User selection logic into a composable
  • Table header configuration into a separate utility
  • Modal handling into a separate component or composable

This would improve maintainability and reusability.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7e01539 and b4728e9.

📒 Files selected for processing (27)
  • kolibri/core/auth/api.py (3 hunks)
  • kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue (2 hunks)
  • kolibri/plugins/facility/assets/src/composables/useUserManagement.js (3 hunks)
  • kolibri/plugins/facility/assets/src/constants.js (1 hunks)
  • kolibri/plugins/facility/assets/src/modules/pluginModule.js (2 hunks)
  • kolibri/plugins/facility/assets/src/routes.js (2 hunks)
  • kolibri/plugins/facility/assets/src/utils.js (3 hunks)
  • kolibri/plugins/facility/assets/src/views/UserCreatePage.vue (0 hunks)
  • kolibri/plugins/facility/assets/src/views/UserEditPage.vue (1 hunks)
  • kolibri/plugins/facility/assets/src/views/UserPage/index.vue (0 hunks)
  • kolibri/plugins/facility/assets/src/views/users/NewUsersPage.vue (1 hunks)
  • kolibri/plugins/facility/assets/src/views/users/UsersRootPage/__test__/UsersRootPage.spec.js (1 hunks)
  • kolibri/plugins/facility/assets/src/views/users/UsersRootPage/index.vue (1 hunks)
  • kolibri/plugins/facility/assets/src/views/users/common/DeleteUserModal.vue (2 hunks)
  • kolibri/plugins/facility/assets/src/views/users/common/ResetUserPasswordModal.vue (2 hunks)
  • kolibri/plugins/facility/assets/src/views/users/common/UsersTable.vue (1 hunks)
  • kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/ClassesSelect.vue (1 hunks)
  • kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/index.vue (1 hunks)
  • packages/kolibri-common/components/PaginatedListContainerWithBackend.vue (1 hunks)
  • packages/kolibri-common/components/labels/CoreInfoIcon/index.vue (1 hunks)
  • packages/kolibri-common/components/userAccounts/FullNameTextbox.vue (1 hunks)
  • packages/kolibri-common/components/userAccounts/PasswordTextbox.vue (1 hunks)
  • packages/kolibri-common/components/userAccounts/UsernameTextbox.vue (1 hunks)
  • packages/kolibri-common/strings/bulkUserManagementStrings.js (2 hunks)
  • packages/kolibri-common/strings/enhancedQuizManagementStrings.js (0 hunks)
  • packages/kolibri-common/uiText/userKinds.js (2 hunks)
  • packages/kolibri/uiText/commonCoreStrings.js (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/kolibri-common/strings/enhancedQuizManagementStrings.js
  • kolibri/plugins/facility/assets/src/views/UserCreatePage.vue
  • kolibri/plugins/facility/assets/src/views/UserPage/index.vue
🧰 Additional context used
🧬 Code Graph Analysis (3)
kolibri/plugins/facility/assets/src/modules/pluginModule.js (1)
kolibri/plugins/facility/assets/src/constants.js (2)
  • PageNames (1-26)
  • PageNames (1-26)
packages/kolibri-common/uiText/userKinds.js (1)
packages/kolibri/uiText/commonCoreStrings.js (2)
  • coreStrings (9-1608)
  • coreStrings (9-1608)
kolibri/plugins/facility/assets/src/routes.js (2)
kolibri/plugins/facility/assets/src/utils.js (1)
  • getSidePanelRoutes (82-92)
kolibri/plugins/facility/assets/src/constants.js (2)
  • PageNames (1-26)
  • PageNames (1-26)
🔇 Additional comments (42)
packages/kolibri-common/components/labels/CoreInfoIcon/index.vue (1)

15-18: Verify KTooltip’s appendToOverlay prop

We weren’t able to locate KTooltip’s prop definitions in the repository—if the installed KDS version doesn’t expose appendToOverlay, it will silently no-op.

• File: packages/kolibri-common/components/labels/CoreInfoIcon/index.vue
Lines: 15–18

  <KTooltip
    reference="icon"
    appendToOverlay
    :class="{ ltr: !isRtl }"

Please confirm against the Kolibri Design System version in use that KTooltip defines a prop named appendToOverlay. If it does not, update to the correct portal prop (e.g. appendToBody, appendTo) or bump the KDS dependency accordingly.

kolibri/plugins/facility/assets/src/views/UserEditPage.vue (1)

157-157: Import path for IdentifierTextbox is correct and consistent

  • Verified that IdentifierTextbox.vue exists at kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/IdentifierTextbox.vue.
  • The only other import is in kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/index.vue, which correctly uses a relative import (./IdentifierTextbox).

No further updates are required.

packages/kolibri-common/components/userAccounts/UsernameTextbox.vue (1)

100-105: LGTM! Clean implementation of reset functionality.

The reset method provides a clean way to reset the validation state by clearing the blurred flag. This is consistent with similar methods in other user account components and follows good practices for form reset functionality.

packages/kolibri-common/components/userAccounts/FullNameTextbox.vue (1)

75-80: LGTM! Consistent reset functionality across user account components.

The reset method implementation is consistent with similar methods in other user account components and provides a clean way to reset the validation state.

kolibri/plugins/facility/assets/src/views/users/common/ResetUserPasswordModal.vue (2)

74-74: Event emission standardization looks good.

The change from 'cancel' to 'close' event emission after successful password reset is consistent with the modal's closing behavior pattern.


10-10: Verified: No remaining ‘@cancel’ listeners on ResetUserPasswordModal

  • ResetUserPasswordModal is only used in UsersTable.vue.
  • No instances of @cancel handlers remain.
  • UsersTable.vue correctly listens for @close="closeModal".

All parent components handle the new close event.

packages/kolibri-common/components/PaginatedListContainerWithBackend.vue (1)

4-4: LGTM! Good optimization for conditional rendering.

The conditional rendering of the <KGrid> component based on slot presence is a sensible optimization that prevents unnecessary DOM elements when no filters are provided. This improves performance and keeps the DOM clean.

kolibri/plugins/facility/assets/src/views/users/common/DeleteUserModal.vue (1)

9-9: Good standardization of modal events.

The change from cancel to close standardizes modal closure handling, and adding the change event after successful deletion enables proper reactive updates in parent components.

Also applies to: 48-49

kolibri/plugins/facility/assets/src/views/users/UsersRootPage/__test__/UsersRootPage.spec.js (1)

5-5: Path adjustments align with directory restructuring.

The import path updates correctly reflect the new directory structure after the user management components were reorganized.

Also applies to: 11-11

kolibri/plugins/coach/assets/src/views/quizzes/CreateExamPage/index.vue (1)

121-121: Good centralization of common UI strings.

Moving the saveAndClose$ string from enhancedQuizManagementStrings to coreStrings promotes consistency and reduces duplication of common UI text across modules.

Also applies to: 172-172

packages/kolibri-common/components/userAccounts/PasswordTextbox.vue (1)

132-139: Well-implemented reset method for form state management.

The new reset() method provides a clean way to reset internal validation state without affecting the main password value. The implementation is consistent with similar methods in other user input components and properly documented.

kolibri/core/auth/api.py (1)

34-34: Properly implemented date-based filtering for user management.

The new date_joined__gt and date_joined__lt filters are correctly implemented using Django REST framework patterns. The import, filter definitions, and Meta class registration are all properly done to support the new "New Users" feature.

Also applies to: 320-327, 374-375

kolibri/plugins/facility/assets/src/composables/useUserManagement.js (3)

8-8: LGTM: Good refactor to options object pattern

The change from a single activeFacilityId parameter to an options object with destructuring is a good practice that makes the function more extensible and self-documenting.


30-30: LGTM: Proper null safety with optional chaining

The use of optional chaining (dateJoinedGt?.toISOString()) properly handles cases where dateJoinedGt might be undefined, and the pickBy function will filter out null/undefined values.


65-68: LGTM: Enhanced watcher with deep comparison

Using isEqual for deep comparison of filter arrays is appropriate and prevents unnecessary fetchUsers calls when the filter values haven't actually changed.

kolibri/plugins/facility/assets/src/modules/pluginModule.js (2)

29-29: LGTM: Consistent with route refactor

Removing the USER_CREATE_PAGE exception from the condition is correct since this page is being replaced by the side panel approach. The logic now only preserves state when navigating from USER_MGMT_PAGE to USER_EDIT_PAGE.


85-85: LGTM: Updated to use new side panel route

The route name change from USER_CREATE_PAGE to ADD_NEW_USER_SIDE_PANEL__NEW_USERS is consistent with the new constants and the side panel-based user creation flow.

packages/kolibri/uiText/commonCoreStrings.js (2)

138-142: LGTM: Well-documented translation strings

The new translation keys are properly formatted with clear messages and helpful context for translators. The saveAndClose string supports the side panel workflow, while adminsLabel and superAdminsLabel support the user role management features.


292-299: LGTM: Consistent role label strings

The adminsLabel and superAdminsLabel strings follow the established pattern and provide clear context for their usage in user role management.

kolibri/plugins/facility/assets/src/constants.js (2)

8-8: LGTM: Consistent page name constant

The NEW_USERS_PAGE constant follows the established naming pattern and supports the new "new users" functionality.


15-25: LGTM: Consistent side panel constants

The new side panel constants with __NEW_USERS suffix follow the established pattern and enable context-specific side panels for the new users page. This approach maintains consistency while providing the necessary routing flexibility.

kolibri/plugins/facility/assets/src/views/users/NewUsersPage.vue (6)

140-141: LGTM: Proper date calculation for filtering

The date threshold calculation correctly subtracts the number of days from the current date to create the filter for "new users". This works well with the dateJoinedGt parameter in the composable.


151-154: LGTM: Proper composable integration

The integration with the updated useUserManagement composable correctly passes both activeFacilityId and dateJoinedGt parameters, enabling the "new users" filtering functionality.


26-35: LGTM: Conditional rendering and proper props

The conditional rendering based on facilityUsers.length is appropriate, and the props passed to UsersTable are correctly structured. The filterPageName prop correctly uses the new __NEW_USERS variant.


79-99: LGTM: Well-designed empty state

The empty state provides a clear message and appropriate call-to-action button, following good UX practices. The styling is clean and the content is helpful for users.


36-77: LGTM: Consistent bulk action implementation

The bulk action buttons for assigning coaches, enrolling in classes, removing from classes, and deleting users all follow the same pattern and correctly use the __NEW_USERS variants of the side panel routes.


208-238: LGTM: Clean and appropriate styling

The scoped CSS provides clean, responsive styling for both the header and empty state. The use of flexbox and proper spacing creates a good user experience.

kolibri/plugins/facility/assets/src/utils.js (3)

6-11: LGTM! Import reorganization reflects new directory structure.

The imports have been properly updated to reflect the new directory structure under views/users/sidePanels/.


75-80: LGTM! User creation side panel route added correctly.

The new route for ADD_NEW_USER_SIDE_PANEL is properly integrated into the sidePanelRoutes array.


82-92: LGTM! Function signature updated to support route name suffixing.

The change from variadic arguments to an array parameter with optional suffix is a good improvement for flexibility and type safety.

kolibri/plugins/facility/assets/src/views/users/UsersRootPage/index.vue (1)

1-214: LGTM! Well-structured user management component.

The component is well-implemented with:

  • Proper use of Vue 3 composition API
  • Clean separation of concerns between template, script, and styles
  • Appropriate integration with routing and state management
  • Good use of composables for shared functionality
kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/ClassesSelect.vue (1)

1-116: LGTM! Well-implemented multi-select component.

The component correctly handles:

  • Multi-selection with "All Classes" option
  • Proper state management with computed properties
  • Clean event handling and prop validation
kolibri/plugins/facility/assets/src/routes.js (3)

15-16: LGTM! Import statements updated for new component structure.

The imports correctly reflect the new user management components and their location.


79-93: LGTM! Route configuration updated correctly.

The USER_MGMT_PAGE route now uses UsersRootPage component with proper side panel routes configuration.


96-114: LGTM! New route for recently created users.

The NEW_USERS_PAGE route is properly configured with:

  • Correct component and path
  • Facility parameter guard
  • Side panel routes with 'NEW_USERS' suffix for proper namespacing
kolibri/plugins/facility/assets/src/views/users/common/UsersTable.vue (2)

1-655: LGTM! Comprehensive user table implementation.

The component is well-implemented with:

  • Proper URL-based state management for pagination, filtering, and sorting
  • Good use of Vue 3 composition API
  • Effective debouncing for search input
  • Proper cleanup in lifecycle hooks
  • Comprehensive accessibility features

121-122: Verify empty KOptionalText usage for “Date Joined” column

I noticed that in UsersTable.vue the sixth column renders

<KOptionalText :text="''" />

– resulting in an always-empty cell. This is likely meant to show each user’s date_joined value instead. Please:

• Confirm what the tableRows computed property is supplying at index 6.
• If it’s intended to display the join date, replace the empty string with the actual field, for example:

<KOptionalText :text="row.date_joined" />

• Otherwise, clarify why an empty placeholder is needed here.

packages/kolibri-common/uiText/userKinds.js (2)

1-14: Well-structured refactoring!

The extraction of getUserKindDisplayMap as a standalone function improves code reusability and follows the DRY principle. The function correctly handles the conditional logic for coach types and learner labels.


16-34: Clean integration with existing component API

The computed property correctly uses the extracted function while maintaining the component's existing prop interface.

kolibri/plugins/facility/assets/src/views/users/sidePanels/UserCreate/index.vue (2)

320-324: Good handling of optional password requirement

The logic correctly sets the password to NOT_SPECIFIED when password input is not required based on facility configuration.


228-230: Fix type mismatch in kind initialization

The kind ref is initialized with an object from userTypeOptions, but should be initialized with just the first option to maintain consistency.

-kind.value = userTypeOptions[0]; // Reset to Learner
+kind.value = userTypeOptions[0];

Note: The current implementation is actually correct since kind is used as an object throughout the component (e.g., kind.value.value on lines 247 and 253).

packages/kolibri-common/strings/bulkUserManagementStrings.js (1)

153-160: Well-structured localization strings

All new strings follow the established patterns with clear messages and helpful context descriptions for translators. The naming conventions are consistent with the existing codebase.

Also applies to: 263-299

Copy link
Member

@nucleogenesis nucleogenesis left a comment

Choose a reason for hiding this comment

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

I've given the code a solid first pass and nothing jumped out at me. I've also tested locally and things worked as expected. Great work @AlexVelezLl -- on to full QA!

@nucleogenesis
Copy link
Member

@radinamatic

An additional note of something to focus on, particularly with a11y in mind:

Multiple-selection classes dropdown

At the bottom of the creation form, you'll see a dropdown for assigning to classes which opens to present multiple options. I've tested this w/ keyboard nav successfully, but it'd be a great place for your focus.

No need to focus on much outside the workflow itself for adding multiple users. See the notes below about existing known issues

__

@AlexVelezLl I found two items of note after I submitted my review (cc @radinamatic so you're aware)

  1. I just tested this after I submitted my review 😅 - can you put the "Are you sure?" modal when trying to close the side panel when the user could lose their work in the form please.
  2. There is no value present in the "Creation date" column (although the search works)

@radinamatic radinamatic requested review from pcenov and radinamatic July 9, 2025 13:07
@radinamatic
Copy link
Member

On the first cursory QA pass while testing in both Firefox and Chrome on Ubuntu, all workflows seem to be working as specified! 👍🏽

Two questions/comments:

  1. Found mildly confusing that the button Save and close did nothing if the form was empty. Yes, there was an error notification in the first field, but from the user perspective it seems like the button is not working. I would suggest to check that all form fields are empty (that is a check that form is not partially filled), and in that case pressing the Save and close should simply close the form, same as the Close ✖️ button in the upper right corner. cc @jtamiace
  2. How long do the newly added users remain in the New users page? I created several users in one browser, than opened a different browser to repeat the process and previously created users were still there. If that is a transitory container for the new users, seeing there previously created users from another session (different browsers might mean different admins adding users, for example), is also confusing... 🤔
bum-new-user.mp4

@nucleogenesis
Copy link
Member

Thanks for the review @radinamatic - for your note (1) we could change the text on that button from "Close" to "Save & Close" when the form has been changed (and back when it's an empty form)? We won't block this PR on it but we can follow-up on this workflow.

As for (2) it's just a page listing the users added in the last 30 days. Perhaps text explaining that to the user here could help?

@pcenov
Copy link
Member

pcenov commented Jul 10, 2025

Hi @AlexVelezLl some additional observations:

  1. At Facility > Classes > Class we have 2 options - to assign coaches to the class or/and to enroll learners. But when I am creating a new user at the bottom of the form there is a drop-down called "Enroll in class" with a checkbox called "Assign to all classes".
    This can be confusing because when I was creating a Coach user, I was expecting to actually assign that coach to the class while the coach got enrolled as a learner. So the question is - are we going to support assigning coaches to a class through this form? If not, then it's probably just a matter of changing the text "Assign to all classes" to "Enroll in all classes".
assign.to.all.classes.mp4
  1. In your video recording I can see the user type badges displayed next to the name of the non-learner users (Super admin, Admin, Coach and Facility coach) but when I create a new non-learner user they are shown in the table without any user type badges:

2025-07-10_11-15-31

  1. The classes in "Enroll in a class" are not sorted in any way which makes it difficult to find the appropriate class:

2025-07-10_11-33-15

  1. If I have selected multiple classes then the list should get truncated otherwise it goes out of the field:

2025-07-10_11-39-20

  1. If I try to enroll a user to an already deleted class I am getting an unhandled error:
2025-07-10_11-47-27.mp4
  1. In mobile view the buttons are centered and should be discussed whether that's the expected layout:

2025-07-10_11-50-44

@AlexVelezLl
Copy link
Member Author

Thanks @pcenov!

  1. I have fixed this by changing the label and the action when a learner is selected vs when its not. If we are creating a learner it will display enroll to a class and will enroll the learner on save, and when creating a coah or admin the label will say assign to a class and will assign the coach to the class.
  2. Just fixed this!
  3. I think this can be a follow up issue. I tried to do this, but it seems some other tweaks will be needed.
  4. I already opened an issue to solve this in KDS: [KSelect] Overflowed display value kolibri-design-system#1066. Once we fix this there, it will automatically be fixed here.
  5. This can also be in a follow up issue, since we need to make some decissions around how we handle these kind of errors.
  6. I have aligned them to the right for now, but would like to hear @tomiwaoLE's or @jtamiace's thoughts.
image

Copy link
Member

@nucleogenesis nucleogenesis left a comment

Choose a reason for hiding this comment

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

Code changes all look good to me - I tested things out locally and the issues @pcenov mentioned appear to be resolved 👍

Copy link
Member

@pcenov pcenov left a comment

Choose a reason for hiding this comment

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

Excellent @AlexVelezLl - I have filed 2 follow-up issues as suggested and confirm that the ones that you mention as fixed are indeed fixed now.

@AlexVelezLl AlexVelezLl force-pushed the improved-user-creation branch from b910dc3 to 0631dc8 Compare July 11, 2025 19:06
@nucleogenesis
Copy link
Member

Just noting I smoke tested after the rebase and confirmed that bulk enrollment still works as expected along w/ user creation and it was really nice doing both at once right now :)

Good to merge once the checks are all green

@nucleogenesis nucleogenesis merged commit f071a1d into learningequality:develop Jul 11, 2025
51 checks passed
@AlexVelezLl AlexVelezLl deleted the improved-user-creation branch August 5, 2025 21:03
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
APP: Coach Re: Coach App (lessons, quizzes, groups, reports, etc.) APP: Facility Re: Facility App (user/class management, facility settings, csv import/export, etc.) DEV: backend Python, databases, networking, filesystem... DEV: frontend SIZE: very large
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Bulk User Management: Improved user creation
4 participants