Refactor and fix importer state bugs
- Fixed invalid file uploads leaving the importer in a hanging state without exiting - Make the import UI always show the upload status page so that error logs are visible
This commit is contained in:
parent
cfec13c589
commit
3bf405fc1c
|
@ -79,6 +79,7 @@ class TheFormDef extends React.PureComponent {
|
|||
message: "Error",
|
||||
description: e.message
|
||||
})
|
||||
this.props.fetchimportState()
|
||||
this.setState({ formLoading: false })
|
||||
})
|
||||
}
|
||||
|
@ -289,7 +290,7 @@ class Importing extends React.PureComponent {
|
|||
)}
|
||||
|
||||
<Row className="import-container">
|
||||
<Col span="10" offset="3">
|
||||
<Col span={10} offset={3}>
|
||||
<div className="stats center">
|
||||
<div>
|
||||
<Progress type="line" percent={progressPercent} />
|
||||
|
|
|
@ -90,10 +90,7 @@ func handleImportSubscribers(c echo.Context) error {
|
|||
dir, files, err := impSess.ExtractZIP(out.Name(), 1)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError,
|
||||
fmt.Sprintf("Error extracting ZIP file: %v", err))
|
||||
} else if len(files) == 0 {
|
||||
return echo.NewHTTPError(http.StatusBadRequest,
|
||||
"No CSV files found to import.")
|
||||
fmt.Sprintf("Error processing ZIP file: %v", err))
|
||||
}
|
||||
go impSess.LoadCSV(dir+"/"+files[0], rune(r.Delim[0]))
|
||||
}
|
||||
|
|
|
@ -59,9 +59,8 @@ type Importer struct {
|
|||
db *sql.DB
|
||||
notifCB models.AdminNotifCallback
|
||||
|
||||
isImporting bool
|
||||
stop chan bool
|
||||
status *Status
|
||||
status Status
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
|
@ -108,7 +107,7 @@ func New(upsert *sql.Stmt, blacklist *sql.Stmt, db *sql.DB, notifCB models.Admin
|
|||
stop: make(chan bool, 1),
|
||||
db: db,
|
||||
notifCB: notifCB,
|
||||
status: &Status{Status: StatusNone, logBuf: bytes.NewBuffer(nil)},
|
||||
status: Status{Status: StatusNone, logBuf: bytes.NewBuffer(nil)},
|
||||
}
|
||||
|
||||
return &im
|
||||
|
@ -122,7 +121,7 @@ func (im *Importer) NewSession(fName, mode string, listIDs []int) (*Session, err
|
|||
}
|
||||
|
||||
im.Lock()
|
||||
im.status = &Status{Status: StatusImporting,
|
||||
im.status = Status{Status: StatusImporting,
|
||||
Name: fName,
|
||||
logBuf: bytes.NewBuffer(nil)}
|
||||
im.Unlock()
|
||||
|
@ -170,7 +169,7 @@ func (im *Importer) setStatus(status string) {
|
|||
im.Unlock()
|
||||
}
|
||||
|
||||
// setStatus get's the Importer's status.
|
||||
// getStatus get's the Importer's status.
|
||||
func (im *Importer) getStatus() string {
|
||||
im.RLock()
|
||||
status := im.status.Status
|
||||
|
@ -179,6 +178,18 @@ func (im *Importer) getStatus() string {
|
|||
return status
|
||||
}
|
||||
|
||||
// isDone returns true if the importer is working (importing|stopping).
|
||||
func (im *Importer) isDone() bool {
|
||||
s := true
|
||||
im.RLock()
|
||||
if im.getStatus() == StatusImporting || im.getStatus() == StatusStopping {
|
||||
s = false
|
||||
}
|
||||
im.RUnlock()
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
// incrementImportCount sets the Importer's "imported" counter.
|
||||
func (im *Importer) incrementImportCount(n int) {
|
||||
im.Lock()
|
||||
|
@ -290,14 +301,26 @@ func (s *Session) Start() {
|
|||
s.im.sendNotif(StatusFinished)
|
||||
}
|
||||
|
||||
// Stop stops an active import session.
|
||||
func (s *Session) Stop() {
|
||||
close(s.subQueue)
|
||||
}
|
||||
|
||||
// ExtractZIP takes a ZIP file's path and extracts all .csv files in it to
|
||||
// a temporary directory, and returns the name of the temp directory and the
|
||||
// list of extracted .csv files.
|
||||
func (s *Session) ExtractZIP(srcPath string, maxCSVs int) (string, []string, error) {
|
||||
if s.im.isImporting {
|
||||
if s.im.isDone() {
|
||||
return "", nil, ErrIsImporting
|
||||
}
|
||||
|
||||
failed := true
|
||||
defer func() {
|
||||
if failed {
|
||||
s.im.setStatus(StatusFailed)
|
||||
}
|
||||
}()
|
||||
|
||||
z, err := zip.OpenReader(srcPath)
|
||||
if err != nil {
|
||||
return "", nil, err
|
||||
|
@ -355,12 +378,18 @@ func (s *Session) ExtractZIP(srcPath string, maxCSVs int) (string, []string, err
|
|||
}
|
||||
}
|
||||
|
||||
if len(files) == 0 {
|
||||
s.log.Println("no CSV files found in the ZIP")
|
||||
return "", nil, errors.New("no CSV files found in the ZIP")
|
||||
}
|
||||
|
||||
failed = false
|
||||
return dir, files, nil
|
||||
}
|
||||
|
||||
// LoadCSV loads a CSV file and validates and imports the subscriber entries in it.
|
||||
func (s *Session) LoadCSV(srcPath string, delim rune) error {
|
||||
if s.im.isImporting {
|
||||
if s.im.isDone() {
|
||||
return ErrIsImporting
|
||||
}
|
||||
|
||||
|
@ -488,11 +517,11 @@ func (s *Session) LoadCSV(srcPath string, delim rune) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// Stop sends a signal to stop all existing imports.
|
||||
// Stop sends a signal to stop the existing import.
|
||||
func (im *Importer) Stop() {
|
||||
if im.getStatus() != StatusImporting {
|
||||
im.Lock()
|
||||
im.status = &Status{Status: StatusNone}
|
||||
im.status = Status{Status: StatusNone}
|
||||
im.Unlock()
|
||||
return
|
||||
}
|
||||
|
@ -526,10 +555,10 @@ func (s *Session) mapCSVHeaders(csvHdrs []string, knownHdrs map[string]bool) map
|
|||
// ValidateFields validates incoming subscriber field values.
|
||||
func ValidateFields(s SubReq) error {
|
||||
if !govalidator.IsEmail(s.Email) {
|
||||
return errors.New("invalid `email`")
|
||||
return errors.New(`invalid email "` + s.Email + `"`)
|
||||
}
|
||||
if !govalidator.IsByteLength(s.Name, 1, stdInputMaxLen) {
|
||||
return errors.New("invalid or empty `name`")
|
||||
return errors.New(`invalid or empty name "` + s.Name + `"`)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
|
Loading…
Reference in New Issue