Skip to content

Commit

Permalink
Merge pull request #26 from bruinenxyz/hungle/bru-1047-create-databas…
Browse files Browse the repository at this point in the history
…e-management-page

BRU-1047 Create database management page
  • Loading branch information
hungle-bruinen authored Apr 4, 2024
2 parents 6b2c55a + 3ee3782 commit 04c2920
Show file tree
Hide file tree
Showing 4 changed files with 350 additions and 0 deletions.
121 changes: 121 additions & 0 deletions frontend/src/app/databases/[databaseId]/database-details.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
"use client";
import {
Button,
Divider,
H6,
Icon,
IconName,
InputGroup,
MenuItem,
Text,
TextArea,
} from "@blueprintjs/core";
import { Select } from "@blueprintjs/select";
import Loading from "@/app/loading";
import { ErrorDisplay } from "@/components/error-display";
import ColorPicker from "@/components/color-picker";
import IconPicker from "@/components/icon-picker";
import { useState } from "react";
import { useField } from "@/utils/use-field";
import * as _ from "lodash";
import { CleanDatabase, UpdateDatabaseSchema } from "@/definitions";
import { useUpdateDatabase } from "@/data/use-database";

const DatabaseDetails = ({ database }: { database: CleanDatabase }) => {
const [editDetails, setEditDetails] = useState<boolean>(false);
const nameField = useField<string>(database.name);

const { trigger: updateDatabase, isMutating } = useUpdateDatabase(
database.id,
);

function resetFields() {
nameField.onValueChange(database.name);
}

function isValidDatabaseUpdate() {
const result = UpdateDatabaseSchema.safeParse({
name: !nameField.value ? undefined : nameField.value,
});
return result.success;
}

async function handleUpdateDatabase() {
const pendingUpdate = {
name: nameField.value,
};
const parsedUpdate = UpdateDatabaseSchema.parse(pendingUpdate);
await updateDatabase(parsedUpdate);
setEditDetails(false);
}

function renderDatabaseDetails() {
return (
<>
<div className="flex flex-row items-center justify-between mb-2">
<H6 className="mb-0">Database Details</H6>
<Button
small
minimal
icon="edit"
text="Edit"
onClick={() => setEditDetails(true)}
/>
</div>
<div className="flex flex-col gap-2 mt-1">
<div className="flex flex-row justify-between">
<Text>Name</Text>
<Text className="ml-2 bp5-text-muted">{database.name}</Text>
</div>
</div>
</>
);
}

function renderEditableDetails() {
return (
<>
<H6 className="my-1">Database Details</H6>
{isMutating ? (
<Loading />
) : (
<div className="flex flex-col gap-3 mt-2">
<div className="flex flex-row items-center justify-between">
<Text>Name</Text>
<InputGroup id="name-input" {...nameField} />
</div>
<div className="flex flex-row items-center justify-end gap-2">
<Button
small
outlined
icon="cross"
intent="danger"
text="Cancel"
onClick={() => {
setEditDetails(false);
resetFields();
}}
/>
<Button
small
icon="tick"
intent="primary"
text="Save"
disabled={!isValidDatabaseUpdate()}
onClick={handleUpdateDatabase}
/>
</div>
</div>
)}
</>
);
}

return (
<div className="flex flex-col h-full p-3 overflow-y-auto">
{editDetails ? renderEditableDetails() : renderDatabaseDetails()}
</div>
);
};

export default DatabaseDetails;
93 changes: 93 additions & 0 deletions frontend/src/app/databases/[databaseId]/delete-database.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { useField } from "@/utils/use-field";
import { useRouter } from "next/navigation";
import {
Button,
Dialog,
DialogBody,
DialogFooter,
Divider,
Callout,
FormGroup,
InputGroup,
Spinner,
SpinnerSize,
Text,
} from "@blueprintjs/core";
import { useState } from "react";
import { useSWRConfig } from "swr";
import { useDeleteDatabase } from "@/data/use-database";

export default function DeleteDatabase({ database, onClose }: any) {
const router = useRouter();
const { mutate } = useSWRConfig();
const [isOpen, setIsOpen] = useState(true);
const [isSubmitting, setIsSubmitting] = useState(false);

const { isMutating, trigger: deleteDatabase } = useDeleteDatabase(
database.id,
);

const confirmField = useField<string>("");

async function submitDelete() {
setIsOpen(false);
setIsSubmitting(true);
await deleteDatabase();
mutate(`/databases`);
setIsSubmitting(false);
onClose(false);
router.push(`/databases`);
}

return (
<>
<Dialog isOpen={isSubmitting} title="Deleting database...">
<DialogBody>
<Spinner size={SpinnerSize.STANDARD} />
</DialogBody>
</Dialog>
<Dialog
isOpen={isOpen}
isCloseButtonShown
title="Delete database"
onClose={() => {
onClose(false);
}}
>
<DialogBody>
<div className="flex flex-row">
<Text>Name:</Text>
<Text className="ml-2 bp5-text-muted">{database.name}</Text>
</div>
<Divider className="my-2" />
<Callout
intent="warning"
icon="warning-sign"
title="Database deletion is permanent"
/>
<Text className="mt-2 text-center">
To confirm deletion, please type the database name in the field
below before clicking submit.
</Text>
<FormGroup className="mt-2">
<InputGroup
id="confirm-input"
{...confirmField}
autoFocus={false}
/>
</FormGroup>
</DialogBody>
<DialogFooter
actions={
<Button
intent="danger"
text="Delete"
disabled={confirmField.value !== database.name}
onClick={() => submitDelete()}
/>
}
/>
</Dialog>
</>
);
}
122 changes: 122 additions & 0 deletions frontend/src/app/databases/[databaseId]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
"use client";
import {
Button,
IconName,
Menu,
MenuItem,
Popover,
Section,
Tab,
Tabs,
Tooltip,
H4,
} from "@blueprintjs/core";
import Loading from "@/app/loading";
import { ErrorDisplay } from "@/components/error-display";
import SquareIcon, { SquareIconSize } from "@/components/icon/square-icon";
import DatabaseDetails from "./database-details";
import React, { useState } from "react";
import * as _ from "lodash";
import DeleteDatabase from "./delete-database";
import { useDatabase } from "@/data/use-database";

enum DatabaseTabEnum {
DETAILS = "DETAILS",
}

export default function Page({ params }: { params: { databaseId: string } }) {
const [currentTab, setCurrentTab] = useState<DatabaseTabEnum>(
DatabaseTabEnum.DETAILS,
);
const [deleteDatabaseOpen, setDeleteDatabaseOpen] = useState<boolean>(false);
//const hasPermissionEditOntology = useUserPermission(ONTOLOGY_EDIT_PERMISSION);

const {
data: database,
isLoading: isLoadingDatabase,
error: databaseError,
} = useDatabase(params.databaseId);

if (isLoadingDatabase) {
return <Loading />;
}

if (databaseError || !database) {
console.log("Database error", databaseError);
return (
<ErrorDisplay
title="Cannot get database details"
description={databaseError.message}
/>
);
}

function renderRightElement() {
return (
<div className="flex flex-row items-center shrink-0">
<Tabs
animate
selectedTabId={currentTab}
id="section-tabs"
key="horizontal"
renderActiveTabPanelOnly={true}
onChange={(tabId: DatabaseTabEnum) => setCurrentTab(tabId)}
>
<Tab
id={DatabaseTabEnum.DETAILS}
title={
<Button
className="bp5-minimal"
icon="application"
text="Details"
/>
}
/>
</Tabs>
<Popover
placement="bottom-start"
content={
<Menu className="flex flex-col">
<MenuItem
icon="trash"
text="Delete database"
onClick={() => setDeleteDatabaseOpen(true)}
/>
</Menu>
}
>
<Button
className="ml-3"
alignText="left"
rightIcon="caret-down"
text="Options"
/>
</Popover>
</div>
);
}

function renderObjectContent() {
switch (currentTab) {
default:
return <DatabaseDetails database={database!} />;
}
}

return (
<Section
className="flex flex-col max-h-full col-span-2"
icon={
<SquareIcon icon={"cube"} color={"gray"} size={SquareIconSize.LARGE} />
}
title={<H4 className="m-0">{database!.name}</H4>}
subtitle={database?.created_at.toString().split("T")[0]}
rightElement={renderRightElement()}
>
{renderObjectContent()}
{deleteDatabaseOpen && (
<DeleteDatabase database={database!} onClose={setDeleteDatabaseOpen} />
)}
</Section>
);
}
14 changes: 14 additions & 0 deletions frontend/src/data/use-database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ export const useDatabases = () => {
return { data, error, isLoading, isValidating, mutate };
};

export const useDatabase = (id?: string) => {
const { data, error, isLoading, isValidating, mutate } =
useSWR<CleanDatabase>(id ? `/databases/${id}` : null, backendGet);
return { data, error, isLoading, isValidating, mutate };
};

export const useCreateDatabase = () => {
const { data, error, trigger, isMutating } = useSWRMutation(
"/databases",
Expand Down Expand Up @@ -50,3 +56,11 @@ export const useUpdateDatabase = (id: string) => {

return { data, error, trigger, isMutating };
};

export const useDeleteDatabase = (id?: string) => {
const { data, error, trigger, isMutating } = useSWRMutation<string[]>(
id ? `/databases/${id}` : null,
backendDelete,
);
return { data, error, trigger, isMutating };
};

0 comments on commit 04c2920

Please sign in to comment.