Add error display to file upload

This commit is contained in:
Eli Ribble 2026-04-15 19:02:25 +00:00
parent 344f4bcaa5
commit 66d35428fa
No known key found for this signature in database
7 changed files with 93 additions and 28 deletions

View file

@ -114,6 +114,15 @@ var FileuploadFiles = Table[
Generated: false,
AutoIncr: false,
},
Error: column{
Name: "error",
DBType: "text",
Default: "",
Comment: "",
Nullable: false,
Generated: false,
AutoIncr: false,
},
},
Indexes: fileuploadFileIndexes{
FilePkey: index{
@ -184,11 +193,12 @@ type fileuploadFileColumns struct {
SizeBytes column
FileUUID column
Committer column
Error column
}
func (c fileuploadFileColumns) AsSlice() []column {
return []column{
c.ID, c.ContentType, c.Created, c.CreatorID, c.Deleted, c.Name, c.OrganizationID, c.Status, c.SizeBytes, c.FileUUID, c.Committer,
c.ID, c.ContentType, c.Created, c.CreatorID, c.Deleted, c.Name, c.OrganizationID, c.Status, c.SizeBytes, c.FileUUID, c.Committer, c.Error,
}
}

View file

@ -0,0 +1,6 @@
-- +goose Up
ALTER TABLE fileupload.file ADD COLUMN error TEXT;
UPDATE fileupload.file SET error = '';
ALTER TABLE fileupload.file ALTER COLUMN error SET NOT NULL;
-- +goose Down
ALTER TABLE fileupload.file DROP COLUMN error;

View file

@ -38,6 +38,7 @@ type FileuploadFile struct {
SizeBytes int32 `db:"size_bytes" `
FileUUID uuid.UUID `db:"file_uuid" `
Committer null.Val[int32] `db:"committer" `
Error string `db:"error" `
R fileuploadFileR `db:"-" `
}
@ -65,7 +66,7 @@ type fileuploadFileR struct {
func buildFileuploadFileColumns(alias string) fileuploadFileColumns {
return fileuploadFileColumns{
ColumnsExpr: expr.NewColumnsExpr(
"id", "content_type", "created", "creator_id", "deleted", "name", "organization_id", "status", "size_bytes", "file_uuid", "committer",
"id", "content_type", "created", "creator_id", "deleted", "name", "organization_id", "status", "size_bytes", "file_uuid", "committer", "error",
).WithParent("fileupload.file"),
tableAlias: alias,
ID: psql.Quote(alias, "id"),
@ -79,6 +80,7 @@ func buildFileuploadFileColumns(alias string) fileuploadFileColumns {
SizeBytes: psql.Quote(alias, "size_bytes"),
FileUUID: psql.Quote(alias, "file_uuid"),
Committer: psql.Quote(alias, "committer"),
Error: psql.Quote(alias, "error"),
}
}
@ -96,6 +98,7 @@ type fileuploadFileColumns struct {
SizeBytes psql.Expression
FileUUID psql.Expression
Committer psql.Expression
Error psql.Expression
}
func (c fileuploadFileColumns) Alias() string {
@ -121,10 +124,11 @@ type FileuploadFileSetter struct {
SizeBytes omit.Val[int32] `db:"size_bytes" `
FileUUID omit.Val[uuid.UUID] `db:"file_uuid" `
Committer omitnull.Val[int32] `db:"committer" `
Error omit.Val[string] `db:"error" `
}
func (s FileuploadFileSetter) SetColumns() []string {
vals := make([]string, 0, 11)
vals := make([]string, 0, 12)
if s.ID.IsValue() {
vals = append(vals, "id")
}
@ -158,6 +162,9 @@ func (s FileuploadFileSetter) SetColumns() []string {
if !s.Committer.IsUnset() {
vals = append(vals, "committer")
}
if s.Error.IsValue() {
vals = append(vals, "error")
}
return vals
}
@ -195,6 +202,9 @@ func (s FileuploadFileSetter) Overwrite(t *FileuploadFile) {
if !s.Committer.IsUnset() {
t.Committer = s.Committer.MustGetNull()
}
if s.Error.IsValue() {
t.Error = s.Error.MustGet()
}
}
func (s *FileuploadFileSetter) Apply(q *dialect.InsertQuery) {
@ -203,7 +213,7 @@ func (s *FileuploadFileSetter) Apply(q *dialect.InsertQuery) {
})
q.AppendValues(bob.ExpressionFunc(func(ctx context.Context, w io.StringWriter, d bob.Dialect, start int) ([]any, error) {
vals := make([]bob.Expression, 11)
vals := make([]bob.Expression, 12)
if s.ID.IsValue() {
vals[0] = psql.Arg(s.ID.MustGet())
} else {
@ -270,6 +280,12 @@ func (s *FileuploadFileSetter) Apply(q *dialect.InsertQuery) {
vals[10] = psql.Raw("DEFAULT")
}
if s.Error.IsValue() {
vals[11] = psql.Arg(s.Error.MustGet())
} else {
vals[11] = psql.Raw("DEFAULT")
}
return bob.ExpressSlice(ctx, w, d, start, vals, "", ", ", "")
}))
}
@ -279,7 +295,7 @@ func (s FileuploadFileSetter) UpdateMod() bob.Mod[*dialect.UpdateQuery] {
}
func (s FileuploadFileSetter) Expressions(prefix ...string) []bob.Expression {
exprs := make([]bob.Expression, 0, 11)
exprs := make([]bob.Expression, 0, 12)
if s.ID.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
@ -358,6 +374,13 @@ func (s FileuploadFileSetter) Expressions(prefix ...string) []bob.Expression {
}})
}
if s.Error.IsValue() {
exprs = append(exprs, expr.Join{Sep: " = ", Exprs: []bob.Expression{
psql.Quote(append(prefix, "error")...),
psql.Arg(s.Error),
}})
}
return exprs
}
@ -1074,6 +1097,7 @@ type fileuploadFileWhere[Q psql.Filterable] struct {
SizeBytes psql.WhereMod[Q, int32]
FileUUID psql.WhereMod[Q, uuid.UUID]
Committer psql.WhereNullMod[Q, int32]
Error psql.WhereMod[Q, string]
}
func (fileuploadFileWhere[Q]) AliasedAs(alias string) fileuploadFileWhere[Q] {
@ -1093,6 +1117,7 @@ func buildFileuploadFileWhere[Q psql.Filterable](cols fileuploadFileColumns) fil
SizeBytes: psql.Where[Q, int32](cols.SizeBytes),
FileUUID: psql.Where[Q, uuid.UUID](cols.FileUUID),
Committer: psql.WhereNull[Q, int32](cols.Committer),
Error: psql.Where[Q, string](cols.Error),
}
}

View file

@ -192,9 +192,11 @@ func JobImport(ctx context.Context, txn bob.Executor, file_id int32) error {
err = importCSV(ctx, file_id, parseCSVFlyover, processCSVFlyover)
}
if err != nil {
log.Debug().Err(err).Msg("failed to import CSV")
_, err := psql.Update(
um.Table("fileupload.file"),
um.SetCol("status").ToArg("error"),
um.SetCol("error").ToArg(err.Error()),
um.Where(psql.Quote("id").EQ(psql.Arg(file_id))),
).Exec(ctx, db.PGInstance.BobDB)
if err != nil {

View file

@ -36,6 +36,7 @@ const (
type Upload struct {
Created time.Time `db:"created" json:"created"`
Error string `db:"error" json:"error"`
Filename string `db:"filename" json:"filename"`
ID int32 `db:"id" json:"id"`
RecordCount int `db:"recordcount" json:"recordcount"`
@ -92,6 +93,7 @@ func NewUpload(ctx context.Context, u User, upload file.Upload, t enums.Fileuplo
Created: omit.From(time.Now()),
CreatorID: omit.From(int32(u.ID)),
Deleted: omitnull.FromPtr[time.Time](nil),
Error: omit.From(""),
Name: omit.From(upload.Name),
OrganizationID: omit.From(u.Organization.ID),
Status: omit.From(enums.FileuploadFilestatustypeUploaded),
@ -167,6 +169,7 @@ func UploadList(ctx context.Context, org Organization) ([]Upload, error) {
"file.created AS created",
//"file.creator_id",
//"file.deleted",
"file.error AS error",
"file.id AS id",
"file.name AS filename",
//"file.organization_id",
@ -235,9 +238,9 @@ func getUploadDetailPool(ctx context.Context, file *models.FileuploadFile) (*Upl
Tags: tags,
})
}
log.Debug().Str("status", file.Status.String()).Int32("id", file.ID).Msg("returning")
return &Upload{
Created: file.Created,
Error: file.Error,
Filename: file.Name,
ID: file.ID,
RecordCount: len(pool_rows),

View file

@ -559,6 +559,17 @@ export interface ReviewTaskListResponse {
}
export interface UploadDTO {
created: string;
error: string;
filename: string;
id: number;
recordcount: number;
status: string;
type: string;
csv_pool?: CSVPoolDetail;
}
export interface UploadOptions {
created: Date;
error: string;
filename: string;
id: number;
recordcount: number;
@ -567,25 +578,29 @@ export interface UploadDTO {
csv_pool?: CSVPoolDetail;
}
export class Upload {
constructor(
public created: Date,
public filename: string,
public id: number,
public recordcount: number,
public status: string,
public type: string,
public csv_pool?: CSVPoolDetail,
) {}
created: Date;
error: string;
filename: string;
id: number;
recordcount: number;
status: string;
type: string;
csv_pool?: CSVPoolDetail;
constructor(options: UploadOptions) {
this.created = options.created;
this.error = options.error;
this.filename = options.filename;
this.id = options.id;
this.recordcount = options.recordcount;
this.status = options.status;
this.type = options.type;
this.csv_pool = options.csv_pool;
}
static fromJSON(json: UploadDTO): Upload {
return new Upload(
new Date(json.created),
json.filename,
json.id,
json.recordcount,
json.status,
json.type,
json.csv_pool,
);
return new Upload({
...json,
created: new Date(json.created),
});
}
}

View file

@ -150,12 +150,11 @@ tr.has-error {
<p>loading</p>
</div>
<div v-else>
<MapMultipoint
:bounds="session.organization!.service_area"
<MapLocator
:markers="[]"
:organizationId="session.organization!.id"
:tegola="session.urls?.tegola ?? ''"
></MapMultipoint>
/>
</div>
</div>
@ -202,8 +201,13 @@ tr.has-error {
</div>
<template v-else>
<div v-if="upload.error" class="alert alert-error" role="alert">
<i class="bi bi-exclamation-triangle me-2"></i>
<strong>Error:</strong> Your upload failed to parse correctly. The
specific error was: '{{ upload.error }}'
</div>
<div
v-if="
v-else-if="
!upload.csv_pool?.pools || upload.csv_pool.pools.length === 0
"
class="alert alert-warning"