diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 6880f1c2f4..d02f667316 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -277,6 +277,7 @@ export enum Submit { DatabaseDelete = 'submit_database_delete', DatabaseUpdateName = 'submit_database_update_name', DatabaseImportCsv = 'submit_database_import_csv', + DatabaseBackupDelete = 'submit_database_backup_delete', ColumnCreate = 'submit_column_create', ColumnUpdate = 'submit_column_update', diff --git a/src/lib/components/multiSelectTable.svelte b/src/lib/components/multiSelectTable.svelte index cd35b38e65..ce0196e342 100644 --- a/src/lib/components/multiSelectTable.svelte +++ b/src/lib/components/multiSelectTable.svelte @@ -1,5 +1,13 @@ {#key computeKey} @@ -104,13 +186,7 @@ if (confirmDeletion) { showConfirmDeletion = true; } else { - const state = await onDelete?.(selectedRows); - if (state instanceof Error) { - // user should handle error on their own! - } else { - notifySuccess(); - selectedRows = []; - } + await consumeDeleteOperation(); } }}>Delete @@ -129,16 +205,13 @@ disableModal = true; onDeleteError = null; - const state = await onDelete?.(selectedRows); - if (state instanceof Error) { - disableModal = false; - onDeleteError = state.message || `Failed to delete ${resource}s`; - } else { - notifySuccess(); - selectedRows = []; - disableModal = false; + const allDeleted = await consumeDeleteOperation(); + + if (allDeleted) { showConfirmDeletion = false; } + + disableModal = false; }}> {@const selectionCount = selectedRows.length} diff --git a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte index 3912746505..e8b9c7a298 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/+page.svelte @@ -9,6 +9,7 @@ import { AvatarInitials, Copy, + type DeleteOperation, type DeleteOperationState, Empty, EmptySearch, @@ -60,20 +61,22 @@ ); } - async function handleDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((userId) => { - return sdk.forProject(page.params.region, page.params.project).users.delete({ userId }); - }); + async function handleDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((userId) => + sdk.forProject(page.params.region, page.params.project).users.delete({ userId }) + ); try { - await Promise.all(promises); - trackEvent(Submit.UserDelete, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.UserDelete); - return error; + if (result.error) { + trackError(result.error, Submit.UserDelete); + } else { + trackEvent(Submit.UserDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.USERS); } + + return result; } diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte index 18b2eb2d9e..8a58aec9a1 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/+page.svelte @@ -12,6 +12,7 @@ SearchQuery, PaginationWithLimit, type DeleteOperationState, + type DeleteOperation, MultiSelectionTable } from '$lib/components'; import Create from '../createTeam.svelte'; @@ -44,20 +45,22 @@ ); }; - async function handleDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((teamId) => { - return sdk.forProject(page.params.region, page.params.project).teams.delete({ teamId }); - }); + async function handleDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((teamId) => + sdk.forProject(page.params.region, page.params.project).teams.delete({ teamId }) + ); try { - await Promise.all(promises); - trackEvent(Submit.TeamDelete, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.TeamDelete); - return error; + if (result.error) { + trackError(result.error, Submit.TeamDelete); + } else { + trackEvent(Submit.TeamDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.TEAMS); } + + return result; } diff --git a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte index 98999a3059..d9368560f3 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/teams/team-[team]/members/+page.svelte @@ -6,6 +6,7 @@ AvatarInitials, PaginationWithLimit, MultiSelectionTable, + type DeleteOperation, type DeleteOperationState } from '$lib/components'; import { Button } from '$lib/elements/forms'; @@ -29,23 +30,25 @@ let showDelete = $state(false); let selectedMembership: Models.Membership | null = $state(null); - async function handleBulkDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((membershipId) => { - return sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ + async function handleBulkDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((membershipId) => + sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ teamId: page.params.team, membershipId - }); - }); + }) + ); try { - await Promise.all(promises); - trackEvent(Submit.MembershipUpdate, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.MembershipUpdate); - return error; + if (result.error) { + trackError(result.error, Submit.MembershipUpdate); + } else { + trackEvent(Submit.MembershipUpdate, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.MEMBERSHIPS); } + + return result; } diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte index 5d9e956afc..e1f053b952 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/identities/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte index 0c0b5525be..6f2b293555 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/memberships/+page.svelte @@ -4,6 +4,7 @@ import { AvatarInitials, type DeleteOperationState, + type DeleteOperation, MultiSelectionTable } from '$lib/components'; import { Button } from '$lib/elements/forms'; @@ -23,14 +24,14 @@ let showDelete = $state(false); let selectedMembership: Models.Membership | null = $state(null); - async function handleBulkDelete(selectedRows: string[]): Promise { + async function handleBulkDelete(batchDelete: DeleteOperation): Promise { // Precompute a lookup map from membershipId to teamId for efficient access const membershipIdToTeamId: Record = {}; for (const membership of data.memberships.memberships) { membershipIdToTeamId[membership.$id] = membership.teamId; } - const promises = selectedRows.map((membershipId) => + const result = await batchDelete((membershipId) => sdk.forProject(page.params.region, page.params.project).teams.deleteMembership({ teamId: membershipIdToTeamId[membershipId] || '', membershipId @@ -38,14 +39,16 @@ ); try { - await Promise.all(promises); - trackEvent(Submit.MembershipUpdate, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.MembershipUpdate); - return error; + if (result.error) { + trackError(result.error, Submit.MembershipUpdate); + } else { + trackEvent(Submit.MembershipUpdate, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.MEMBERSHIPS); } + + return result; } diff --git a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte index e71859a430..96414e5588 100644 --- a/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/auth/user-[user]/targets/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte index d335931601..7cee5aacc8 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/backups/table.svelte @@ -3,6 +3,7 @@ Card, Confirm, type DeleteOperationState, + type DeleteOperation, Modal, MultiSelectionTable } from '$lib/components'; @@ -18,7 +19,7 @@ import { columns } from './store'; import { database } from '../store'; import type { BackupArchive, BackupPolicy } from '$lib/sdk/backups'; - import { Click, trackEvent } from '$lib/actions/analytics'; + import { Click, Submit, trackError, trackEvent } from '$lib/actions/analytics'; import { copy } from '$lib/helpers/copy'; import { LabelCard } from '$lib/components/index.js'; import DualTimeView from '$lib/components/dualTimeView.svelte'; @@ -110,39 +111,44 @@ } } - async function deleteBackups(selectedRows: string[]): Promise { - const promises = selectedRows.map((archiveId) => { - return sdk + async function deleteSingleBackup(archiveId: string) { + try { + await sdk .forProject(page.params.region, page.params.project) .backups.deleteArchive(archiveId); - }); - try { - await Promise.all(promises); + addNotification({ + type: 'success', + message: 'Backup deleted' + }); - if (selectedBackup) { - addNotification({ - type: 'success', - message: '1 backup deleted' - }); - } + showDelete = false; + selectedBackup = null; + await invalidate(Dependencies.BACKUPS); } catch (error) { - if (selectedBackup) { - addNotification({ - type: 'error', - message: error.message - }); + addNotification({ + type: 'error', + message: error.message + }); + } + } + + async function deleteBackups(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((archiveId) => + sdk.forProject(page.params.region, page.params.project).backups.deleteArchive(archiveId) + ); + + try { + if (result.error) { + trackError(result.error, Submit.DatabaseBackupDelete); } else { - return error; + trackEvent(Submit.DatabaseBackupDelete); } } finally { - if (selectedBackup) { - showDelete = false; - selectedBackup = null; - } - await invalidate(Dependencies.BACKUPS); } + + return result; } async function restoreBackup() { @@ -299,7 +305,7 @@ bind:open={showDelete} onSubmit={async () => { if (!selectedBackup) return; - await deleteBackups([selectedBackup.$id]); + await deleteSingleBackup(selectedBackup.$id); }}> Are you sure you want to delete the {getCleanBackupName(selectedBackup)} backup? diff --git a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte index fa19cde37a..6a5c733521 100644 --- a/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/databases/database-[database]/table.svelte @@ -3,7 +3,12 @@ import { resolve } from '$app/paths'; import { page } from '$app/state'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { Id, MultiSelectionTable, type DeleteOperationState } from '$lib/components'; + import { + Id, + MultiSelectionTable, + type DeleteOperation, + type DeleteOperationState + } from '$lib/components'; import { Dependencies } from '$lib/constants'; import DualTimeView from '$lib/components/dualTimeView.svelte'; import { canWriteTables } from '$lib/stores/roles'; @@ -20,23 +25,26 @@ data: PageData; } = $props(); - async function onDelete(selectedTables: string[]): Promise { - const promises = selectedTables.map((tableId) => + async function onDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((tableId) => sdk.forProject(page.params.region, page.params.project).tablesDB.deleteTable({ databaseId: page.params.database, tableId }) ); + try { - await Promise.all(promises); - trackEvent(Submit.TableDelete); - } catch (error) { - trackError(error, Submit.TableDelete); - return error; + if (result.error) { + trackError(result.error, Submit.TableDelete); + } else { + trackEvent(Submit.TableDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.TABLES); subNavigation.update(); } + + return result; } function getTableHref(table: Models.Table) { diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte index f681c04efa..1e6915c383 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte index 7b46acc096..94672a8d69 100644 --- a/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/functions/function-[function]/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte index b096f25618..464237e79e 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/+page.svelte @@ -3,6 +3,7 @@ import { page } from '$app/state'; import { type DeleteOperationState, + type DeleteOperation, Empty, EmptyFilter, EmptySearch, @@ -99,22 +100,24 @@ } ]); - async function handleDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((id) => + async function handleDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((id) => sdk .forProject(page.params.region, page.params.project) .messaging.delete({ messageId: id }) ); try { - await Promise.all(promises); - trackEvent(Submit.MessagingMessageDelete, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.MessagingMessageDelete); - return error; + if (result.error) { + trackError(result.error, Submit.MessagingMessageDelete); + } else { + trackEvent(Submit.MessagingMessageDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.MESSAGING_MESSAGES); } + + return result; } onMount(() => { diff --git a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte index a54178b3db..f7ed222813 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/providers/table.svelte @@ -1,7 +1,12 @@ diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte index f5525754e7..709529b0f3 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/table.svelte @@ -1,7 +1,12 @@ diff --git a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte index c9a64e9afb..113d9d76eb 100644 --- a/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/messaging/topics/topic-[topic]/table.svelte @@ -2,7 +2,12 @@ import { invalidate } from '$app/navigation'; import { base } from '$app/paths'; import { Submit, trackError, trackEvent } from '$lib/actions/analytics'; - import { type DeleteOperationState, Id, MultiSelectionTable } from '$lib/components'; + import { + type DeleteOperationState, + type DeleteOperation, + Id, + MultiSelectionTable + } from '$lib/components'; import { Dependencies } from '$lib/constants'; import type { PageData } from './$types'; import ProviderType from '../../providerType.svelte'; @@ -32,8 +37,8 @@ return record; }); - async function handleDelete(selectedRows: string[]): Promise { - async function deleteSubscriber(subscriberId: string) { + async function handleDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete(async (subscriberId) => { await sdk .forProject(page.params.region, page.params.project) .messaging.deleteSubscriber({ @@ -44,19 +49,19 @@ const { target } = subscribers[subscriberId]; const { [target.$id]: _, ...rest } = $targetsById; $targetsById = rest; - } - - const promises = selectedRows.map((id) => deleteSubscriber(id)); + }); try { - await Promise.all(promises); - trackEvent(Submit.MessagingTopicSubscriberDelete, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.MessagingTopicSubscriberDelete); - return error; + if (result.error) { + trackError(result.error, Submit.MessagingTopicSubscriberDelete); + } else { + trackEvent(Submit.MessagingTopicSubscriberDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.MESSAGING_TOPIC_SUBSCRIBERS); } + + return result; } diff --git a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte index 9cfb56a832..535be9bd6d 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/(components)/table.svelte @@ -67,7 +67,7 @@ allowSelection={$canWriteKeys} showSuccessNotification={false} resource={`${capitalize(label)} key`} - onDelete={(selectedRows) => { + onDelete={(_, selectedRows) => { showDeleteModal = true; selectedKeys = selectedRows; }}> diff --git a/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte b/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte index 22d0bce778..de3cbcb2fb 100644 --- a/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte @@ -81,7 +81,11 @@ import { page } from '$app/state'; import type { PageProps } from './$types'; import type { Models } from '@appwrite.io/console'; - import { type DeleteOperationState, MultiSelectionTable } from '$lib/components'; + import { + type DeleteOperationState, + type DeleteOperation, + MultiSelectionTable + } from '$lib/components'; import { sdk } from '$lib/stores/sdk'; import { Submit, trackError } from '$lib/actions/analytics'; import { invalidate } from '$app/navigation'; @@ -129,24 +133,26 @@ } async function handlePlatformDelete( - selectedPlatforms: string[] + batchDelete: DeleteOperation ): Promise { - const promises = selectedPlatforms.map((platformId) => { - return sdk.forConsole.projects.deletePlatform({ + const result = await batchDelete((platformId) => + sdk.forConsole.projects.deletePlatform({ projectId: page.params.project, platformId - }); - }); + }) + ); try { - await Promise.all(promises); - trackEvent(Submit.PlatformDelete, { total: selectedPlatforms.length }); - } catch (error) { - trackError(error, Submit.PlatformDelete); - return error; + if (result.error) { + trackError(result.error, Submit.PlatformDelete); + } else { + trackEvent(Submit.PlatformDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.PROJECT); } + + return result; } setOverviewAction(Action); diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte index 690411c1e5..d135498a66 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/deployments/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte index da4890035b..de25047153 100644 --- a/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte +++ b/src/routes/(console)/project-[region]-[project]/sites/site-[site]/logs/table.svelte @@ -1,5 +1,10 @@ diff --git a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte index 8caf3c05a5..886169f114 100644 --- a/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte +++ b/src/routes/(console)/project-[region]-[project]/storage/bucket-[bucket]/+page.svelte @@ -6,6 +6,7 @@ import { Avatar, type DeleteOperationState, + type DeleteOperation, Empty, EmptySearch, MultiSelectionTable, @@ -65,23 +66,25 @@ showDelete = true; } - async function handleBulkDelete(selectedRows: string[]): Promise { - const promises = selectedRows.map((fileId) => { - return sdk.forProject(page.params.region, page.params.project).storage.deleteFile({ + async function handleBulkDelete(batchDelete: DeleteOperation): Promise { + const result = await batchDelete((fileId) => + sdk.forProject(page.params.region, page.params.project).storage.deleteFile({ bucketId: page.params.bucket, fileId - }); - }); + }) + ); try { - await Promise.all(promises); - trackEvent(Submit.FileDelete, { total: selectedRows.length }); - } catch (error) { - trackError(error, Submit.FileDelete); - return error; + if (result.error) { + trackError(result.error, Submit.FileDelete); + } else { + trackEvent(Submit.FileDelete, { total: result.deleted.length }); + } } finally { await invalidate(Dependencies.FILES); } + + return result; } const beforeunload = (event: BeforeUnloadEvent) => {