diff --git a/e2e-tests/playwright/lib/src/server/default_config.ts b/e2e-tests/playwright/lib/src/server/default_config.ts index 6da4e0ba442..b6fdbe35e4d 100644 --- a/e2e-tests/playwright/lib/src/server/default_config.ts +++ b/e2e-tests/playwright/lib/src/server/default_config.ts @@ -849,6 +849,7 @@ const defaultServerConfig: AdminConfig = { APIKey: '', }, TargetLanguages: ['en'], + Workers: 4, TimeoutMs: 5000, Agents: { LLMServiceID: '', diff --git a/server/channels/app/server.go b/server/channels/app/server.go index 68d4953c05d..a388f64f38d 100644 --- a/server/channels/app/server.go +++ b/server/channels/app/server.go @@ -1488,6 +1488,12 @@ func (s *Server) initJobs() { s.Jobs.RegisterJobType(model.JobTypePushProxyAuth, builder.MakeWorker(), builder.MakeScheduler()) } + if s.AutoTranslation != nil { + s.Jobs.RegisterJobType(model.JobTypeAutoTranslationRecovery, + s.AutoTranslation.MakeWorker(), + s.AutoTranslation.MakeScheduler()) + } + s.Jobs.RegisterJobType( model.JobTypeMigrations, migrations.MakeWorker(s.Jobs, s.Store()), diff --git a/server/einterfaces/autotranslation.go b/server/einterfaces/autotranslation.go index 99bbe619103..240bd540edb 100644 --- a/server/einterfaces/autotranslation.go +++ b/server/einterfaces/autotranslation.go @@ -7,6 +7,7 @@ import ( "context" "github.com/mattermost/mattermost/server/public/model" + ejobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs" ) // AutoTranslationInterface defines the enterprise advanced auto-translation functionality. @@ -75,4 +76,12 @@ type AutoTranslationInterface interface { // Shutdown gracefully shuts down the auto-translation service. Shutdown() error + + // MakeWorker creates a worker for the autotranslation recovery sweep job. + // The worker picks up stuck translations and re-queues them for processing. + MakeWorker() model.Worker + + // MakeScheduler creates a scheduler for the autotranslation recovery sweep job. + // The scheduler runs periodically to detect stuck translations. + MakeScheduler() ejobs.Scheduler } diff --git a/server/einterfaces/mocks/AutoTranslationInterface.go b/server/einterfaces/mocks/AutoTranslationInterface.go index c8486da84c1..10c09d42b54 100644 --- a/server/einterfaces/mocks/AutoTranslationInterface.go +++ b/server/einterfaces/mocks/AutoTranslationInterface.go @@ -7,6 +7,8 @@ package mocks import ( context "context" + jobs "github.com/mattermost/mattermost/server/v8/einterfaces/jobs" + mock "github.com/stretchr/testify/mock" model "github.com/mattermost/mattermost/server/public/model" @@ -214,6 +216,46 @@ func (_m *AutoTranslationInterface) IsUserEnabled(channelID string, userID strin return r0, r1 } +// MakeScheduler provides a mock function with no fields +func (_m *AutoTranslationInterface) MakeScheduler() jobs.Scheduler { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for MakeScheduler") + } + + var r0 jobs.Scheduler + if rf, ok := ret.Get(0).(func() jobs.Scheduler); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(jobs.Scheduler) + } + } + + return r0 +} + +// MakeWorker provides a mock function with no fields +func (_m *AutoTranslationInterface) MakeWorker() model.Worker { + ret := _m.Called() + + if len(ret) == 0 { + panic("no return value specified for MakeWorker") + } + + var r0 model.Worker + if rf, ok := ret.Get(0).(func() model.Worker); ok { + r0 = rf() + } else { + if ret.Get(0) != nil { + r0 = ret.Get(0).(model.Worker) + } + } + + return r0 +} + // SetChannelEnabled provides a mock function with given fields: channelID, enabled func (_m *AutoTranslationInterface) SetChannelEnabled(channelID string, enabled bool) *model.AppError { ret := _m.Called(channelID, enabled) diff --git a/server/i18n/en.json b/server/i18n/en.json index 1074740a23d..955201a9409 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -9904,6 +9904,10 @@ "id": "model.config.is_valid.autotranslation.timeout.app_error", "translation": "Invalid timeout for autotranslation settings. Must be a positive number." }, + { + "id": "model.config.is_valid.autotranslation.workers.app_error", + "translation": "Workers must be between 1 and 32." + }, { "id": "model.config.is_valid.cache_type.app_error", "translation": "Cache type must be either lru or redis." diff --git a/server/public/model/config.go b/server/public/model/config.go index a66d0834c2e..c9cd407f9fd 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -2787,6 +2787,7 @@ type AutoTranslationSettings struct { Enable *bool `access:"site_localization,cloud_restrictable"` Provider *string `access:"site_localization,cloud_restrictable"` TargetLanguages *[]string `access:"site_localization,cloud_restrictable"` + Workers *int `access:"site_localization,cloud_restrictable"` TimeoutMs *int `access:"site_localization,cloud_restrictable"` LibreTranslate *LibreTranslateProviderSettings `access:"site_localization,cloud_restrictable"` Agents *AgentsProviderSettings `access:"site_localization,cloud_restrictable"` @@ -2815,6 +2816,10 @@ func (s *AutoTranslationSettings) SetDefaults() { s.TargetLanguages = &[]string{"en"} } + if s.Workers == nil { + s.Workers = NewPointer(4) + } + if s.TimeoutMs == nil { s.TimeoutMs = NewPointer(5000) } @@ -4823,6 +4828,11 @@ func (s *AutoTranslationSettings) isValid() *AppError { return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeout.app_error", nil, "", http.StatusBadRequest) } + // Validate workers if set (must be between 1 and 32) + if s.Workers != nil && (*s.Workers < 1 || *s.Workers > 32) { + return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.workers.app_error", nil, "", http.StatusBadRequest) + } + return nil } diff --git a/server/public/model/job.go b/server/public/model/job.go index 3382c920d1e..500a05e459e 100644 --- a/server/public/model/job.go +++ b/server/public/model/job.go @@ -47,6 +47,7 @@ const ( JobTypePushProxyAuth = "push_proxy_auth" JobTypeRecap = "recap" JobTypeDeleteExpiredPosts = "delete_expired_posts" + JobTypeAutoTranslationRecovery = "autotranslation_recovery" JobStatusPending = "pending" JobStatusInProgress = "in_progress" diff --git a/webapp/platform/types/src/config.ts b/webapp/platform/types/src/config.ts index 12013157dcd..718c5f9f4a6 100644 --- a/webapp/platform/types/src/config.ts +++ b/webapp/platform/types/src/config.ts @@ -755,6 +755,7 @@ export type LocalizationSettings = { export type AutoTranslationSettings = { Enable: boolean; TargetLanguages: string[]; + Workers: number; Provider: '' | 'libretranslate' | 'agents'; LibreTranslate: { URL: string;