Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,15 @@
import type { Column, ColumnType } from '$lib/helpers/types';
import { Container } from '$lib/layout';
import { preferences } from '$lib/stores/preferences';
import { Icon, Layout, Divider, Tooltip } from '@appwrite.io/pink-svelte';
import {
Icon,
Layout,
Divider,
Tooltip,
Selector,
Typography,
Dialog
} from '@appwrite.io/pink-svelte';
import type { PageProps } from './$types';
import FilePicker from '$lib/components/filePicker.svelte';
import { page } from '$app/state';
Expand All @@ -21,7 +29,7 @@
IconUpload,
IconDownload
} from '@appwrite.io/pink-icons-svelte';
import { type Models } from '@appwrite.io/console';
import { OnDuplicate, type Models } from '@appwrite.io/console';
import { sdk } from '$lib/stores/sdk';
import { goto } from '$app/navigation';
import { resolve } from '$app/paths';
Expand Down Expand Up @@ -49,7 +57,11 @@

let isRefreshing = $state(false);
let showImportJson = $state(false);
let showImportOptions = $state(false);
let showCustomColumnsModal = $state(false);
let importOnDuplicate: OnDuplicate = $state(OnDuplicate.Fail);
let pendingFile: Models.File | null = $state(null);
let pendingLocalFile = $state(false);

let columnsError: string = $state(null);
let spreadsheet: SpreadSheet | null = $state(null);
Expand All @@ -74,17 +86,28 @@
return queryParam ? `${url}?query=${encodeURIComponent(queryParam)}` : url;
}

async function onSelect(file: Models.File, localFile = false) {
function onSelect(file: Models.File, localFile = false) {
pendingFile = file;
pendingLocalFile = localFile;
importOnDuplicate = OnDuplicate.Fail;
showImportOptions = true;
}

async function startImport() {
if (!pendingFile) return;

showImportOptions = false;
$isCollectionsJsonImportInProgress = true;

try {
await sdk
.forProject(page.params.region, page.params.project)
.migrations.createJSONImport({
bucketId: file.bucketId,
fileId: file.$id,
bucketId: pendingFile.bucketId,
fileId: pendingFile.$id,
resourceId: `${page.params.database}:${page.params.collection}`,
internalFile: localFile
internalFile: pendingLocalFile,
onDuplicate: importOnDuplicate
});

addNotification({
Expand All @@ -101,6 +124,7 @@
});
} finally {
$isCollectionsJsonImportInProgress = false;
pendingFile = null;
}
}

Expand Down Expand Up @@ -343,6 +367,52 @@
}} />
{/if}

<Dialog title="Import options" bind:open={showImportOptions}>
<Layout.Stack gap="l">
<Typography.Text variant="m-400">
Choose how to handle documents that already exist in this collection.
</Typography.Text>
<Layout.Stack gap="m">
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Fail}
label="Fail on duplicate (default)">
<svelte:fragment slot="description">
Import aborts on the first document with a matching ID.
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Skip}
label="Skip existing documents">
<svelte:fragment slot="description">
Documents with matching IDs will be silently skipped.
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Overwrite}
label="Overwrite existing documents">
<svelte:fragment slot="description">
Documents with matching IDs will be updated with the imported data.
</svelte:fragment>
</Selector.Radio>
</Layout.Stack>
</Layout.Stack>
<svelte:fragment slot="footer">
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
<Button text on:click={() => (showImportOptions = false)}>Cancel</Button>
<Button on:click={startImport}>Start import</Button>
</Layout.Stack>
</svelte:fragment>
</Dialog>

<Modal
title="Custom columns"
bind:error={columnsError}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,16 @@
import { Container } from '$lib/layout';
import { preferences } from '$lib/stores/preferences';
import { canWriteTables, canWriteRows } from '$lib/stores/roles';
import { Icon, Layout, Divider, Tooltip, Typography, Link } from '@appwrite.io/pink-svelte';
import {
Dialog,
Icon,
Layout,
Divider,
Selector,
Tooltip,
Typography,
Link
} from '@appwrite.io/pink-svelte';
import type { PageData } from './$types';
import {
tableColumns,
Expand All @@ -34,7 +43,7 @@
IconUpload,
IconDownload
} from '@appwrite.io/pink-icons-svelte';
import type { Models } from '@appwrite.io/console';
import { OnDuplicate, type Models } from '@appwrite.io/console';
import CreateRow from '$database/table-[table]/rows/create.svelte';
import { onDestroy } from 'svelte';
import { isCloud } from '$lib/system';
Expand All @@ -56,6 +65,10 @@

let isRefreshing = false;
let showImportCSV = false;
let showImportOptions = false;
let importOnDuplicate: OnDuplicate = OnDuplicate.Fail;
let pendingFile: Models.File | null = null;
let pendingLocalFile = false;

// todo: might need a type fix here.
const filterColumns = writable<Column[]>([]);
Expand Down Expand Up @@ -107,17 +120,28 @@

$: disableButton = canShowSuggestionsSheet;

async function onSelect(file: Models.File, localFile = false) {
function onSelect(file: Models.File, localFile = false) {
pendingFile = file;
pendingLocalFile = localFile;
importOnDuplicate = OnDuplicate.Fail;
showImportOptions = true;
}

async function startImport() {
if (!pendingFile) return;

showImportOptions = false;
$isTablesCsvImportInProgress = true;

try {
await sdk
.forProject(page.params.region, page.params.project)
.migrations.createCSVImport({
bucketId: file.bucketId,
fileId: file.$id,
bucketId: pendingFile.bucketId,
fileId: pendingFile.$id,
resourceId: `${page.params.database}:${page.params.table}`,
internalFile: localFile
internalFile: pendingLocalFile,
onDuplicate: importOnDuplicate
});

addNotification({
Expand All @@ -134,6 +158,7 @@
});
} finally {
$isTablesCsvImportInProgress = false;
pendingFile = null;
}
}

Expand Down Expand Up @@ -434,6 +459,52 @@
}} />
{/if}

<Dialog title="Import options" bind:open={showImportOptions}>
<Layout.Stack gap="l">
<Typography.Text variant="m-400">
Choose how to handle documents that already exist in this table.
</Typography.Text>
<Layout.Stack gap="m">
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Fail}
label="Fail on duplicate (default)">
<svelte:fragment slot="description">
Migration aborts on the first row with a matching ID.
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Skip}
label="Skip existing documents">
<svelte:fragment slot="description">
Documents with matching IDs will be silently skipped.
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Overwrite}
label="Overwrite existing documents">
<svelte:fragment slot="description">
Documents with matching IDs will be updated with the imported data.
</svelte:fragment>
</Selector.Radio>
</Layout.Stack>
</Layout.Stack>
<svelte:fragment slot="footer">
<Layout.Stack direction="row" gap="s" justifyContent="flex-end">
<Button text on:click={() => (showImportOptions = false)}>Cancel</Button>
<Button on:click={startImport}>Start import</Button>
</Layout.Stack>
</svelte:fragment>
</Dialog>

<CreateRow
{table}
bind:showSheet={$showRowCreateSheet.show}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
AppwriteMigrationResource,
FirebaseMigrationResource,
NHostMigrationResource,
OnDuplicate,
SupabaseMigrationResource
} from '@appwrite.io/console';
import { started } from '../stores';
Expand All @@ -23,6 +24,7 @@
Fieldset,
Icon,
Layout,
Selector,
Typography
} from '@appwrite.io/pink-svelte';
import { Link } from '$lib/elements';
Expand All @@ -38,6 +40,8 @@
import { capitalize } from '$lib/helpers/string';
import { page } from '$app/state';

let importOnDuplicate: OnDuplicate = OnDuplicate.Fail;

const onExit = () => {
resetImportStores();
};
Expand All @@ -46,6 +50,15 @@
try {
const resources = migrationFormToResources($formData, $provider.provider);

// Gate onDuplicate to Fail when databases isn't selected. The radios
// are only shown when databases.root is checked, but the local value
// persists across toggles — without this gate, deselecting databases
// after picking Overwrite/Skip would silently apply that mode to
// other resource types (users, teams, functions, etc.) on submit.
const importOptions = {
onDuplicate: $formData.databases.root ? importOnDuplicate : OnDuplicate.Fail
};

switch ($provider.provider) {
case 'appwrite': {
await sdk
Expand All @@ -54,7 +67,8 @@
resources: resources as AppwriteMigrationResource[],
endpoint: $provider.endpoint,
projectId: $provider.projectID,
apiKey: $provider.apiKey
apiKey: $provider.apiKey,
...importOptions
});

await invalidate(Dependencies.MIGRATIONS);
Expand All @@ -70,7 +84,8 @@
databaseHost: $provider.host,
username: $provider.username || 'postgres',
password: $provider.password,
port: $provider.port || 5432
port: $provider.port || 5432,
...importOptions
});
await invalidate(Dependencies.MIGRATIONS);
break;
Expand All @@ -80,7 +95,8 @@
.forProject(page.params.region, page.params.project)
.migrations.createFirebaseMigration({
resources: resources as FirebaseMigrationResource[],
serviceAccount: $provider.serviceAccount
serviceAccount: $provider.serviceAccount,
...importOptions
});
await invalidate(Dependencies.MIGRATIONS);
break;
Expand All @@ -95,7 +111,8 @@
adminSecret: $provider.adminSecret,
database: $provider.database || $provider.subdomain,
username: $provider.username || 'postgres',
password: $provider.password
password: $provider.password,
...importOptions
});

await invalidate(Dependencies.MIGRATIONS);
Expand Down Expand Up @@ -196,6 +213,46 @@
projectSdk={sdk.forProject(page.params.region, page.params.project)} />
</Layout.Stack>
</Fieldset>

{#if $formData.databases.root}
<Fieldset legend="Import options">
<Layout.Stack gap="m">
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Fail}
label="Fail on duplicate (default)">
<svelte:fragment slot="description">
Migration aborts on the first existing resource (database,
table, column, index, or row).
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Skip}
label="Skip existing resources">
<svelte:fragment slot="description">
Existing resources are left untouched. Only resources missing on
the destination are created.
</svelte:fragment>
</Selector.Radio>
<Selector.Radio
size="s"
bind:group={importOnDuplicate}
name="importOnDuplicate"
value={OnDuplicate.Overwrite}
label="Overwrite existing resources">
<svelte:fragment slot="description">
Existing resources are updated to match the source. Schema drift
and row data are both reconciled.
</svelte:fragment>
</Selector.Radio>
</Layout.Stack>
</Fieldset>
{/if}
Comment thread
premtsd-code marked this conversation as resolved.
{/if}
</Layout.Stack>
</Layout.Stack>
Expand Down