Skip to content

Header-only workbook in bulk assignments is rejected as having no columns instead of no data rows #1645

Open

Description

@joshunrau

parseWorkbook gives the wrong error for an .xlsx file whose first sheet has a header row but no data rows. It takes the headers from the keys of the first object returned by sheet_to_json. For a header-only sheet that array is empty, so the header list is empty too and the user is told "No columns were found. The first row must contain column headers." That is wrong, because the first row does contain headers. The CSV/paste path handles the same content correctly and says "The file contains headers but no data rows."

Where

apps/web/src/utils/bulk-assignments.ts:287-291:

const rows = utils.sheet_to_json<{ [key: string]: unknown }>(workbook.Sheets[sheetName]!, { defval: '', raw: true });
const headers = Object.keys(rows[0] ?? {}).map((header) => header.trim());

buildResult (apps/web/src/utils/bulk-assignments.ts:149-150) then fails on headers.length === 0 before it reaches the "no data rows" check at line 164.

Reproduce

  1. Create a workbook whose only sheet holds a single cell subjectId in A1.
  2. Upload it in the bulk remote assignment wizard.
  • Actual: "No columns were found. The first row must contain column headers."
  • Expected: "The file contains headers but no data rows.", the same message parseDelimitedText('subjectId\n') gives.

Tests

apps/web/src/utils/__tests__/bulk-assignments.test.ts, test 'should reject a sheet holding only a header row' (in the parseWorkbook block). It pins the wrong message with toContain('No columns were found'). When fixing, change the assertion to toContain('no data rows').

Suggested fix

Read the headers from the sheet's first row rather than from the first data object. For example, take utils.sheet_to_json<unknown[]>(sheet, { header: 1 })[0] for the headers and keep the existing call for the rows.

Found while raising unit test coverage in #1632.

Activity

  1. added
    BugType: existing behavior is wrong
    Priority: LowNice to have, cosmetic or speculative
    Difficulty: LowIsolated change, about 2 hours or less
    Area: Webapps/web (clinician-facing React SPA)
    Good First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributor
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: Webapps/web (clinician-facing React SPA)BugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessGood First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributorPriority: LowNice to have, cosmetic or speculative

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions