DEV Community

Miran
Miran

Posted on

My CSV Import Needed a Debugging Surface, Not Just a Success Toast

A bulk import can partially succeed, so I wanted the result screen to explain every row instead of reducing the whole file to success or failure.

A CSV import result panel showing separate Imported, Warning, and Failed row outcomes with row numbers and reasons, emphasizing row-level debugging after a bulk shift import
A CSV upload can finish successfully while one of its rows never becomes a shift.

That makes a message like this almost useless:

Import complete
Enter fullscreen mode Exit fullscreen mode

Complete in what sense?

If the file contained 40 assigned shifts, I wanted to know whether all 40 were created, whether three were rejected, or whether one was imported with something the owner should double-check.

That pushed me to think about the import result as a debugging surface rather than a confirmation toast.

The file isn't the smallest useful unit

For this import, one CSV row represents one assigned shift.

A simplified row needs things like:

shift_date
start_time_local
end_time_local
assigned_member_email
client_name
address_full
Enter fullscreen mode Exit fullscreen mode

There are optional fields too, but those required values are enough to show the problem.

Imagine a file with three rows:

Row 2
Maria
maria@example.com
May 12, 9:00 AM
Maple Street home

Row 3
Jordan
jordan@example.com
May 12, 9:00 AM
Oak Street office

Row 4
Sam
sam-old@example.com
May 13, 6:00 PM
Pine Avenue clinic
Enter fullscreen mode Exit fullscreen mode

Rows 2 and 3 may be perfectly usable.

Row 4 may fail because sam-old@example.com does not match an active team member in the workspace.

Rejecting the entire file would make the owner repair one row and then resubmit work that was already valid.

Pretending the whole file succeeded would be worse. Now the owner may assume Sam has a shift that was never created.

So the useful result isn't:

File: success
Enter fullscreen mode Exit fullscreen mode

It's closer to:

Row 2: Imported
Row 3: Imported
Row 4: Failed — Staff email not found
Enter fullscreen mode Exit fullscreen mode

That changes the level at which the UI needs to explain failure.

I separated Failed from Warning

Not every suspicious row should behave like an invalid row.

A scheduling conflict is a good example.

Suppose two assignments overlap for the same cleaner.

That deserves attention, but I don't necessarily want the import tool deciding that the new shift is invalid. The schedule may intentionally contain overlap while an owner is still resolving staffing, or the warning may need human judgment.

So the current import flow treats that case differently:

Imported
Warning
Failed
Enter fullscreen mode Exit fullscreen mode

A Failed row is skipped.

A Warning row is still imported, but something about it should be reviewed.

That distinction matters because status determines whether a shift actually exists after the import.

If both cases were displayed as a generic red error, the owner would still have to guess whether the row was created.

If both were allowed through without explanation, the warning would disappear at exactly the moment it was most useful.

The CSV import guide reflects that distinction directly: valid rows create shifts, invalid rows are skipped, and detected scheduling conflicts remain imported with a warning.

The reason column does more work than the status

A row labeled Failed still leaves another question:

Why?

The result needs enough context to make the next fix obvious.

For the current flow, the useful row-level output includes information such as:

Row number
Assigned staff email
Shift date and time
Client name
Status
Reason
Enter fullscreen mode Exit fullscreen mode

That means someone can look at:

Row 4
sam-old@example.com
May 13 · 6:00 PM
Pine Avenue clinic
Failed
Staff email not found
Enter fullscreen mode Exit fullscreen mode

and go directly back to the corresponding spreadsheet row.

They don't have to infer which of 40 assignments caused the problem.

I like this because debugging a bulk operation should not require reproducing the whole operation mentally.

The result screen already knows which row produced which outcome. Hiding that information behind a generic failure message just transfers the debugging work to the user.

Partial success also creates a retry problem

Once valid rows can succeed while invalid rows fail, retry behavior becomes important.

The current CSV import creates new shifts. It does not overwrite existing ones, and it does not automatically remove duplicates.

That means uploading the exact same file again is not a harmless retry.

If 39 rows succeeded and one failed, fixing that one cell and importing all 40 rows again may create another copy of the 39 successful shifts.

So the debugging surface has another job: make partial success visible enough that the owner knows what already happened.

This is why I don't want the result page to disappear behind a green toast.

The output is part of the import operation.

It tells the user what is safe to fix next.

For the current version, the practical rule is intentionally simple: review the file before importing it again rather than assuming a repeated upload is idempotent.

I could imagine a more advanced importer eventually supporting deduplication keys, dry runs, row selection, or safe retries.

I'm not claiming those exist now.

Clear row-level results solve the smaller problem first.

Silent import was another reason to make the result explicit

Imported shifts are silent by default.

An owner or admin can choose whether imported staff should receive email notifications, but notifications are not something I want quietly coupled to every upload.

That makes the import result especially important.

The upload is not just “I sent a spreadsheet somewhere.”

It may have created dozens of real Unconfirmed shifts.

Some rows may have failed.

Some may carry warnings.

And depending on the selected option, staff notifications may or may not follow.

The UI after the operation needs to make that outcome legible before the owner moves on.

A bulk action deserves a receipt

I used to think of import feedback mostly as validation:

tell the user when something is wrong.

Now I think the more useful model is a receipt.

For each input row:

What did we receive?
What did we do with it?
Did a shift get created?
Is anything worth checking?
If it failed, why?
Enter fullscreen mode Exit fullscreen mode

That is much easier to debug than one message attached to an entire file.

A CSV importer can save someone from creating 40 shifts manually.

But if it turns one bad row into a mystery, it gives some of that saved time right back.

The result screen is not cleanup around the import.

It is part of the feature.

Top comments (0)