Skip to content

feat(invitations): CSV header validation and ext ID conflict resolution#8427

Draft
LWS49 wants to merge 1 commit into
lws49/feat-ext-id-invite-flowfrom
lws49/feat-invite-header-validation
Draft

feat(invitations): CSV header validation and ext ID conflict resolution#8427
LWS49 wants to merge 1 commit into
lws49/feat-ext-id-invite-flowfrom
lws49/feat-invite-header-validation

Conversation

@LWS49
Copy link
Copy Markdown
Collaborator

@LWS49 LWS49 commented Jun 3, 2026

Summary

This PR adds two features to the CSV invitation flow. First, it introduces an external ID conflict resolution prompt: when an uploaded CSV would change existing external IDs on pending invitations or enrolled members, the upload is held and the admin is shown a non-dismissible dialog listing affected records, with options to Keep Existing IDs, Replace All, or Cancel. The hold-and-retry is handled client-side via a useRef holding the original File object, keeping temporary upload state out of Redux. Second, it replaces the column-count heuristic (timeline_template_mismatch) with strict header name validation - the CSV header row must now exactly match the template file's column names (case-insensitive), so column name mismatches like timeline_algorithm vs Timeline produce an actionable error message instead of a numeric column count complaint.

Design decisions

  • Conflict detection uses raise-and-rollback - PendingExternalIdUpdates is a StandardError (not ActiveRecord::Rollback), so it propagates out of the transaction naturally and guarantees a rollback without a manual guard.
  • File held in useRef, not Redux - the retry after conflict resolution reuses the original File object from the first submit, keeping transient upload state out of the store.
  • Header validation compares all columns, not just name/email - the old detection only checked col 0 and col 1; validation now compares the full header row against the canonical template, so misnamed columns anywhere in the row are caught.

Regression prevention

Tests cover: header row acceptance (exact match, case-insensitive), header rejection (wrong column names for both timeline and no-timeline modes), headerless CSV passthrough, conflict detection (non-destructive first-pass rollback), Keep Existing and Replace All resolution flows, ExternalIdConflictTable column rendering and null external ID display, ExternalIdConflictPrompt conditional section rendering and callback firing.

Manual testing confirmed all 8 scenarios: valid template upload, invalid-header rejection, headerless CSV, timeline-template-to-no-timeline-course rejection, conflict prompt appearance, Keep Existing, Replace All, and Cancel.

Backward compat: admins uploading CSVs without external ID changes are unaffected. CSVs without a header row continue to parse as before. Admins using old-format column names (e.g., timeline_algorithm, external_id) will now see an actionable error directing them to the template file rather than a column-count message.

image

@LWS49 LWS49 force-pushed the lws49/feat-invite-header-validation branch from 37a5204 to 211f960 Compare June 3, 2026 08:45
@LWS49 LWS49 changed the title Lws49/feat invite header validation feat(invitations): CSV header validation and ext ID conflict resolution Jun 3, 2026
… resolution

- validate CSV column headers explicitly instead of checking column count
- detect external ID changes on existing users/invitations before applying;
  surface a confirmation prompt (Keep Existing / Replace) with a side-by-side
  Current / New External ID table capped at 320px for large uploads
- conflict resolution wired into both file upload and individual invite form;
  file ref preserved on Go Back so admin need not re-select
- add ExternalIdResolution and PendingExternalIdConflict types; update invite
  API and operations layer to detect and return conflict payload
- controller rescues PendingExternalIdUpdates, renders jbuilder partial;
  concern branches on @resolution to populate pending vs updated arrays
- i18n: add EN/KO/ZH translations for conflict prompt and new table columns
@LWS49 LWS49 force-pushed the lws49/feat-invite-header-validation branch from 211f960 to 67de552 Compare June 3, 2026 09:38
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.

1 participant