Skip to content

Commit

Permalink
Merge pull request #17030 from guerler/grids_user_admin
Browse files Browse the repository at this point in the history
Vueify Admin User Grid
  • Loading branch information
bgruening authored Dec 2, 2023
2 parents a5b5ebf + a0834c0 commit 2f8abf9
Show file tree
Hide file tree
Showing 24 changed files with 790 additions and 311 deletions.
64 changes: 64 additions & 0 deletions client/src/api/schema/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1592,6 +1592,14 @@ export interface paths {
/** Remove the object from user's favorites */
delete: operations["remove_favorite_api_users__user_id__favorites__object_type___object_id__delete"];
};
"/api/users/{user_id}/recalculate_disk_usage": {
/** Triggers a recalculation of the current user disk usage. */
put: operations["recalculate_disk_usage_by_user_id_api_users__user_id__recalculate_disk_usage_put"];
};
"/api/users/{user_id}/send_activation_email": {
/** Sends activation email to user. */
post: operations["send_activation_email_api_users__user_id__send_activation_email_post"];
};
"/api/users/{user_id}/theme/{theme}": {
/** Set the user's theme choice */
put: operations["set_theme_api_users__user_id__theme__theme__put"];
Expand Down Expand Up @@ -18988,6 +18996,62 @@ export interface operations {
};
};
};
recalculate_disk_usage_by_user_id_api_users__user_id__recalculate_disk_usage_put: {
/** Triggers a recalculation of the current user disk usage. */
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;
};
/** @description The ID of the user to get. */
path: {
user_id: string;
};
};
responses: {
/** @description The asynchronous task summary to track the task state. */
200: {
content: {
"application/json": components["schemas"]["AsyncTaskResultSummary"];
};
};
/** @description The background task was submitted but there is no status tracking ID available. */
204: never;
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
send_activation_email_api_users__user_id__send_activation_email_post: {
/** Sends activation email to user. */
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;
};
/** @description The ID of the user to get. */
path: {
user_id: string;
};
};
responses: {
/** @description Successful Response */
200: {
content: {
"application/json": Record<string, never>;
};
};
/** @description Validation Error */
422: {
content: {
"application/json": components["schemas"]["HTTPValidationError"];
};
};
};
};
set_theme_api_users__user_id__theme__theme__put: {
/** Set the user's theme choice */
parameters: {
Expand Down
10 changes: 9 additions & 1 deletion client/src/api/users.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
import { fetcher } from "@/api/schema";

export const recalculateDiskUsage = fetcher.path("/api/users/current/recalculate_disk_usage").method("put").create();
export const createApiKey = fetcher.path("/api/users/{user_id}/api_key").method("post").create();
export const deleteUser = fetcher.path("/api/users/{user_id}").method("delete").create();
export const fetchQuotaUsages = fetcher.path("/api/users/{user_id}/usage").method("get").create();
export const recalculateDiskUsage = fetcher.path("/api/users/current/recalculate_disk_usage").method("put").create();
export const recalculateDiskUsageByUserId = fetcher
.path("/api/users/{user_id}/recalculate_disk_usage")
.method("put")
.create();
export const sendActivationEmail = fetcher.path("/api/users/{user_id}/send_activation_email").method("post").create();
export const undeleteUser = fetcher.path("/api/users/deleted/{user_id}/undelete").method("post").create();

const getUsers = fetcher.path("/api/users").method("get").create();
export async function getAllUsers() {
Expand Down
14 changes: 14 additions & 0 deletions client/src/components/Grid/GridElements/GridBoolean.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<script setup lang="ts">
import { faCheck, faTimes } from "@fortawesome/free-solid-svg-icons";
interface Props {
value?: boolean;
}
defineProps<Props>();
</script>

<template>
<icon v-if="value" :icon="faCheck" />
<icon v-else :icon="faTimes" />
</template>
15 changes: 10 additions & 5 deletions client/src/components/Grid/GridElements/GridOperations.vue
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import { faCaretDown } from "@fortawesome/free-solid-svg-icons";
import { FontAwesomeIcon } from "@fortawesome/vue-fontawesome";
import type { Operation, RowData } from "@/components/Grid/configs/types";
import { useConfig } from "@/composables/config";
import type { GalaxyConfiguration } from "@/stores/configurationStore";
library.add(faCaretDown);
const { config, isConfigLoaded } = useConfig();
interface Props {
rowData: RowData;
operations: Array<Operation>;
title: string;
}
const props = defineProps<Props>();
Expand All @@ -21,23 +26,23 @@ const emit = defineEmits<{
/**
* Availibility of operations might required certain conditions
*/
function hasCondition(conditionHandler: (rowData: RowData) => Boolean) {
return conditionHandler ? conditionHandler(props.rowData) : true;
function hasCondition(conditionHandler: (rowData: RowData, config: GalaxyConfiguration) => Boolean) {
return conditionHandler ? conditionHandler(props.rowData, config) : true;
}
</script>

<template>
<span>
<span v-if="isConfigLoaded">
<button
id="grid-operations"
data-toggle="dropdown"
aria-haspopup="true"
aria-expanded="false"
class="ui-link font-weight-bold">
<FontAwesomeIcon icon="caret-down" class="fa-lg" />
<span class="font-weight-bold">{{ rowData.title }}</span>
<span class="font-weight-bold">{{ title }}</span>
</button>
<div v-if="operations" class="dropdown-menu" aria-labelledby="dataset-dropdown">
<div class="dropdown-menu" aria-labelledby="dataset-dropdown">
<span v-for="(operation, operationIndex) in operations" :key="operationIndex">
<button
v-if="operation && operation.condition && hasCondition(operation.condition)"
Expand Down
2 changes: 1 addition & 1 deletion client/src/components/Grid/GridElements/GridText.vue
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<script setup lang="ts">
interface Props {
text?: string;
text?: string | number;
}
defineProps<Props>();
</script>
Expand Down
41 changes: 30 additions & 11 deletions client/src/components/Grid/GridList.test.js
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import { createTestingPinia } from "@pinia/testing";
import { mount } from "@vue/test-utils";
import flushPromises from "flush-promises";
import { PiniaVuePlugin } from "pinia";
import { getLocalVue } from "tests/jest/helpers";

import { useConfig } from "@/composables/config";
import Filtering from "@/utils/filtering";

import MountTarget from "./GridList.vue";

const localVue = getLocalVue();

jest.useFakeTimers();

jest.mock("composables/config");
useConfig.mockReturnValue({
config: {
value: {
disabled: false,
enabled: true,
},
},
isConfigLoaded: true,
});

jest.mock("vue-router/composables");

const localVue = getLocalVue();
localVue.use(PiniaVuePlugin);

const testGrid = {
actions: [
{
Expand All @@ -33,24 +50,24 @@ const testGrid = {
key: "operation",
title: "operation",
type: "operations",
condition: jest.fn(),
condition: jest.fn(() => true),
operations: [
{
title: "operation-title-1",
icon: "operation-icon-1",
condition: () => true,
condition: (_, config) => config.value.enabled,
handler: jest.fn(),
},
{
title: "operation-title-2",
icon: "operation-icon-2",
condition: () => false,
condition: (_, config) => config.value.disabled,
handler: jest.fn(),
},
{
title: "operation-title-3",
icon: "operation-icon-3",
condition: () => true,
condition: (_, config) => config.value.enabled,
handler: () => ({
status: "success",
message: "Operation-3 has been executed.",
Expand Down Expand Up @@ -79,9 +96,11 @@ const testGrid = {
};

function createTarget(propsData) {
const pinia = createTestingPinia({ stubActions: false });
return mount(MountTarget, {
localVue,
propsData,
pinia,
stubs: {
Icon: true,
},
Expand All @@ -91,7 +110,7 @@ function createTarget(propsData) {
describe("GridList", () => {
it("basic rendering", async () => {
const wrapper = createTarget({
config: testGrid,
gridConfig: testGrid,
});
const findInput = wrapper.find("[data-description='filter text input']");
expect(findInput.attributes().placeholder).toBe("search tests");
Expand Down Expand Up @@ -123,7 +142,7 @@ describe("GridList", () => {

it("header rendering", async () => {
const wrapper = createTarget({
config: testGrid,
gridConfig: testGrid,
});
await wrapper.vm.$nextTick();
for (const [fieldIndex, field] of Object.entries(testGrid.fields)) {
Expand All @@ -133,7 +152,7 @@ describe("GridList", () => {

it("operation handling", async () => {
const wrapper = createTarget({
config: testGrid,
gridConfig: testGrid,
});
await wrapper.vm.$nextTick();
const dropdown = wrapper.find("[data-description='grid cell 0-2']");
Expand All @@ -155,7 +174,7 @@ describe("GridList", () => {

it("filter handling", async () => {
const wrapper = createTarget({
config: testGrid,
gridConfig: testGrid,
});
await wrapper.vm.$nextTick();
const filterInput = wrapper.find("[data-description='filter text input']");
Expand All @@ -168,7 +187,7 @@ describe("GridList", () => {

it("pagination", async () => {
const wrapper = createTarget({
config: testGrid,
gridConfig: testGrid,
limit: 2,
});
await wrapper.vm.$nextTick();
Expand Down
Loading

0 comments on commit 2f8abf9

Please sign in to comment.