diff --git a/pkg/modules/migration/handler/listeners.go b/pkg/modules/migration/handler/listeners.go index e24c89ccb..8a52e319f 100644 --- a/pkg/modules/migration/handler/listeners.go +++ b/pkg/modules/migration/handler/listeners.go @@ -17,12 +17,14 @@ package handler import ( - "encoding/json" - + "code.vikunja.io/api/pkg/config" "code.vikunja.io/api/pkg/events" "code.vikunja.io/api/pkg/log" "code.vikunja.io/api/pkg/modules/migration" "code.vikunja.io/api/pkg/notifications" + "encoding/json" + "fmt" + "github.com/getsentry/sentry-go" "github.com/ThreeDotsLabs/watermill/message" ) @@ -31,6 +33,16 @@ func RegisterListeners() { events.RegisterListener((&MigrationRequestedEvent{}).Name(), &MigrationListener{}) } +// Only used for sentry +type migrationFailedError struct { + MigratorKind string + OriginalError error +} + +func (m *migrationFailedError) Error() string { + return fmt.Sprintf("migration from %s failed, original error message was: %s", m.MigratorKind, m.OriginalError.Error()) +} + // MigrationListener represents a listener type MigrationListener struct { } @@ -59,13 +71,46 @@ func (s *MigrationListener) Handle(msg *message.Message) (err error) { ms := event.Migrator.(migration.Migrator) - m, err := migration.StartMigration(ms, event.User) + m, err := migrateInListener(ms, event) + if err != nil { + log.Errorf("[Migration] Migration %d from %s for user %d failed. Error was: %s", m.ID, event.MigratorKind, event.User.ID, err.Error()) + + var nerr error + if config.SentryEnabled.GetBool() { + nerr = notifications.Notify(event.User, &MigrationFailedReportedNotification{ + MigratorName: ms.Name(), + }) + sentry.CaptureException(&migrationFailedError{ + MigratorKind: event.MigratorKind, + OriginalError: err, + }) + } else { + nerr = notifications.Notify(event.User, &MigrationFailedNotification{ + MigratorName: ms.Name(), + Error: err, + }) + } + if nerr != nil { + log.Errorf("[Migration] Could not sent failed migration notification for migration %d to user %d, error was: %s", m.ID, event.User.ID, err.Error()) + } + + // Still need to finish the migration, otherwise restarting will not work + err = migration.FinishMigration(m) + if err != nil { + log.Errorf("[Migration] Could not finish migration %d for user %d, error was: %s", m.ID, event.User.ID, err.Error()) + } + } + + return nil // We do not want the queue to restart this job as we've already handled the error. +} + +func migrateInListener(ms migration.Migrator, event *MigrationRequestedEvent) (m *migration.Status, err error) { + m, err = migration.StartMigration(ms, event.User) if err != nil { return } log.Debugf("[Migration] Starting migration %d from %s for user %d", m.ID, event.MigratorKind, event.User.ID) - err = ms.Migrate(event.User) if err != nil { return @@ -73,6 +118,7 @@ func (s *MigrationListener) Handle(msg *message.Message) (err error) { err = migration.FinishMigration(m) if err != nil { + log.Errorf("[Migration] Could not finish migration %d for user %d, error was: %s", m.ID, event.User.ID, err.Error()) return } @@ -80,6 +126,7 @@ func (s *MigrationListener) Handle(msg *message.Message) (err error) { MigratorName: ms.Name(), }) if err != nil { + log.Errorf("[Migration] Could not sent migration success notification for migration %d to user %d, error was: %s", m.ID, event.User.ID, err.Error()) return } diff --git a/pkg/modules/migration/handler/notifications.go b/pkg/modules/migration/handler/notifications.go index 4768b0ffd..defd2e5c5 100644 --- a/pkg/modules/migration/handler/notifications.go +++ b/pkg/modules/migration/handler/notifications.go @@ -49,3 +49,57 @@ func (n *MigrationDoneNotification) ToDB() interface{} { func (n *MigrationDoneNotification) Name() string { return "migration.done" } + +// MigrationFailedReportedNotification represents a MigrationFailedReportedNotification notification +type MigrationFailedReportedNotification struct { + MigratorName string +} + +// ToMail returns the mail notification for MigrationFailedReportedNotification +func (n *MigrationFailedReportedNotification) ToMail() *notifications.Mail { + kind := cases.Title(language.English).String(n.MigratorName) + + return notifications.NewMail(). + Subject("The migration from " + kind + " to Vikunja was has failed"). + Line("Looks like the move from " + kind + " didn't go as planned this time."). + Line("No worries, though! Just give it another shot by starting over the same way you did before. Sometimes, these hiccups happen because of glitches on " + kind + "'s end, but trying again often does the trick."). + Line("We've got the error message on our radar and are on it to get it sorted out soon.") +} + +// ToDB returns the MigrationFailedReportedNotification notification in a format which can be saved in the db +func (n *MigrationFailedReportedNotification) ToDB() interface{} { + return nil +} + +// Name returns the name of the notification +func (n *MigrationFailedReportedNotification) Name() string { + return "migration.failed.reported" +} + +// MigrationFailedNotification represents a MigrationFailedNotification notification +type MigrationFailedNotification struct { + MigratorName string + Error error +} + +// ToMail returns the mail notification for MigrationFailedNotification +func (n *MigrationFailedNotification) ToMail() *notifications.Mail { + kind := cases.Title(language.English).String(n.MigratorName) + + return notifications.NewMail(). + Subject("The migration from " + kind + " to Vikunja was has failed"). + Line("Looks like the move from " + kind + " didn't go as planned this time."). + Line("No worries, though! Just give it another shot by starting over the same way you did before. Sometimes, these hiccups happen because of glitches on " + kind + "'s end, but trying again often does the trick."). + Line("We bumped into a little error along the way: `" + n.Error.Error() + "`."). + Line("Please drop us a note about this [in the forum](https://community.vikunja.io/) or any of the usual places so that we can take a look at why it failed.") +} + +// ToDB returns the MigrationFailedNotification notification in a format which can be saved in the db +func (n *MigrationFailedNotification) ToDB() interface{} { + return nil +} + +// Name returns the name of the notification +func (n *MigrationFailedNotification) Name() string { + return "migration.failed" +}