diff --git a/cmd/mattermost/commands/export.go b/cmd/mattermost/commands/export.go index 1fefc98550..5aee98b0a9 100644 --- a/cmd/mattermost/commands/export.go +++ b/cmd/mattermost/commands/export.go @@ -170,7 +170,11 @@ func buildExportCmdF(format string) func(command *cobra.Command, args []string) if warningsCount == 0 { CommandPrettyPrintln("SUCCESS: Your data was exported.") } else { - CommandPrettyPrintln(fmt.Sprintf("WARNING: %d warnings encountered, see warning.txt for details.", warningsCount)) + if format == model.COMPLIANCE_EXPORT_TYPE_GLOBALRELAY || format == model.COMPLIANCE_EXPORT_TYPE_GLOBALRELAY_ZIP { + CommandPrettyPrintln(fmt.Sprintf("WARNING: %d warnings encountered, see logs for details.", warningsCount)) + } else { + CommandPrettyPrintln(fmt.Sprintf("WARNING: %d warnings encountered, see warning.txt for details.", warningsCount)) + } } auditRec := a.MakeAuditRecord("buildExport", audit.Success) diff --git a/jobs/jobs.go b/jobs/jobs.go index 70b845dc89..9cb07c7808 100644 --- a/jobs/jobs.go +++ b/jobs/jobs.go @@ -174,5 +174,9 @@ func (srv *JobServer) CheckForPendingJobsByType(jobType string) (bool, *model.Ap } func (srv *JobServer) GetLastSuccessfulJobByType(jobType string) (*model.Job, *model.AppError) { - return srv.Store.Job().GetNewestJobByStatusAndType(model.JOB_STATUS_SUCCESS, jobType) + statuses := []string{model.JOB_STATUS_SUCCESS} + if jobType == model.JOB_TYPE_MESSAGE_EXPORT { + statuses = []string{model.JOB_STATUS_WARNING, model.JOB_STATUS_SUCCESS} + } + return srv.Store.Job().GetNewestJobByStatusesAndType(statuses, jobType) } diff --git a/jobs/schedulers_test.go b/jobs/schedulers_test.go index 4324493bdb..45914e38bc 100644 --- a/jobs/schedulers_test.go +++ b/jobs/schedulers_test.go @@ -52,7 +52,7 @@ func TestScheduler(t *testing.T) { Type: model.JOB_TYPE_MESSAGE_EXPORT, } // mock job store doesn't return a previously successful job, forcing fallback to config - mockStore.JobStore.On("GetNewestJobByStatusAndType", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(job, nil) + mockStore.JobStore.On("GetNewestJobByStatusesAndType", mock.AnythingOfType("[]string"), mock.AnythingOfType("string")).Return(job, nil) mockStore.JobStore.On("GetCountByStatusAndType", mock.AnythingOfType("string"), mock.AnythingOfType("string")).Return(int64(1), nil) jobServer := &JobServer{ diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 09258cf832..c13c2fdf61 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -4027,6 +4027,24 @@ func (s *OpenTracingLayerJobStore) GetNewestJobByStatusAndType(status string, jo return result, err } +func (s *OpenTracingLayerJobStore) GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, *model.AppError) { + origCtx := s.Root.Store.Context() + span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "JobStore.GetNewestJobByStatusesAndType") + s.Root.Store.SetContext(newCtx) + defer func() { + s.Root.Store.SetContext(origCtx) + }() + + defer span.Finish() + result, err := s.JobStore.GetNewestJobByStatusesAndType(statuses, jobType) + if err != nil { + span.LogFields(spanlog.Error(err)) + ext.Error.Set(span, true) + } + + return result, err +} + func (s *OpenTracingLayerJobStore) Save(job *model.Job) (*model.Job, *model.AppError) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "JobStore.Save") diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index b0fc19b959..80ef4638fe 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -2540,6 +2540,12 @@ func (s *RetryLayerJobStore) GetNewestJobByStatusAndType(status string, jobType } +func (s *RetryLayerJobStore) GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, *model.AppError) { + + return s.JobStore.GetNewestJobByStatusesAndType(statuses, jobType) + +} + func (s *RetryLayerJobStore) Save(job *model.Job) (*model.Job, *model.AppError) { return s.JobStore.Save(job) diff --git a/store/sqlstore/job_store.go b/store/sqlstore/job_store.go index e759aab9c9..1e538c1861 100644 --- a/store/sqlstore/job_store.go +++ b/store/sqlstore/job_store.go @@ -6,6 +6,7 @@ package sqlstore import ( "database/sql" "net/http" + "strings" sq "github.com/Masterminds/squirrel" @@ -205,8 +206,11 @@ func (jss SqlJobStore) GetAllByStatus(status string) ([]*model.Job, *model.AppEr } return statuses, nil } - func (jss SqlJobStore) GetNewestJobByStatusAndType(status string, jobType string) (*model.Job, *model.AppError) { + return jss.GetNewestJobByStatusesAndType([]string{status}, jobType) +} + +func (jss SqlJobStore) GetNewestJobByStatusesAndType(status []string, jobType string) (*model.Job, *model.AppError) { query, args, err := jss.getQueryBuilder(). Select("*"). From("Jobs"). @@ -219,7 +223,7 @@ func (jss SqlJobStore) GetNewestJobByStatusAndType(status string, jobType string var job *model.Job if err = jss.GetReplica().SelectOne(&job, query, args...); err != nil && err != sql.ErrNoRows { - return nil, model.NewAppError("SqlJobStore.GetNewestJobByStatusAndType", "store.sql_job.get_newest_job_by_status_and_type.app_error", nil, "Status="+status+", "+err.Error(), http.StatusInternalServerError) + return nil, model.NewAppError("SqlJobStore.GetNewestJobByStatusAndType", "store.sql_job.get_newest_job_by_status_and_type.app_error", nil, "Status="+strings.Join(status, ",")+", "+err.Error(), http.StatusInternalServerError) } return job, nil } diff --git a/store/store.go b/store/store.go index 302039595f..b0012f4877 100644 --- a/store/store.go +++ b/store/store.go @@ -569,6 +569,7 @@ type JobStore interface { GetAllByTypePage(jobType string, offset int, limit int) ([]*model.Job, *model.AppError) GetAllByStatus(status string) ([]*model.Job, *model.AppError) GetNewestJobByStatusAndType(status string, jobType string) (*model.Job, *model.AppError) + GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, *model.AppError) GetCountByStatusAndType(status string, jobType string) (int64, *model.AppError) Delete(id string) (string, *model.AppError) } diff --git a/store/storetest/job_store.go b/store/storetest/job_store.go index d66933ce3c..463d79363f 100644 --- a/store/storetest/job_store.go +++ b/store/storetest/job_store.go @@ -21,6 +21,7 @@ func TestJobStore(t *testing.T, ss store.Store) { t.Run("JobGetAllPage", func(t *testing.T) { testJobGetAllPage(t, ss) }) t.Run("JobGetAllByStatus", func(t *testing.T) { testJobGetAllByStatus(t, ss) }) t.Run("GetNewestJobByStatusAndType", func(t *testing.T) { testJobStoreGetNewestJobByStatusAndType(t, ss) }) + t.Run("GetNewestJobByStatusesAndType", func(t *testing.T) { testJobStoreGetNewestJobByStatusesAndType(t, ss) }) t.Run("GetCountByStatusAndType", func(t *testing.T) { testJobStoreGetCountByStatusAndType(t, ss) }) t.Run("JobUpdateOptimistically", func(t *testing.T) { testJobUpdateOptimistically(t, ss) }) t.Run("JobUpdateStatusUpdateStatusOptimistically", func(t *testing.T) { testJobUpdateStatusUpdateStatusOptimistically(t, ss) }) @@ -261,6 +262,66 @@ func testJobStoreGetNewestJobByStatusAndType(t *testing.T, ss store.Store) { assert.Nil(t, received) } +func testJobStoreGetNewestJobByStatusesAndType(t *testing.T, ss store.Store) { + jobType1 := model.NewId() + jobType2 := model.NewId() + status1 := model.NewId() + status2 := model.NewId() + + jobs := []*model.Job{ + { + Id: model.NewId(), + Type: jobType1, + CreateAt: 1001, + Status: status1, + }, + { + Id: model.NewId(), + Type: jobType1, + CreateAt: 1000, + Status: status1, + }, + { + Id: model.NewId(), + Type: jobType2, + CreateAt: 1003, + Status: status1, + }, + { + Id: model.NewId(), + Type: jobType1, + CreateAt: 1004, + Status: status2, + }, + } + + for _, job := range jobs { + _, err := ss.Job().Save(job) + require.Nil(t, err) + defer ss.Job().Delete(job.Id) + } + + received, err := ss.Job().GetNewestJobByStatusesAndType([]string{status1, status2}, jobType1) + assert.Nil(t, err) + assert.EqualValues(t, jobs[3].Id, received.Id) + + received, err = ss.Job().GetNewestJobByStatusesAndType([]string{model.NewId(), model.NewId()}, model.NewId()) + assert.Nil(t, err) + assert.Nil(t, received) + + received, err = ss.Job().GetNewestJobByStatusesAndType([]string{status2}, jobType2) + assert.Nil(t, err) + assert.Nil(t, received) + + received, err = ss.Job().GetNewestJobByStatusesAndType([]string{status1}, jobType2) + assert.Nil(t, err) + assert.EqualValues(t, jobs[2].Id, received.Id) + + received, err = ss.Job().GetNewestJobByStatusesAndType([]string{}, jobType1) + assert.Nil(t, err) + assert.Nil(t, received) +} + func testJobStoreGetCountByStatusAndType(t *testing.T, ss store.Store) { jobType1 := model.NewId() jobType2 := model.NewId() diff --git a/store/storetest/mocks/JobStore.go b/store/storetest/mocks/JobStore.go index 79513c832b..394089a79f 100644 --- a/store/storetest/mocks/JobStore.go +++ b/store/storetest/mocks/JobStore.go @@ -210,6 +210,31 @@ func (_m *JobStore) GetNewestJobByStatusAndType(status string, jobType string) ( return r0, r1 } +// GetNewestJobByStatusesAndType provides a mock function with given fields: statuses, jobType +func (_m *JobStore) GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, *model.AppError) { + ret := _m.Called(statuses, jobType) + + var r0 *model.Job + if rf, ok := ret.Get(0).(func([]string, string) *model.Job); ok { + r0 = rf(statuses, jobType) + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(*model.Job) + } + } + + var r1 *model.AppError + if rf, ok := ret.Get(1).(func([]string, string) *model.AppError); ok { + r1 = rf(statuses, jobType) + } else { + if ret.Get(1) != nil { + r1 = ret.Get(1).(*model.AppError) + } + } + + return r0, r1 +} + // Save provides a mock function with given fields: job func (_m *JobStore) Save(job *model.Job) (*model.Job, *model.AppError) { ret := _m.Called(job) diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 8f56094be1..290510df40 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -3663,6 +3663,22 @@ func (s *TimerLayerJobStore) GetNewestJobByStatusAndType(status string, jobType return result, err } +func (s *TimerLayerJobStore) GetNewestJobByStatusesAndType(statuses []string, jobType string) (*model.Job, *model.AppError) { + start := timemodule.Now() + + result, err := s.JobStore.GetNewestJobByStatusesAndType(statuses, jobType) + + elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second) + if s.Root.Metrics != nil { + success := "false" + if err == nil { + success = "true" + } + s.Root.Metrics.ObserveStoreMethodDuration("JobStore.GetNewestJobByStatusesAndType", success, elapsed) + } + return result, err +} + func (s *TimerLayerJobStore) Save(job *model.Job) (*model.Job, *model.AppError) { start := timemodule.Now()