From f5da24f9d1acb7d1c0a1461f6d1bf3665526c345 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 10:27:17 +0300 Subject: [PATCH 01/14] Add admin roles grid config module --- .../src/components/Grid/configs/adminRoles.ts | 206 ++++++++++++++++++ 1 file changed, 206 insertions(+) create mode 100644 client/src/components/Grid/configs/adminRoles.ts diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts new file mode 100644 index 000000000000..f20585336953 --- /dev/null +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -0,0 +1,206 @@ +import { + faPlus, + faTrash, + faTrashRestore, + faKey, + faEdit, +} from "@fortawesome/free-solid-svg-icons"; +import { useEventBus } from "@vueuse/core"; +import axios from "axios"; + +//import { createApiKey, deleteUser, recalculateDiskUsageByUserId, sendActivationEmail, undeleteUser } from "@/api/users"; +import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/filtering"; +import { withPrefix } from "@/utils/redirect"; +import { errorMessageAsString } from "@/utils/simple-error"; + +import type { ActionArray, Config, FieldArray } from "./types"; + +const { emit } = useEventBus("grid-router-push"); + +/** + * Local types + */ +type RoleEntry = Record; + +/** + * Request and return data from server + */ +async function getData(offset: number, limit: number, search: string, sort_by: string, sort_desc: boolean) { + const query = { + limit: String(limit), + offset: String(offset), + search: search, + sort_by: sort_by, + sort_desc: String(sort_desc), + }; + const queryString = new URLSearchParams(query).toString(); + const { data } = await axios.get(withPrefix(`/admin/roles_list?${queryString}`)); + return [data.rows, data.total_row_count]; +} + +/** + * Actions are grid-wide operations + */ +const actions: ActionArray = [ + { + title: "Create New Role", + icon: faPlus, + handler: () => { + emit("/admin/form/create_role"); + }, + }, +]; + +/** + * Declare columns to be displayed + */ +const fields: FieldArray = [ + { + key: "name", + title: "Name", + type: "operations", + condition: (data: RoleEntry) => !data.deleted && !data.purged, + operations: [ + { + title: "Edit Details", + icon: faEdit, + condition: (data: RoleEntry) => !data.deleted, + handler: (data: RoleEntry) => { + emit(`/admin/form/rename_role?id=${data.id}`); + }, + }, + { + title: "Edit Permissions", + icon: faKey, + condition: (data: RoleEntry) => !data.deleted, + handler: (data: RoleEntry) => { + emit(`/admin/form/manage_users_and_groups_for_role?id=${data.id}`); + }, + }, + { + title: "Delete", + icon: faTrash, + condition: (data: RoleEntry) => !data.deleted, + handler: async (data: RoleEntry) => { + try { + //await deleteRole({ user_id: String(data.id), purge: false }); + return { + status: "success", + message: `'${data.username}' has been deleted.`, + }; + } catch (e) { + return { + status: "danger", + message: `Failed to delete '${data.username}': ${errorMessageAsString(e)}`, + }; + } + }, + }, + { + title: "Purge", + icon: faTrash, + condition: (data: RoleEntry) => !!data.deleted && !data.purged, + handler: async (data: RoleEntry) => { + try { + //await deleteUser({ user_id: String(data.id), purge: true }); + return { + status: "success", + message: `'${data.username}' has been purged.`, + }; + } catch (e) { + return { + status: "danger", + message: `Failed to purge '${data.username}': ${errorMessageAsString(e)}`, + }; + } + }, + }, + { + title: "Restore", + icon: faTrashRestore, + condition: (data: RoleEntry) => !!data.deleted && !data.purged, + handler: async (data: RoleEntry) => { + try { + //await undeleteUser({ user_id: String(data.id) }); + return { + status: "success", + message: `'${data.username}' has been restored.`, + }; + } catch (e) { + return { + status: "danger", + message: `Failed to restore '${data.username}': ${errorMessageAsString(e)}`, + }; + } + }, + }, + ], + }, + { + key: "description", + title: "Description", + type: "text", + }, + { + key: "type", + title: "Type", + type: "text", + }, + { + key: "groups", + title: "Groups", + type: "text", + }, + { + key: "users", + title: "Users", + type: "text", + }, + { + key: "status", + title: "Status", + type: "text", + }, + { + key: "update_time", + title: "Updated", + type: "date", + }, +]; + +const validFilters: Record> = { + email: { placeholder: "email", type: String, handler: contains("email"), menuItem: true }, + username: { placeholder: "username", type: String, handler: contains("username"), menuItem: true }, + deleted: { + placeholder: "Filter on deleted entries", + type: Boolean, + boolType: "is", + handler: equals("deleted", "deleted", toBool), + menuItem: true, + }, + purged: { + placeholder: "Filter on purged entries", + type: Boolean, + boolType: "is", + handler: equals("purged", "purged", toBool), + menuItem: true, + }, +}; + +/** + * Grid configuration + */ +const config: Config = { + id: "roles-grid", + actions: actions, + fields: fields, + filtering: new Filtering(validFilters, undefined, false, false), + getData: getData, + plural: "Roles", + sortBy: "name", + sortDesc: true, + sortKeys: ["description", "name", "update_time"], + title: "Roles", +}; + +export default config; From 9f0b1431114a620391de405ab316759c38d70422 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 10:33:10 +0300 Subject: [PATCH 02/14] Adjust roles grid definition in admin controller --- .../webapps/galaxy/controllers/admin.py | 83 ++----------------- 1 file changed, 9 insertions(+), 74 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index ade0de78b7ca..96d1ddd7a400 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -159,27 +159,7 @@ def apply_query_filter(self, query, **kwargs): return query -class RoleListGrid(grids.Grid): - class NameColumn(grids.TextColumn): - def get_value(self, trans, grid, role): - return escape(role.name) - - class DescriptionColumn(grids.TextColumn): - def get_value(self, trans, grid, role): - if role.description: - return escape(role.description) - return "" - - class TypeColumn(grids.TextColumn): - def get_value(self, trans, grid, role): - return role.type - - class StatusColumn(grids.GridColumn): - def get_value(self, trans, grid, role): - if role.deleted: - return "deleted" - return "" - +class RoleListGrid(grids.GridData): class GroupsColumn(grids.GridColumn): def get_value(self, trans, grid, role): if role.groups: @@ -198,62 +178,17 @@ def get_value(self, trans, grid, role): model_class = model.Role default_sort_key = "name" columns = [ - NameColumn( - "Name", - key="name", - link=(lambda item: dict(action="form/manage_users_and_groups_for_role", id=item.id, webapp="galaxy")), - model_class=model.Role, - attach_popup=True, - filterable="advanced", - target="top", - ), - DescriptionColumn( - "Description", key="description", model_class=model.Role, attach_popup=False, filterable="advanced" - ), - TypeColumn("Type", key="type", model_class=model.Role, attach_popup=False, filterable="advanced"), - GroupsColumn("Groups", attach_popup=False), - UsersColumn("Users", attach_popup=False), - StatusColumn("Status", attach_popup=False), - # Columns that are valid for filtering but are not visible. - grids.DeletedColumn("Deleted", key="deleted", visible=False, filterable="advanced"), + grids.GridColumn("Name", key="name"), + grids.GridColumn("Description", key="description"), + grids.GridColumn("Type", key="type"), + GroupsColumn("Groups"), + UsersColumn("Users"), + grids.GridColumn("Deleted", key="deleted", escape=False), + grids.GridColumn("Purged", key="purged", escape=False), grids.GridColumn("Last Updated", key="update_time"), ] - columns.append( - grids.MulticolFilterColumn( - "Search", - cols_to_filter=[columns[0], columns[1], columns[2]], - key="free-text-search", - visible=False, - filterable="standard", - ) - ) - global_actions = [grids.GridAction("Add new role", url_args=dict(action="form/create_role"))] - operations = [ - grids.GridOperation( - "Edit Name/Description", - condition=(lambda item: not item.deleted), - allow_multiple=False, - url_args=dict(action="form/rename_role"), - ), - grids.GridOperation( - "Edit Permissions", - condition=(lambda item: not item.deleted), - allow_multiple=False, - url_args=dict(action="form/manage_users_and_groups_for_role", webapp="galaxy"), - ), - grids.GridOperation("Delete", condition=(lambda item: not item.deleted), allow_multiple=True), - grids.GridOperation("Undelete", condition=(lambda item: item.deleted), allow_multiple=True), - grids.GridOperation("Purge", condition=(lambda item: item.deleted), allow_multiple=True), - ] - standard_filters = [ - grids.GridColumnFilter("Active", args=dict(deleted=False)), - grids.GridColumnFilter("Deleted", args=dict(deleted=True)), - grids.GridColumnFilter("All", args=dict(deleted="All")), - ] - num_rows_per_page = 50 - use_paging = True - def apply_query_filter(self, trans, query, **kwargs): + def apply_query_filter(self, query, **kwargs): return query.filter(model.Role.type != model.Role.types.PRIVATE) From a211c34446c7a8ffd3b24867473fa92804f935b6 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 11:05:04 +0300 Subject: [PATCH 03/14] Replace legacy grid in router, display gridlist component instead for admin roles --- client/src/components/Grid/configs/adminRoles.ts | 9 ++++----- client/src/entry/analysis/routes/admin-routes.js | 5 +++-- lib/galaxy/webapps/galaxy/controllers/admin.py | 4 ++-- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index f20585336953..6250a7edfc4a 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -35,7 +35,7 @@ async function getData(offset: number, limit: number, search: string, sort_by: s }; const queryString = new URLSearchParams(query).toString(); const { data } = await axios.get(withPrefix(`/admin/roles_list?${queryString}`)); - return [data.rows, data.total_row_count]; + return [data.rows, data.rows_total]; } /** @@ -59,7 +59,6 @@ const fields: FieldArray = [ key: "name", title: "Name", type: "operations", - condition: (data: RoleEntry) => !data.deleted && !data.purged, operations: [ { title: "Edit Details", @@ -157,9 +156,9 @@ const fields: FieldArray = [ type: "text", }, { - key: "status", - title: "Status", - type: "text", + key: "deleted", + title: "Deleted", + type: "boolean", }, { key: "update_time", diff --git a/client/src/entry/analysis/routes/admin-routes.js b/client/src/entry/analysis/routes/admin-routes.js index 7d30e68abc59..fca8fa1a1b91 100644 --- a/client/src/entry/analysis/routes/admin-routes.js +++ b/client/src/entry/analysis/routes/admin-routes.js @@ -1,4 +1,5 @@ import { getGalaxyInstance } from "app"; +import adminRolesGrid from "components/Grid/configs/adminRoles"; import ActiveInvocations from "components/admin/ActiveInvocations"; import DataManager from "components/admin/DataManager/DataManager"; import DataManagerJob from "components/admin/DataManager/DataManagerJob"; @@ -148,9 +149,9 @@ export default [ }, { path: "roles", - component: Grid, + component: GridList, props: { - urlBase: "admin/roles_list", + config: adminRolesGrid, }, }, { diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 96d1ddd7a400..7e860a14a3b2 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -181,8 +181,8 @@ def get_value(self, trans, grid, role): grids.GridColumn("Name", key="name"), grids.GridColumn("Description", key="description"), grids.GridColumn("Type", key="type"), - GroupsColumn("Groups"), - UsersColumn("Users"), + GroupsColumn("Groups", key="groups"), + UsersColumn("Users", key="users"), grids.GridColumn("Deleted", key="deleted", escape=False), grids.GridColumn("Purged", key="purged", escape=False), grids.GridColumn("Last Updated", key="update_time"), From edfa1b1d58ce6678c75f8ef80fd9501e04d611fc Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 11:28:37 +0300 Subject: [PATCH 04/14] Add advanced filter query for admin roles grid --- .../src/components/Grid/configs/adminRoles.ts | 19 ++--------- .../src/entry/analysis/routes/admin-routes.js | 2 +- .../webapps/galaxy/controllers/admin.py | 33 ++++++++++++++++++- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index 6250a7edfc4a..df45e803e2d5 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -1,10 +1,4 @@ -import { - faPlus, - faTrash, - faTrashRestore, - faKey, - faEdit, -} from "@fortawesome/free-solid-svg-icons"; +import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons"; import { useEventBus } from "@vueuse/core"; import axios from "axios"; @@ -168,8 +162,8 @@ const fields: FieldArray = [ ]; const validFilters: Record> = { - email: { placeholder: "email", type: String, handler: contains("email"), menuItem: true }, - username: { placeholder: "username", type: String, handler: contains("username"), menuItem: true }, + name: { placeholder: "name", type: String, handler: contains("name"), menuItem: true }, + description: { placeholder: "description", type: String, handler: contains("description"), menuItem: true }, deleted: { placeholder: "Filter on deleted entries", type: Boolean, @@ -177,13 +171,6 @@ const validFilters: Record> = handler: equals("deleted", "deleted", toBool), menuItem: true, }, - purged: { - placeholder: "Filter on purged entries", - type: Boolean, - boolType: "is", - handler: equals("purged", "purged", toBool), - menuItem: true, - }, }; /** diff --git a/client/src/entry/analysis/routes/admin-routes.js b/client/src/entry/analysis/routes/admin-routes.js index fca8fa1a1b91..c2de2260f0ee 100644 --- a/client/src/entry/analysis/routes/admin-routes.js +++ b/client/src/entry/analysis/routes/admin-routes.js @@ -1,5 +1,4 @@ import { getGalaxyInstance } from "app"; -import adminRolesGrid from "components/Grid/configs/adminRoles"; import ActiveInvocations from "components/admin/ActiveInvocations"; import DataManager from "components/admin/DataManager/DataManager"; import DataManagerJob from "components/admin/DataManager/DataManagerJob"; @@ -20,6 +19,7 @@ import ResetMetadata from "components/admin/ResetMetadata"; import SanitizeAllow from "components/admin/SanitizeAllow"; import FormGeneric from "components/Form/FormGeneric"; import adminUsersGridConfig from "components/Grid/configs/adminUsers"; +import adminRolesGrid from "components/Grid/configs/adminRoles"; import Grid from "components/Grid/Grid"; import GridList from "components/Grid/GridList"; import RegisterForm from "components/Login/RegisterForm"; diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 7e860a14a3b2..ceac82ec286e 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -189,7 +189,38 @@ def get_value(self, trans, grid, role): ] def apply_query_filter(self, query, **kwargs): - return query.filter(model.Role.type != model.Role.types.PRIVATE) + INDEX_SEARCH_FILTERS = { + "description": "description", + "name": "name", + "is": "is", + } + search_query = kwargs.get("search") + parsed_search = parse_filters_structured(search_query, INDEX_SEARCH_FILTERS) + query = query.filter(self.model_class.type != self.model_class.types.PRIVATE) + deleted = False + for term in parsed_search.terms: + if isinstance(term, FilteredTerm): + key = term.filter + q = term.text + if key == "name": + query = query.filter(text_column_filter(self.model_class.name, term)) + if key == "description": + query = query.filter(text_column_filter(self.model_class.description, term)) + elif key == "is": + if q == "deleted": + deleted = True + elif isinstance(term, RawTextTerm): + query = query.filter( + raw_text_column_filter( + [ + self.model_class.description, + self.model_class.name, + ], + term, + ) + ) + query = query.filter(self.model_class.deleted == (true() if deleted else false())) + return query class GroupListGrid(grids.Grid): From 94810f83054db18974f617d0c4597788d07abb6a Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 13:18:17 +0300 Subject: [PATCH 05/14] Use GridConfig for admin roles grid --- client/src/components/Grid/configs/adminRoles.ts | 6 +++--- client/src/entry/analysis/routes/admin-routes.js | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index df45e803e2d5..2274b6a5e06a 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -7,7 +7,7 @@ import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/f import { withPrefix } from "@/utils/redirect"; import { errorMessageAsString } from "@/utils/simple-error"; -import type { ActionArray, Config, FieldArray } from "./types"; +import type { ActionArray, FieldArray, GridConfig } from "./types"; const { emit } = useEventBus("grid-router-push"); @@ -176,7 +176,7 @@ const validFilters: Record> = /** * Grid configuration */ -const config: Config = { +const gridConfig: GridConfig = { id: "roles-grid", actions: actions, fields: fields, @@ -189,4 +189,4 @@ const config: Config = { title: "Roles", }; -export default config; +export default gridConfig; diff --git a/client/src/entry/analysis/routes/admin-routes.js b/client/src/entry/analysis/routes/admin-routes.js index c2de2260f0ee..85527b95ee5b 100644 --- a/client/src/entry/analysis/routes/admin-routes.js +++ b/client/src/entry/analysis/routes/admin-routes.js @@ -151,7 +151,7 @@ export default [ path: "roles", component: GridList, props: { - config: adminRolesGrid, + gridConfig: adminRolesGrid, }, }, { From d29cfc5426017edcab76c43d7d73df494065e620 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:09:08 +0300 Subject: [PATCH 06/14] Add delete role api endpoint --- client/src/api/roles.ts | 2 ++ client/src/api/schema/schema.ts | 28 +++++++++++++++++++ .../src/components/Grid/configs/adminRoles.ts | 10 +++---- .../src/entry/analysis/routes/admin-routes.js | 4 +-- lib/galaxy/managers/roles.py | 7 +++++ lib/galaxy/webapps/galaxy/api/roles.py | 8 ++++++ 6 files changed, 52 insertions(+), 7 deletions(-) diff --git a/client/src/api/roles.ts b/client/src/api/roles.ts index 61fde1360a92..df9b410e0fda 100644 --- a/client/src/api/roles.ts +++ b/client/src/api/roles.ts @@ -5,3 +5,5 @@ export async function getAllRoles() { const { data } = await getRoles({}); return data; } + +export const deleteRole = fetcher.path("/api/roles/{id}").method("delete").create(); diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 697f187d4188..5b4bef0bd5b9 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1361,6 +1361,8 @@ export interface paths { "/api/roles/{id}": { /** Show */ get: operations["show_api_roles__id__get"]; + /** Delete */ + delete: operations["delete_api_roles__id__delete"]; }; "/api/short_term_storage/{storage_request_id}": { /** Serve the staged download specified by request ID. */ @@ -17622,6 +17624,32 @@ export interface operations { }; }; }; + delete_api_roles__id__delete: { + /** Delete */ + parameters: { + /** @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. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; serve_api_short_term_storage__storage_request_id__get: { /** Serve the staged download specified by request ID. */ parameters: { diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index 2274b6a5e06a..945c8a237fa8 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -2,7 +2,7 @@ import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/fre import { useEventBus } from "@vueuse/core"; import axios from "axios"; -//import { createApiKey, deleteUser, recalculateDiskUsageByUserId, sendActivationEmail, undeleteUser } from "@/api/users"; +import { deleteRole } from "@/api/roles"; import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/filtering"; import { withPrefix } from "@/utils/redirect"; import { errorMessageAsString } from "@/utils/simple-error"; @@ -76,15 +76,15 @@ const fields: FieldArray = [ condition: (data: RoleEntry) => !data.deleted, handler: async (data: RoleEntry) => { try { - //await deleteRole({ user_id: String(data.id), purge: false }); + await deleteRole({ id: String(data.id) }); return { status: "success", - message: `'${data.username}' has been deleted.`, + message: `'${data.name}' has been deleted.`, }; } catch (e) { return { status: "danger", - message: `Failed to delete '${data.username}': ${errorMessageAsString(e)}`, + message: `Failed to delete '${data.name}': ${errorMessageAsString(e)}`, }; } }, @@ -95,7 +95,7 @@ const fields: FieldArray = [ condition: (data: RoleEntry) => !!data.deleted && !data.purged, handler: async (data: RoleEntry) => { try { - //await deleteUser({ user_id: String(data.id), purge: true }); + await deleteRole({ id: String(data.id) }); return { status: "success", message: `'${data.username}' has been purged.`, diff --git a/client/src/entry/analysis/routes/admin-routes.js b/client/src/entry/analysis/routes/admin-routes.js index 85527b95ee5b..7be38abcfc62 100644 --- a/client/src/entry/analysis/routes/admin-routes.js +++ b/client/src/entry/analysis/routes/admin-routes.js @@ -18,8 +18,8 @@ import NotificationsManagement from "components/admin/Notifications/Notification import ResetMetadata from "components/admin/ResetMetadata"; import SanitizeAllow from "components/admin/SanitizeAllow"; import FormGeneric from "components/Form/FormGeneric"; +import adminRolesGridConfig from "components/Grid/configs/adminRoles"; import adminUsersGridConfig from "components/Grid/configs/adminUsers"; -import adminRolesGrid from "components/Grid/configs/adminRoles"; import Grid from "components/Grid/Grid"; import GridList from "components/Grid/GridList"; import RegisterForm from "components/Login/RegisterForm"; @@ -151,7 +151,7 @@ export default [ path: "roles", component: GridList, props: { - gridConfig: adminRolesGrid, + gridConfig: adminRolesGridConfig, }, }, { diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index fbe96ba2b2ad..312f952c72f3 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -37,6 +37,13 @@ class RoleManager(base.ModelManager[model.Role]): user_assoc = model.UserRoleAssociation group_assoc = model.GroupRoleAssociation + def delete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: + role.deleted = True + trans.sa_session.add(role) + with transaction(trans.sa_session): + trans.sa_session.commit() + return role + def get(self, trans: ProvidesUserContext, role_id: int) -> model.Role: """ Method loads the role from the DB based on the given role id. diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index 92d6369285d0..6c97448e38ac 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -55,3 +55,11 @@ def create( ) -> RoleModelResponse: role = self.role_manager.create_role(trans, role_definition_model) return role_to_model(role) + + @router.delete("/api/roles/{id}", require_admin=True) + def delete( + self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans + ) -> RoleModelResponse: + role = self.role_manager.get(trans, id) + role = self.role_manager.delete(trans, role) + return role_to_model(role) From 26fedd265748af4cfae8cc33ea1772a801ffdefb Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:16:20 +0300 Subject: [PATCH 07/14] Add undelete roles to api --- client/src/api/roles.ts | 1 + client/src/api/schema/schema.ts | 30 +++++++++++++++++++ .../src/components/Grid/configs/adminRoles.ts | 8 ++--- lib/galaxy/managers/roles.py | 22 +++++++++----- lib/galaxy/webapps/galaxy/api/roles.py | 8 +++++ 5 files changed, 58 insertions(+), 11 deletions(-) diff --git a/client/src/api/roles.ts b/client/src/api/roles.ts index df9b410e0fda..d261fbad731b 100644 --- a/client/src/api/roles.ts +++ b/client/src/api/roles.ts @@ -7,3 +7,4 @@ export async function getAllRoles() { } export const deleteRole = fetcher.path("/api/roles/{id}").method("delete").create(); +export const undeleteRole = fetcher.path("/api/roles/{id}/undelete").method("post").create(); diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 5b4bef0bd5b9..65729bba984e 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1364,6 +1364,10 @@ export interface paths { /** Delete */ delete: operations["delete_api_roles__id__delete"]; }; + "/api/roles/{id}/undelete": { + /** Undelete */ + post: operations["undelete_api_roles__id__undelete_post"]; + }; "/api/short_term_storage/{storage_request_id}": { /** Serve the staged download specified by request ID. */ get: operations["serve_api_short_term_storage__storage_request_id__get"]; @@ -17650,6 +17654,32 @@ export interface operations { }; }; }; + undelete_api_roles__id__undelete_post: { + /** Undelete */ + parameters: { + /** @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. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; serve_api_short_term_storage__storage_request_id__get: { /** Serve the staged download specified by request ID. */ parameters: { diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index 945c8a237fa8..256b96b684f6 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -2,7 +2,7 @@ import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/fre import { useEventBus } from "@vueuse/core"; import axios from "axios"; -import { deleteRole } from "@/api/roles"; +import { deleteRole, undeleteRole } from "@/api/roles"; import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/filtering"; import { withPrefix } from "@/utils/redirect"; import { errorMessageAsString } from "@/utils/simple-error"; @@ -114,15 +114,15 @@ const fields: FieldArray = [ condition: (data: RoleEntry) => !!data.deleted && !data.purged, handler: async (data: RoleEntry) => { try { - //await undeleteUser({ user_id: String(data.id) }); + await undeleteRole({ id: String(data.id) }); return { status: "success", - message: `'${data.username}' has been restored.`, + message: `'${data.name}' has been restored.`, }; } catch (e) { return { status: "danger", - message: `Failed to restore '${data.username}': ${errorMessageAsString(e)}`, + message: `Failed to restore '${data.name}': ${errorMessageAsString(e)}`, }; } }, diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index 312f952c72f3..b8f021ef76ea 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -37,13 +37,6 @@ class RoleManager(base.ModelManager[model.Role]): user_assoc = model.UserRoleAssociation group_assoc = model.GroupRoleAssociation - def delete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: - role.deleted = True - trans.sa_session.add(role) - with transaction(trans.sa_session): - trans.sa_session.commit() - return role - def get(self, trans: ProvidesUserContext, role_id: int) -> model.Role: """ Method loads the role from the DB based on the given role id. @@ -108,6 +101,21 @@ def create_role(self, trans: ProvidesUserContext, role_definition_model: RoleDef trans.sa_session.commit() return role + def delete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: + role.deleted = True + trans.sa_session.add(role) + with transaction(trans.sa_session): + trans.sa_session.commit() + return role + + def undelete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: + if not role.deleted: + return (f"Role '{role.name}' has not been deleted, so it cannot be undeleted.", "error") + role.deleted = False + trans.sa_session.add(role) + with transaction(trans.sa_session): + trans.sa_session.commit() + return role def get_roles_by_ids(session: Session, role_ids): stmt = select(Role).where(Role.id.in_(role_ids)) diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index 6c97448e38ac..ed5f37b79c58 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -63,3 +63,11 @@ def delete( role = self.role_manager.get(trans, id) role = self.role_manager.delete(trans, role) return role_to_model(role) + + @router.post("/api/roles/{id}/undelete", require_admin=True) + def undelete( + self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans + ) -> RoleModelResponse: + role = self.role_manager.get(trans, id) + role = self.role_manager.undelete(trans, role) + return role_to_model(role) From 3819ed9abb84d5b027d2b49e45c82936a449aefa Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:19:07 +0300 Subject: [PATCH 08/14] Remove deleted column from role grid config, restore previous operation name --- client/src/components/Grid/configs/adminRoles.ts | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index 256b96b684f6..1d9bddf808a2 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -55,7 +55,7 @@ const fields: FieldArray = [ type: "operations", operations: [ { - title: "Edit Details", + title: "Edit Name/Description", icon: faEdit, condition: (data: RoleEntry) => !data.deleted, handler: (data: RoleEntry) => { @@ -149,11 +149,6 @@ const fields: FieldArray = [ title: "Users", type: "text", }, - { - key: "deleted", - title: "Deleted", - type: "boolean", - }, { key: "update_time", title: "Updated", From 96b75dfcf1c998507c923785a0eb5102bdcdc160 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:21:34 +0300 Subject: [PATCH 09/14] Move role purge function from admin controller to roles manager --- lib/galaxy/managers/roles.py | 33 ++++++++++++++++ .../webapps/galaxy/controllers/admin.py | 39 ------------------- 2 files changed, 33 insertions(+), 39 deletions(-) diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index b8f021ef76ea..5fb48f908f10 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -108,6 +108,39 @@ def delete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: trans.sa_session.commit() return role + def purge(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: + # This method should only be called for a Role that has previously been deleted. + # Purging a deleted Role deletes all of the following from the database: + # - UserRoleAssociations where role_id == Role.id + # - DefaultUserPermissions where role_id == Role.id + # - DefaultHistoryPermissions where role_id == Role.id + # - GroupRoleAssociations where role_id == Role.id + # - DatasetPermissionss where role_id == Role.id + if not role.deleted: + return (f"Role '{role.name}' has not been deleted, so it cannot be purged.", "error") + # Delete UserRoleAssociations + for ura in role.users: + user = trans.sa_session.query(trans.app.model.User).get(ura.user_id) + # Delete DefaultUserPermissions for associated users + for dup in user.default_permissions: + if role == dup.role: + trans.sa_session.delete(dup) + # Delete DefaultHistoryPermissions for associated users + for history in user.histories: + for dhp in history.default_permissions: + if role == dhp.role: + trans.sa_session.delete(dhp) + trans.sa_session.delete(ura) + # Delete GroupRoleAssociations + for gra in role.groups: + trans.sa_session.delete(gra) + # Delete DatasetPermissionss + for dp in role.dataset_actions: + trans.sa_session.delete(dp) + with transaction(trans.sa_session): + trans.sa_session.commit() + return role + def undelete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: if not role.deleted: return (f"Role '{role.name}' has not been deleted, so it cannot be undeleted.", "error") diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index ceac82ec286e..22d91f869861 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -832,8 +832,6 @@ def roles_list(self, trans, **kwargs): message, status = self._delete_role(trans, ids) elif operation == "undelete": message, status = self._undelete_role(trans, ids) - elif operation == "purge": - message, status = self._purge_role(trans, ids) if message and status: kwargs["message"] = util.sanitize_text(message) kwargs["status"] = status @@ -1060,43 +1058,6 @@ def _undelete_role(self, trans, ids): undeleted_roles += f" {role.name}" return ("Undeleted %d roles: %s" % (count, undeleted_roles), "done") - def _purge_role(self, trans, ids): - # This method should only be called for a Role that has previously been deleted. - # Purging a deleted Role deletes all of the following from the database: - # - UserRoleAssociations where role_id == Role.id - # - DefaultUserPermissions where role_id == Role.id - # - DefaultHistoryPermissions where role_id == Role.id - # - GroupRoleAssociations where role_id == Role.id - # - DatasetPermissionss where role_id == Role.id - message = "Purged %d roles: " % len(ids) - for role_id in ids: - role = get_role(trans, role_id) - if not role.deleted: - return (f"Role '{role.name}' has not been deleted, so it cannot be purged.", "error") - # Delete UserRoleAssociations - for ura in role.users: - user = trans.sa_session.query(trans.app.model.User).get(ura.user_id) - # Delete DefaultUserPermissions for associated users - for dup in user.default_permissions: - if role == dup.role: - trans.sa_session.delete(dup) - # Delete DefaultHistoryPermissions for associated users - for history in user.histories: - for dhp in history.default_permissions: - if role == dhp.role: - trans.sa_session.delete(dhp) - trans.sa_session.delete(ura) - # Delete GroupRoleAssociations - for gra in role.groups: - trans.sa_session.delete(gra) - # Delete DatasetPermissionss - for dp in role.dataset_actions: - trans.sa_session.delete(dp) - with transaction(trans.sa_session): - trans.sa_session.commit() - message += f" {role.name} " - return (message, "done") - @web.legacy_expose_api @web.require_admin def groups_list(self, trans, **kwargs): From 16863a471e4792dcf426bf8d4218b0971042754a Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:26:42 +0300 Subject: [PATCH 10/14] Add role purge api endpoint --- client/src/api/roles.ts | 1 + client/src/api/schema/schema.ts | 30 +++++++++++++++++++ .../src/components/Grid/configs/adminRoles.ts | 12 ++++---- lib/galaxy/webapps/galaxy/api/roles.py | 8 +++++ 4 files changed, 45 insertions(+), 6 deletions(-) diff --git a/client/src/api/roles.ts b/client/src/api/roles.ts index d261fbad731b..4667ca53a2b5 100644 --- a/client/src/api/roles.ts +++ b/client/src/api/roles.ts @@ -7,4 +7,5 @@ export async function getAllRoles() { } export const deleteRole = fetcher.path("/api/roles/{id}").method("delete").create(); +export const purgeRole = fetcher.path("/api/roles/{id}/purge").method("post").create(); export const undeleteRole = fetcher.path("/api/roles/{id}/undelete").method("post").create(); diff --git a/client/src/api/schema/schema.ts b/client/src/api/schema/schema.ts index 65729bba984e..cb551a3c25e3 100644 --- a/client/src/api/schema/schema.ts +++ b/client/src/api/schema/schema.ts @@ -1364,6 +1364,10 @@ export interface paths { /** Delete */ delete: operations["delete_api_roles__id__delete"]; }; + "/api/roles/{id}/purge": { + /** Purge */ + post: operations["purge_api_roles__id__purge_post"]; + }; "/api/roles/{id}/undelete": { /** Undelete */ post: operations["undelete_api_roles__id__undelete_post"]; @@ -17654,6 +17658,32 @@ export interface operations { }; }; }; + purge_api_roles__id__purge_post: { + /** Purge */ + parameters: { + /** @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. */ + header?: { + "run-as"?: string; + }; + path: { + id: string; + }; + }; + responses: { + /** @description Successful Response */ + 200: { + content: { + "application/json": components["schemas"]["RoleModelResponse"]; + }; + }; + /** @description Validation Error */ + 422: { + content: { + "application/json": components["schemas"]["HTTPValidationError"]; + }; + }; + }; + }; undelete_api_roles__id__undelete_post: { /** Undelete */ parameters: { diff --git a/client/src/components/Grid/configs/adminRoles.ts b/client/src/components/Grid/configs/adminRoles.ts index 1d9bddf808a2..b1365e27b7c9 100644 --- a/client/src/components/Grid/configs/adminRoles.ts +++ b/client/src/components/Grid/configs/adminRoles.ts @@ -2,7 +2,7 @@ import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/fre import { useEventBus } from "@vueuse/core"; import axios from "axios"; -import { deleteRole, undeleteRole } from "@/api/roles"; +import { deleteRole, purgeRole, undeleteRole } from "@/api/roles"; import Filtering, { contains, equals, toBool, type ValidFilter } from "@/utils/filtering"; import { withPrefix } from "@/utils/redirect"; import { errorMessageAsString } from "@/utils/simple-error"; @@ -92,18 +92,18 @@ const fields: FieldArray = [ { title: "Purge", icon: faTrash, - condition: (data: RoleEntry) => !!data.deleted && !data.purged, + condition: (data: RoleEntry) => !!data.deleted, handler: async (data: RoleEntry) => { try { - await deleteRole({ id: String(data.id) }); + await purgeRole({ id: String(data.id) }); return { status: "success", - message: `'${data.username}' has been purged.`, + message: `'${data.name}' has been purged.`, }; } catch (e) { return { status: "danger", - message: `Failed to purge '${data.username}': ${errorMessageAsString(e)}`, + message: `Failed to purge '${data.name}': ${errorMessageAsString(e)}`, }; } }, @@ -111,7 +111,7 @@ const fields: FieldArray = [ { title: "Restore", icon: faTrashRestore, - condition: (data: RoleEntry) => !!data.deleted && !data.purged, + condition: (data: RoleEntry) => !!data.deleted, handler: async (data: RoleEntry) => { try { await undeleteRole({ id: String(data.id) }); diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index ed5f37b79c58..84f885ae63c8 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -64,6 +64,14 @@ def delete( role = self.role_manager.delete(trans, role) return role_to_model(role) + @router.post("/api/roles/{id}/purge", require_admin=True) + def purge( + self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans + ) -> RoleModelResponse: + role = self.role_manager.get(trans, id) + role = self.role_manager.purge(trans, role) + return role_to_model(role) + @router.post("/api/roles/{id}/undelete", require_admin=True) def undelete( self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans From 75f3cb8622ca85098ccbadfc33c9801fa7dd4f16 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:31:39 +0300 Subject: [PATCH 11/14] Fix linting --- lib/galaxy/managers/roles.py | 1 + lib/galaxy/webapps/galaxy/api/roles.py | 12 +++--------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index 5fb48f908f10..4a5b8db27eb7 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -150,6 +150,7 @@ def undelete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: trans.sa_session.commit() return role + def get_roles_by_ids(session: Session, role_ids): stmt = select(Role).where(Role.id.in_(role_ids)) return session.scalars(stmt).all() diff --git a/lib/galaxy/webapps/galaxy/api/roles.py b/lib/galaxy/webapps/galaxy/api/roles.py index 84f885ae63c8..53ef695a0aca 100644 --- a/lib/galaxy/webapps/galaxy/api/roles.py +++ b/lib/galaxy/webapps/galaxy/api/roles.py @@ -57,25 +57,19 @@ def create( return role_to_model(role) @router.delete("/api/roles/{id}", require_admin=True) - def delete( - self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans - ) -> RoleModelResponse: + def delete(self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans) -> RoleModelResponse: role = self.role_manager.get(trans, id) role = self.role_manager.delete(trans, role) return role_to_model(role) @router.post("/api/roles/{id}/purge", require_admin=True) - def purge( - self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans - ) -> RoleModelResponse: + def purge(self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans) -> RoleModelResponse: role = self.role_manager.get(trans, id) role = self.role_manager.purge(trans, role) return role_to_model(role) @router.post("/api/roles/{id}/undelete", require_admin=True) - def undelete( - self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans - ) -> RoleModelResponse: + def undelete(self, id: DecodedDatabaseIdField, trans: ProvidesUserContext = DependsOnTrans) -> RoleModelResponse: role = self.role_manager.get(trans, id) role = self.role_manager.undelete(trans, role) return role_to_model(role) From 396a1392b97afe9679a4e2658b66039d62871bb0 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 16:33:12 +0300 Subject: [PATCH 12/14] Remove legacy role operation handlers from admin controller --- .../webapps/galaxy/controllers/admin.py | 41 ------------------- 1 file changed, 41 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 22d91f869861..491d33fc6069 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -820,21 +820,6 @@ def tool_versions_list(self, trans, **kwd): @web.json @web.require_admin def roles_list(self, trans, **kwargs): - message = kwargs.get("message") - status = kwargs.get("status") - if "operation" in kwargs: - id = kwargs.get("id", None) - if not id: - message, status = (f"Invalid role id ({str(id)}) received.", "error") - ids = util.listify(id) - operation = kwargs["operation"].lower().replace("+", " ") - if operation == "delete": - message, status = self._delete_role(trans, ids) - elif operation == "undelete": - message, status = self._undelete_role(trans, ids) - if message and status: - kwargs["message"] = util.sanitize_text(message) - kwargs["status"] = status return self.role_list_grid(trans, **kwargs) @web.legacy_expose_api @@ -1032,32 +1017,6 @@ def manage_users_and_groups_for_role(self, trans, payload=None, **kwd): "message": f"Role '{role.name}' has been updated with {len(in_users)} associated users and {len(in_groups)} associated groups." } - def _delete_role(self, trans, ids): - message = "Deleted %d roles: " % len(ids) - for role_id in ids: - role = get_role(trans, role_id) - role.deleted = True - trans.sa_session.add(role) - with transaction(trans.sa_session): - trans.sa_session.commit() - message += f" {role.name} " - return (message, "done") - - def _undelete_role(self, trans, ids): - count = 0 - undeleted_roles = "" - for role_id in ids: - role = get_role(trans, role_id) - if not role.deleted: - return (f"Role '{role.name}' has not been deleted, so it cannot be undeleted.", "error") - role.deleted = False - trans.sa_session.add(role) - with transaction(trans.sa_session): - trans.sa_session.commit() - count += 1 - undeleted_roles += f" {role.name}" - return ("Undeleted %d roles: %s" % (count, undeleted_roles), "done") - @web.legacy_expose_api @web.require_admin def groups_list(self, trans, **kwargs): From 8671f659160c04f072274c1ae0e357a9a3af0e55 Mon Sep 17 00:00:00 2001 From: guerler Date: Fri, 1 Dec 2023 18:28:13 +0300 Subject: [PATCH 13/14] Raise errors instead of returning messages --- lib/galaxy/managers/roles.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/lib/galaxy/managers/roles.py b/lib/galaxy/managers/roles.py index 4a5b8db27eb7..ecb221cc8044 100644 --- a/lib/galaxy/managers/roles.py +++ b/lib/galaxy/managers/roles.py @@ -117,7 +117,9 @@ def purge(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: # - GroupRoleAssociations where role_id == Role.id # - DatasetPermissionss where role_id == Role.id if not role.deleted: - return (f"Role '{role.name}' has not been deleted, so it cannot be purged.", "error") + raise galaxy.exceptions.RequestParameterInvalidException( + f"Role '{role.name}' has not been deleted, so it cannot be purged." + ) # Delete UserRoleAssociations for ura in role.users: user = trans.sa_session.query(trans.app.model.User).get(ura.user_id) @@ -143,7 +145,9 @@ def purge(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: def undelete(self, trans: ProvidesUserContext, role: model.Role) -> model.Role: if not role.deleted: - return (f"Role '{role.name}' has not been deleted, so it cannot be undeleted.", "error") + raise galaxy.exceptions.RequestParameterInvalidException( + f"Role '{role.name}' has not been deleted, so it cannot be undeleted." + ) role.deleted = False trans.sa_session.add(role) with transaction(trans.sa_session): From 5b42118ea9b14e8013917ee1d9ff32cc918ae7e6 Mon Sep 17 00:00:00 2001 From: guerler Date: Sat, 2 Dec 2023 19:55:19 +0300 Subject: [PATCH 14/14] Adjust roles grid data provider --- .../webapps/galaxy/controllers/admin.py | 47 ++++++++++--------- 1 file changed, 24 insertions(+), 23 deletions(-) diff --git a/lib/galaxy/webapps/galaxy/controllers/admin.py b/lib/galaxy/webapps/galaxy/controllers/admin.py index 491d33fc6069..f6068d87df13 100644 --- a/lib/galaxy/webapps/galaxy/controllers/admin.py +++ b/lib/galaxy/webapps/galaxy/controllers/admin.py @@ -194,31 +194,32 @@ def apply_query_filter(self, query, **kwargs): "name": "name", "is": "is", } - search_query = kwargs.get("search") - parsed_search = parse_filters_structured(search_query, INDEX_SEARCH_FILTERS) - query = query.filter(self.model_class.type != self.model_class.types.PRIVATE) deleted = False - for term in parsed_search.terms: - if isinstance(term, FilteredTerm): - key = term.filter - q = term.text - if key == "name": - query = query.filter(text_column_filter(self.model_class.name, term)) - if key == "description": - query = query.filter(text_column_filter(self.model_class.description, term)) - elif key == "is": - if q == "deleted": - deleted = True - elif isinstance(term, RawTextTerm): - query = query.filter( - raw_text_column_filter( - [ - self.model_class.description, - self.model_class.name, - ], - term, + query = query.filter(self.model_class.type != self.model_class.types.PRIVATE) + search_query = kwargs.get("search") + if search_query: + parsed_search = parse_filters_structured(search_query, INDEX_SEARCH_FILTERS) + for term in parsed_search.terms: + if isinstance(term, FilteredTerm): + key = term.filter + q = term.text + if key == "name": + query = query.filter(text_column_filter(self.model_class.name, term)) + if key == "description": + query = query.filter(text_column_filter(self.model_class.description, term)) + elif key == "is": + if q == "deleted": + deleted = True + elif isinstance(term, RawTextTerm): + query = query.filter( + raw_text_column_filter( + [ + self.model_class.description, + self.model_class.name, + ], + term, + ) ) - ) query = query.filter(self.model_class.deleted == (true() if deleted else false())) return query