feat(migration): add skip rows option to CSV import

Allow users to skip the first N data rows when importing CSV files.
This is useful when the CSV contains metadata rows before the actual
task data begins. Adds skip_rows to ImportConfig (backend) and a
number input in the parsing options UI (frontend).
This commit is contained in:
kolaente 2026-03-05 12:24:11 +01:00
parent e031a8d428
commit 56c4d97b50
4 changed files with 27 additions and 0 deletions

View File

@ -651,6 +651,7 @@
"parsingOptions": "Parsing Options",
"delimiter": "Delimiter",
"dateFormat": "Date Format",
"skipRows": "Skip Rows",
"mapColumns": "Map Columns",
"example": "e.g.",
"preview": "Preview",

View File

@ -46,6 +46,7 @@ export interface ImportConfig {
delimiter: string
quote_char: string
date_format: string
skip_rows: number
mapping: ColumnMapping[]
}

View File

@ -83,6 +83,17 @@
</select>
</div>
</div>
<div class="option-group">
<label for="skipRows">{{ $t('migrate.csv.skipRows') }}</label>
<input
id="skipRows"
v-model.number="config.skip_rows"
type="number"
class="input"
min="0"
@change="updatePreview"
>
</div>
</div>
</div>
@ -240,6 +251,7 @@ const config = ref<ImportConfig>({
delimiter: ',',
quote_char: '"',
date_format: '2006-01-02',
skip_rows: 0,
mapping: [],
})
@ -314,6 +326,7 @@ async function handleFileUpload() {
delimiter: result.delimiter,
quote_char: result.quote_char,
date_format: result.date_format,
skip_rows: 0,
mapping: result.suggested_mapping,
}
@ -376,6 +389,7 @@ function resetToUpload() {
delimiter: ',',
quote_char: '"',
date_format: '2006-01-02',
skip_rows: 0,
mapping: [],
}
}

View File

@ -126,6 +126,7 @@ type ImportConfig struct {
Delimiter string `json:"delimiter"`
QuoteChar string `json:"quote_char"`
DateFormat string `json:"date_format"`
SkipRows int `json:"skip_rows"`
Mapping []ColumnMapping `json:"mapping"`
}
@ -398,6 +399,11 @@ func PreviewImport(file io.ReaderAt, size int64, config ImportConfig) (*PreviewR
return nil, &migration.ErrNotACSVFile{}
}
// Skip rows if configured
if config.SkipRows > 0 && config.SkipRows < len(rows) {
rows = rows[config.SkipRows:]
}
result := &PreviewResult{
Tasks: make([]PreviewTask, 0, minInt(5, len(rows))),
TotalRows: len(rows),
@ -567,6 +573,11 @@ func MigrateWithConfig(u *user.User, file io.ReaderAt, size int64, config Import
return &migration.ErrNotACSVFile{}
}
// Skip rows if configured
if config.SkipRows > 0 && config.SkipRows < len(rows) {
rows = rows[config.SkipRows:]
}
if len(rows) == 0 {
return &migration.ErrFileIsEmpty{}
}