Skip to content

Commit

Permalink
Merge pull request galaxyproject#17118 from guerler/grids_roles_admin
Browse files Browse the repository at this point in the history
Vueify Admin Roles Grid
  • Loading branch information
mvdbeek authored Dec 6, 2023
2 parents 5db8fbc + 5b42118 commit f129d90
Show file tree
Hide file tree
Showing 7 changed files with 395 additions and 157 deletions.
4 changes: 4 additions & 0 deletions client/src/api/roles.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,7 @@ export async function getAllRoles() {
const { data } = await getRoles({});
return data;
}

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();
88 changes: 88 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1361,6 +1361,16 @@ export interface paths {
"/api/roles/{id}": {
/** Show */
get: operations["show_api_roles__id__get"];
/** 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"];
};
"/api/short_term_storage/{storage_request_id}": {
/** Serve the staged download specified by request ID. */
Expand Down Expand Up @@ -17622,6 +17632,84 @@ 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"];
};
};
};
};
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: {
/** @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: {
Expand Down
187 changes: 187 additions & 0 deletions client/src/components/Grid/configs/adminRoles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { useEventBus } from "@vueuse/core";
import axios from "axios";

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";

import type { ActionArray, FieldArray, GridConfig } from "./types";

const { emit } = useEventBus<string>("grid-router-push");

/**
* Local types
*/
type RoleEntry = Record<string, unknown>;

/**
* 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.rows_total];
}

/**
* 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",
operations: [
{
title: "Edit Name/Description",
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({ id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been deleted.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to delete '${data.name}': ${errorMessageAsString(e)}`,
};
}
},
},
{
title: "Purge",
icon: faTrash,
condition: (data: RoleEntry) => !!data.deleted,
handler: async (data: RoleEntry) => {
try {
await purgeRole({ id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been purged.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to purge '${data.name}': ${errorMessageAsString(e)}`,
};
}
},
},
{
title: "Restore",
icon: faTrashRestore,
condition: (data: RoleEntry) => !!data.deleted,
handler: async (data: RoleEntry) => {
try {
await undeleteRole({ id: String(data.id) });
return {
status: "success",
message: `'${data.name}' has been restored.`,
};
} catch (e) {
return {
status: "danger",
message: `Failed to restore '${data.name}': ${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: "update_time",
title: "Updated",
type: "date",
},
];

const validFilters: Record<string, ValidFilter<string | boolean | undefined>> = {
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,
boolType: "is",
handler: equals("deleted", "deleted", toBool),
menuItem: true,
},
};

/**
* Grid configuration
*/
const gridConfig: GridConfig = {
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 gridConfig;
5 changes: 3 additions & 2 deletions client/src/entry/analysis/routes/admin-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ 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 Grid from "components/Grid/Grid";
import GridList from "components/Grid/GridList";
Expand Down Expand Up @@ -148,9 +149,9 @@ export default [
},
{
path: "roles",
component: Grid,
component: GridList,
props: {
urlBase: "admin/roles_list",
gridConfig: adminRolesGridConfig,
},
},
{
Expand Down
53 changes: 53 additions & 0 deletions lib/galaxy/managers/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,59 @@ 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 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:
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)
# 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:
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):
trans.sa_session.commit()
return role


def get_roles_by_ids(session: Session, role_ids):
stmt = select(Role).where(Role.id.in_(role_ids))
Expand Down
18 changes: 18 additions & 0 deletions lib/galaxy/webapps/galaxy/api/roles.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,21 @@ 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)

@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) -> RoleModelResponse:
role = self.role_manager.get(trans, id)
role = self.role_manager.undelete(trans, role)
return role_to_model(role)
Loading

0 comments on commit f129d90

Please sign in to comment.