Skip to content

Commit

Permalink
[studio] add admin screen #1505 (#1506)
Browse files Browse the repository at this point in the history
* studio - admin view

* studio - lint rules

* studio - unit tests

* studio - unit tests

* studio - unit tests mock

* studio - unit tests mock
  • Loading branch information
janavlachova authored Jan 19, 2025
1 parent 1a0a3f1 commit 1725025
Show file tree
Hide file tree
Showing 38 changed files with 696 additions and 194 deletions.
2 changes: 2 additions & 0 deletions agdb_studio/.eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ module.exports = {
root: true,
extends: [
"plugin:vue/vue3-essential",
"plugin:vue/vue3-strongly-recommended",
"plugin:vue/vue3-recommended",
"eslint:recommended",
"@vue/eslint-config-typescript",
"@vue/eslint-config-prettier/skip-formatting",
Expand Down
28 changes: 25 additions & 3 deletions agdb_studio/src/components/auth/LoginForm.spec.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi, beforeEach } from "vitest";
import { mount } from "@vue/test-utils";
import LoginForm from "@/components/auth/LoginForm.vue";

const { loginMock, logoutMock, pushMock } = vi.hoisted(() => {
const { loginMock, logoutMock, pushMock, currentRoute } = vi.hoisted(() => {
return {
loginMock: vi.fn(),
logoutMock: vi.fn(),
pushMock: vi.fn(),
currentRoute: {
value: {
query: {
redirect: "/home",
} as { redirect?: string },
},
},
};
});

Expand All @@ -22,6 +29,7 @@ vi.mock("@/router", () => {
return {
default: {
push: pushMock,
currentRoute,
},
};
});
Expand All @@ -30,10 +38,24 @@ describe("LoginForm", () => {
beforeEach(() => {
vi.clearAllMocks();
});
it("runs successful login on click", async () => {
it("runs successful login on click and redirects from query", async () => {
loginMock.mockResolvedValue(true);

const wrapper = mount(LoginForm);
currentRoute.value.query.redirect = "/home";
await wrapper.find('input[type="text"]#username').setValue("test");
await wrapper.find('input[type="password"]#password').setValue("test");

await wrapper.find(".login-form>form").trigger("submit");

expect(loginMock).toHaveBeenCalled();
expect(pushMock).toHaveBeenCalledWith("/home");
});
it("runs successful login on click and redirects to home", async () => {
loginMock.mockResolvedValue(true);

const wrapper = mount(LoginForm);
currentRoute.value.query.redirect = undefined;
await wrapper.find('input[type="text"]#username').setValue("test");
await wrapper.find('input[type="password"]#password').setValue("test");

Expand Down
9 changes: 6 additions & 3 deletions agdb_studio/src/components/auth/LoginForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ const onLogin = async () => {
clearError();
login(username.value, password.value)
.then(async () => {
await router.push({ name: "home" });
const redirect = router.currentRoute.value.query.redirect;
await router.push(
typeof redirect === "string" ? redirect : { name: "home" },
);
loading.value = false;
})
.catch((e) => {
Expand All @@ -36,14 +39,14 @@ const onLogin = async () => {
<form @submit.prevent="onLogin">
<div>
<label for="username">Username:</label>
<input type="text" id="username" v-model="username" required />
<input id="username" v-model="username" type="text" required />
</div>
<div>
<label for="password">Password:</label>
<input
type="password"
id="password"
v-model="password"
type="password"
required
/>
</div>
Expand Down
14 changes: 7 additions & 7 deletions agdb_studio/src/components/base/content/AgdbContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ onMounted(() => {
inputs.get(part.input.key) !== undefined &&
part.input.type === 'select'
"
:value="getInputValue(props.contentKey, part.input.key)"
:name="part.input.key"
@change="
(event: Event) => {
setInputValue(
Expand All @@ -51,27 +53,26 @@ onMounted(() => {
);
}
"
:value="getInputValue(props.contentKey, part.input.key)"
:name="part.input.key"
>
<option
v-for="(option, index) in part.input.options"
:key="index"
v-for="option in part.input.options"
:key="option.value"
:value="option.value"
>
{{ option.label }}
</option>
</select>
<input
v-else-if="inputs.get(part.input.key) !== undefined"
:name="part.input.key"
:type="part.input.type"
:ref="
(el) => {
if (part.input?.autofocus)
autofocusElement = el;
}
"
:name="part.input.key"
:type="part.input.type"
:value="getInputValue(props.contentKey, part.input.key)"
@input="
(event: Event) => {
setInputValue(
Expand All @@ -81,7 +82,6 @@ onMounted(() => {
);
}
"
:value="getInputValue(props.contentKey, part.input.key)"
/>
<FadeTransition>
<div v-if="part.input.error" class="error-message">
Expand Down
4 changes: 2 additions & 2 deletions agdb_studio/src/components/base/dropdown/AgdbDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ const buttonRef = ref<HTMLElement>();

<template>
<div class="agdb-dropdown">
<button type="button" class="trigger" @click="toggle" ref="buttonRef">
<button ref="buttonRef" type="button" class="trigger" @click="toggle">
<slot name="trigger"></slot>
</button>
<Teleport to="body">
<SlideUpTransition>
<DropdownContent
v-on-click-outside="close"
:button-ref="buttonRef"
:opened="opened"
v-on-click-outside="close"
@close="close"
>
<slot name="content"></slot>
Expand Down
10 changes: 6 additions & 4 deletions agdb_studio/src/components/base/dropdown/DropdownContent.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import { computed, ref } from "vue";
const props = defineProps({
opened: { type: Boolean, required: true },
buttonRef: { type: HTMLElement, required: false },
buttonRef: { type: HTMLElement, required: false, default: null },
});
defineEmits(["close"]);
const contentRef = ref<HTMLElement>();
const contentStyle = computed(() => {
Expand Down Expand Up @@ -33,11 +35,11 @@ const contentStyle = computed(() => {

<template>
<div
class="content"
@click="$emit('close')"
v-if="opened"
:style="contentStyle"
ref="contentRef"
class="content"
:style="contentStyle"
@click="$emit('close')"
>
<slot></slot>
</div>
Expand Down
6 changes: 3 additions & 3 deletions agdb_studio/src/components/base/menu/AgdbMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ const openSubmenu = (key: string) => {
<li
v-for="action in props.actions"
:key="action.key"
class="menu-item"
:data-key="action.key"
@click.prevent="
(event: MouseEvent) => {
if (action.actions) {
Expand All @@ -26,9 +28,7 @@ const openSubmenu = (key: string) => {
action.action({ event });
}
"
class="menu-item"
@mouseover="openSubmenu(action.key)"
:data-key="action.key"
>
<a
href="#"
Expand All @@ -43,8 +43,8 @@ const openSubmenu = (key: string) => {
</a>
<SlideUpTransition>
<AgdbMenu
class="sub-menu"
v-if="openedSubmenu === action.key && action.actions"
class="sub-menu"
:actions="action.actions"
/>
</SlideUpTransition>
Expand Down
12 changes: 6 additions & 6 deletions agdb_studio/src/components/base/modal/AgdbModal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -27,33 +27,33 @@ watch(modalIsVisible, async () => {
<header class="modal-header">
<h3>{{ modal.header }}</h3>
<button
@click="closeModal"
class="button button-transparent"
data-testid="close-modal"
@click="closeModal"
>
<ClCloseMd />
</button>
</header>
<form id="modal-form">
<AgdbContent
:content="modal.content"
:contentKey="KEY_MODAL"
:content-key="KEY_MODAL"
class="modal-body"
/>
</form>
<footer class="modal-footer">
<button
v-for="button in buttons"
:key="button.text"
@click.prevent="button.action"
:class="button.className"
:type="button.type ?? 'button'"
:form="button.type === 'submit' ? 'modal-form' : undefined"
:ref="
(el) => {
if (button.type === 'submit') autofocusElement = el;
}
"
:class="button.className"
:type="button.type ?? 'button'"
:form="button.type === 'submit' ? 'modal-form' : undefined"
@click.prevent="button.action"
>
{{ button.text }}
</button>
Expand Down
2 changes: 1 addition & 1 deletion agdb_studio/src/components/base/table/AgdbTable.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ provide(INJECT_KEY_COLUMNS, columns);

<template>
<div class="agdb-table">
<AgdbTableHeader :tableKey="name" />
<AgdbTableHeader :table-key="name" />
<template v-for="row in rows" :key="row[0]">
<AgdbTableRow :row="row[1]" :columns="columns" />
</template>
Expand Down
20 changes: 19 additions & 1 deletion agdb_studio/src/components/base/table/AgdbTableRow.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
import { mount } from "@vue/test-utils";
import AgdbTableRow from "./AgdbTableRow.vue";
import { columnsMap, TABLE_NAME, tableConfig } from "@/tests/tableMocks";
import { describe, it, expect } from "vitest";
import { describe, it, expect, vi } from "vitest";
import {
INJECT_KEY_COLUMNS,
INJECT_KEY_TABLE_NAME,
} from "@/composables/table/constants";
import { addTable } from "@/composables/table/tableConfig";

const { fetchDbUsers, isDbRoleType } = vi.hoisted(() => {
return {
fetchDbUsers: vi.fn().mockResolvedValue({ data: [] }),
isDbRoleType: vi.fn().mockReturnValue(true),
};
});

vi.mock("@/composables/db/dbUsersStore", () => {
return {
useDbUsersStore: () => {
return {
fetchDbUsers,
isDbRoleType,
};
},
};
});

describe("TableRow", () => {
addTable({
name: TABLE_NAME,
Expand Down
2 changes: 1 addition & 1 deletion agdb_studio/src/components/base/table/AgdbTableRow.vue
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,8 @@ const toggleExpandRow = (): void => {
</div>
<div v-if="rowDetailsComponent">
<button
@click="toggleExpandRow"
class="button button-transparent expand-row"
@click="toggleExpandRow"
>
<AkChevronDownSmall v-if="!rowExpanded" />
<AkChevronUpSmall v-else />
Expand Down
4 changes: 2 additions & 2 deletions agdb_studio/src/components/db/DbAddForm.vue
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ const add = (event: Event) => {
<template>
<div class="db-add-form">
<h2>Add Database</h2>
<form @submit="add" id="db-add-form">
<form id="db-add-form" @submit="add">
<input
type="text"
v-model="name"
type="text"
placeholder="Name"
name="db-name"
/>
Expand Down
2 changes: 1 addition & 1 deletion agdb_studio/src/components/db/DbDetails.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const {
handleUsernameClick,
} = vi.hoisted(() => {
return {
fetchDbUsers: vi.fn(),
fetchDbUsers: vi.fn().mockResolvedValue({ data: [] }),
isDbRoleType: vi.fn().mockReturnValue(true),
handleRemoveUser: vi.fn(),
handleAddUser: vi.fn(),
Expand Down
5 changes: 3 additions & 2 deletions agdb_studio/src/components/db/DbDetails.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const props = defineProps({
row: {
type: Object as PropType<TRow>,
required: false,
default: undefined,
},
});
Expand Down Expand Up @@ -58,10 +59,10 @@ onMounted(() => {
<li v-for="user in users" :key="user.username" class="user-item">
<span
class="username"
@click="() => handleUsernameClick(user.username, user.role)"
:class="{
clickable: !isOwner(user.username) && canEditUsers,
}"
@click="() => handleUsernameClick(user.username, user.role)"
>{{ user.username }}</span
>
<span class="role">
Expand All @@ -70,8 +71,8 @@ onMounted(() => {
<button
v-if="user.username !== dbParams.owner && canEditUsers"
class="button button-transparent remove-button"
@click="handleRemoveUser(user.username)"
title="Remove user"
@click="handleRemoveUser(user.username)"
>
<ClCloseMd class="remove-icon" />
</button>
Expand Down
1 change: 1 addition & 0 deletions agdb_studio/src/components/header/UserDropdown.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ vi.mock("@/composables/user/account", () => {
return {
useAccount: () => ({
username: "testUser",
admin: { value: false },
}),
};
});
Expand Down
4 changes: 2 additions & 2 deletions agdb_studio/src/components/header/UserDropdown.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@ const { username } = useAccount();

<template>
<AgdbDropdown>
<template v-slot:trigger>
<template #trigger>
<div class="user-dropdown-trigger button">
<ClUser02 />
<span>{{ username }}</span>
</div>
</template>
<template v-slot:content>
<template #content>
<UserMenu />
</template>
</AgdbDropdown>
Expand Down
1 change: 1 addition & 0 deletions agdb_studio/src/components/header/UserMenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ vi.mock("@/composables/user/account", () => {
return {
useAccount: () => ({
username: "testUser",
admin: { value: false },
}),
};
});
Expand Down
Loading

0 comments on commit 1725025

Please sign in to comment.