Skip to content

Conversation

@abnegate
Copy link
Member

@abnegate abnegate commented Oct 22, 2025

What does this PR do?

(Provide a description of what this PR does.)

Test Plan

(Write your test plan here. If you changed any code, please provide us with clear instructions on how you verified your changes work.)

Related PRs and Issues

(If this PR is related to any other PR or resolves any issue or related to any issue link all related PR and issues here.)

Have you read the Contributing Guidelines on issues?

(Write your answer here.)

Summary by CodeRabbit

  • New Features

    • CSV export wizard for tables: select columns, apply filters, set delimiter/header, and generate filenames.
    • Real-time CSV export progress box with per-export status, auto-download on completion, and error details modal.
    • Toolbar import/export icon actions with export tracking, improved tooltips, and refined toolbar animations.
    • Checkboxes now support optional truncation behavior.
  • Chores

    • Updated package manager version.

✏️ Tip: You can customize this high-level summary in your review settings.

@railway-app
Copy link

railway-app bot commented Oct 22, 2025

This PR was not deployed automatically as @abnegate does not have access to the Railway project.

In order to get automatic PR deploys, please add @abnegate to your workspace on Railway.

@appwrite
Copy link

appwrite bot commented Oct 22, 2025

Console

Project ID: 688b7bf400350cbd60e9

Sites (1)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Queued Queued View Logs Preview URL QR Code

Tip

Cursor pagination performs better than offset pagination when loading further pages.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Oct 22, 2025

Walkthrough

Adds CSV export functionality: a new table-level export wizard page to configure and create CSV export migrations; a realtime, collapsible CSV export progress component that tracks migrations, auto-downloads completed exports, and surfaces errors; re-exports CsvExportBox from components index and integrates it into the project layout; toolbar UI changes in the table view to add import/export icon actions and an export URL helper; two new analytics enum variants for CSV export events; a new truncate prop on the input checkbox component; and a package.json packageManager value change from pnpm@10.20.0 to pnpm@10.18.3.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

  • src/lib/components/csvExportBox.svelte: review state model, migrations subscription and update/dedup logic, auto-download behavior, notification extraction, and error-modal rendering.
  • src/routes/.../export/+page.svelte: review column selection/initialization, show more/less logic, filter/tag handling, filename construction, SDK payload, validation, and error handling.
  • src/routes/.../table/+page.svelte and src/routes/(console)/project-[region]-[project]/+layout.svelte: UI wiring for new icon actions, export URL builder, and integration of CsvExportBox.
  • src/lib/actions/analytics.ts: ensure new enum variants (click/submit database export CSV) align with tracking usage.
  • src/lib/elements/forms/inputCheckbox.svelte: new truncate prop surface and propagation to underlying checkbox.
  • src/lib/components/index.ts and package.json: small manifest/exports changes.

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'CSV export' is vague and generic, lacking specificity about what CSV export functionality was added or modified. Consider a more descriptive title that clarifies the main change, such as 'Add CSV export wizard and real-time progress tracking' or 'Implement database table CSV export feature'.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-csv-export

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a9385fb and abbe7e1.

📒 Files selected for processing (1)
  • src/lib/components/csvExportBox.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/lib/components/csvExportBox.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@abnegate abnegate marked this pull request as ready for review October 23, 2025 11:11
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🧹 Nitpick comments (6)
src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte (1)

95-105: Good fix; add Retry analytics and avoid string-literal drift.

  • Condition now correctly excludes 'verifying' from Retry. OK.
  • Add tracking for Retry to match Delete.
  • Consider centralizing status literals (enum/const) to prevent future typos.

Apply:

 {#if domain.status !== 'verified' && domain.status !== 'verifying'}
   <ActionMenu.Item.Button
     leadingIcon={IconRefresh}
     on:click={(e) => {
       selectedDomain = domain;
       showRetry = true;
+      trackEvent(Click.DomainRetryDomainVerificationClick, {
+        source: 'settings_domain_overview'
+      });
       toggle(e);
     }}>
     Retry
   </ActionMenu.Item.Button>
 {/if}
src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte (1)

110-113: Avoid as cast in runtime checks.

Coerce safely to string to keep types narrow without assertions.

- disabled={!log?.scheduledAt || (status as string) !== 'scheduled'}
+ disabled={!log?.scheduledAt || String(status) !== 'scheduled'}
src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte (1)

113-116: Mirror: prefer safe coercion over as cast.

Keeps typing honest and avoids accidental non-string comparisons.

- disabled={!selectedLog?.scheduledAt ||
-     (selectedLog.status as string) !== 'scheduled'}
+ disabled={!selectedLog?.scheduledAt ||
+     String(selectedLog.status) !== 'scheduled'}
src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (1)

29-30: Track user actions and close the popover on click.

  • Emit Click events for Import/Export to complement Submit metrics.
  • Close the popover before opening picker or navigating.
- import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
+ import { Submit, Click, trackError, trackEvent } from '$lib/actions/analytics';
@@
-  <ActionMenu.Item.Button
-      leadingIcon={IconUpload}
-      on:click={() => (showImportCSV = true)}>
+  <ActionMenu.Item.Button
+      leadingIcon={IconUpload}
+      on:click={() => {
+        trackEvent(Click.DatabaseImportCsv);
+        toggle();
+        showImportCSV = true;
+      }}>
     Import CSV
   </ActionMenu.Item.Button>
   <ActionMenu.Item.Button
       leadingIcon={IconDownload}
-      on:click={() =>
-          goto(
+      on:click={() => {
+          trackEvent(Click.DatabaseExportCsv);
+          toggle();
+          goto(
               `${base}/project-${page.params.region}-${page.params.project}/databases/database-${page.params.database}/table-${page.params.table}/export`
-          )}>
+          );
+      }}>
     Export CSV
   </ActionMenu.Item.Button>

Also applies to: 169-193

src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (1)

139-146: Harden error handling for unknown error shapes

error.message may be undefined. Normalize to a string to avoid runtime issues.

-} catch (error) {
-    addNotification({
-        type: 'error',
-        message: error.message
-    });
-
-    trackError(error, Submit.DatabaseExportCsv);
-}
+} catch (error) {
+    const message = error instanceof Error ? error.message : String(error);
+    addNotification({ type: 'error', message });
+    trackError(error, Submit.DatabaseExportCsv);
+}
src/lib/components/csvExportBox.svelte (1)

92-95: Normalize error to string in download failure path

e.message may be undefined. Convert to a safe string.

-                              } catch (e) {
+                              } catch (e) {
                                   addNotification({
                                       type: 'error',
-                                      message: `Failed to download file: ${e.message}`
+                                      message: `Failed to download file: ${e instanceof Error ? e.message : String(e)}`
                                   });
                               }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f34f2c2 and 573a258.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (11)
  • package.json (2 hunks)
  • src/lib/actions/analytics.ts (2 hunks)
  • src/lib/components/csvExportBox.svelte (1 hunks)
  • src/lib/components/index.ts (1 hunks)
  • src/routes/(console)/project-[region]-[project]/+layout.svelte (2 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (4 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/sheet.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/functions/function-[function]/executions/table.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/overview/platforms/+page.svelte (2 hunks)
  • src/routes/(console)/project-[region]-[project]/settings/domains/table.svelte (1 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (7)
package.json (2)

98-98: Pnpm version bump is verified as safe—no breaking changes between versions.

pnpm 10.18.3 is a patch release with bug fixes, not breaking changes relative to 10.15.1. The version bump from 10.15.1 to 10.18.3 is a routine minor version update with only patch-level fixes and poses no compatibility concerns.


25-25: The review comment's concerns about pkg.pr.new are overstated and based on incomplete context.

The git diff shows that the pkg.pr.new pattern for @appwrite.io/console already exists in origin/main (commit 636ed39). This PR updates the commit reference to 14f6db1 within the same preview package service—it does not introduce the pattern. Additionally, pkg.vc dependencies are already established at lines 27–29 in the codebase.

The flagged concerns (reliability, reproducibility, maintenance burden) would apply to the existing main branch, not to this change. If the pkg.pr.new approach requires architectural or process changes, that decision should be addressed upstream, not as a blocker for this feature branch.

The pnpm version bump (10.15.1 → 10.18.3) is a minor routine update with no critical concerns.

Likely an incorrect or invalid review comment.

src/lib/components/index.ts (1)

16-16: Public export added correctly.

CsvExportBox is now available via components barrel. LGTM.

src/routes/(console)/project-[region]-[project]/+layout.svelte (1)

2-2: CsvExportBox integration looks good.

Assuming it self-hides when inactive, this matches existing pattern. No issues.

Also applies to: 124-125

src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (2)

186-189: Export route verified as present and correctly configured.

The export route exists at the correct path: src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte. The navigation URL in the code correctly maps to this route structure.


98-106: <!-- [scratchpad_start]
[task verification_2/2 final]
[observations]

  • Migrations class imported from @appwrite.io/console (line 19 of sdk.ts)
  • Instantiated at lines 86 and 131 in sdk.ts
  • Method called: migrations.createCSVImport({bucketId, fileId, resourceId, internalFile})
  • Package.json uses PR build: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@14f6db1
  • npm view shows published version is 1.10.0
  • Web search found NO public documentation for migrations.createCSVImport
  • Method not found in released SDK version
  • Only exists in PR build being used
    [analysis]
  • Critical risk: method is NOT in the public/released SDK
  • Only available in experimental PR build
  • Method signature and availability cannot be confirmed against stable release
  • This is exactly what the review comment asked to verify
    [pending]
  • No way to verify if PR build has this method without access to PR source
  • Codebase will break if PR build is not merged or if method signature changes
    [done]
  • Confirmed review concern is valid and actionable
  • Method requires verification against actual SDK being shipped
    [scratchpad_start] -->

Verify that migrations.createCSVImport method exists and is stable in the PR build of @appwrite.io/console currently being shipped.

The method is called at lines 98–106 but does not exist in the published @appwrite.io/console@1.10.0. It only exists in the PR build (@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@14f6db1). Confirm:

  • The PR build has been tested and includes this method.
  • The method signature matches: createCSVImport({ bucketId, fileId, resourceId, internalFile }).
  • The method will remain stable after the PR is merged.
src/lib/components/csvExportBox.svelte (1)

255-264: Svelte event binding bug: use on:click, not onclick

onclick={...} won’t execute as Svelte expects; handlers won’t fire. Switch to on:click.

-<button
+<button
     class="upload-box-button"
     class:is-open={isOpen}
     aria-label="toggle upload box"
-    onclick={() => (isOpen = !isOpen)}>
+    on:click={() => (isOpen = !isOpen)}>
-<button class="upload-box-button" aria-label="close export box" onclick={clear}>
+<button class="upload-box-button" aria-label="close export box" on:click={clear}>
⛔ Skipped due to learnings
Learnt from: ItzNotABug
PR: appwrite/console#2373
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/options.svelte:33-42
Timestamp: 2025-09-25T04:31:05.213Z
Learning: Svelte 5 uses `onclick` instead of `on:click` for event handlers. Event handlers are now properties like any other rather than directives that require the `on:` prefix.

@abnegate abnegate marked this pull request as draft October 23, 2025 12:07
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8c43b0f and f637513.

📒 Files selected for processing (4)
  • package.json (2 hunks)
  • src/lib/components/csvExportBox.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (5 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • src/lib/components/csvExportBox.svelte
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-09-30T07:41:06.679Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2425
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte:454-468
Timestamp: 2025-09-30T07:41:06.679Z
Learning: In `src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte`, the column suggestions API (console.suggestColumns) has a maximum limit of 7 columns returned, which aligns with the initial placeholder count of 7 in customColumns.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
📚 Learning: 2025-10-13T05:13:54.542Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/table.svelte:33-39
Timestamp: 2025-10-13T05:13:54.542Z
Learning: In Svelte 5, `import { page } from '$app/state'` provides a reactive state proxy that can be accessed directly (e.g., `page.params`), unlike the older `import { page } from '$app/stores'` which returns a readable store requiring the `$page` syntax for auto-subscription in components.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-09-08T13:20:47.308Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2316
File: src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte:29-29
Timestamp: 2025-09-08T13:20:47.308Z
Learning: The Form.svelte component in the Appwrite console creates a FormContext with isSubmitting as writable(false) and expects consumers to work with Svelte writable stores, not plain booleans.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
📚 Learning: 2025-09-25T04:33:19.632Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2373
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte:28-33
Timestamp: 2025-09-25T04:33:19.632Z
Learning: In the Appwrite console createTable component, manual submit state management (like setting creatingTable = true) is not needed because: 1) The Modal component handles submit state internally via submissionLoader, preventing double submissions, and 2) After successful table creation, the entire view is destroyed and replaced with the populated table view, ending the component lifecycle.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
🔇 Additional comments (10)
src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (4)

5-6: LGTM! Imports support new navigation and UI.

The new imports for navigation (goto, resolve) and icons (IconUpload, IconDownload) properly support the CSV export and improved UI functionality.

Also applies to: 35-37


104-104: LGTM! Naming convention update.

The change from createCsvMigration to createCSVImport follows consistent PascalCase naming, aligning with createCSVExport used elsewhere in this PR.


128-140: LGTM! Clean URL construction with query preservation.

The function properly uses resolve for type-safe path construction and preserves the current query parameter, ensuring filter state is maintained when navigating to the export page.


192-237: LGTM! Well-structured UI improvements.

The new tooltip-wrapped icon buttons provide better UX with space-efficient layout. Analytics tracking for the export action is properly implemented, and the navigation flow to the export wizard is clean.

src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (6)

29-62: LGTM! Clean Svelte 5 state management.

The state variables properly use $state and $derived runes. The isSubmitting writable correctly binds to the Form component's internal state (line 147), and derived values like localTags, visibleColumns, selectedColumnCount, and tableUrl are computed efficiently using $derived and $derived.by.


65-77: LGTM! Type-safe URL construction with query preservation.

The tableUrl derived value properly uses resolve for type-safe path construction and preserves the query parameter to maintain filter state when navigating back to the table view.


79-94: LGTM! Clean helper functions.

The helper functions are well-structured. The removeLocalFilter function correctly creates a new Map (line 81) to ensure Svelte's reactivity is triggered.


96-138: LGTM! Robust export handler with validation and error handling.

The export function includes proper validation (at least one column selected), comprehensive error handling with user notifications, and analytics tracking for both success and error cases. The API call correctly maps export options and conditionally includes filters based on the exportWithFilters checkbox.


146-251: LGTM! Well-structured wizard UI with responsive layout.

The wizard form is well-organized with clear sections for column selection and export options. The responsive grid layout (columns={3} columnsS={1}) adapts properly to different viewports. The "Export with filters" checkbox is appropriately disabled when no filters exist, and the Export button correctly validates that at least one column is selected before allowing submission.


140-144: LGTM! Proper initialization and UX styling.

The onMount hook correctly initializes column selections and creates a local copy of queries from the store. The CSS class for disabled checkboxes improves UX by removing the pointer cursor when the "Export with filters" option is unavailable.

Also applies to: 253-257

# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
# Conflicts:
#	package.json
#	pnpm-lock.yaml
#	src/routes/(console)/project-[region]-[project]/functions/create-function/template-[template]/+page.svelte
#	src/routes/(console)/project-[region]-[project]/sites/create-site/templates/template-[template]/+page.svelte
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (3)
src/lib/components/csvExportBox.svelte (1)

66-86: XSS risk via HTML injection in notifications and @html rendering.

The code renders unescaped HTML from tableName and bucketName via addNotification({ isHtml: true, ... }) and {@html text(...)}. If a user creates a table or bucket with a malicious name (e.g., <script>alert('xss')</script>), it will execute in the browser.

Apply the escaping fix suggested in the prior review, or refactor to avoid HTML strings entirely by rendering names in Svelte markup (e.g., <b>{tableName}</b> instead of building HTML strings).

src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (2)

143-147: Add preventDefault to utility buttons inside form.

The "Select all" and "Deselect all" buttons are inside the <Form> component and may trigger form submission when clicked. Add the preventDefault modifier or set type="button" to prevent accidental submissions.

Apply this diff:

-<Button compact on:click={selectAllColumns}>Select all</Button>
+<Button compact on:click|preventDefault={selectAllColumns}>Select all</Button>
-<Button compact on:click={deselectAllColumns}>Deselect all</Button>
+<Button compact on:click|preventDefault={deselectAllColumns}>Deselect all</Button>

164-164: Add preventDefault to "Show more/less" button.

The "Show more/less" button is also inside the form and needs the same fix.

Apply this diff:

-<Button compact on:click={() => (showAllColumns = !showAllColumns)}>
+<Button compact on:click|preventDefault={() => (showAllColumns = !showAllColumns)}>
🧹 Nitpick comments (4)
src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (1)

138-150: Consider preserving all query parameters and extracting shared URL logic.

The function only preserves the query parameter, potentially dropping other URL search params. Additionally, this logic is duplicated in export/+page.svelte (lines 56-68).

Consider using URLSearchParams to preserve all query parameters:

 function getTableExportUrl() {
-    const queryParam = page.url.searchParams.get('query');
     const url = resolve(
         '/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export',
         {
             region: page.params.region,
             project: page.params.project,
             database: page.params.database,
             table: page.params.table
         }
     );
-    return queryParam ? `${url}?query=${queryParam}` : url;
+    const params = new URLSearchParams(page.url.searchParams);
+    return params.size > 0 ? `${url}?${params.toString()}` : url;
 }

For better maintainability, consider extracting this URL-building logic into a shared helper function, since the same pattern appears in src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte at lines 56-68.

src/lib/components/csvExportBox.svelte (2)

179-196: Add error handling for API calls and realtime subscription.

The migrations.list() call and realtime subscription lack error handling. If the initial load fails, users won't be notified and the UI state may be inconsistent.

Consider adding error handling:

 onMount(() => {
     sdk.forProject(page.params.region, page.params.project)
         .migrations.list({
             queries: [
                 Query.equal('destination', 'CSV'),
                 Query.equal('status', ['pending', 'processing'])
             ]
         })
         .then((migrations) => {
             migrations.migrations.forEach(updateOrAddItem);
+        })
+        .catch((error) => {
+            console.error('Failed to load CSV exports:', error);
+            addNotification({
+                type: 'error',
+                message: 'Failed to load export status'
+            });
         });

     return realtime.forConsole(page.params.region, 'console', (response) => {
+        try {
             if (!response.channels.includes(`projects.${getProjectId()}`)) return;
             if (response.events.includes('migrations.*')) {
                 updateOrAddItem(response.payload as Payload);
             }
+        } catch (error) {
+            console.error('Error processing realtime update:', error);
+        }
     });
 });

89-145: Consider extracting sub-functions for better modularity.

The updateOrAddItem function handles multiple concerns: validation, state updates, automatic downloads, and notifications. While readable, extracting helper functions (e.g., shouldUpdateItem, handleCompletedExport, handleFailedExport) could improve maintainability.

Based on learnings

src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (1)

56-68: Duplicate URL building logic and limited query param preservation.

This URL building logic duplicates getTableExportUrl() from +page.svelte (lines 138-150) and has the same limitation of only preserving the query parameter.

Consider extracting this into a shared helper function that preserves all query parameters. For example, create a getTableUrl() helper in a shared utilities file:

export function getTableUrl(
    params: { region: string; project: string; database: string; table: string },
    searchParams: URLSearchParams,
    suffix = ''
) {
    const path = suffix 
        ? `/(console)/project-[region]-[project]/databases/database-[database]/table-[table]${suffix}`
        : '/(console)/project-[region]-[project]/databases/database-[database]/table-[table]';
    
    const url = resolve(path, {
        region: params.region,
        project: params.project,
        database: params.database,
        table: params.table
    });
    
    return searchParams.size > 0 ? `${url}?${searchParams.toString()}` : url;
}

Then use it in both files:

const tableUrl = getTableUrl(page.params, page.url.searchParams);
// or
const exportUrl = getTableUrl(page.params, page.url.searchParams, '/export');
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f637513 and e0836fb.

📒 Files selected for processing (5)
  • package.json (1 hunks)
  • src/lib/actions/analytics.ts (2 hunks)
  • src/lib/components/csvExportBox.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (5 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • package.json
  • src/lib/actions/analytics.ts
🧰 Additional context used
🧠 Learnings (6)
📚 Learning: 2025-09-30T07:41:06.679Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2425
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte:454-468
Timestamp: 2025-09-30T07:41:06.679Z
Learning: In `src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte`, the column suggestions API (console.suggestColumns) has a maximum limit of 7 columns returned, which aligns with the initial placeholder count of 7 in customColumns.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-09-08T13:20:47.308Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2316
File: src/routes/(console)/project-[region]-[project]/functions/create-function/deploy/+page.svelte:29-29
Timestamp: 2025-09-08T13:20:47.308Z
Learning: The Form.svelte component in the Appwrite console creates a FormContext with isSubmitting as writable(false) and expects consumers to work with Svelte writable stores, not plain booleans.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
📚 Learning: 2025-09-25T04:33:19.632Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2373
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/createTable.svelte:28-33
Timestamp: 2025-09-25T04:33:19.632Z
Learning: In the Appwrite console createTable component, manual submit state management (like setting creatingTable = true) is not needed because: 1) The Modal component handles submit state internally via submissionLoader, preventing double submissions, and 2) After successful table creation, the entire view is destroyed and replaced with the populated table view, ending the component lifecycle.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/export/+page.svelte
📚 Learning: 2025-11-19T11:22:42.553Z
Learnt from: atharvadeosthale
Repo: appwrite/console PR: 2512
File: src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte:51-83
Timestamp: 2025-11-19T11:22:42.553Z
Learning: In src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte, the Cursor integration URL format `https://cursor.com/link/prompt` with the `text` query parameter is correct and functional.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-11-19T11:22:42.553Z
Learnt from: atharvadeosthale
Repo: appwrite/console PR: 2512
File: src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte:51-83
Timestamp: 2025-11-19T11:22:42.553Z
Learning: In src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte, the Lovable integration URL format `https://lovable.dev/` with `autosubmit` and `prompt` as query parameters (set via searchParams) is correct and functional.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-10-13T05:13:54.542Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/table.svelte:33-39
Timestamp: 2025-10-13T05:13:54.542Z
Learning: In Svelte 5, `import { page } from '$app/state'` provides a reactive state proxy that can be accessed directly (e.g., `page.params`), unlike the older `import { page } from '$app/stores'` which returns a readable store requiring the `$page` syntax for auto-subscription in components.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
🔇 Additional comments (2)
src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (1)

203-249: LGTM!

The new icon-based CSV import/export actions and expand/collapse controls are well-structured. The Export CSV button properly tracks analytics before navigation, and all buttons are correctly wrapped in Tooltips for better UX.

src/lib/components/csvExportBox.svelte (1)

132-144: Verify auto-download behavior on export completion.

Line 134 automatically downloads the exported file when the migration status becomes completed. This happens even if the user has navigated away from the export context. While the notification also provides a manual "Download" button, the automatic download might be unexpected behavior.

Please confirm this is the intended UX. If not, consider removing the automatic download and relying solely on the notification's "Download" button for user-initiated downloads.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e0836fb and ae4ed9c.

📒 Files selected for processing (1)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (6 hunks)
🧰 Additional context used
🧠 Learnings (3)
📚 Learning: 2025-09-30T07:41:06.679Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2425
File: src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte:454-468
Timestamp: 2025-09-30T07:41:06.679Z
Learning: In `src/routes/(console)/project-[region]-[project]/databases/database-[database]/(suggestions)/empty.svelte`, the column suggestions API (console.suggestColumns) has a maximum limit of 7 columns returned, which aligns with the initial placeholder count of 7 in customColumns.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-11-19T11:22:42.553Z
Learnt from: atharvadeosthale
Repo: appwrite/console PR: 2512
File: src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte:51-83
Timestamp: 2025-11-19T11:22:42.553Z
Learning: In src/routes/(console)/project-[region]-[project]/overview/platforms/llmBanner.svelte, the Cursor integration URL format `https://cursor.com/link/prompt` with the `text` query parameter is correct and functional.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
📚 Learning: 2025-10-13T05:13:54.542Z
Learnt from: ItzNotABug
Repo: appwrite/console PR: 2413
File: src/routes/(console)/project-[region]-[project]/databases/table.svelte:33-39
Timestamp: 2025-10-13T05:13:54.542Z
Learning: In Svelte 5, `import { page } from '$app/state'` provides a reactive state proxy that can be accessed directly (e.g., `page.params`), unlike the older `import { page } from '$app/stores'` which returns a readable store requiring the `$page` syntax for auto-subscription in components.

Applied to files:

  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (5)
src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (5)

5-6: LGTM!

The navigation imports are correctly added to support the new CSV export functionality.


37-39: LGTM!

The icon imports are correctly added and used in the template.


195-196: LGTM!

The layout adjustment correctly adds spacing for the new action buttons.


207-236: LGTM!

The refactored Import CSV and Export CSV buttons correctly use icon-based UI with tooltips. The Export CSV functionality properly tracks analytics and navigates to the export URL. The disabled conditions are correctly preserved from the existing button logic.

Note: The Export CSV functionality depends on the getTableExportUrl() function, which has a URL encoding issue that should be fixed.


260-278: LGTM!

The expand/collapse button correctly wraps the existing functionality in a tooltip, maintaining the same behavior while improving UX consistency with other icon buttons.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

♻️ Duplicate comments (3)
src/lib/components/csvExportBox.svelte (3)

66-69: XSS vulnerability remains unresolved.

The isHtml: true flag on line 69 combined with the unescaped table variable in the message creates an XSS risk, as previously flagged. User-controlled table names are rendered as HTML without sanitization.

Based on past review comments, the recommended fix is to either:

  1. Remove isHtml: true and render names as plain text, or
  2. Sanitize all user input before including in HTML strings
     addNotification({
         type,
         message,
-        isHtml: true,
+        isHtml: false,
         timeout: 10000,

167-179: XSS vulnerability: HTML injection via unescaped user input.

The text() function creates HTML strings with unescaped tableName and bucketName on lines 168-169, which are then rendered via {@html ...} on line 237. This is a critical XSS vulnerability as previously flagged.

As noted in past comments, sanitize or refactor to avoid HTML string construction:

 function text(status: string, tableName = '', bucketName = '') {
-    const table = tableName ? `<b>${tableName}</b>` : '';
-    const bucket = bucketName ? `<b>${bucketName}</b>` : 'bucket';
+    const table = tableName || '';
+    const bucket = bucketName || 'bucket';
     switch (status) {
         case 'completed':
         case 'failed':
             return `Export to ${bucket} ${status}`;
         case 'processing':
             return `Exporting ${table} to ${bucket}`;
         default:
             return 'Preparing export...';
     }
 }

Then in the template, use proper Svelte markup instead of {@html}:

<Typography.Text>
    {#if value.status === 'processing'}
        Exporting <b>{value.table}</b> to <b>{value.bucketName ?? 'bucket'}</b>
    {:else if value.status === 'completed' || value.status === 'failed'}
        Export to <b>{value.bucketName ?? 'bucket'}</b> {value.status}
    {:else}
        Preparing export...
    {/if}
</Typography.Text>

237-242: Unsafe HTML rendering in template.

The {@html text(...)} directive renders unescaped user input from the text() function, creating an XSS vulnerability. This is part of the same security issue flagged in previous comments.

See the suggested fix in the review comment for lines 167-179, which shows how to refactor the template to use safe Svelte markup instead of {@html}.

🧹 Nitpick comments (2)
src/lib/components/csvExportBox.svelte (2)

94-94: Potential undefined access in array destructuring.

If resourceId.split(':') returns fewer than 2 elements, tableId will be undefined. While this is handled later, the destructuring pattern [_, tableId] doesn't clearly signal this intent.

-    const [_, tableId] = resourceId.split(':') ?? [];
+    const parts = resourceId.split(':');
+    const tableId = parts[1];

Or use optional destructuring more explicitly:

-    const [_, tableId] = resourceId.split(':') ?? [];
+    const tableId = resourceId.split(':')[1] ?? undefined;

153-165: Consider extracting magic numbers to named constants.

The progress percentages (10, 60, 100, 30) are hardcoded. While the current implementation is clear, using named constants would improve maintainability.

const PROGRESS_PENDING = 10;
const PROGRESS_PROCESSING = 60;
const PROGRESS_COMPLETED = 100;
const PROGRESS_DEFAULT = 30;

function graphSize(status: string): number {
    switch (status) {
        case 'pending':
            return PROGRESS_PENDING;
        case 'processing':
            return PROGRESS_PROCESSING;
        case 'completed':
        case 'failed':
            return PROGRESS_COMPLETED;
        default:
            return PROGRESS_DEFAULT;
    }
}
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d58bc8f and 751b3cd.

📒 Files selected for processing (2)
  • src/lib/components/csvExportBox.svelte (1 hunks)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte (7 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/+page.svelte
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: e2e
  • GitHub Check: build
🔇 Additional comments (4)
src/lib/components/csvExportBox.svelte (4)

181-199: LGTM: Proper realtime subscription setup.

The lifecycle correctly fetches existing migrations and sets up a realtime subscription using realtime.forConsole(). The cleanup function is properly returned for unsubscription.


201-205: LGTM: Clean reactive state declarations.

Proper use of Svelte 5 runes with $state and $derived. The showCsvExportBox derived state efficiently tracks whether to display the component.


270-287: LGTM: Safe error details modal.

The error modal properly handles JSON parsing failures and displays formatted error details. The try-catch inside the map ensures individual parse errors don't break the entire display.


289-331: LGTM: Clean scoped styles.

The SCSS is well-organized with proper scoping and uses CSS custom properties for theming. The progress bar styling with is-danger modifier is a good pattern.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
src/lib/components/csvExportBox.svelte (4)

34-49: Remove isHtml: true to prevent XSS.

Setting isHtml: true (line 46) with errorMessage that may contain unsanitized content creates an XSS vulnerability. Error messages could include table names or other user-controlled data.

Apply this diff:

     addNotification({
         type: 'error',
         message: errorMessage,
-        isHtml: true,
+        isHtml: false,
         timeout: 10000,
     });

Based on past review comments, this XSS concern was previously flagged.


91-96: Reconsider automatic download on completion.

Automatically downloading exports on completion (line 94) may be unexpected, especially if multiple exports complete or the user navigated away. This was flagged in previous reviews as potentially intrusive.

Consider requiring explicit user action via the notification button instead:

     switch (status) {
         case 'completed':
-            if (downloadUrl) {
-                downloadExportedFile(downloadUrl);
-            }
+            addNotification({
+                type: 'success',
+                message: `Export of ${tableName ?? 'table'} completed`,
+                buttons: [{
+                    name: 'Download',
+                    method: () => downloadExportedFile(downloadUrl)
+                }]
+            });
             break;

Based on past review comments.


121-133: Sanitize tableName or avoid HTML rendering to prevent XSS.

The text() function wraps tableName in <b> tags without escaping, and the result is rendered via {@html ...} on line 191. If tableName contains HTML/script tags, this creates an XSS vulnerability.

Solution 1 (preferred): Remove HTML and use Svelte's safe rendering:

In the template (line 191), replace:

-{@html text(value.status, value.table)}
+{text(value.status, value.table)}

Update the text() function to return plain text:

 function text(status: string, tableName = '') {
-    const table = tableName ? `<b>${tableName}</b>` : '';
+    const table = tableName || 'table';
     switch (status) {
         case 'completed':
-            return `Exporting ${table} completed`;
+            return `Exporting "${table}" completed`;

Solution 2: Add HTML escaping:

+function escapeHtml(s: string): string {
+    return s.replace(/[&<>"']/g, (c) =>
+        ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' }[c] || c)
+    );
+}
+
 function text(status: string, tableName = '') {
-    const table = tableName ? `<b>${tableName}</b>` : '';
+    const table = tableName ? `<b>${escapeHtml(tableName)}</b>` : '';

Based on past review comments.


26-32: Add fallback for popup-blocked downloads.

window.open() may be blocked by popup blockers when triggered automatically (line 94). Consider using an <a> element with download attribute or fallback to showing a notification with a manual download button.

Apply this diff to add a fallback:

-    function downloadExportedFile(downloadUrl: string) {
-        if (!downloadUrl) {
-            return;
-        }
-
-        window.open(downloadUrl, '_blank');
+    function downloadExportedFile(downloadUrl: string) {
+        if (!downloadUrl) {
+            return;
+        }
+
+        const opened = window.open(downloadUrl, '_blank');
+        if (!opened) {
+            addNotification({
+                type: 'info',
+                message: 'Download ready. Click to download your CSV export.',
+                buttons: [{
+                    name: 'Download',
+                    method: () => {
+                        const a = document.createElement('a');
+                        a.href = downloadUrl;
+                        a.download = '';
+                        a.click();
+                    }
+                }]
+            });
+        }
     }
🧹 Nitpick comments (1)
src/lib/components/csvExportBox.svelte (1)

91-100: Consider using status constants for maintainability.

Status strings like 'completed', 'failed', 'pending', 'processing' are used throughout (also in graphSize, lines 67-68, 107-119). Consider defining constants to avoid typos and improve maintainability.

Example:

const ExportStatus = {
    PENDING: 'pending',
    PROCESSING: 'processing',
    COMPLETED: 'completed',
    FAILED: 'failed'
} as const;

type ExportStatusType = typeof ExportStatus[keyof typeof ExportStatus];

Then use ExportStatus.COMPLETED instead of 'completed'.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b354f45 and a9385fb.

📒 Files selected for processing (1)
  • src/lib/components/csvExportBox.svelte (1 hunks)
🧰 Additional context used
🪛 GitHub Actions: Tests
src/lib/components/csvExportBox.svelte

[warning] 1-1: Code style issues found by Prettier. Run 'prettier --write' to fix.

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: e2e
🔇 Additional comments (1)
src/lib/components/csvExportBox.svelte (1)

135-153: LGTM - Real-time subscription properly implemented.

The component correctly uses realtime.forConsole() for subscriptions and fetches initial state on mount with appropriate queries.

@abnegate abnegate merged commit 4a95f3a into main Nov 25, 2025
4 checks passed
@abnegate abnegate deleted the feat-csv-export branch November 25, 2025 03:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants