Skip to content

Commit

Permalink
Move group operations from legacy admin controller to api endpoint, a…
Browse files Browse the repository at this point in the history
…nd link to client
  • Loading branch information
guerler committed Dec 2, 2023
1 parent 9a471ac commit 7b65a63
Show file tree
Hide file tree
Showing 6 changed files with 319 additions and 61 deletions.
6 changes: 5 additions & 1 deletion client/src/api/groups.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
import axios from "axios";

import { components } from "@/api/schema";
import { components, fetcher } from "@/api/schema";

type GroupModel = components["schemas"]["GroupModel"];
export async function getAllGroups(): Promise<GroupModel[]> {
const { data } = await axios.get("/api/groups");
return data;
}

export const deleteGroup = fetcher.path("/api/groups/{id}").method("delete").create();
export const purgeGroup = fetcher.path("/api/groups/{id}/purge").method("post").create();
export const undeleteGroup = fetcher.path("/api/groups/{id}/undelete").method("post").create();
90 changes: 90 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -451,6 +451,18 @@ export interface paths {
*/
delete: operations["delete_api_groups__group_id__users__user_id__delete"];
};
"/api/groups/{id}": {
/** Delete */
delete: operations["delete_api_groups__id__delete"];
};
"/api/groups/{id}/purge": {
/** Purge */
post: operations["purge_api_groups__id__purge_post"];
};
"/api/groups/{id}/undelete": {
/** Undelete */
post: operations["undelete_api_groups__id__undelete_post"];
};
"/api/help/forum/search": {
/**
* Search the Galaxy Help forum.
Expand Down Expand Up @@ -12385,6 +12397,84 @@ export interface operations {
};
};
};
delete_api_groups__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": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
purge_api_groups__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": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
undelete_api_groups__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": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
search_forum_api_help_forum_search_get: {
/**
* Search the Galaxy Help forum.
Expand Down
176 changes: 176 additions & 0 deletions client/src/components/Grid/configs/adminGroups.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
import { faEdit, faKey, faPlus, faTrash, faTrashRestore } from "@fortawesome/free-solid-svg-icons";
import { useEventBus } from "@vueuse/core";
import axios from "axios";

import { deleteGroup, purgeGroup, undeleteGroup } from "@/api/groups";
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 GroupEntry = 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/groups_list?${queryString}`));
return [data.rows, data.rows_total];
}

/**
* Actions are grid-wide operations
*/
const actions: ActionArray = [
{
title: "Create New Group",
icon: faPlus,
handler: () => {
emit("/admin/form/create_group");
},
},
];

/**
* Declare columns to be displayed
*/
const fields: FieldArray = [
{
key: "name",
title: "Name",
type: "operations",
operations: [
{
title: "Edit Name/Description",
icon: faEdit,
condition: (data: GroupEntry) => !data.deleted,
handler: (data: GroupEntry) => {
emit(`/admin/form/rename_group?id=${data.id}`);
},
},
{
title: "Edit Permissions",
icon: faKey,
condition: (data: GroupEntry) => !data.deleted,
handler: (data: GroupEntry) => {
emit(`/admin/form/manage_users_and_groups_for_role?id=${data.id}`);
},
},
{
title: "Delete",
icon: faTrash,
condition: (data: GroupEntry) => !data.deleted,
handler: async (data: GroupEntry) => {
try {
await deleteGroup({ 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: GroupEntry) => !!data.deleted,
handler: async (data: GroupEntry) => {
try {
await purgeGroup({ 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: GroupEntry) => !!data.deleted,
handler: async (data: GroupEntry) => {
try {
await undeleteGroup({ 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: "roles",
title: "Roles",
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 },
deleted: {
placeholder: "Filter on deleted entries",
type: Boolean,
boolType: "is",
handler: equals("deleted", "deleted", toBool),
menuItem: true,
},
};

/**
* Grid configuration
*/
const gridConfig: GridConfig = {
id: "groups-grid",
actions: actions,
fields: fields,
filtering: new Filtering(validFilters, undefined, false, false),
getData: getData,
plural: "Groups",
sortBy: "name",
sortDesc: true,
sortKeys: ["name", "update_time"],
title: "Groups",
};

export default gridConfig;
33 changes: 33 additions & 0 deletions lib/galaxy/managers/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,39 @@ def update(self, trans: ProvidesAppContext, group_id: int, payload: GroupCreateP
item["url"] = self._url_for(trans, "show_group", group_id=encoded_id)
return item

def delete(self, trans: ProvidesAppContext, group_id: int):
group = self._get_group(trans.sa_session, group_id)
group.deleted = True
trans.sa_session.add(group)
with transaction(trans.sa_session):
trans.sa_session.commit()

def purge(self, trans: ProvidesAppContext, group_id: int):
group = self._get_group(trans.sa_session, group_id)
if not group.deleted:
raise galaxy.exceptions.RequestParameterInvalidException(
f"Group '{groups.name}' has not been deleted, so it cannot be purged."
)
# Delete UserGroupAssociations
for uga in group.users:
trans.sa_session.delete(uga)
# Delete GroupRoleAssociations
for gra in group.roles:
trans.sa_session.delete(gra)
with transaction(trans.sa_session):
trans.sa_session.commit()

def undelete(self, trans: ProvidesAppContext, group_id: int):
group = self._get_group(trans.sa_session, group_id)
if not group.deleted:
raise galaxy.exceptions.RequestParameterInvalidException(
f"Group '{groups.name}' has not been deleted, so it cannot be undeleted."
)
group.deleted = False
trans.sa_session.add(group)
with transaction(trans.sa_session):
trans.sa_session.commit()

def _url_for(self, trans, name, **kwargs):
return trans.url_builder(name, **kwargs)

Expand Down
15 changes: 15 additions & 0 deletions lib/galaxy/webapps/galaxy/api/groups.py
Original file line number Diff line number Diff line change
Expand Up @@ -82,3 +82,18 @@ def update(
payload: GroupCreatePayload = Body(...),
) -> GroupResponse:
return self.manager.update(trans, group_id, payload)

@router.delete("/api/groups/{id}", require_admin=True)
def delete(self, id: DecodedDatabaseIdField, trans: ProvidesAppContext = DependsOnTrans):
group = self.manager.get(trans, id)
self.manager.delete(trans, group)

@router.post("/api/groups/{id}/purge", require_admin=True)
def purge(self, id: DecodedDatabaseIdField, trans: ProvidesAppContext = DependsOnTrans):
group = self.manager.get(trans, id)
self.manager.purge(trans, group)

@router.post("/api/groups/{id}/undelete", require_admin=True)
def undelete(self, id: DecodedDatabaseIdField, trans: ProvidesAppContext = DependsOnTrans):
group = self.manager.get(trans, id)
self.manager.undelete(trans, group)
Loading

0 comments on commit 7b65a63

Please sign in to comment.