When pasted or uploaded delimited text has a malformed line, parseDelimitedText reports a row number one lower than the row the user sees in their file. It passes Papa Parse's zero-based error.row through unchanged. Every other row-numbered error in the bulk remote assignment wizard counts the header as row 1, which is what BulkParseError.row documents. So the wizard's error list (Row N:) points the user at the line above the one that is actually broken, and a single file can get row numbers from both schemes depending on which check fails.
Where
apps/web/src/utils/bulk-assignments.ts:261, which passes the parser's row through as is:
throw new BulkParseFailure(fatal.map((error) => ({ message: error.message, row: error.row })));
apps/web/src/utils/bulk-assignments.ts:68, where BulkParseError.row is documented as the "1-based index of the offending row as the user sees it in their file".
apps/web/src/utils/bulk-assignments.ts:339-340, where resolveSubjectIds uses index + 2, so the first data row is row 2.
apps/web/src/components/BulkRemoteAssignmentWizard/ErrorList.tsx:32-34 renders the number as Row ${error.row}:.
Reproduce
Paste this into the wizard (or call parseDelimitedText with it):
The unterminated quote is on line 2 of the file.
- Actual:
{ message: 'Quoted field unterminated', row: 1 }, shown as "Row 1: Quoted field unterminated".
- Expected:
row: 2.
The offset is the same further down. subjectId\na\nb\n"c has the bad quote on line 4, and Papa Parse reports row: 3.
Tests
apps/web/src/utils/__tests__/bulk-assignments.test.ts, test "should reject malformed quoting with the parser's error message". It currently asserts only the message, because a row number of 1 was deliberately left unpinned. When fixing, change it back to assert the full error, [{ message: 'Quoted field unterminated', row: 2 }].
Suggested fix
Map error.row to error.row + 1 when it is defined. Papa Parse's row counts the header line from zero, so this gives the same header-is-row-1 numbering that resolveSubjectIds uses.
Found while raising unit test coverage in #1632.
When pasted or uploaded delimited text has a malformed line,
parseDelimitedTextreports a row number one lower than the row the user sees in their file. It passes Papa Parse's zero-basederror.rowthrough unchanged. Every other row-numbered error in the bulk remote assignment wizard counts the header as row 1, which is whatBulkParseError.rowdocuments. So the wizard's error list (Row N:) points the user at the line above the one that is actually broken, and a single file can get row numbers from both schemes depending on which check fails.Where
apps/web/src/utils/bulk-assignments.ts:261, which passes the parser's row through as is:apps/web/src/utils/bulk-assignments.ts:68, whereBulkParseError.rowis documented as the "1-based index of the offending row as the user sees it in their file".apps/web/src/utils/bulk-assignments.ts:339-340, whereresolveSubjectIdsusesindex + 2, so the first data row is row 2.apps/web/src/components/BulkRemoteAssignmentWizard/ErrorList.tsx:32-34renders the number asRow ${error.row}:.Reproduce
Paste this into the wizard (or call
parseDelimitedTextwith it):The unterminated quote is on line 2 of the file.
{ message: 'Quoted field unterminated', row: 1 }, shown as "Row 1: Quoted field unterminated".row: 2.The offset is the same further down.
subjectId\na\nb\n"chas the bad quote on line 4, and Papa Parse reportsrow: 3.Tests
apps/web/src/utils/__tests__/bulk-assignments.test.ts, test"should reject malformed quoting with the parser's error message". It currently asserts only the message, because a row number of 1 was deliberately left unpinned. When fixing, change it back to assert the full error,[{ message: 'Quoted field unterminated', row: 2 }].Suggested fix
Map
error.rowtoerror.row + 1when it is defined. Papa Parse's row counts the header line from zero, so this gives the same header-is-row-1 numbering thatresolveSubjectIdsuses.Found while raising unit test coverage in #1632.