Skip to content

Commit

Permalink
[studio] print audit to modal window #1449 (#1451)
Browse files Browse the repository at this point in the history
studio - print audit log and display db name in header
  • Loading branch information
janavlachova authored Jan 3, 2025
1 parent 968daee commit 9617b88
Show file tree
Hide file tree
Showing 6 changed files with 205 additions and 8 deletions.
104 changes: 102 additions & 2 deletions agdb_studio/src/components/base/table/AgdbCellMenu.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const { fetchDatabases } = vi.hoisted(() => {
fetchDatabases: vi.fn(),
};
});
const { modalIsVisible, onConfirm, modal, hideModal } = useModal();

vi.mock("@/composables/db/dbStore", () => {
return {
Expand All @@ -23,6 +24,7 @@ vi.mock("@/composables/db/dbStore", () => {
describe("AgdbCellMenu", () => {
beforeEach(() => {
vi.clearAllMocks();
hideModal();
});
it("should open and close on click", async () => {
const wrapper = mount(AgdbCellMenu, {
Expand Down Expand Up @@ -85,8 +87,10 @@ describe("AgdbCellMenu", () => {
});
it("should open the modal on click when confirmation is required", async () => {
const deleteAction = vi.fn();
const question = "Are you sure you want to delete this database?";
const header = "Delete Database";
const deleteConfirmation = convertArrayOfStringsToContent([
"Are you sure you want to delete this database?",
question,
"This will permanently delete all data.",
]);
const wrapper = mount(AgdbCellMenu, {
Expand All @@ -97,6 +101,7 @@ describe("AgdbCellMenu", () => {
label: "Delete",
action: deleteAction,
confirmation: deleteConfirmation,
confirmationHeader: header,
},
],
},
Expand Down Expand Up @@ -124,10 +129,11 @@ describe("AgdbCellMenu", () => {
await action.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").exists()).toBe(false);
const { modalIsVisible, onConfirm } = useModal();
expect(modalIsVisible.value).toBe(true);
onConfirm.value?.();
expect(deleteAction).toHaveBeenCalledOnce();
expect(modal.content[0].paragraph?.at(0)?.text).toBe(question);
expect(modal.header).toBe(header);
});
it("should not close the dropdown if item has no action", async () => {
const wrapper = mount(AgdbCellMenu, {
Expand Down Expand Up @@ -159,4 +165,98 @@ describe("AgdbCellMenu", () => {
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").exists()).toBe(true);
});

it("should use header function if provided", async () => {
const deleteAction = vi.fn();
const question = "Are you sure you want to delete this database?";
const header = vi.fn().mockReturnValue("Test Header");
const deleteConfirmation = convertArrayOfStringsToContent([
question,
"This will permanently delete all data.",
]);
const wrapper = mount(AgdbCellMenu, {
props: {
actions: [
{
key: "delete",
label: "Delete",
action: deleteAction,
confirmation: deleteConfirmation,
confirmationHeader: header,
},
],
},
global: {
provide: {
[INJECT_KEY_ROW]: {
value: {
role: "admin",
owner: "admin",
db: "test",
db_type: "memory",
size: 2656,
backup: 0,
},
},
},
},
});
const trigger = wrapper.find(".trigger");
expect(wrapper.find(".content").exists()).toBe(false);
trigger.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").isVisible()).toBe(true);
const action = wrapper.find(".menu-item[data-key=delete]");
await action.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").exists()).toBe(false);
expect(header).toHaveBeenCalled();
expect(modal.content[0].paragraph?.at(0)?.text).toBe(question);
expect(modal.header).toBe("Test Header");
});
it("should set the header to the default if no header function is provided", async () => {
const deleteAction = vi.fn();
const question = "Are you sure you want to delete this database?";
const deleteConfirmation = convertArrayOfStringsToContent([
question,
"This will permanently delete all data.",
]);
const wrapper = mount(AgdbCellMenu, {
props: {
actions: [
{
key: "delete",
label: "Delete",
action: deleteAction,
confirmation: deleteConfirmation,
},
],
},
global: {
provide: {
[INJECT_KEY_ROW]: {
value: {
role: "admin",
owner: "admin",
db: "test",
db_type: "memory",
size: 2656,
backup: 0,
},
},
},
},
});
const trigger = wrapper.find(".trigger");
expect(wrapper.find(".content").exists()).toBe(false);
trigger.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").isVisible()).toBe(true);
const action = wrapper.find(".menu-item[data-key=delete]");
await action.trigger("click");
await wrapper.vm.$nextTick();
expect(wrapper.find(".content").exists()).toBe(false);
expect(modal.content[0].paragraph?.at(0)?.text).toBe(question);
expect(modal.header).toBe("Confirm action");
});
});
9 changes: 8 additions & 1 deletion agdb_studio/src/components/base/table/AgdbCellMenu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,14 @@ const mapActions = (actions: Action[]): Action[] => {
: action.confirmation
? ({ event }: ActionProps<undefined>) =>
showModal({
header: "Confirm action",
header: action.confirmationHeader
? typeof action.confirmationHeader ===
"function"
? action.confirmationHeader({
params: row?.value,
})
: action.confirmationHeader
: "Confirm action",
content: action.confirmation,
onConfirm: () =>
runAction({ event, params: undefined }),
Expand Down
59 changes: 58 additions & 1 deletion agdb_studio/src/composables/db/dbConfig.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { describe, it, expect, beforeEach, vi } from "vitest";
import { dbActions, dbColumns } from "./dbConfig";
import {
dbActions,
dbColumns,
getConfirmationHeaderFn,
type DbActionProps,
} from "./dbConfig";
import {
db_backup,
db_restore,
Expand All @@ -15,9 +20,12 @@ import {
import { useContentInputs } from "../content/inputs";
import { KEY_MODAL } from "../modal/constants";
import { ref } from "vue";
import useModal from "../modal/modal";

const { addInput, setInputValue, clearAllInputs } = useContentInputs();

const { modalIsVisible, modal } = useModal();

describe("dbConfig", () => {
describe("dbColumns", () => {
it("should have correct db columns", () => {
Expand Down Expand Up @@ -108,5 +116,54 @@ describe("dbConfig", () => {
expect(api).not.toHaveBeenCalled();
clearAllInputs();
});

it("should print the empty audit log", async () => {
const action = dbActions.find((action) => action.key === "audit");
const params = { db: "test_db", owner: "test_owner" };
await action?.action({ params });
expect(db_audit).toHaveBeenCalledWith(params);

expect(modalIsVisible.value).toBe(true);
expect(modal.header).toBe("Audit log of test_owner/test_db");
expect(modal.content).toHaveLength(1);
});
it("should print the audit log", async () => {
const action = dbActions.find((action) => action.key === "audit");
const params = { db: "test_db", owner: "test_owner" };
db_audit.mockResolvedValueOnce({
data: [
{
timestamp: "123",
user: "test_user",
query: "test_query",
},
{
timestamp: "456",
user: "test_user2",
query: "test_query2",
},
],
});
await action?.action({ params });
expect(db_audit).toHaveBeenCalledWith(params);

expect(modalIsVisible.value).toBe(true);
expect(modal.header).toBe("Audit log of test_owner/test_db");
expect(modal.content).toHaveLength(2);
expect(modal.content[0].paragraph?.at(0)?.text).toBe(
"123 | test_user | test_query",
);
expect(modal.content[1].paragraph?.at(0)?.text).toBe(
"456 | test_user2 | test_query2",
);
});
});
describe("getConfirmationHeaderFn", () => {
it("should return correct header", () => {
const header = getConfirmationHeaderFn({
params: { db: "test_db", owner: "test_owner" },
} as unknown as DbActionProps);
expect(header).toBe("Confirm action for test_owner/test_db");
});
});
});
38 changes: 35 additions & 3 deletions agdb_studio/src/composables/db/dbConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,35 @@ import { dateFormatter } from "@/composables/table/utils";
import { convertArrayOfStringsToContent } from "@/composables/content/utils";
import { useContentInputs } from "../content/inputs";
import { KEY_MODAL } from "../modal/constants";
import useModal from "../modal/modal";

const { getInputValue } = useContentInputs();
const { showModal } = useModal();

export type DbActionProps = ActionProps<ServerDatabase>;

const getConfirmationHeaderFn = ({ params }: DbActionProps) =>
`Confirm action for ${params.owner}/${params.db}`;

type DbActionProps = ActionProps<ServerDatabase>;
const dbActions: Action[] = [
{
key: "audit",
label: "Audit",
action: ({ params }: DbActionProps) =>
client.value?.db_audit(params).then((res) => {
console.log(res.data);
const content = res.data.length
? convertArrayOfStringsToContent(
res.data.map(
(item) =>
`${item.timestamp} | ${item.user} | ${item.query}`,
),
)
: convertArrayOfStringsToContent(["No audit logs found."]);

showModal({
header: `Audit log of ${params.owner}/${params.db}`,
content,
});
}),
},
{
Expand All @@ -30,6 +48,7 @@ const dbActions: Action[] = [
"This will swap the existing backup with the current db.",
]),
],
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "clear",
Expand All @@ -47,6 +66,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["clear", "all"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "db",
Expand All @@ -60,6 +80,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["clear", "database"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "audit",
Expand All @@ -72,6 +93,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["clear", "audit"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "backup",
Expand All @@ -84,6 +106,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["clear", "backup"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
],
},
Expand All @@ -102,6 +125,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["convert", "memory"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "file",
Expand All @@ -114,6 +138,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["convert", "file"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "mapped",
Expand All @@ -126,6 +151,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["convert", "mapped"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
],
},
Expand All @@ -152,6 +178,7 @@ const dbActions: Action[] = [
},
},
],
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "delete",
Expand All @@ -167,6 +194,7 @@ const dbActions: Action[] = [
{ emphesizedWords: ["all data"] },
),
],
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "optimize",
Expand All @@ -177,6 +205,7 @@ const dbActions: Action[] = [
["Are you sure you want to optimize this database?"],
{ emphesizedWords: ["optimize"] },
),
confirmationHeader: getConfirmationHeaderFn,
},

{
Expand All @@ -190,6 +219,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["remove"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "rename",
Expand All @@ -214,6 +244,7 @@ const dbActions: Action[] = [
},
},
],
confirmationHeader: getConfirmationHeaderFn,
},
{
key: "restore",
Expand All @@ -226,6 +257,7 @@ const dbActions: Action[] = [
],
{ emphesizedWords: ["restore"] },
),
confirmationHeader: getConfirmationHeaderFn,
},
];

Expand All @@ -247,4 +279,4 @@ const dbColumns = [
},
];

export { dbActions, dbColumns };
export { dbActions, dbColumns, getConfirmationHeaderFn };
Loading

0 comments on commit 9617b88

Please sign in to comment.