Optimize dataset get_edit API by deferring expensive role queries

Move get_legitimate_roles, get_private_role_user_emails_dict, and
can_manage_dataset calls into the conditional branch where they are
actually needed (only when user is logged in AND dataset has permission
actions set). This avoids expensive full-table role/email queries for
anonymous users, public datasets, and non-shareable datasets.

Also scope get_private_role_user_emails_dict to only fetch email
mappings for the specific role IDs returned by get_legitimate_roles,
rather than scanning all private roles in the system.
This commit is contained in:
mvdbeek
2026-03-23 17:32:40 +01:00
parent 31c718f06c
commit 056f2c0257
2 changed files with 20 additions and 11 deletions
+8 -2
View File
@@ -53,8 +53,14 @@ def get_displayable_roles(session, trans_user, user_is_admin, security_agent):
return roles
def get_private_role_user_emails_dict(session) -> dict[int, str]:
"""Return a mapping of private role ids to user emails."""
def get_private_role_user_emails_dict(session, role_ids: set[int] | None = None) -> dict[int, str]:
"""Return a mapping of private role ids to user emails.
If role_ids is provided, only return mappings for roles in that set,
avoiding a full table scan on large instances.
"""
stmt = select(UserRoleAssociation.role_id, User.email).join(Role).join(User).where(Role.type == Role.types.PRIVATE)
if role_ids is not None:
stmt = stmt.where(UserRoleAssociation.role_id.in_(role_ids))
roleid_email_tuples = session.execute(stmt).all()
return dict(roleid_email_tuples)
@@ -159,17 +159,8 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
]
ldatatypes.sort()
private_role_emails = get_private_role_user_emails_dict(trans.sa_session)
role_tuples = []
for role in trans.app.security_agent.get_legitimate_roles(trans, data.dataset, "root"):
displayed_name = private_role_emails.get(role.id, role.name)
role_tuples.append((displayed_name, trans.security.encode_id(role.id)))
data_metadata = list(data.metadata.spec.items())
converters_collection = [(key, value.name) for key, value in data.get_converter_types().items()]
can_manage_dataset = trans.app.security_agent.can_manage_dataset(
trans.get_current_user_roles(), data.dataset
)
# attribute editing
attribute_inputs = [
{"name": "name", "type": "text", "label": "Name", "value": data.get_display_name()},
@@ -258,6 +249,18 @@ class DatasetInterface(BaseUIController, UsesAnnotations, UsesItemRatings, UsesE
{"name": "not_shareable", "type": "hidden", "label": permission_message, "readonly": True}
)
elif data.dataset.actions:
can_manage_dataset = trans.app.security_agent.can_manage_dataset(
trans.get_current_user_roles(), data.dataset
)
legitimate_roles = trans.app.security_agent.get_legitimate_roles(trans, data.dataset, "root")
legitimate_role_ids = {role.id for role in legitimate_roles}
private_role_emails = get_private_role_user_emails_dict(
trans.sa_session, role_ids=legitimate_role_ids
)
role_tuples = [
(private_role_emails.get(role.id, role.name), trans.security.encode_id(role.id))
for role in legitimate_roles
]
in_roles = {}
for action, roles in trans.app.security_agent.get_permissions(data.dataset).items():
in_roles[action.action] = [trans.security.encode_id(role.id) for role in roles]