Use one timeout config for requests to translation providers (#34957)
Some checks are pending
API / build (push) Waiting to run
Server CI / Compute Go Version (push) Waiting to run
Server CI / Check mocks (push) Blocked by required conditions
Server CI / Check go mod tidy (push) Blocked by required conditions
Server CI / check-style (push) Blocked by required conditions
Server CI / Check serialization methods for hot structs (push) Blocked by required conditions
Server CI / Vet API (push) Blocked by required conditions
Server CI / Check migration files (push) Blocked by required conditions
Server CI / Generate email templates (push) Blocked by required conditions
Server CI / Check store layers (push) Blocked by required conditions
Server CI / Check mmctl docs (push) Blocked by required conditions
Server CI / Postgres with binary parameters (push) Blocked by required conditions
Server CI / Postgres (push) Blocked by required conditions
Server CI / Postgres (FIPS) (push) Blocked by required conditions
Server CI / Generate Test Coverage (push) Blocked by required conditions
Server CI / Run mmctl tests (push) Blocked by required conditions
Server CI / Run mmctl tests (FIPS) (push) Blocked by required conditions
Server CI / Build mattermost server app (push) Blocked by required conditions
Web App CI / check-lint (push) Waiting to run
Web App CI / check-i18n (push) Blocked by required conditions
Web App CI / check-types (push) Blocked by required conditions
Web App CI / test (platform) (push) Blocked by required conditions
Web App CI / test (mattermost-redux) (push) Blocked by required conditions
Web App CI / test (channels shard 1/4) (push) Blocked by required conditions
Web App CI / test (channels shard 2/4) (push) Blocked by required conditions
Web App CI / test (channels shard 3/4) (push) Blocked by required conditions
Web App CI / test (channels shard 4/4) (push) Blocked by required conditions
Web App CI / upload-coverage (push) Blocked by required conditions
Web App CI / build (push) Blocked by required conditions

This commit is contained in:
Ben Cooke 2026-01-29 10:00:22 -05:00 committed by GitHub
parent 2b075c9b74
commit 36173a4948
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 71 additions and 81 deletions

View file

@ -844,17 +844,12 @@ const defaultServerConfig: AdminConfig = {
AutoTranslationSettings: {
Enable: false,
Provider: '',
TargetLanguages: ['en'],
TimeoutsMs: {
Short: 1200,
Medium: 2500,
Long: 6000,
Notification: 300,
},
LibreTranslate: {
URL: '',
APIKey: '',
},
TargetLanguages: ['en'],
TimeoutMs: 5000,
Agents: {
LLMServiceID: '',
},

View file

@ -9905,20 +9905,8 @@
"translation": "Unsupported autotranslation provider."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.long.app_error",
"translation": "Invalid long timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.medium.app_error",
"translation": "Invalid medium timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.notification.app_error",
"translation": "Invalid notification timeout for autotranslation settings. Must be a positive number."
},
{
"id": "model.config.is_valid.autotranslation.timeouts.short.app_error",
"translation": "Invalid short timeout for autotranslation settings. Must be a positive number."
"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.cache_type.app_error",

View file

@ -2787,24 +2787,11 @@ type AutoTranslationSettings struct {
Enable *bool `access:"site_localization,cloud_restrictable"`
Provider *string `access:"site_localization,cloud_restrictable"`
TargetLanguages *[]string `access:"site_localization,cloud_restrictable"`
TimeoutsMs *AutoTranslationTimeoutsInMs `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"`
}
// AutoTranslationTimeoutsInMs defines content-aware timeout thresholds.
// Based on LibreTranslate benchmark findings, timeouts are set according to content length:
// - Short: ≤200 runes
// - Medium: ≤500 runes
// - Long: >500 runes
// - Notification: preserved for notification-specific timeout requirements
type AutoTranslationTimeoutsInMs struct {
Short *int `access:"site_localization,cloud_restrictable"` // ≤200 runes, default: 1200ms
Medium *int `access:"site_localization,cloud_restrictable"` // ≤500 runes, default: 2500ms
Long *int `access:"site_localization,cloud_restrictable"` // >500 runes, default: 6000ms
Notification *int `access:"site_localization,cloud_restrictable"` // Notification timeout, default: 300ms
}
// LibreTranslateProviderSettings configures the LibreTranslate translation provider.
type LibreTranslateProviderSettings struct {
URL *string `access:"site_localization,cloud_restrictable"` // LibreTranslate server URL
@ -2828,10 +2815,9 @@ func (s *AutoTranslationSettings) SetDefaults() {
s.TargetLanguages = &[]string{"en"}
}
if s.TimeoutsMs == nil {
s.TimeoutsMs = &AutoTranslationTimeoutsInMs{}
if s.TimeoutMs == nil {
s.TimeoutMs = NewPointer(5000)
}
s.TimeoutsMs.SetDefaults()
if s.LibreTranslate == nil {
s.LibreTranslate = &LibreTranslateProviderSettings{}
@ -2844,24 +2830,6 @@ func (s *AutoTranslationSettings) SetDefaults() {
s.Agents.SetDefaults()
}
func (s *AutoTranslationTimeoutsInMs) SetDefaults() {
if s.Short == nil {
s.Short = NewPointer(1200)
}
if s.Medium == nil {
s.Medium = NewPointer(2500)
}
if s.Long == nil {
s.Long = NewPointer(6000)
}
if s.Notification == nil {
s.Notification = NewPointer(300)
}
}
func (s *LibreTranslateProviderSettings) SetDefaults() {
if s.URL == nil {
s.URL = NewPointer("")
@ -4850,20 +4818,9 @@ func (s *AutoTranslationSettings) isValid() *AppError {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.provider.unsupported.app_error", nil, "", http.StatusBadRequest)
}
// Validate timeouts if set
if s.TimeoutsMs != nil {
if s.TimeoutsMs.Short != nil && *s.TimeoutsMs.Short <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeouts.short.app_error", nil, "", http.StatusBadRequest)
}
if s.TimeoutsMs.Medium != nil && *s.TimeoutsMs.Medium <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeouts.medium.app_error", nil, "", http.StatusBadRequest)
}
if s.TimeoutsMs.Long != nil && *s.TimeoutsMs.Long <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeouts.long.app_error", nil, "", http.StatusBadRequest)
}
if s.TimeoutsMs.Notification != nil && *s.TimeoutsMs.Notification <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeouts.notification.app_error", nil, "", http.StatusBadRequest)
}
// Validate timeout if set (must be positive)
if s.TimeoutMs != nil && *s.TimeoutMs <= 0 {
return NewAppError("Config.IsValid", "model.config.is_valid.autotranslation.timeout.app_error", nil, "", http.StatusBadRequest)
}
return nil

View file

@ -2649,10 +2649,7 @@ func TestAutoTranslationSettingsDefaults(t *testing.T) {
require.False(t, *c.AutoTranslationSettings.Enable)
require.Equal(t, "", *c.AutoTranslationSettings.Provider)
require.Equal(t, 1200, *c.AutoTranslationSettings.TimeoutsMs.Short)
require.Equal(t, 2500, *c.AutoTranslationSettings.TimeoutsMs.Medium)
require.Equal(t, 6000, *c.AutoTranslationSettings.TimeoutsMs.Long)
require.Equal(t, 300, *c.AutoTranslationSettings.TimeoutsMs.Notification)
require.Equal(t, 5000, *c.AutoTranslationSettings.TimeoutMs)
require.Equal(t, "", *c.AutoTranslationSettings.LibreTranslate.URL)
require.Equal(t, "", *c.AutoTranslationSettings.LibreTranslate.APIKey)
// TODO: Enable Agents provider in future release

View file

@ -2,7 +2,7 @@
// See LICENSE.txt for license information.
import React, {useCallback, useMemo, useState} from 'react';
import {defineMessages, FormattedMessage} from 'react-intl';
import {defineMessage, defineMessages, FormattedMessage} from 'react-intl';
import {Link} from 'react-router-dom';
import type {AutoTranslationSettings} from '@mattermost/types/config';
@ -14,6 +14,7 @@ import {
SectionContent,
SectionHeader,
} from 'components/admin_console/system_properties/controls';
import TextSetting from 'components/admin_console/text_setting';
import useGetAgentsBridgeEnabled from 'components/common/hooks/useGetAgentsBridgeEnabled';
import Toggle from 'components/toggle';
@ -52,6 +53,12 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
return settings;
});
// Track timeout input separately to allow intermediate invalid states while typing
const [timeoutInputValue, setTimeoutInputValue] = useState<string>(
String(autoTranslationSettings.TimeoutMs || 5000),
);
const [timeoutError, setTimeoutError] = useState<string>('');
const handleChange = useCallback((id: string, value: AutoTranslationSettings[keyof AutoTranslationSettings] | string[]) => {
const updatedSettings = {
...autoTranslationSettings,
@ -61,6 +68,22 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
props.onChange(props.id, updatedSettings);
}, [props, autoTranslationSettings]);
const handleTimeoutChange = useCallback((id: string, value: string) => {
setTimeoutInputValue(value);
const numValue = parseInt(value, 10);
if (value === '' || isNaN(numValue) || numValue <= 0) {
setTimeoutError('Timeout must be a positive number');
// Propagate 0 so backend validation will reject the save
handleChange(id, 0);
return;
}
setTimeoutError('');
handleChange(id, numValue);
}, [handleChange]);
const handleToggle = useCallback(() => {
const newValue = !autoTranslationSettings.Enable;
const newSettings = {
@ -234,6 +257,32 @@ export default function AutoTranslation(props: SystemConsoleCustomSettingsCompon
disabled={props.disabled || props.setByEnv}
setByEnv={props.setByEnv}
/>
<TextSetting
id='TimeoutMs'
label={
<FormattedMessage
id='admin.site.localization.autoTranslationTimeoutTitle'
defaultMessage='Translation timeout (ms):'
/>
}
placeholder={defineMessage({
id: 'admin.site.localization.autoTranslationTimeoutPlaceholder',
defaultMessage: 'e.g.: 5000',
})}
helpText={timeoutError ? (
<span className='autotranslation-error error-message'>{timeoutError}</span>
) : (
<FormattedMessage
id='admin.site.localization.autoTranslationTimeoutDescription'
defaultMessage='Maximum time in milliseconds to wait for a translation response. Default is 5000ms (5 seconds).'
/>
)}
type='number'
value={timeoutInputValue}
setByEnv={props.setByEnv}
onChange={handleTimeoutChange}
disabled={props.disabled}
/>
</SectionContent>
}
</AdminSection>

View file

@ -91,3 +91,9 @@
}
}
.autotranslation-error.error-message {
color: var(--error-text);
font-size: 12px;
font-weight: 400;
}

View file

@ -2863,6 +2863,9 @@
"admin.site.localization.autoTranslationProviderLibreTranslateURLExample": "e.g.: \"https://libretranslate.yourdomain.com\"",
"admin.site.localization.autoTranslationProviderLibreTranslateURLTitle": "LibreTranslate API Endpoint:",
"admin.site.localization.autoTranslationProviderTitle": "Translation provider",
"admin.site.localization.autoTranslationTimeoutDescription": "Maximum time in milliseconds to wait for a translation response. Default is 5000ms (5 seconds).",
"admin.site.localization.autoTranslationTimeoutPlaceholder": "e.g.: 5000",
"admin.site.localization.autoTranslationTimeoutTitle": "Translation timeout (ms):",
"admin.site.localization.enableAutoTranslationDescription": "Configure auto-translation for channels and direct messages",
"admin.site.localization.enableAutoTranslationTitle": "Auto-translation",
"admin.site.localization.goToAgentsConfig": "Go to Agents plugin config",

View file

@ -763,12 +763,7 @@ export type AutoTranslationSettings = {
Agents?: {
LLMServiceID: string;
};
TimeoutsMs: {
Short: number;
Medium: number;
Long: number;
Notification: number;
};
TimeoutMs: number;
};
export type SamlSettings = {