diff --git a/.github/workflows/forms-flow-api-ci.yml b/.github/workflows/forms-flow-api-ci.yml index 583a61d066..5a8d271fa4 100644 --- a/.github/workflows/forms-flow-api-ci.yml +++ b/.github/workflows/forms-flow-api-ci.yml @@ -35,7 +35,7 @@ jobs: strategy: matrix: - python-version: [3.12.1] + python-version: [3.12.2] steps: - uses: actions/checkout@v2 diff --git a/forms-flow-api/src/formsflow_api/resources/user.py b/forms-flow-api/src/formsflow_api/resources/user.py index 2fbe10ea9d..cf43b66fb2 100644 --- a/forms-flow-api/src/formsflow_api/resources/user.py +++ b/forms-flow-api/src/formsflow_api/resources/user.py @@ -1,4 +1,5 @@ """Resource to call Keycloak Service API calls and filter responses.""" + from http import HTTPStatus from flask import current_app, g, request @@ -165,7 +166,7 @@ def get(): # pylint: disable=too-many-locals kc_admin = KeycloakFactory.get_instance() if group_name: (users_list, users_count) = kc_admin.get_users( - page_no, limit, role, group_name, count + page_no, limit, role, group_name, count, search ) user_service = UserService() response = { diff --git a/forms-flow-api/src/formsflow_api/services/factory/keycloak_admin.py b/forms-flow-api/src/formsflow_api/services/factory/keycloak_admin.py index 819312946d..0b33b3b2cf 100644 --- a/forms-flow-api/src/formsflow_api/services/factory/keycloak_admin.py +++ b/forms-flow-api/src/formsflow_api/services/factory/keycloak_admin.py @@ -24,7 +24,13 @@ def update_group(self, group_id: str, data: Dict): @abstractmethod def get_users( # pylint: disable-msg=too-many-arguments - self, page_no: int, limit: int, role: bool, group_name: str, count: bool + self, + page_no: int, + limit: int, + role: bool, + group_name: str, + count: bool, + search: str, ): """Get users.""" raise NotImplementedError("Method not implemented") diff --git a/forms-flow-api/src/formsflow_api/services/factory/keycloak_client_service.py b/forms-flow-api/src/formsflow_api/services/factory/keycloak_client_service.py index 5cfc0f6f09..f12b4c3d91 100644 --- a/forms-flow-api/src/formsflow_api/services/factory/keycloak_client_service.py +++ b/forms-flow-api/src/formsflow_api/services/factory/keycloak_client_service.py @@ -1,4 +1,5 @@ """Keycloak Admin implementation for client related operations.""" + from typing import Dict, List from flask import current_app @@ -6,7 +7,7 @@ from formsflow_api_utils.utils.user_context import UserContext, user_context from formsflow_api.constants import BusinessErrorCode -from formsflow_api.services import KeycloakAdminAPIService +from formsflow_api.services import KeycloakAdminAPIService, UserService from .keycloak_admin import KeycloakAdmin @@ -17,6 +18,7 @@ class KeycloakClientService(KeycloakAdmin): def __init__(self): """Initialize client.""" self.client = KeycloakAdminAPIService() + self.user_service = UserService() def __populate_user_roles(self, user_list: List) -> List: """Collects roles for a user list and populates the role attribute.""" @@ -26,13 +28,6 @@ def __populate_user_roles(self, user_list: List) -> List: ) return user_list - # Keycloak doesn't provide count API for this one - def __get_users_count(self, client_id: str, group_name: str): - """Returns user list count under a group.""" - url_path = f"clients/{client_id}/roles/{group_name}/users" - user_list = self.client.get_request(url_path) - return len(user_list) - def get_analytics_groups(self, page_no: int, limit: int): """Get analytics roles.""" return self.client.get_analytics_roles(page_no, limit) @@ -47,7 +42,13 @@ def get_group(self, group_id: str): return response def get_users( # pylint: disable-msg=too-many-arguments - self, page_no: int, limit: int, role: bool, group_name: str, count: bool + self, + page_no: int, + limit: int, + role: bool, + group_name: str, + count: bool, + search: str, ): """Get users under this client with formsflow-reviewer role.""" # group_name was hardcoded before as `formsflow-reviewer` make sure @@ -57,10 +58,12 @@ def get_users( # pylint: disable-msg=too-many-arguments ) client_id = self.client.get_client_id() url = f"clients/{client_id}/roles/{group_name}/users" - if page_no and limit: - url += f"?first={(page_no - 1) * limit}&max={limit}" users_list = self.client.get_request(url) - users_count = self.__get_users_count(client_id, group_name) if count else None + if search: + users_list = self.user_service.user_search(search, users_list) + users_count = len(users_list) if count else None + if page_no and limit: + users_list = self.user_service.paginate(users_list, page_no, limit) if role: users_list = self.__populate_user_roles(users_list) return (users_list, users_count) @@ -149,19 +152,8 @@ def get_tenant_users( url = f"users?q=tenantKey:{tenant_key}" current_app.logger.debug("Getting tenant users...") result = self.client.get_request(url) - search_fields = ["username", "firstName", "lastName", "email"] if search: - result = [ - item - for item in result - if any(search in item[key] for key in search_fields) - ] + result = self.user_service.user_search(search, result) count = len(result) if count else None - result = self.paginate(result, page_no, limit) + result = self.user_service.paginate(result, page_no, limit) return result, count - - def paginate(self, data, page_number, page_size): - """Paginate data.""" - start_index = (page_number - 1) * page_size - end_index = start_index + page_size - return data[start_index:end_index] diff --git a/forms-flow-api/src/formsflow_api/services/factory/keycloak_factory.py b/forms-flow-api/src/formsflow_api/services/factory/keycloak_factory.py index aa8e9d0921..9d6fe25c66 100644 --- a/forms-flow-api/src/formsflow_api/services/factory/keycloak_factory.py +++ b/forms-flow-api/src/formsflow_api/services/factory/keycloak_factory.py @@ -1,4 +1,5 @@ """Keycloak factory implementation.""" + from flask import current_app from .keycloak_admin import KeycloakAdmin diff --git a/forms-flow-api/src/formsflow_api/services/factory/keycloak_group_service.py b/forms-flow-api/src/formsflow_api/services/factory/keycloak_group_service.py index 6b995aa54b..afea75a1a2 100644 --- a/forms-flow-api/src/formsflow_api/services/factory/keycloak_group_service.py +++ b/forms-flow-api/src/formsflow_api/services/factory/keycloak_group_service.py @@ -1,4 +1,5 @@ """Keycloak implementation for keycloak group related operations.""" + from typing import Dict, List import requests @@ -6,7 +7,7 @@ from formsflow_api_utils.exceptions import BusinessException from formsflow_api.constants import BusinessErrorCode -from formsflow_api.services import KeycloakAdminAPIService +from formsflow_api.services import KeycloakAdminAPIService, UserService from .keycloak_admin import KeycloakAdmin @@ -17,6 +18,7 @@ class KeycloakGroupService(KeycloakAdmin): def __init__(self): """Initialize client.""" self.client = KeycloakAdminAPIService() + self.user_service = UserService() def __populate_user_groups(self, user_list: List) -> List: """Collect groups for a user list and populate the role attribute.""" @@ -26,13 +28,6 @@ def __populate_user_groups(self, user_list: List) -> List: ) return user_list - # Keycloak doesn't provide count API for this one - def __get_users_count(self, group_id: str): - """Returns user list count under a group.""" - url_path = f"groups/{group_id}/members?briefRepresentation=true" - user_list = self.client.get_request(url_path) - return len(user_list) - def get_analytics_groups(self, page_no: int, limit: int): """Get analytics groups.""" return self.client.get_analytics_groups(page_no, limit) @@ -43,7 +38,13 @@ def get_group(self, group_id: str): return self.format_response(response) def get_users( # pylint: disable-msg=too-many-arguments - self, page_no: int, limit: int, role: bool, group_name: str, count: bool + self, + page_no: int, + limit: int, + role: bool, + group_name: str, + count: bool, + search: str, ): """Get users under formsflow-reviewer group.""" user_list: List[Dict] = [] @@ -54,10 +55,12 @@ def get_users( # pylint: disable-msg=too-many-arguments group = self.client.get_request(url_path=f"group-by-path/{group_name}") group_id = group.get("id") url_path = f"groups/{group_id}/members" - if page_no and limit: - url_path += f"?first={(page_no - 1) * limit}&max={limit}" user_list = self.client.get_request(url_path) - user_count = self.__get_users_count(group_id) if count else None + if search: + user_list = self.user_service.user_search(search, user_list) + user_count = len(user_list) if count else None + if page_no and limit: + user_list = self.user_service.paginate(user_list, page_no, limit) if role: user_list = self.__populate_user_groups(user_list) return (user_list, user_count) diff --git a/forms-flow-api/src/formsflow_api/services/filter.py b/forms-flow-api/src/formsflow_api/services/filter.py index ba2a2cfa17..c51b2d604e 100644 --- a/forms-flow-api/src/formsflow_api/services/filter.py +++ b/forms-flow-api/src/formsflow_api/services/filter.py @@ -74,7 +74,7 @@ def get_user_filters(**kwargs): filter_obj = Filter( name="All Tasks", variables=[ - {"name": "applicationId", "label": "Application Id"}, + {"name": "applicationId", "label": "Submission ID"}, {"name": "formName", "label": "Form Name"}, ], status="active", @@ -105,7 +105,7 @@ def get_user_filters(**kwargs): ) filter_data = filter_schema.dump(filters, many=True) default_variables = [ - {"name": "applicationId", "label": "Application Id"}, + {"name": "applicationId", "label": "Submission ID"}, {"name": "formName", "label": "Form Name"}, ] # User who created the filter or admin have edit permission. diff --git a/forms-flow-api/src/formsflow_api/services/user.py b/forms-flow-api/src/formsflow_api/services/user.py index 7817766970..96601bd0b0 100644 --- a/forms-flow-api/src/formsflow_api/services/user.py +++ b/forms-flow-api/src/formsflow_api/services/user.py @@ -56,3 +56,31 @@ def get_users(self, query_params, users_list): for user in users_list: response.append(UserService._as_dict(user)) return response + + def user_search(self, search, user_list): + """ + Replicates Keycloak users search functionality. + + Searches for a given string within usernames, + first names, last names, or email addresses. + + Parameters: + - search (str): The string to search for within user attributes. + - user_list (list): A list of users to search within. + + Returns: + - list: A filtered list of users matching the search criteria. + """ + search_fields = ["username", "firstName", "lastName", "email"] + result = [ + item + for item in user_list + if any(search in item.get(key, "") for key in search_fields) + ] + return result + + def paginate(self, data, page_number, page_size): + """Paginate the provided data.""" + start_index = (page_number - 1) * page_size + end_index = start_index + page_size + return data[start_index:end_index] diff --git a/forms-flow-web/src/components/ServiceFlow/list/search/TaskFilterViewComponent.js b/forms-flow-web/src/components/ServiceFlow/list/search/TaskFilterViewComponent.js index 47a4188c68..e645681bd0 100644 --- a/forms-flow-web/src/components/ServiceFlow/list/search/TaskFilterViewComponent.js +++ b/forms-flow-web/src/components/ServiceFlow/list/search/TaskFilterViewComponent.js @@ -276,7 +276,7 @@ const TaskFilterViewComponent = React.memo( rows[rows.length - 1].push(