diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 86cb7c1cc58..f6639af5b5b 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -13342,6 +13342,12 @@ export interface components { * @description Payload schema for creating a group. */ GroupCreatePayload: { + /** + * auto-create role + * @description If true, create a new role with the same name as the group and associate it. + * @default false + */ + auto_create_role: boolean; /** name of the group */ name: string; /** @@ -40889,7 +40895,14 @@ export interface operations { }; index_api_roles_get: { parameters: { - query?: never; + query?: { + /** @description Search by role name or user email (for private roles). */ + search?: string | null; + /** @description The maximum number of roles to return. */ + limit?: number | null; + /** @description Number of roles to skip. */ + offset?: number | null; + }; header?: { /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ "run-as"?: string | null; @@ -43324,6 +43337,10 @@ export interface operations { f_name?: string | null; /** @description Filter on username OR email */ f_any?: string | null; + /** @description Maximum number of users to return. */ + limit?: number | null; + /** @description Number of users to skip. */ + offset?: number | null; }; header?: { /** @description The user ID that will be used to effectively make this API call. Only admins and designated users can make API calls on behalf of other users. */ diff --git a/client/src/components/admin/GroupForm.vue b/client/src/components/admin/GroupForm.vue new file mode 100644 index 00000000000..9d2785c3a7d --- /dev/null +++ b/client/src/components/admin/GroupForm.vue @@ -0,0 +1,244 @@ + + + diff --git a/client/src/entry/analysis/routes/admin-routes.js b/client/src/entry/analysis/routes/admin-routes.js index 999ef0c81b3..e87fa3ce7b0 100644 --- a/client/src/entry/analysis/routes/admin-routes.js +++ b/client/src/entry/analysis/routes/admin-routes.js @@ -16,6 +16,7 @@ import DataTypes from "@/components/admin/DataTypes.vue"; import ToolboxDependencies from "@/components/admin/Dependencies/Landing.vue"; import DisplayApplications from "@/components/admin/DisplayApplications.vue"; import ErrorStack from "@/components/admin/ErrorStack.vue"; +import GroupForm from "@/components/admin/GroupForm.vue"; import JobsList from "@/components/admin/JobsList.vue"; import BroadcastForm from "@/components/admin/Notifications/BroadcastForm.vue"; import NotificationForm from "@/components/admin/Notifications/NotificationForm.vue"; @@ -206,10 +207,9 @@ export default [ }, { path: "form/manage_users_and_roles_for_group", - component: FormGeneric, + component: GroupForm, props: (route) => ({ - url: `/admin/manage_users_and_roles_for_group?id=${route.query.id}`, - redirect: "/admin/groups", + groupId: route.query.id, }), }, { @@ -226,11 +226,7 @@ export default [ }, { path: "form/create_group", - component: FormGeneric, - props: { - url: "/admin/create_group", - redirect: "/admin/groups", - }, + component: GroupForm, }, { path: "form/create_quota", diff --git a/client/src/utils/navigation/navigation.yml b/client/src/utils/navigation/navigation.yml index 666bc98d354..56a65f3ae65 100644 --- a/client/src/utils/navigation/navigation.yml +++ b/client/src/utils/navigation/navigation.yml @@ -1247,7 +1247,7 @@ admin: registration_form: 'form#registration' groups_grid: '#groups-grid' roles_grid: '#roles-grid' - groups_create_view: '#submit:not([aria-disabled])' + groups_create_view: '#admin-group-submit' libraries: diff --git a/lib/galaxy/managers/groups.py b/lib/galaxy/managers/groups.py index e204a17a89d..413db99c119 100644 --- a/lib/galaxy/managers/groups.py +++ b/lib/galaxy/managers/groups.py @@ -52,8 +52,19 @@ class GroupsManager: group = model.Group(name=name) sa_session.add(group) + role_ids = list(payload.role_ids) + if payload.auto_create_role: + existing_role = sa_session.scalars(select(model.Role).where(model.Role.name == name).limit(1)).first() + if existing_role: + raise Conflict(f"A role with name '{name}' already exists") + role = model.Role(name=name, description=f"Role for group {name}") + sa_session.add(role) + sa_session.flush() + gra = model.GroupRoleAssociation(group, role) + sa_session.add(gra) + trans.app.security_agent.set_group_user_and_role_associations( - group, user_ids=payload.user_ids, role_ids=payload.role_ids + group, user_ids=payload.user_ids, role_ids=role_ids ) sa_session.commit() diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index 99d6bc1441e..53d2c5c040f 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -66,8 +66,21 @@ class RoleManager(base.ModelManager[model.Role]): return role - def list_displayable_roles(self, trans: ProvidesUserContext) -> list[Role]: - return get_displayable_roles(trans.sa_session, trans.user, trans.user_is_admin, trans.app.security_agent) + def list_displayable_roles( + self, + trans: ProvidesUserContext, + search: str | None = None, + limit: int | None = None, + offset: int = 0, + ) -> list[Role]: + return get_displayable_roles( + trans.sa_session, + trans.user, + trans.user_is_admin, + search=search, + limit=limit, + offset=offset, + ) def create_role(self, trans: ProvidesUserContext, role_definition_model: RoleDefinitionModel) -> model.Role: name = role_definition_model.name diff --git a/lib/galaxy/model/db/role.py b/lib/galaxy/model/db/role.py index 16cd210c06c..6071da26bd0 100644 --- a/lib/galaxy/model/db/role.py +++ b/lib/galaxy/model/db/role.py @@ -1,6 +1,8 @@ from sqlalchemy import ( and_, false, + func, + or_, select, ) @@ -44,13 +46,44 @@ def get_roles_by_ids(session: galaxy_scoped_session, role_ids): return session.scalars(stmt).all() -def get_displayable_roles(session, trans_user, user_is_admin, security_agent): - roles = [] +def get_displayable_roles( + session, + trans_user, + user_is_admin, + search: str | None = None, + limit: int | None = None, + offset: int = 0, +): stmt = select(Role).where(Role.deleted == false()) - for role in session.scalars(stmt): - if user_is_admin or security_agent.ok_to_display(trans_user, role): - roles.append(role) - return roles + if not user_is_admin: + if trans_user: + # Non-admin users see: all non-private/non-sharing roles, + # plus their own private role and their own sharing roles. + user_role_ids = select(UserRoleAssociation.role_id).where(UserRoleAssociation.user_id == trans_user.id) + stmt = stmt.where( + or_( + ~Role.type.in_((Role.types.PRIVATE, Role.types.SHARING)), + and_(Role.type.in_((Role.types.PRIVATE, Role.types.SHARING)), Role.id.in_(user_role_ids)), + ) + ) + else: + # Anonymous: exclude private and sharing roles entirely + stmt = stmt.where(~Role.type.in_((Role.types.PRIVATE, Role.types.SHARING))) + if search: + # LEFT JOIN to User via UserRoleAssociation for private roles only, + # so coalesce(User.email, Role.name) gives the displayed name. + stmt = stmt.outerjoin( + UserRoleAssociation, + and_(UserRoleAssociation.role_id == Role.id, Role.type == Role.types.PRIVATE), + ).outerjoin(User, UserRoleAssociation.user_id == User.id) + displayed_name = func.coalesce(User.email, Role.name) + stmt = stmt.where(displayed_name.ilike(f"%{search}%")) + stmt = stmt.order_by(Role.id) + if offset: + stmt = stmt.offset(offset) + if limit is not None: + stmt = stmt.limit(limit) + return session.scalars(stmt).all() def get_private_role_user_emails_dict(session, role_ids: set[int] | None = None) -> dict[int, str]: diff --git a/lib/galaxy/model/db/user.py b/lib/galaxy/model/db/user.py index 16ce1dff810..39a6ddff644 100644 --- a/lib/galaxy/model/db/user.py +++ b/lib/galaxy/model/db/user.py @@ -57,6 +57,8 @@ def get_users_for_index( is_admin: bool = False, expose_user_email: bool = False, expose_user_name: bool = False, + limit: int | None = None, + offset: int = 0, ) -> Sequence[User]: stmt = select(User) if f_email and (is_admin or expose_user_email): @@ -77,6 +79,11 @@ def get_users_for_index( stmt = stmt.where(User.deleted == true()) else: stmt = stmt.where(User.deleted == false()) + stmt = stmt.order_by(User.email) + if offset: + stmt = stmt.offset(offset) + if limit is not None: + stmt = stmt.limit(limit) return session.scalars(stmt).all() diff --git a/lib/galaxy/schema/groups.py b/lib/galaxy/schema/groups.py index 3998096f81b..0cbf11ca35d 100644 --- a/lib/galaxy/schema/groups.py +++ b/lib/galaxy/schema/groups.py @@ -69,6 +69,11 @@ class GroupCreatePayload(Model): [], title="role IDs", ) + auto_create_role: bool = Field( + False, + title="auto-create role", + description="If true, create a new role with the same name as the group and associate it.", + ) @partial_model() diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index 499890f3278..59cde92f88b 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -3,8 +3,12 @@ API operations on Role objects. """ import logging +from typing import Optional -from fastapi import Body +from fastapi import ( + Body, + Query, +) from galaxy.managers.context import ProvidesUserContext from galaxy.schema.schema import ( @@ -22,6 +26,24 @@ from galaxy.webapps.galaxy.services.roles import RolesService log = logging.getLogger(__name__) +SearchRolesQueryParam: Optional[str] = Query( + default=None, + title="Search filter", + description="Search by role name or user email (for private roles).", +) +LimitRolesQueryParam: Optional[int] = Query( + default=None, + ge=1, + title="Limit", + description="The maximum number of roles to return.", +) +OffsetRolesQueryParam: Optional[int] = Query( + default=0, + ge=0, + title="Offset", + description="Number of roles to skip.", +) + # Empty paths (e.g. /api/roles) only work if a prefix is defined right here. # https://github.com/tiangolo/fastapi/pull/415/files @@ -33,8 +55,14 @@ class FastAPIRoles: service: RolesService = depends(RolesService) @router.get("/api/roles") - def index(self, trans: ProvidesUserContext = DependsOnTrans) -> RoleListResponse: - return self.service.get_index(trans=trans) + def index( + self, + trans: ProvidesUserContext = DependsOnTrans, + search: Optional[str] = SearchRolesQueryParam, + limit: Optional[int] = LimitRolesQueryParam, + offset: Optional[int] = OffsetRolesQueryParam, + ) -> RoleListResponse: + return self.service.get_index(trans=trans, search=search, limit=limit, offset=offset) @router.get("/api/roles/{id}") def show(self, id: RoleIDPathParam, trans: ProvidesUserContext = DependsOnTrans) -> RoleModelResponse: diff --git a/lib/galaxy/webapps/galaxy/api/users.py b/lib/galaxy/webapps/galaxy/api/users.py index e1c5ab39486..b256f277197 100644 --- a/lib/galaxy/webapps/galaxy/api/users.py +++ b/lib/galaxy/webapps/galaxy/api/users.py @@ -640,8 +640,14 @@ class FastAPIUsers: f_email: Optional[str] = FilterEmailQueryParam, f_name: Optional[str] = FilterNameQueryParam, f_any: Optional[str] = FilterAnyQueryParam, + limit: Optional[int] = Query( + default=None, ge=1, title="Limit", description="Maximum number of users to return." + ), + offset: Optional[int] = Query(default=0, ge=0, title="Offset", description="Number of users to skip."), ) -> list[MaybeLimitedUserModel]: - return self.service.get_index(trans=trans, deleted=deleted, f_email=f_email, f_name=f_name, f_any=f_any) + return self.service.get_index( + trans=trans, deleted=deleted, f_email=f_email, f_name=f_name, f_any=f_any, limit=limit, offset=offset + ) @router.get( "/api/users/{user_id}", diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index fad077a308e..77cf315fbaa 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -17,7 +17,6 @@ from galaxy.exceptions import ( RequestParameterInvalidException, ) from galaxy.managers.quotas import QuotaManager -from galaxy.model.db.role import get_private_role_user_emails_dict from galaxy.model.index_filter_util import ( raw_text_column_filter, text_column_filter, @@ -763,153 +762,6 @@ class AdminGalaxy(controller.BaseUIController): trans.sa_session.commit() return {"message": f"Group '{old_name}' has been renamed to '{new_name}'."} - @web.legacy_expose_api - @web.require_admin - def manage_users_and_roles_for_group(self, trans, payload=None, **kwd): - group_id = kwd.get("id") - if not group_id: - return self.message_exception(trans, f"Invalid group id ({str(group_id)}) received") - group = get_group(trans, group_id) - if trans.request.method == "GET": - in_users = [] - all_users = [] - in_roles = [] - all_roles = [] - for user in ( - trans.sa_session.query(trans.app.model.User) - .filter(trans.app.model.User.table.c.deleted == false()) - .order_by(trans.app.model.User.table.c.email) - ): - if user in [x.user for x in group.users]: - in_users.append(trans.security.encode_id(user.id)) - all_users.append((user.email, trans.security.encode_id(user.id))) - - private_role_emails = get_private_role_user_emails_dict(trans.sa_session) - - for role in ( - trans.sa_session.query(trans.app.model.Role) - .filter(trans.app.model.Role.deleted == false()) - .order_by(trans.app.model.Role.name) - ): - if role in [x.role for x in group.roles]: - in_roles.append(trans.security.encode_id(role.id)) - displayed_name = private_role_emails.get(role.id, role.name) - all_roles.append((displayed_name, trans.security.encode_id(role.id))) - return { - "title": f"Group '{group.name}'", - "message": f"Group '{group.name}' is currently associated with {len(in_users)} user(s) and {len(in_roles)} role(s).", - "status": "info", - "inputs": [ - build_select_input("in_roles", "Roles", all_roles, in_roles), - build_select_input("in_users", "Users", all_users, in_users), - ], - } - else: - user_ids = [trans.security.decode_id(id) for id in util.listify(payload.get("in_users"))] - role_ids = [trans.security.decode_id(id) for id in util.listify(payload.get("in_roles"))] - try: - trans.app.security_agent.set_group_user_and_role_associations( - group, user_ids=user_ids, role_ids=role_ids - ) - return { - "message": f"Group '{group.name}' has been updated with {len(user_ids)} associated users and {len(role_ids)} associated roles." - } - except RequestParameterInvalidException: - return self.message_exception(trans, "One or more invalid user/role id has been provided.") - - @web.legacy_expose_api - @web.require_admin - def create_group(self, trans, payload=None, **kwd): - if trans.request.method == "GET": - all_users = [] - all_roles = [] - for user in ( - trans.sa_session.query(trans.app.model.User) - .filter(trans.app.model.User.table.c.deleted == false()) - .order_by(trans.app.model.User.table.c.email) - ): - all_users.append((user.email, trans.security.encode_id(user.id))) - - private_role_emails = get_private_role_user_emails_dict(trans.sa_session) - - for role in ( - trans.sa_session.query(trans.app.model.Role) - .filter(trans.app.model.Role.deleted == false()) - .order_by(trans.app.model.Role.name) - ): - displayed_name = private_role_emails.get(role.id, role.name) - all_roles.append((displayed_name, trans.security.encode_id(role.id))) - return { - "title": "Create Group", - "title_id": "create-group", - "inputs": [ - {"name": "name", "label": "Name"}, - build_select_input("in_roles", "Roles", all_roles, []), - build_select_input("in_users", "Users", all_users, []), - { - "name": "auto_create", - "label": "Create a new role of the same name for this group:", - "type": "boolean", - "optional": True, - }, - ], - } - else: - name = util.restore_text(payload.get("name", "")) - auto_create_checked = payload.get("auto_create") - in_users = [ - trans.sa_session.query(trans.app.model.User).get(trans.security.decode_id(x)) - for x in util.listify(payload.get("in_users")) - ] - in_roles = [ - trans.sa_session.query(trans.app.model.Role).get(trans.security.decode_id(x)) - for x in util.listify(payload.get("in_roles")) - ] - if not name: - return self.message_exception(trans, "Enter a valid name.") - elif trans.sa_session.query(trans.app.model.Group).filter(trans.app.model.Group.name == name).first(): - return self.message_exception( - trans, - "Group names must be unique and a group with that name already exists, so choose another name.", - ) - elif None in in_users or None in in_roles: - return self.message_exception(trans, "One or more invalid user/role id has been provided.") - else: - # Create the role - group = trans.app.model.Group(name=name) - trans.sa_session.add(group) - # Create the UserRoleAssociations - for user in in_users: - uga = trans.app.model.UserGroupAssociation(user, group) - trans.sa_session.add(uga) - # Create the GroupRoleAssociations - for role in in_roles: - gra = trans.app.model.GroupRoleAssociation(group, role) - trans.sa_session.add(gra) - if auto_create_checked: - # Check if role with same name already exists - if trans.sa_session.query(trans.app.model.Role).filter(trans.app.model.Role.name == name).first(): - return self.message_exception( - trans, - "A role with that name already exists, so choose another name or disable role creation.", - ) - # Create the role - role = trans.app.model.Role(name=name, description=f"Role for group {name}") - trans.sa_session.add(role) - # Associate the group with the role - gra = trans.model.GroupRoleAssociation(group, role) - trans.sa_session.add(gra) - num_in_roles = len(in_roles) + 1 - else: - num_in_roles = len(in_roles) - trans.sa_session.commit() - message = f"Group '{group.name}' has been created with {len(in_users)} associated users and {num_in_roles} associated roles." - if auto_create_checked: - message += ( - "One of the roles associated with this group is the newly created role with the same name." - ) - return {"message": message} - @web.expose @web.require_admin def create_new_user(self, trans, **kwd): diff --git a/lib/galaxy/webapps/galaxy/services/roles.py b/lib/galaxy/webapps/galaxy/services/roles.py index 666808c85f1..93bba884fbc 100644 --- a/lib/galaxy/webapps/galaxy/services/roles.py +++ b/lib/galaxy/webapps/galaxy/services/roles.py @@ -38,14 +38,17 @@ class RolesService(ServiceBase): super().__init__(security) self.role_manager = role_manager - def get_index(self, trans: ProvidesUserContext) -> RoleListResponse: - roles = self.role_manager.list_displayable_roles(trans) + def get_index( + self, + trans: ProvidesUserContext, + search: Optional[str] = None, + limit: Optional[int] = None, + offset: Optional[int] = 0, + ) -> RoleListResponse: + roles = self.role_manager.list_displayable_roles(trans, search=search, limit=limit, offset=offset or 0) role_ids = {r.id for r in roles} private_role_emails = get_private_role_user_emails_dict(trans.sa_session, role_ids=role_ids) - data = [] - for role in roles: - displayed_name = private_role_emails.get(role.id, role.name) - data.append(role_to_model(role, displayed_name)) + data = [role_to_model(role, private_role_emails.get(role.id, role.name)) for role in roles] return RoleListResponse(root=data) def show(self, trans: ProvidesUserContext, id: DecodedDatabaseIdField) -> RoleModelResponse: diff --git a/lib/galaxy/webapps/galaxy/services/users.py b/lib/galaxy/webapps/galaxy/services/users.py index a12271f3dd5..39aa35c03a4 100644 --- a/lib/galaxy/webapps/galaxy/services/users.py +++ b/lib/galaxy/webapps/galaxy/services/users.py @@ -204,6 +204,8 @@ class UsersService(ServiceBase): f_email: Optional[str], f_name: Optional[str], f_any: Optional[str], + limit: Optional[int] = None, + offset: Optional[int] = 0, ) -> list[MaybeLimitedUserModel]: # never give any info to non-authenticated users if not trans.user and not trans.user_is_bootstrap_admin: @@ -234,6 +236,8 @@ class UsersService(ServiceBase): trans.user_is_admin, trans.app.config.expose_user_email, trans.app.config.expose_user_name, + limit=limit, + offset=offset or 0, ) rval: list[MaybeLimitedUserModel] = [] for user in users: diff --git a/lib/galaxy_test/api/test_groups.py b/lib/galaxy_test/api/test_groups.py index 11f4c33cb85..df36595e2c0 100644 --- a/lib/galaxy_test/api/test_groups.py +++ b/lib/galaxy_test/api/test_groups.py @@ -31,6 +31,31 @@ class TestGroupsApi(ApiTestCase): response = self._post("groups", payload, admin=True, json=True) self._assert_status_code_is(response, 400) + def test_create_with_auto_create_role(self): + name = f"auto-role-group-{self.dataset_populator.get_random_name()}" + payload = self._build_valid_group_payload(name) + payload["auto_create_role"] = True + response = self._post("groups", payload, admin=True, json=True) + self._assert_status_code_is(response, 200) + group = response.json()[0] + self._assert_valid_group(group) + # Verify role with same name was created and associated with the group + roles = self._get(f"groups/{group['id']}/roles", admin=True).json() + role_names = [r["name"] for r in roles] + assert name in role_names + + def test_create_with_auto_create_role_conflict(self): + """Auto-create role should fail if a role with the same name already exists.""" + name = f"auto-role-conflict-{self.dataset_populator.get_random_name()}" + # First create a role with this name + role_payload = {"name": name, "description": "A test role.", "user_ids": [self.dataset_populator.user_id()]} + self._post("roles", role_payload, admin=True, json=True) + # Now try to create a group with auto_create_role - should conflict + payload = self._build_valid_group_payload(name) + payload["auto_create_role"] = True + response = self._post("groups", payload, admin=True, json=True) + self._assert_status_code_is(response, 409) + def test_create_duplicated_name_raises_409(self): payload = self._build_valid_group_payload() response = self._post("groups", payload, admin=True, json=True) diff --git a/lib/galaxy_test/api/test_roles.py b/lib/galaxy_test/api/test_roles.py index 1f8638ad382..33d1e6b3a69 100644 --- a/lib/galaxy_test/api/test_roles.py +++ b/lib/galaxy_test/api/test_roles.py @@ -130,6 +130,36 @@ class TestRolesApi(ApiTestCase): response = self._get(f"roles/{different_user_role_id}") assert_status_code_is(response, 404) + @requires_admin + def test_list_with_pagination(self): + self._create_role() + self._create_role() + # Test limit + response = self._get("roles", data={"limit": 1}, admin=True) + assert_status_code_is(response, 200) + data = response.json() + assert len(data) == 1 + # Test limit + offset returns different result + response_offset = self._get("roles", data={"limit": 1, "offset": 1}, admin=True) + assert_status_code_is(response_offset, 200) + data_offset = response_offset.json() + assert len(data_offset) == 1 + assert data[0]["id"] != data_offset[0]["id"] + + @requires_admin + def test_list_with_search(self): + unique = self.dataset_populator.get_random_name() + role = self._create_role(name=f"searchable-{unique}") + response = self._get("roles", data={"search": unique}, admin=True) + assert_status_code_is(response, 200) + data = response.json() + assert any(r["id"] == role["id"] for r in data) + # Search for non-existing returns empty + response = self._get("roles", data={"search": "nonexistent-xyz-99999"}, admin=True) + assert_status_code_is(response, 200) + data = response.json() + assert len(data) == 0 + @requires_admin def test_create_only_admin(self): response = self._post("roles", json=True) diff --git a/lib/galaxy_test/api/test_users.py b/lib/galaxy_test/api/test_users.py index 850d67affb9..20206605afb 100644 --- a/lib/galaxy_test/api/test_users.py +++ b/lib/galaxy_test/api/test_users.py @@ -47,6 +47,23 @@ class TestUsersApi(ApiTestCase): all_deleted_users = all_deleted_users_response_2.json() assert len([u for u in all_deleted_users if u["email"] == TEST_USER_EMAIL_INDEX_DELETED]) == 1 + @requires_admin + def test_index_with_pagination(self): + dataset_populator = DatasetPopulator(self.galaxy_interactor) + self._setup_user(f"pagination_test_{dataset_populator.get_random_name()}@bx.psu.edu") + self._setup_user(f"pagination_test_{dataset_populator.get_random_name()}@bx.psu.edu") + response = self._get("users", data={"limit": 1}, admin=True) + self._assert_status_code_is(response, 200) + data = response.json() + assert len(data) == 1 + # With offset + response_offset = self._get("users", data={"limit": 1, "offset": 1}, admin=True) + self._assert_status_code_is(response_offset, 200) + data_offset = response_offset.json() + assert len(data_offset) == 1 + # Different results + assert data[0]["id"] != data_offset[0]["id"] + def test_index_anon(self): with self._different_user(anon=True): all_users_response = self._get("users") diff --git a/lib/galaxy_test/selenium/test_admin_app.py b/lib/galaxy_test/selenium/test_admin_app.py index 3bd09d9cd10..2d6e25d30d6 100644 --- a/lib/galaxy_test/selenium/test_admin_app.py +++ b/lib/galaxy_test/selenium/test_admin_app.py @@ -182,7 +182,6 @@ class TestAdminApp(SeleniumTestCase): assert title_element.text == "Local Data" self.screenshot("admin_local_data") - @selenium_only("Not yet migrated to support Playwright backend") @selenium_test @requires_admin def test_admin_user_display(self): diff --git a/test/unit/data/model/db/test_role.py b/test/unit/data/model/db/test_role.py index 27382f128b4..f50ca9e6bf4 100644 --- a/test/unit/data/model/db/test_role.py +++ b/test/unit/data/model/db/test_role.py @@ -151,8 +151,8 @@ def test_get_displayable_roles(session, make_role, make_user_and_role): admin_role1 = make_role(type="admin", name="admin-role-1", description="Description of admin-role1") make_role(type="admin", description="Description of admin-role1", deleted=True) - user_is_admin, security_agent = True, None - roles = get_displayable_roles(session, user1, user_is_admin, security_agent) + user_is_admin = True + roles = get_displayable_roles(session, user1, user_is_admin) assert len(roles) == 3 assert roles[0].id == private_role1.id assert roles[1].id == private_role2.id