Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New Database Deletion Flow #1293

Merged
merged 26 commits into from
Nov 28, 2024
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
ada3580
remove: checkboxes at the database level.
ItzNotABug Aug 13, 2024
8e77586
add: `collections` to the `store`.
ItzNotABug Aug 14, 2024
5455d4f
add: loaded `collections` to store.
ItzNotABug Aug 14, 2024
35c2bab
add: load `collections` if empty.
ItzNotABug Aug 14, 2024
b9e60ac
update: settings page content.
ItzNotABug Aug 14, 2024
c8a8735
update: new deletion flow modal.
ItzNotABug Aug 14, 2024
9c73f19
lint: format.
ItzNotABug Aug 14, 2024
8121e05
update: message.
ItzNotABug Aug 19, 2024
4fff9bb
address comments: max dialog height, load collections on demand.
ItzNotABug Aug 19, 2024
37b3724
ran: `npm run format`.
ItzNotABug Aug 19, 2024
e93ec9e
address comments: load 25 items on iteration, add a `show less` option.
ItzNotABug Aug 20, 2024
ed13848
address comments: make table horizontally scrollable.
ItzNotABug Aug 20, 2024
c827934
update: increase width.
ItzNotABug Aug 20, 2024
c4a1e63
revert: `--host` flag
ItzNotABug Aug 20, 2024
3ab08d7
fix: remove inner table's borders.
ItzNotABug Aug 21, 2024
d5ebe37
Merge branch 'database-deletion-new-flow' of https://github.com/ItzNo…
ItzNotABug Aug 21, 2024
76e0190
ran: `npm run format`.
ItzNotABug Aug 21, 2024
6df67b8
Merge branch 'main' into database-deletion-new-flow.
ItzNotABug Aug 27, 2024
2df4bea
revert: pnpm-lock.yaml
ItzNotABug Aug 27, 2024
fcbedbc
Merge branch 'main' into `database-deletion-new-flow`.
ItzNotABug Oct 3, 2024
54b87a0
address comments.
ItzNotABug Oct 3, 2024
4a1c2fe
address comments.
ItzNotABug Oct 3, 2024
607567c
address comments.
ItzNotABug Oct 3, 2024
2519693
address comments.
ItzNotABug Oct 3, 2024
0af3bba
Merge branch 'main' into 'database-deletion-new-flow'.
ItzNotABug Nov 26, 2024
5ddd465
update: address design comments.
ItzNotABug Nov 27, 2024
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 @@ -3,22 +3,26 @@ import { sdk } from '$lib/stores/sdk';
import { getLimit, getPage, getView, pageToOffset, View } from '$lib/helpers/load';
import type { PageLoad } from './$types';
import { CARD_LIMIT, Dependencies } from '$lib/constants';
import { collections as collectionsStore } from './store';

export const load: PageLoad = async ({ params, url, route, depends }) => {
depends(Dependencies.COLLECTIONS);
const page = getPage(url);
const limit = getLimit(url, route, CARD_LIMIT);
const view = getView(url, route, View.Grid);
const offset = pageToOffset(page, limit);
const collections = await sdk.forProject.databases.listCollections(params.database, [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('')
]);

collectionsStore.set(collections);

return {
offset,
limit,
view,
collections: await sdk.forProject.databases.listCollections(params.database, [
Query.limit(limit),
Query.offset(offset),
Query.orderDesc('')
])
collections
};
};
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,56 @@
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Modal } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Button, FormList, InputText } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { database } from './store';
import { database, collections } from './store';
import {
TableBody,
TableCellHead,
TableHeader,
TableCell,
TableRow,
TableScroll
} from '$lib/elements/table';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Query } from '@appwrite.io/console';
const databaseId = $page.params.database;

export let showDelete = false;

let error = null;
let databaseName: string = null;
let isLoadingDocumentsCount = false;

async function listCollections() {
isLoadingDocumentsCount = true;

try {
/* can we improve this? */
const collectionPromises = $collections.collections.map(async (collection) => {
const { total: totalDocuments } = await sdk.forProject.databases.listDocuments(
databaseId,
collection.$id,
[Query.limit(1)] /* limit 1, just to get the total count */
);

return {
id: collection.$id,
name: collection.name,
count: totalDocuments,
updatedAt: collection.$updatedAt
};
});

return await Promise.all(collectionPromises);
} catch {
error = true;
} finally {
isLoadingDocumentsCount = false;
}
}

const handleDelete = async () => {
try {
await sdk.forProject.databases.delete(databaseId);
Expand Down Expand Up @@ -39,11 +81,84 @@
bind:show={showDelete}
onSubmit={handleDelete}
headerDivider={false}>
<p class="text" data-private>
Are you sure you want to delete <b>{$database.name}</b>?
</p>
{#await listCollections()}
<div class="u-flex u-main-center">
<div class="loader" />
</div>
{:then collections}
{#if error}
<p class="text" data-private>
Are you sure you want to delete <b>{$database.name}</b>?
</p>
{:else if collections.length > 0}
<p class="text" data-private>
The following collections and all data associated with <b>{$database.name}</b> will
be permanently deleted.
<b>This action is irreversible.</b>
</p>

<TableScroll dense noMargin isSticky class="table limited-modal-height">
<TableHeader>
<TableCellHead>Collection</TableCellHead>
<TableCellHead># of Documents</TableCellHead>
<TableCellHead>Last Updated</TableCellHead>
</TableHeader>
<TableBody>
{#each collections as collection}
<TableRow>
<TableCell title="Collection">{collection.name}</TableCell>
<TableCell title="Documents">{collection.count}</TableCell>
<TableCell>{toLocaleDateTime(collection.updatedAt)}</TableCell>
</TableRow>
{/each}
</TableBody>
</TableScroll>

<FormList class="u-padding-block-start-24">
<InputText
label={`Confirm the database name to continue`}
placeholder="Enter {$database.name} to continue"
id="database-name"
autofocus
required
bind:value={databaseName} />
</FormList>
{:else}
<p class="text" data-private>
Are you sure you want to delete <b>{$database.name}</b>?
</p>
{/if}
{/await}

<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
<Button secondary submit>Delete</Button>
<Button
secondary
submit
disabled={isLoadingDocumentsCount ||
($collections.total > 0 &&
!error &&
(!databaseName || databaseName !== $database.name))}>
Delete
</Button>
</svelte:fragment>
</Modal>

<style>
:global(.limited-modal-height .table-wrapper) {
max-height: 450px; /* tentative, 650px [in design] looks too big and requires a scroll */
overflow-y: scroll;
scrollbar-width: none;
-ms-overflow-style: none;
}

:global(.limited-modal-height .table-wrapper)::-webkit-scrollbar {
display: none;
}

@media (max-width: 1024px) {
:global(.limited-modal-height .table-wrapper) {
max-height: 275px;
}
}
</style>
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
import { sdk } from '$lib/stores/sdk';
import { onMount } from 'svelte';
import Delete from '../delete.svelte';
import { database } from '../store';
import { database, collections } from '../store';

let showDelete = false;
let showError: false | 'name' | 'email' | 'password' = false;
Expand Down Expand Up @@ -93,14 +93,14 @@
</div>

<p>
The database will be permanently deleted, including all data associated with this
team. This action is irreversible.
The database will be permanently deleted, including all the collections within it.
ItzNotABug marked this conversation as resolved.
Show resolved Hide resolved
This action is irreversible.
</p>
<svelte:fragment slot="aside">
<BoxAvatar>
<svelte:fragment slot="title">
<h6 class="u-bold u-trim-1">{$database.name}</h6>
<span>Last updated: {toLocaleDateTime($database.$updatedAt)}</span>
<span>{$collections.total} Collections</span>
</svelte:fragment>
</BoxAvatar>
</svelte:fragment>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { get } from 'svelte/store';
import { Dependencies } from '$lib/constants';
import { collections as collectionsStore } from '../store';
import { sdk } from '$lib/stores/sdk';

export async function load({ depends, params }) {
depends(Dependencies.COLLECTIONS);

let collections = get(collectionsStore);

if (collections.total === 0) {
collections = await sdk.forProject.databases.listCollections(params.database);
collectionsStore.set(collections);
}

return { collections };
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { derived, writable } from 'svelte/store';

export const database = derived(page, ($page) => $page.data.database as Models.Database);
export const showCreate = writable(false);
export const collections = writable<Models.CollectionList>({ total: 0, collections: [] });

export const columns = writable<Column[]>([
{ id: '$id', title: 'Collection ID', type: 'string', show: true, width: 150 },
Expand Down
97 changes: 2 additions & 95 deletions src/routes/console/project-[project]/databases/table.svelte
Original file line number Diff line number Diff line change
@@ -1,66 +1,26 @@
<script lang="ts">
import { invalidate } from '$app/navigation';
import { base } from '$app/paths';
import { page } from '$app/stores';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { Id, Modal } from '$lib/components';
import FloatingActionBar from '$lib/components/floatingActionBar.svelte';
import { Dependencies } from '$lib/constants';
import { Button } from '$lib/elements/forms';
import { Id } from '$lib/components';
import {
TableBody,
TableCell,
TableCellHead,
TableCellHeadCheck,
TableCellText,
TableHeader,
TableRowLink,
TableScroll,
TableCellCheck
TableScroll
} from '$lib/elements/table';
import { toLocaleDateTime } from '$lib/helpers/date';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import type { PageData } from './$types';
import { columns } from './store';

export let data: PageData;
const projectId = $page.params.project;

let selected: string[] = [];
let showDelete = false;
let deleting = false;

async function handleDelete() {
showDelete = false;

const promises = selected.map((databaseId) => sdk.forProject.databases.delete(databaseId));
try {
await Promise.all(promises);
trackEvent(Submit.DatabaseDelete);
addNotification({
type: 'success',
message: `${selected.length} database${selected.length > 1 ? 's' : ''} deleted`
});
invalidate(Dependencies.DATABASES);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
trackError(error, Submit.DatabaseDelete);
} finally {
selected = [];
showDelete = false;
}
}
</script>

<TableScroll>
<TableHeader>
<TableCellHeadCheck
bind:selected
pageItemsIds={data.databases.databases.map((c) => c.$id)} />
{#each $columns as column}
{#if column.show}
<TableCellHead width={column.width}>{column.title}</TableCellHead>
Expand All @@ -71,7 +31,6 @@
{#each data.databases.databases as database}
<TableRowLink
href={`${base}/console/project-${projectId}/databases/database-${database.$id}`}>
<TableCellCheck bind:selectedIds={selected} id={database.$id} />
{#each $columns as column}
{#if column.show}
{#if column.id === '$id'}
Expand All @@ -97,55 +56,3 @@
{/each}
</TableBody>
</TableScroll>

<FloatingActionBar show={selected.length > 0}>
<div class="u-flex u-cross-center u-main-space-between actions">
<div class="u-flex u-cross-center u-gap-8">
<span class="indicator body-text-2 u-bold">{selected.length}</span>
<p>
<span class="is-only-desktop">
{selected.length > 1 ? 'databases' : 'database'}
</span>
selected
</p>
</div>

<div class="u-flex u-cross-center u-gap-8">
<Button text on:click={() => (selected = [])}>Cancel</Button>
<Button secondary on:click={() => (showDelete = true)}>
<p>Delete</p>
</Button>
</div>
</div>
</FloatingActionBar>

<Modal
title="Delete Database"
icon="exclamation"
state="warning"
bind:show={showDelete}
onSubmit={handleDelete}
headerDivider={false}
closable={!deleting}>
<p class="text" data-private>
Are you sure you want to delete <b>{selected.length}</b>
{selected.length > 1 ? 'databases' : 'database'}?
</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showDelete = false)} disabled={deleting}>Cancel</Button>
<Button secondary submit disabled={deleting}>Delete</Button>
</svelte:fragment>
</Modal>

<style lang="scss">
.actions {
.indicator {
border-radius: 0.25rem;
background: hsl(var(--color-information-100));
color: hsl(var(--color-neutral-0));

padding: 0rem 0.375rem;
display: inline-block;
}
}
</style>