Skip to content

Conversation

HarshMN2345
Copy link
Member

@HarshMN2345 HarshMN2345 commented Sep 24, 2025

What does this PR do?

  • Add “View details” action for failed CSV imports in csvImportBox.svelte
  • Open modal displaying parsed JSON errors using Modal and Code

Test Plan

image

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?

yes

Summary by CodeRabbit

  • New Features

    • Failed CSV imports show a “View details” action that opens a modal with readable, JSON-formatted error details (copy/scroll supported).
    • Import items can include optional error details and the UI attempts client-side parsing for clearer display.
  • Style

    • Added inline error indicator and link-styled “View details”.
    • Reduced completed/failed progress graph height and clarified status text.
    • Minor layout/width adjustments for improved readability.
  • Chores

    • Link element now forwards additional attributes to anchors/buttons for more flexible usage.

Copy link

appwrite bot commented Sep 24, 2025

Console

Project ID: 688b7bf400350cbd60e9

Sites (2)
Site Status Logs Preview QR
 console-stage
688b7cf6003b1842c9dc
Ready Ready View Logs Preview URL QR Code
 console-cloud
688b7c18002b9b871a8f
Ready Ready View Logs Preview URL QR Code

Note

You can use Avatars API to generate QR code for any text or URLs.

Copy link
Contributor

coderabbitai bot commented Sep 24, 2025

Walkthrough

Adds an optional errors?: string[] to ImportItem and surfaces import errors in the UI. Failed items render an inline error row with an exclamation icon and a "View details" link that opens an "Import error" modal showing parsed/pretty-printed JSON (falls back to raw text), with copy and scroll support. Adjusts graph height for completed/failed statuses (100 → 60), updates some status text, and widens upload-box-content. Also updates link component to forward $$restProps to its rendered anchor or button.

Suggested reviewers

  • ItzNotABug

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The title "feat: show CSV import error details with inline indicator and modal" concisely and accurately summarizes the main change of adding an inline error indicator and a modal for CSV import error details. It is specific, uses a conventional commit prefix, and avoids vague wording or extraneous information. The single-sentence phrasing is clear and appropriate for teammates scanning PR history.
Docstring Coverage ✅ Passed No functions found in the changes. Docstring coverage check skipped.
✨ Finishing touches
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat-SER-290-Improve-csv-import-error-flow

📜 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 e9730d4 and ae43a4d.

📒 Files selected for processing (1)
  • src/lib/components/csvImportBox.svelte (8 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 (10)
src/lib/components/csvImportBox.svelte (10)

11-13: LGTM! Appropriate imports for the new error details feature.

The new imports (Icon, IconExclamationCircle, Modal, Code) are all used in the error display functionality.


19-19: LGTM! Updated Link import aligns with component changes.

The updated Link component now forwards $$restProps, making it compatible with the styling approach used in the error details link.


24-24: LGTM! Optional errors field added to ImportItem type.

The optional errors?: string[] field properly extends the ImportItem interface to support error details.


119-122: LGTM! Proper extraction and assignment of error data.

The code correctly extracts errors from the payload when they exist as an array and assigns them to the ImportItem.


148-148: Graph size change looks correct but verify design requirements.

The change from 100% to 60% for completed/failed states reduces the visual emphasis on final states. This appears intentional to match the visual treatment of other intermediate states.

The web search results don't provide specific design guidance for progress bar states in completed/failed CSV imports. However, the change to reduce the graph size from 100% to 60% appears to be intentional based on the past review comments context.


157-159: Text should reflect final states for completed/failed imports.

The current text still shows "Importing CSV file..." for completed and failed states, which doesn't accurately reflect the final status.

This issue was already flagged in previous reviews. The completed and failed cases should use explicit end-state messages rather than ongoing "Importing" text.


190-204: LGTM! Well-structured error details modal functionality.

The state management and parsing logic for the error details modal is well implemented:

  • Clear separation of concerns with distinct state variables
  • Robust JSON parsing with fallback to raw text
  • Proper modal trigger functionality

248-266: LGTM! Appropriate error indicator and details access.

The error UI implementation is well-designed:

  • Clear visual indicator with exclamation icon
  • Accessible link for viewing details
  • Proper error state detection
  • Good use of Layout.Stack for consistent spacing

277-283: LGTM! Clean modal implementation for error details.

The modal implementation correctly:

  • Uses proper binding for show state
  • Hides footer as appropriate for this use case
  • Displays parsed errors with copy and scroll functionality
  • Uses appropriate spacing with Layout.Stack

305-305: LGTM! Width increase accommodates new error details UI.

The increase from 304px to 324px provides appropriate space for the error details link and icon without cramping the layout.


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.

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/lib/components/csvImportBox.svelte (2)

108-123: Do not skip updates when only errors change (failed state).

If a second payload arrives with the same status but with error details, the current skip logic prevents updating the map, so the "View details" modal may never show content.

Apply this diff to consider error changes when deciding to skip:

-            const isDone = (s: string) => s === 'completed' || s === 'failed';
-            const isInProgress = (s: string) => ['pending', 'processing', 'uploading'].includes(s);
-
-            const shouldSkip =
-                (existing && isDone(existing.status) && isInProgress(status)) ||
-                existing?.status === status;
+            const isDone = (s: string) => s === 'completed' || s === 'failed';
+            const isInProgress = (s: string) => ['pending', 'processing', 'uploading'].includes(s);
+            const incomingErrors = Array.isArray((importData as Payload).errors)
+                ? ((importData as Payload).errors as string[])
+                : undefined;
+            const errorsEqual =
+                JSON.stringify(existing?.errors ?? []) === JSON.stringify(incomingErrors ?? []);
+            const shouldSkip =
+                (existing && isDone(existing.status) && isInProgress(status)) ||
+                (existing?.status === status && errorsEqual);
 
             if (shouldSkip) return items;
 
             const next = new Map(items);
-            const errors = Array.isArray((importData as Payload).errors)
-                ? ((importData as Payload).errors as string[])
-                : undefined;
-            next.set(importData.$id, { status, table: tableName ?? undefined, errors });
+            next.set(importData.$id, {
+                status,
+                table: tableName ?? undefined,
+                errors: incomingErrors
+            });
             return next;

153-164: Sanitize table name to prevent XSS via {@html}.

Table names are user-controlled. Rendering with {@html} and embedding the name directly is an XSS risk.

Apply this diff to sanitize the name inside the text builder:

     function text(status: string, collectionName = '') {
-        const name = collectionName ? `<b>${collectionName}</b>` : '';
+        const name = collectionName ? `<b>${escapeHtml(collectionName)}</b>` : '';
         switch (status) {
             case 'completed':
-            case 'failed':
-                return `Importing CSV file${name ? ` to ${name}` : ''}`;
+                return `CSV import finished${name ? ` for ${name}` : ''}.`;
+            case 'failed':
+                return `CSV import failed${name ? ` for ${name}` : ''}.`;
             case 'processing':
                 return `Importing CSV file${name ? ` to ${name}` : ''}`;
             default:
                 return 'Preparing CSV for import...';
         }
     }

Add this helper (outside this range, e.g., near other helpers):

function escapeHtml(value: string): string {
    const map: Record<string, string> = {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        '"': '&quot;',
        "'": '&#39;',
        '/': '&#x2F;',
        '`': '&#x60;',
        '=': '&#x3D;'
    };
    return value.replace(/[&<>"'`=\/]/g, (ch) => map[ch] ?? ch);
}
🧹 Nitpick comments (4)
src/lib/components/csvImportBox.svelte (4)

192-195: Avoid opening an empty modal when no errors are available.

Guard against undefined/empty arrays.

Apply this diff:

-    function openDetails(errors: string[] | undefined) {
-        selectedErrors = errors ?? [];
-        showDetails = true;
-    }
+    function openDetails(errors: string[] | undefined) {
+        if (!errors?.length) return;
+        selectedErrors = errors;
+        showDetails = true;
+    }

257-263: Render “View details” only when errors exist.

Prevents a no-op click and a blank modal.

Apply this diff:

-                                                There was an import issue.
-                                                <button
-                                                    class="link-button"
-                                                    on:click={() => openDetails(value.errors)}
-                                                    >View details</button>
+                                                There was an import issue.
+                                                {#if value.errors && value.errors.length}
+                                                    <button
+                                                        class="link-button"
+                                                        on:click={() => openDetails(value.errors)}
+                                                        >View details</button>
+                                                {/if}

276-282: Pluralize modal title based on error count.

Minor UX polish.

Apply this diff:

-<Modal title="Import error" bind:show={showDetails} hideFooter>
+<Modal
+    title={selectedErrors.length > 1 ? 'Import errors' : 'Import error'}
+    bind:show={showDetails}
+    hideFooter>

327-334: Add focus-visible style for keyboard accessibility.

Custom link-styled button currently lacks a visible focus indicator.

Apply this diff:

 .link-button {
   background: none;
   border: none;
   padding: 0;
   color: var(--fgcolor-error);
   text-decoration: underline;
   cursor: pointer;
 }
+ .link-button:focus-visible {
+   outline: 2px solid var(--outline-focus, #5b9dd9);
+   outline-offset: 2px;
+ }
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f5002e and 21940ef.

📒 Files selected for processing (1)
  • src/lib/components/csvImportBox.svelte (10 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 (4)
src/lib/components/csvImportBox.svelte (4)

11-13: LGTM: Correct imports for UI components.

Imports align with usage below.


23-24: LGTM: ImportItem.errors added and optionally propagated.

Type change is consistent with usage in the store updates and UI.


147-147: Verify: Completed/failed progress at 60% looks incomplete.

UX-wise, completed is usually 100%. Please confirm this change with design; otherwise revert to 100%.

Suggested change:

-                return 60;
+                return 100;

304-305: LGTM: Minor width tweak.

No concerns.

Comment on lines 156 to 161
case 'completed':
case 'failed':
return `Import to ${name} ${status}`;
return `Importing CSV file${name ? ` to ${name}` : ''}`;
case 'processing':
return `Importing CSV file${name ? ` to ${name}` : ''}`;
default:
Copy link
Contributor

@coderabbitai coderabbitai bot Sep 24, 2025

Choose a reason for hiding this comment

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

⚠️ Potential issue

Copy fix: Completed/failed messages shouldn't say "Importing".

Use explicit completed/failed text for clarity.

Apply this diff:

-            case 'completed':
-            case 'failed':
-                return `Importing CSV file${name ? ` to ${name}` : ''}`;
+            case 'completed':
+                return `CSV import finished${name ? ` for ${name}` : ''}.`;
+            case 'failed':
+                return `CSV import failed${name ? ` for ${name}` : ''}.`;
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
case 'completed':
case 'failed':
return `Import to ${name} ${status}`;
return `Importing CSV file${name ? ` to ${name}` : ''}`;
case 'processing':
return `Importing CSV file${name ? ` to ${name}` : ''}`;
default:
case 'completed':
return `CSV import finished${name ? ` for ${name}` : ''}.`;
case 'failed':
return `CSV import failed${name ? ` for ${name}` : ''}.`;
case 'processing':
return `Importing CSV file${name ? ` to ${name}` : ''}`;
default:
🤖 Prompt for AI Agents
In src/lib/components/csvImportBox.svelte around lines 156 to 161, the
'completed' and 'failed' cases currently return "Importing CSV file...", which
is incorrect; update those two cases to return explicit end-state messages
(e.g., "CSV import completed" when 'completed' and "CSV import failed" when
'failed'), preserving the existing optional "to {name}" interpolation when name
is present; leave the 'processing' case as-is returning the ongoing "Importing
CSV file" message.

Copy link
Member

Choose a reason for hiding this comment

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

I agree @HarshMN2345

Copy link
Contributor

Choose a reason for hiding this comment

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

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

case 'completed':
case 'failed':
return 100;
return 60;
Copy link
Member

Choose a reason for hiding this comment

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

should be 100% because the process is completed.

Copy link
Member

Choose a reason for hiding this comment

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

why was this marked resolved?

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

🧹 Nitpick comments (2)
src/lib/elements/link.svelte (1)

30-44: Avoid empty target/rel attributes when not external.

Prefer omitting attributes over empty strings to keep DOM clean and avoid oddities with target="".

-        target={external ? '_blank' : ''}
-        rel={external ? 'noopener noreferrer' : ''}>
+        target={external ? '_blank' : undefined}
+        rel={external ? 'noopener noreferrer' : undefined}>
src/lib/components/csvImportBox.svelte (1)

119-123: Details UX: guard empty errors, cap large payloads, and add modal fallback.

  • Hide/disable the link when no errors exist, and show a count to set expectations.
  • Cap the number of errors rendered to avoid massive strings freezing the UI.
  • Provide a friendly fallback in the modal when there’s nothing to show.
@@
-    let showDetails = false;
-    let selectedErrors: string[] = [];
+    let showDetails = false;
+    let selectedErrors: string[] = [];
+    const MAX_ERRORS = 100; // avoid rendering extremely large payloads
@@
-    function openDetails(errors: string[] | undefined) {
-        selectedErrors = errors ?? [];
+    function openDetails(errors: string[] | undefined) {
+        selectedErrors = (errors ?? []).slice(0, MAX_ERRORS);
         showDetails = true;
     }
@@
-                                    {#if value.status === 'failed'}
+                                    {#if value.status === 'failed'}
                                         <Layout.Stack
                                             direction="row"
                                             gap="xs"
                                             alignItems="center"
                                             inline>
@@
-                                            <Typography.Text color="--fgcolor-error">
+                                            <Typography.Text color="--fgcolor-error">
                                                 There was an import issue.
-                                                <Link
-                                                    style="color: inherit"
-                                                    on:click={() => openDetails(value.errors)}
-                                                    >View details</Link>
+                                                {#if value.errors?.length}
+                                                    <Link
+                                                        style="color: inherit"
+                                                        on:click={() => openDetails(value.errors)}>
+                                                        View details ({value.errors.length})
+                                                    </Link>
+                                                {:else}
+                                                    <span>No details available</span>
+                                                {/if}
                                             </Typography.Text>
                                         </Layout.Stack>
                                     {/if}
@@
-<Modal title="Import error" bind:show={showDetails} hideFooter>
-    <Layout.Stack gap="m">
-        <Layout.Stack>
-            <Code language="json" code={parsedErrors.join('\n\n')} withCopy allowScroll />
-        </Layout.Stack>
-    </Layout.Stack>
-</Modal>
+<Modal title="Import error" bind:show={showDetails} hideFooter>
+    <Layout.Stack gap="m">
+        <Layout.Stack>
+            {#if parsedErrors.length}
+                <Code language="json" code={parsedErrors.join('\n\n')} withCopy allowScroll />
+            {:else}
+                <Typography.Text>No error details available.</Typography.Text>
+            {/if}
+        </Layout.Stack>
+    </Layout.Stack>
+</Modal>

Also applies to: 190-205, 248-266, 277-283

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 21940ef and e9730d4.

📒 Files selected for processing (2)
  • src/lib/components/csvImportBox.svelte (8 hunks)
  • src/lib/elements/link.svelte (2 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 (4)
src/lib/elements/link.svelte (1)

32-32: Nice: passthrough props/events now supported on both branches.

Spreading $$restProps on Anchor/Button enables aria-/data-/class and user handlers while keeping your own tracking intact. Good ordering (explicit props after spread) prevents accidental overrides.

Also applies to: 46-55

src/lib/components/csvImportBox.svelte (3)

154-162: Copy fix: completed/failed shouldn’t say “Importing”.

Use explicit end-state messages.

     function text(status: string, collectionName = '') {
         const name = collectionName ? `<b>${collectionName}</b>` : '';
         switch (status) {
-            case 'completed':
-            case 'failed':
-                return `Importing CSV file${name ? ` to ${name}` : ''}`;
+            case 'completed':
+                return `CSV import finished${name ? ` for ${name}` : ''}.`;
+            case 'failed':
+                return `CSV import failed${name ? ` for ${name}` : ''}.`;
             case 'processing':
                 return `Importing CSV file${name ? ` to ${name}` : ''}`;
             default:
                 return 'Preparing CSV for import...';
         }
     }

148-148: Graph height tweak LGTM.

Reducing completed/failed bar height to 60% aligns with the new inline error row and saves vertical space.


305-305: Width increase: verify layout on narrow containers.

324px may overflow in tighter sidebars. Confirm it doesn’t cause horizontal scroll at common breakpoints or with long table names.

Consider making width responsive (e.g., max-width: 100%;) if you spot overflow in QA.

// re-render the key for sheet UI.
import { hash } from '$lib/helpers/string';
import { spreadsheetRenderKey } from '$routes/(console)/project-[region]-[project]/databases/database-[database]/table-[table]/store';
import Link from '$lib/elements/link.svelte';
Copy link
Member

Choose a reason for hiding this comment

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

use import { Link } from '$lib/elements';

Comment on lines 156 to 161
case 'completed':
case 'failed':
return `Import to ${name} ${status}`;
return `Importing CSV file${name ? ` to ${name}` : ''}`;
case 'processing':
return `Importing CSV file${name ? ` to ${name}` : ''}`;
default:
Copy link
Member

Choose a reason for hiding this comment

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

I agree @HarshMN2345

const next = new Map(items);
next.set(importData.$id, { status, table: tableName ?? undefined });
const errors = Array.isArray((importData as Payload).errors)
Copy link
Member

Choose a reason for hiding this comment

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

don't think we need to cast here. we have a similar check above for showCompletionNotification, we can make a helper function to return the error instead of hard type casts and duplicating the logic.

case 'completed':
case 'failed':
return 100;
return 60;
Copy link
Member

Choose a reason for hiding this comment

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

why was this marked resolved?

Comment on lines +196 to +202
parsedErrors = selectedErrors.map((err) => {
try {
return JSON.stringify(JSON.parse(err), null, 2);
} catch {
return err;
}
});
Copy link
Member

Choose a reason for hiding this comment

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

as mentioned above, we should extract this logic to a function since its 2/3 used now.

<Modal title="Import error" bind:show={showDetails} hideFooter>
<Layout.Stack gap="m">
<Layout.Stack>
<Code language="json" code={parsedErrors.join('\n\n')} withCopy allowScroll />
Copy link
Member

Choose a reason for hiding this comment

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

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.

2 participants