[MM-55726] Create batch report worker, add batch report job for exporting users to CSV (#25832)

* Split out migration logic and create generic BatchWorker

* WIP

* WIP

* POC batch reporting

* Oops

* Job hookup

* Working export to file

* PR feedback

* Merge'd

* Fix error handling

* Add API to start report, translations, couple fixes

* Add DMs to send reports to users

* Merge'd

* Update types

* A bit of cleanup

* Some fixes

* Add missing API doc

* PR feedback

* Fix generated

* Fix bug with post creation

* PR feedback

* Add some tests

* PR feedback

* Fix lint

* Some test changes

* Fix tests

* Add comment to explain why we forcibly stop

* Rework of some tests

* Batch report test

* Restrict batch exports to Pro and Enterprise licenses

* Fix erroneous comment

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Devin Binnie
2024-01-19 15:22:17 -05:00
коммит произвёл GitHub
родитель eac9a39677
Коммит f7446d7443
23 изменённых файлов: 1571 добавлений и 361 удалений

Просмотреть файл

@@ -45,51 +45,18 @@ func (ma *MockApp) SetOutOfSync() {
}
func TestBatchMigrationWorker(t *testing.T) {
waitDone := func(t *testing.T, done chan bool, msg string) {
t.Helper()
require.Eventually(t, func() bool {
select {
case <-done:
return true
default:
return false
}
}, 5*time.Second, 100*time.Millisecond, msg)
}
setupBatchWorker := func(t *testing.T, th *TestHelper, mockApp *MockApp, doMigrationBatch func(model.StringMap, store.Store) (model.StringMap, bool, error)) (model.Worker, *model.Job) {
t.Helper()
migrationKey := model.NewId()
timeBetweenBatches := 1 * time.Second
worker := jobs.MakeBatchMigrationWorker(
th.Server.Jobs,
th.Server.Store(),
mockApp,
migrationKey,
timeBetweenBatches,
model.NewId(),
1*time.Second,
doMigrationBatch,
)
th.Server.Jobs.RegisterJobType(migrationKey, worker, nil)
job, appErr := th.Server.Jobs.CreateJob(th.Context, migrationKey, nil)
require.Nil(t, appErr)
done := make(chan bool)
go func() {
defer close(done)
worker.Run()
}()
// When ending the test, ensure we wait for the worker to finish.
t.Cleanup(func() {
waitDone(t, done, "worker did not stop running")
})
// Give the worker time to start running
time.Sleep(500 * time.Millisecond)
job := th.SetupBatchWorker(t, worker.BatchWorker)
return worker, job
}
@@ -106,18 +73,6 @@ func TestBatchMigrationWorker(t *testing.T) {
waitDone(t, stopped, "worker did not stop")
}
waitForJobStatus := func(t *testing.T, th *TestHelper, job *model.Job, status string) {
t.Helper()
require.Eventuallyf(t, func() bool {
actualJob, appErr := th.Server.Jobs.GetJob(th.Context, job.Id)
require.Nil(t, appErr)
require.Equal(t, job.Id, actualJob.Id)
return actualJob.Status == status
}, 5*time.Second, 250*time.Millisecond, "job never transitioned to %s", status)
}
assertJobReset := func(t *testing.T, th *TestHelper, job *model.Job) {
actualJob, appErr := th.Server.Jobs.GetJob(th.Context, job.Id)
require.Nil(t, appErr)
@@ -144,6 +99,34 @@ func TestBatchMigrationWorker(t *testing.T) {
return data
}
t.Run("done after three batches", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
mockApp := &MockApp{}
var worker model.Worker
var job *model.Job
worker, job = setupBatchWorker(t, th, mockApp, func(data model.StringMap, s store.Store) (model.StringMap, bool, error) {
batchNumber := getBatchNumberFromData(t, data)
require.LessOrEqual(t, batchNumber, 3, "only 3 batches should have run")
if batchNumber >= 3 {
go worker.Stop() // Shut down the worker when the job is done
return getDataFromBatchNumber(batchNumber), true, nil
}
batchNumber++
return getDataFromBatchNumber(batchNumber), false, nil
})
// Queue the work to be done
worker.JobChannel() <- *job
th.WaitForJobStatus(t, job, model.JobStatusSuccess)
th.WaitForBatchNumber(t, job, 3)
})
t.Run("clusters not in sync before first batch", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
@@ -165,65 +148,12 @@ func TestBatchMigrationWorker(t *testing.T) {
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusPending)
th.WaitForJobStatus(t, job, model.JobStatusPending)
assertJobReset(t, th, job)
stopWorker(t, worker)
})
t.Run("stop after first batch", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
mockApp := &MockApp{}
var worker model.Worker
var job *model.Job
worker, job = setupBatchWorker(t, th, mockApp, func(data model.StringMap, s store.Store) (model.StringMap, bool, error) {
batchNumber := getBatchNumberFromData(t, data)
require.Equal(t, 1, batchNumber, "only batch 1 should have run")
// Shut down the worker after the first batch to prevent subsequent ones.
go worker.Stop()
batchNumber++
return getDataFromBatchNumber(batchNumber), false, nil
})
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusPending)
})
t.Run("stop after second batch", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
mockApp := &MockApp{}
var worker model.Worker
var job *model.Job
worker, job = setupBatchWorker(t, th, mockApp, func(data model.StringMap, s store.Store) (model.StringMap, bool, error) {
batchNumber := getBatchNumberFromData(t, data)
require.LessOrEqual(t, batchNumber, 2, "only batches 1 and 2 should have run")
// Shut down the worker after the first batch to prevent subsequent ones.
go worker.Stop()
batchNumber++
return getDataFromBatchNumber(batchNumber), false, nil
})
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusPending)
})
t.Run("clusters not in sync after first batch", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
@@ -248,59 +178,9 @@ func TestBatchMigrationWorker(t *testing.T) {
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusPending)
th.WaitForJobStatus(t, job, model.JobStatusPending)
assertJobReset(t, th, job)
stopWorker(t, worker)
})
t.Run("done after first batch", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
mockApp := &MockApp{}
var worker model.Worker
var job *model.Job
worker, job = setupBatchWorker(t, th, mockApp, func(data model.StringMap, s store.Store) (model.StringMap, bool, error) {
batchNumber := getBatchNumberFromData(t, data)
require.Equal(t, 1, batchNumber, "only batch 1 should have run")
// Shut down the worker after the first batch to prevent subsequent ones.
go worker.Stop()
batchNumber++
return getDataFromBatchNumber(batchNumber), true, nil
})
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusSuccess)
})
t.Run("done after three batches", func(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
mockApp := &MockApp{}
var worker model.Worker
var job *model.Job
worker, job = setupBatchWorker(t, th, mockApp, func(data model.StringMap, s store.Store) (model.StringMap, bool, error) {
batchNumber := getBatchNumberFromData(t, data)
require.LessOrEqual(t, batchNumber, 3, "only 3 batches should have run")
// Shut down the worker after the first batch to prevent subsequent ones.
go worker.Stop()
batchNumber++
return getDataFromBatchNumber(batchNumber), true, nil
})
// Queue the work to be done
worker.JobChannel() <- *job
waitForJobStatus(t, th, job, model.JobStatusSuccess)
})
}