Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,35 @@ import {
} from '@/components/ui/dialog';
import { ArrowLeft, Trash, TriangleAlert } from 'lucide-react';

export function DeleteClusterConfirmationModal({
clusterInfo,
export function ConfirmDeletionModal({
typeOfThingBeingDeleted,
nameOfThingBeingDeleted,
isModalOpen,
isDeletingClusterPending,
handleDeleteCluster,
setIsModalOpen,
deletionConfirmed,
deletionPending,
}: {
clusterInfo: { id: string; name: string };
typeOfThingBeingDeleted: string;
nameOfThingBeingDeleted: string;
isModalOpen: boolean;
isDeletingClusterPending: boolean;
handleDeleteCluster: () => void;
setIsModalOpen: (isOpen: boolean) => void;
deletionConfirmed: () => void;
deletionPending: boolean;
}) {
return (
<Dialog open={isModalOpen} onOpenChange={setIsModalOpen}>
<DialogContent className="sm:max-w-[750px]">
<DialogHeader>
<DialogTitle>Are you sure you want to delete this cluster?</DialogTitle>
<DialogTitle>Are you sure you want to delete this {typeOfThingBeingDeleted}?</DialogTitle>
<DialogDescription>This action cannot be undone.</DialogDescription>
</DialogHeader>
<div className="p-3 my-5 text-white rounded-md bg-amber-600">
<p className="flex space-x-1 font-semibold align-baseline">
<TriangleAlert className="inline-block size-5" /> <span>Warning</span>
</p>
<p className="pt-2 text-base">
By deleting cluster <span className="font-semibold">{clusterInfo.name}</span> you will lose the data stored
in it permanently.
By deleting {typeOfThingBeingDeleted} <span className="font-semibold">{nameOfThingBeingDeleted} </span>
you will lose the data stored in it permanently.
</p>
</div>
<DialogFooter>
Expand All @@ -46,8 +48,8 @@ export function DeleteClusterConfirmationModal({
<Button
variant="destructive"
className="rounded-full"
onClick={handleDeleteCluster}
disabled={isDeletingClusterPending}
onClick={deletionConfirmed}
disabled={deletionPending}
>
<Trash /> Delete
</Button>
Expand Down
27 changes: 14 additions & 13 deletions src/features/clusters/ClustersList.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { getRouteApi } from '@tanstack/react-router';
import { ClusterCard } from '@/features/organization/components/ClusterCard';
import { Plus } from 'lucide-react';
import { ConfirmDeletionModal } from '@/components/ConfirmDeletionModal';
import { Button } from '@/components/ui/button';
import { getOrganizationQueryOptions } from '@/features/organization/queries/getOrganizationQuery';
import { NewClusterModal } from '@/features/clusters/modals/NewClusterModal';
import { ClusterCard } from '@/features/organization/components/ClusterCard';
import { getOrganizationQueryOptions } from '@/features/organization/queries/getOrganizationQuery';
import { sortByName } from '@/lib/arrays/sort/byName';
import { groupBy } from '@/lib/group-by';
import { queryKeys } from '@/react-query/constants';
import { useQueryClient, useSuspenseQuery } from '@tanstack/react-query';
import { getRouteApi } from '@tanstack/react-router';
import { Plus } from 'lucide-react';
import { useCallback, useMemo, useState } from 'react';
import { DeleteClusterConfirmationModal } from './modals/DeleteClusterConfirmationModal';
import { useDeleteClusterMutation } from './mutations/deleteCluster';
import { toast } from 'sonner';
import { queryKeys } from '@/react-query/constants';
import { groupBy } from '@/lib/group-by';
import { sortByName } from '@/lib/arrays/sort/byName';
import { useDeleteClusterMutation } from './mutations/deleteCluster';

const route = getRouteApi('');

Expand Down Expand Up @@ -139,12 +139,13 @@ export function ClustersList() {
isModalOpen={isNewClusterModalOpen}
setIsModalOpen={() => setIsNewClusterModalOpen(false)}
/>
<DeleteClusterConfirmationModal
clusterInfo={deleteClusterInfo}
<ConfirmDeletionModal
typeOfThingBeingDeleted="cluster"
nameOfThingBeingDeleted={deleteClusterInfo.name}
isModalOpen={isDeleteClusterModalOpen}
isDeletingClusterPending={isDeletingClusterPending}
handleDeleteCluster={() => handleDeleteCluster(deleteClusterInfo)}
setIsModalOpen={() => setIsDeleteClusterModalOpen(false)}
deletionConfirmed={() => handleDeleteCluster(deleteClusterInfo)}
deletionPending={isDeletingClusterPending}
/>
</>
);
Expand Down
125 changes: 77 additions & 48 deletions src/features/instance/browse/components/BrowseSidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
import { useState } from 'react';
import { getRouteApi, useRouter } from '@tanstack/react-router';
import { useQueryClient } from '@tanstack/react-query';
import { useForm } from 'react-hook-form';
import { zodResolver } from '@hookform/resolvers/zod';
import { z } from 'zod';
import { CreateNewTableModal } from '@/features/instance/browse/modals/CreateNewTableModal';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { ConfirmDeletionModal } from '@/components/ConfirmDeletionModal';
import { Button } from '@/components/ui/button';
import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form';
import { Input } from '@/components/ui/input';
import { ScrollArea } from '@/components/ui/scrollArea';
import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Button } from '@/components/ui/button';
import { ArrowRight, Check, Minus, Plus, Trash } from 'lucide-react';
import { CreateNewTableModal } from '@/features/instance/browse/modals/CreateNewTableModal';
import { useCreateDatabaseSubmitMutation } from '@/features/instance/operations/mutations/createDatabase';
import { Input } from '@/components/ui/input';
import { Form, FormField, FormItem, FormLabel, FormControl, FormMessage } from '@/components/ui/form';
import { toast } from 'sonner';
import { useDeleteDatabaseMutation } from '@/features/instance/operations/mutations/deleteDatabase';
import { DeleteTableData, useDeleteTableMutation } from '@/features/instance/operations/mutations/deleteTable';
import { useDeleteTableMutation } from '@/features/instance/operations/mutations/deleteTable';
import { zodResolver } from '@hookform/resolvers/zod';
import { useQueryClient } from '@tanstack/react-query';
import { getRouteApi, useRouter } from '@tanstack/react-router';
import { ArrowRight, Check, Minus, Plus, Trash } from 'lucide-react';
import { useCallback, useState } from 'react';
import { useForm } from 'react-hook-form';
import { toast } from 'sonner';
import { z } from 'zod';

type BrowseSidebarProps = {
databases: string[];
Expand All @@ -40,6 +41,10 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
const router = useRouter();
const queryClient = useQueryClient();
const { instanceId, schemaName, tableName } = route.useParams();
const [typeOfThingBeingDeleted, setTypeOfThingBeingDeleted] = useState("");
const [nameOfThingBeingDeleted, setNameOfThingBeingDeleted] = useState("");
const [deletionTarget, setDeletionTarget] = useState<{ databaseName?: string; tableName?: string; }>({});
const [isDeleteModalOpen, setIsDeleteModalOpen] = useState(false);

const [isCreatingDatabase, setIsCreatingDatabase] = useState(false);

Expand All @@ -51,8 +56,8 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
});

const { mutate: createNewDatabase } = useCreateDatabaseSubmitMutation();
const { mutate: deleteDatabase } = useDeleteDatabaseMutation();
const { mutate: deleteTable } = useDeleteTableMutation();
const { mutate: deleteDatabase, isPending: isDeletingDatabase } = useDeleteDatabaseMutation();
const { mutate: deleteTable, isPending: isDeletingTable } = useDeleteTableMutation();

const submitNewDatabase = (formData: z.infer<typeof NewDatabaseSchema>) => {
createNewDatabase(formData, {
Expand All @@ -67,34 +72,41 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
});
};

const deleteSelectedDatabase = (databaseName: string) => {
deleteDatabase(databaseName, {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: [instanceId, 'describe_all'], refetchType: 'all' });
await router.invalidate();
toast.success(`Database ${databaseName} deleted successfully`);
onSelectDatabase(undefined);
},
});
};

const deleteSelectedTable = (data: DeleteTableData) => {
deleteTable(data, {
onSuccess: async () => {
await queryClient.invalidateQueries({ queryKey: [instanceId, 'describe_all'], refetchType: 'all' });
await router.invalidate();
toast.success(`Table ${data.tableName} deleted successfully`);
if (data.tableName === tableName) {
onSelectTable(undefined);
}
},
});
};

const handleDeleteTable = (tableName: string) => {
if (!tableName) return;
deleteSelectedTable({ databaseName: schemaName, tableName });
};
const onDeletionConfirmed = useCallback(() => {
const targetDatabaseName = deletionTarget.databaseName;
const targetTableName = deletionTarget.tableName;
if (targetDatabaseName) {
if (targetTableName) {
deleteTable({ databaseName: targetDatabaseName, tableName: targetTableName }, {
onSuccess: async () => {
setIsDeleteModalOpen(false);
await queryClient.invalidateQueries({
queryKey: [instanceId, 'describe_all'],
refetchType: 'all',
});
await router.invalidate();
toast.success(`Table ${targetTableName} deleted successfully`);
if (targetTableName === tableName) {
onSelectTable(undefined);
}
},
});
} else {
deleteDatabase(targetDatabaseName, {
onSuccess: async () => {
setIsDeleteModalOpen(false);
await queryClient.invalidateQueries({
queryKey: [instanceId, 'describe_all'],
refetchType: 'all',
});
await router.invalidate();
toast.success(`Database ${targetDatabaseName} deleted successfully`);
onSelectDatabase(undefined);
},
});
}
}
}, [deleteDatabase, deleteTable, deletionTarget, instanceId, onSelectDatabase, onSelectTable, queryClient, router, tableName]);

return (
<div>
Expand Down Expand Up @@ -135,8 +147,12 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
variant="destructiveOutline"
disabled={!schemaName}
onClick={() => {
if (!schemaName) return;
deleteSelectedDatabase(schemaName);
if (schemaName) {
setTypeOfThingBeingDeleted("database");
setNameOfThingBeingDeleted(schemaName);
setDeletionTarget({ databaseName: schemaName });
setIsDeleteModalOpen(true);
}
}}
>
<Trash />
Expand Down Expand Up @@ -191,9 +207,14 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
{(tables ?? []).map((table) => (
<li key={table} className="flex items-center p-2 border-b hover:bg-grey-700/80 border-grey-700">
<Button
variant="defaultOutline"
variant="destructiveOutline"
onClick={() => {
handleDeleteTable(table);
if (table) {
setTypeOfThingBeingDeleted("table");
setNameOfThingBeingDeleted(table);
setDeletionTarget({ databaseName: schemaName, tableName: table });
setIsDeleteModalOpen(true);
}
}}
>
<Trash className="inline-block " />
Expand All @@ -217,6 +238,14 @@ export function BrowseSidebar({ databases, onSelectDatabase, tables, onSelectTab
{schemaName?.length && (
<CreateNewTableModal databaseName={schemaName} instanceId={instanceId} onSelectTable={onSelectTable} />
)}
<ConfirmDeletionModal
typeOfThingBeingDeleted={typeOfThingBeingDeleted}
nameOfThingBeingDeleted={nameOfThingBeingDeleted}
isModalOpen={isDeleteModalOpen}
setIsModalOpen={() => setIsDeleteModalOpen(false)}
deletionConfirmed={onDeletionConfirmed}
deletionPending={isDeletingDatabase || isDeletingTable}
/>
</div>
);
}