[MM-53990] Support a global retention time of less than 1 day (#25196)
* adding new MessageRetentionHours config --------- Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
@@ -611,7 +611,9 @@ const defaultServerConfig: AdminConfig = {
|
|||||||
EnableFileDeletion: false,
|
EnableFileDeletion: false,
|
||||||
EnableBoardsDeletion: false,
|
EnableBoardsDeletion: false,
|
||||||
MessageRetentionDays: 365,
|
MessageRetentionDays: 365,
|
||||||
|
MessageRetentionHours: 0,
|
||||||
FileRetentionDays: 365,
|
FileRetentionDays: 365,
|
||||||
|
FileRetentionHours: 0,
|
||||||
BoardsRetentionDays: 365,
|
BoardsRetentionDays: 365,
|
||||||
DeletionJobStartTime: '02:00',
|
DeletionJobStartTime: '02:00',
|
||||||
BatchSize: 3000,
|
BatchSize: 3000,
|
||||||
|
|||||||
@@ -122,9 +122,9 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
|||||||
props["AllowCustomThemes"] = "true"
|
props["AllowCustomThemes"] = "true"
|
||||||
props["AllowedThemes"] = ""
|
props["AllowedThemes"] = ""
|
||||||
props["DataRetentionEnableMessageDeletion"] = "false"
|
props["DataRetentionEnableMessageDeletion"] = "false"
|
||||||
props["DataRetentionMessageRetentionDays"] = "0"
|
props["DataRetentionMessageRetentionHours"] = "0"
|
||||||
props["DataRetentionEnableFileDeletion"] = "false"
|
props["DataRetentionEnableFileDeletion"] = "false"
|
||||||
props["DataRetentionFileRetentionDays"] = "0"
|
props["DataRetentionFileRetentionHours"] = "0"
|
||||||
|
|
||||||
props["CustomUrlSchemes"] = strings.Join(c.DisplaySettings.CustomURLSchemes, ",")
|
props["CustomUrlSchemes"] = strings.Join(c.DisplaySettings.CustomURLSchemes, ",")
|
||||||
props["MaxMarkdownNodes"] = strconv.FormatInt(int64(*c.DisplaySettings.MaxMarkdownNodes), 10)
|
props["MaxMarkdownNodes"] = strconv.FormatInt(int64(*c.DisplaySettings.MaxMarkdownNodes), 10)
|
||||||
@@ -204,9 +204,9 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li
|
|||||||
|
|
||||||
if *license.Features.DataRetention {
|
if *license.Features.DataRetention {
|
||||||
props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion)
|
props["DataRetentionEnableMessageDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableMessageDeletion)
|
||||||
props["DataRetentionMessageRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.MessageRetentionDays), 10)
|
props["DataRetentionMessageRetentionHours"] = strconv.FormatInt(int64(c.DataRetentionSettings.GetMessageRetentionHours()), 10)
|
||||||
props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion)
|
props["DataRetentionEnableFileDeletion"] = strconv.FormatBool(*c.DataRetentionSettings.EnableFileDeletion)
|
||||||
props["DataRetentionFileRetentionDays"] = strconv.FormatInt(int64(*c.DataRetentionSettings.FileRetentionDays), 10)
|
props["DataRetentionFileRetentionHours"] = strconv.FormatInt(int64(c.DataRetentionSettings.GetFileRetentionHours()), 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
if license.HasSharedChannels() {
|
if license.HasSharedChannels() {
|
||||||
|
|||||||
@@ -8742,13 +8742,37 @@
|
|||||||
"id": "model.config.is_valid.data_retention.deletion_job_start_time.app_error",
|
"id": "model.config.is_valid.data_retention.deletion_job_start_time.app_error",
|
||||||
"translation": "Data retention job start time must be a 24-hour time stamp in the form HH:MM."
|
"translation": "Data retention job start time must be a 24-hour time stamp in the form HH:MM."
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.file_retention_both_zero.app_error",
|
||||||
|
"translation": "File retention days and file retention hours cannot both be 0."
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"id": "model.config.is_valid.data_retention.file_retention_days_too_low.app_error",
|
"id": "model.config.is_valid.data_retention.file_retention_days_too_low.app_error",
|
||||||
"translation": "File retention must be one day or longer."
|
"translation": "File retention days cannot be less than 0."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.file_retention_hours_too_low.app_error",
|
||||||
|
"translation": "File retention hours cannot be less than 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.file_retention_misconfiguration.app_error",
|
||||||
|
"translation": "File retention days and file retention hours cannot both be greater than 0."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.message_retention_both_zero.app_error",
|
||||||
|
"translation": "Message retention days and message retention hours cannot both be 0."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "model.config.is_valid.data_retention.message_retention_days_too_low.app_error",
|
"id": "model.config.is_valid.data_retention.message_retention_days_too_low.app_error",
|
||||||
"translation": "Message retention must be one day or longer."
|
"translation": "Message retention days cannot be less than 0."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.message_retention_hours_too_low.app_error",
|
||||||
|
"translation": "Message retention hours cannot be less than 0"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "model.config.is_valid.data_retention.message_retention_misconfiguration.app_error",
|
||||||
|
"translation": "Message retention days and message retention hours cannot both be greater than 0."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "model.config.is_valid.directory.app_error",
|
"id": "model.config.is_valid.directory.app_error",
|
||||||
|
|||||||
@@ -829,7 +829,9 @@ func (ts *TelemetryService) trackConfig() {
|
|||||||
"enable_message_deletion": *cfg.DataRetentionSettings.EnableMessageDeletion,
|
"enable_message_deletion": *cfg.DataRetentionSettings.EnableMessageDeletion,
|
||||||
"enable_file_deletion": *cfg.DataRetentionSettings.EnableFileDeletion,
|
"enable_file_deletion": *cfg.DataRetentionSettings.EnableFileDeletion,
|
||||||
"message_retention_days": *cfg.DataRetentionSettings.MessageRetentionDays,
|
"message_retention_days": *cfg.DataRetentionSettings.MessageRetentionDays,
|
||||||
|
"message_retention_hours": *cfg.DataRetentionSettings.MessageRetentionHours,
|
||||||
"file_retention_days": *cfg.DataRetentionSettings.FileRetentionDays,
|
"file_retention_days": *cfg.DataRetentionSettings.FileRetentionDays,
|
||||||
|
"file_retention_hours": *cfg.DataRetentionSettings.FileRetentionHours,
|
||||||
"deletion_job_start_time": *cfg.DataRetentionSettings.DeletionJobStartTime,
|
"deletion_job_start_time": *cfg.DataRetentionSettings.DeletionJobStartTime,
|
||||||
"batch_size": *cfg.DataRetentionSettings.BatchSize,
|
"batch_size": *cfg.DataRetentionSettings.BatchSize,
|
||||||
"time_between_batches": *cfg.DataRetentionSettings.TimeBetweenBatchesMilliseconds,
|
"time_between_batches": *cfg.DataRetentionSettings.TimeBetweenBatchesMilliseconds,
|
||||||
|
|||||||
@@ -207,7 +207,9 @@ const (
|
|||||||
BleveSettingsDefaultBatchSize = 10000
|
BleveSettingsDefaultBatchSize = 10000
|
||||||
|
|
||||||
DataRetentionSettingsDefaultMessageRetentionDays = 365
|
DataRetentionSettingsDefaultMessageRetentionDays = 365
|
||||||
|
DataRetentionSettingsDefaultMessageRetentionHours = 0
|
||||||
DataRetentionSettingsDefaultFileRetentionDays = 365
|
DataRetentionSettingsDefaultFileRetentionDays = 365
|
||||||
|
DataRetentionSettingsDefaultFileRetentionHours = 0
|
||||||
DataRetentionSettingsDefaultBoardsRetentionDays = 365
|
DataRetentionSettingsDefaultBoardsRetentionDays = 365
|
||||||
DataRetentionSettingsDefaultDeletionJobStartTime = "02:00"
|
DataRetentionSettingsDefaultDeletionJobStartTime = "02:00"
|
||||||
DataRetentionSettingsDefaultBatchSize = 3000
|
DataRetentionSettingsDefaultBatchSize = 3000
|
||||||
@@ -2911,8 +2913,10 @@ type DataRetentionSettings struct {
|
|||||||
EnableMessageDeletion *bool `access:"compliance_data_retention_policy"`
|
EnableMessageDeletion *bool `access:"compliance_data_retention_policy"`
|
||||||
EnableFileDeletion *bool `access:"compliance_data_retention_policy"`
|
EnableFileDeletion *bool `access:"compliance_data_retention_policy"`
|
||||||
EnableBoardsDeletion *bool `access:"compliance_data_retention_policy"`
|
EnableBoardsDeletion *bool `access:"compliance_data_retention_policy"`
|
||||||
MessageRetentionDays *int `access:"compliance_data_retention_policy"`
|
MessageRetentionDays *int `access:"compliance_data_retention_policy"` // Deprecated: use `MessageRetentionHours`
|
||||||
FileRetentionDays *int `access:"compliance_data_retention_policy"`
|
MessageRetentionHours *int `access:"compliance_data_retention_policy"`
|
||||||
|
FileRetentionDays *int `access:"compliance_data_retention_policy"` // Deprecated: use `FileRetentionHours`
|
||||||
|
FileRetentionHours *int `access:"compliance_data_retention_policy"`
|
||||||
BoardsRetentionDays *int `access:"compliance_data_retention_policy"`
|
BoardsRetentionDays *int `access:"compliance_data_retention_policy"`
|
||||||
DeletionJobStartTime *string `access:"compliance_data_retention_policy"`
|
DeletionJobStartTime *string `access:"compliance_data_retention_policy"`
|
||||||
BatchSize *int `access:"compliance_data_retention_policy"`
|
BatchSize *int `access:"compliance_data_retention_policy"`
|
||||||
@@ -2937,10 +2941,18 @@ func (s *DataRetentionSettings) SetDefaults() {
|
|||||||
s.MessageRetentionDays = NewInt(DataRetentionSettingsDefaultMessageRetentionDays)
|
s.MessageRetentionDays = NewInt(DataRetentionSettingsDefaultMessageRetentionDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.MessageRetentionHours == nil {
|
||||||
|
s.MessageRetentionHours = NewInt(DataRetentionSettingsDefaultMessageRetentionHours)
|
||||||
|
}
|
||||||
|
|
||||||
if s.FileRetentionDays == nil {
|
if s.FileRetentionDays == nil {
|
||||||
s.FileRetentionDays = NewInt(DataRetentionSettingsDefaultFileRetentionDays)
|
s.FileRetentionDays = NewInt(DataRetentionSettingsDefaultFileRetentionDays)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.FileRetentionHours == nil {
|
||||||
|
s.FileRetentionHours = NewInt(DataRetentionSettingsDefaultFileRetentionHours)
|
||||||
|
}
|
||||||
|
|
||||||
if s.BoardsRetentionDays == nil {
|
if s.BoardsRetentionDays == nil {
|
||||||
s.BoardsRetentionDays = NewInt(DataRetentionSettingsDefaultBoardsRetentionDays)
|
s.BoardsRetentionDays = NewInt(DataRetentionSettingsDefaultBoardsRetentionDays)
|
||||||
}
|
}
|
||||||
@@ -2961,6 +2973,30 @@ func (s *DataRetentionSettings) SetDefaults() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetMessageRetentionHours returns the message retention time as an int.
|
||||||
|
// MessageRetentionHours takes precedence over the deprecated MessageRetentionDays.
|
||||||
|
func (s *DataRetentionSettings) GetMessageRetentionHours() int {
|
||||||
|
if s.MessageRetentionHours != nil && *s.MessageRetentionHours > 0 {
|
||||||
|
return *s.MessageRetentionHours
|
||||||
|
}
|
||||||
|
if s.MessageRetentionDays != nil && *s.MessageRetentionDays > 0 {
|
||||||
|
return *s.MessageRetentionDays * 24
|
||||||
|
}
|
||||||
|
return DataRetentionSettingsDefaultMessageRetentionDays * 24
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFileRetentionHours returns the message retention time as an int.
|
||||||
|
// FileRetentionHours takes precedence over the deprecated FileRetentionDays.
|
||||||
|
func (s *DataRetentionSettings) GetFileRetentionHours() int {
|
||||||
|
if s.FileRetentionHours != nil && *s.FileRetentionHours > 0 {
|
||||||
|
return *s.FileRetentionHours
|
||||||
|
}
|
||||||
|
if s.FileRetentionDays != nil && *s.FileRetentionDays > 0 {
|
||||||
|
return *s.FileRetentionDays * 24
|
||||||
|
}
|
||||||
|
return DataRetentionSettingsDefaultFileRetentionDays * 24
|
||||||
|
}
|
||||||
|
|
||||||
type JobSettings struct {
|
type JobSettings struct {
|
||||||
RunJobs *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none
|
RunJobs *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none
|
||||||
RunScheduler *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none
|
RunScheduler *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none
|
||||||
@@ -4141,14 +4177,38 @@ func (bs *BleveSettings) isValid() *AppError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *DataRetentionSettings) isValid() *AppError {
|
func (s *DataRetentionSettings) isValid() *AppError {
|
||||||
if *s.MessageRetentionDays <= 0 {
|
if s.MessageRetentionDays == nil || *s.MessageRetentionDays < 0 {
|
||||||
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
if *s.FileRetentionDays <= 0 {
|
if s.MessageRetentionHours == nil || *s.MessageRetentionHours < 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_hours_too_low.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if s.FileRetentionDays == nil || *s.FileRetentionDays < 0 {
|
||||||
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_days_too_low.app_error", nil, "", http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if s.FileRetentionHours == nil || *s.FileRetentionHours < 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_hours_too_low.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.MessageRetentionDays > 0 && *s.MessageRetentionHours > 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_misconfiguration.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.FileRetentionDays > 0 && *s.FileRetentionHours > 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_misconfiguration.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.MessageRetentionDays == 0 && *s.MessageRetentionHours == 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.message_retention_both_zero.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
|
if *s.FileRetentionDays == 0 && *s.FileRetentionHours == 0 {
|
||||||
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.file_retention_both_zero.app_error", nil, "", http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
if _, err := time.Parse("15:04", *s.DeletionJobStartTime); err != nil {
|
if _, err := time.Parse("15:04", *s.DeletionJobStartTime); err != nil {
|
||||||
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.deletion_job_start_time.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
return NewAppError("Config.IsValid", "model.config.is_valid.data_retention.deletion_job_start_time.app_error", nil, "", http.StatusBadRequest).Wrap(err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1647,3 +1647,105 @@ func TestConfigDefaultCallsPluginState(t *testing.T) {
|
|||||||
assert.False(t, c1.PluginSettings.PluginStates["com.mattermost.calls"].Enable)
|
assert.False(t, c1.PluginSettings.PluginStates["com.mattermost.calls"].Enable)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestConfigGetMessageRetentionHours(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config Config
|
||||||
|
value int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should return MessageRetentionDays config value in hours by default",
|
||||||
|
config: Config{},
|
||||||
|
value: 8760,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return MessageRetentionHours config value",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
MessageRetentionHours: NewInt(48),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return MessageRetentionHours config value",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
MessageRetentionDays: NewInt(50),
|
||||||
|
MessageRetentionHours: NewInt(48),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return MessageRetentionDays config value in hours",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
MessageRetentionDays: NewInt(50),
|
||||||
|
MessageRetentionHours: NewInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 1200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
test.config.SetDefaults()
|
||||||
|
|
||||||
|
require.Equal(t, test.value, test.config.DataRetentionSettings.GetMessageRetentionHours())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestConfigGetFileRetentionHours(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
config Config
|
||||||
|
value int
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "should return FileRetentionDays config value in hours by default",
|
||||||
|
config: Config{},
|
||||||
|
value: 8760,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return FileRetentionHours config value",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
FileRetentionHours: NewInt(48),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return FileRetentionHours config value",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
FileRetentionDays: NewInt(50),
|
||||||
|
FileRetentionHours: NewInt(48),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 48,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "should return FileRetentionDays config value in hours",
|
||||||
|
config: Config{
|
||||||
|
DataRetentionSettings: DataRetentionSettings{
|
||||||
|
FileRetentionDays: NewInt(50),
|
||||||
|
FileRetentionHours: NewInt(0),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
value: 1200,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, test := range tests {
|
||||||
|
t.Run(test.name, func(t *testing.T) {
|
||||||
|
test.config.SetDefaults()
|
||||||
|
|
||||||
|
require.Equal(t, test.value, test.config.DataRetentionSettings.GetFileRetentionHours())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -13,12 +13,16 @@ describe('components/admin_console/data_retention_settings/data_retention_settin
|
|||||||
EnableMessageDeletion: true,
|
EnableMessageDeletion: true,
|
||||||
EnableFileDeletion: true,
|
EnableFileDeletion: true,
|
||||||
MessageRetentionDays: 100,
|
MessageRetentionDays: 100,
|
||||||
|
MessageRetentionHours: 2400,
|
||||||
FileRetentionDays: 100,
|
FileRetentionDays: 100,
|
||||||
|
FileRetentionHours: 2400,
|
||||||
DeletionJobStartTime: '00:15',
|
DeletionJobStartTime: '00:15',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
customPolicies: {},
|
customPolicies: {},
|
||||||
customPoliciesCount: 0,
|
customPoliciesCount: 0,
|
||||||
|
globalMessageRetentionHours: '2400',
|
||||||
|
globalFileRetentionHours: '2400',
|
||||||
actions: {
|
actions: {
|
||||||
getDataRetentionCustomPolicies: jest.fn().mockResolvedValue([]),
|
getDataRetentionCustomPolicies: jest.fn().mockResolvedValue([]),
|
||||||
createJob: jest.fn(),
|
createJob: jest.fn(),
|
||||||
|
|||||||
@@ -37,6 +37,8 @@ type Props = {
|
|||||||
config: DeepPartial<AdminConfig>;
|
config: DeepPartial<AdminConfig>;
|
||||||
customPolicies: DataRetentionCustomPolicies;
|
customPolicies: DataRetentionCustomPolicies;
|
||||||
customPoliciesCount: number;
|
customPoliciesCount: number;
|
||||||
|
globalMessageRetentionHours: string | undefined;
|
||||||
|
globalFileRetentionHours: string | undefined;
|
||||||
actions: {
|
actions: {
|
||||||
getDataRetentionCustomPolicies: (page: number) => Promise<{ data: DataRetentionCustomPolicies }>;
|
getDataRetentionCustomPolicies: (page: number) => Promise<{ data: DataRetentionCustomPolicies }>;
|
||||||
createJob: (job: JobTypeBase) => Promise<{ data: any }>;
|
createJob: (job: JobTypeBase) => Promise<{ data: any }>;
|
||||||
@@ -147,6 +149,52 @@ export default class DataRetentionSettings extends React.PureComponent<Props, St
|
|||||||
];
|
];
|
||||||
return columns;
|
return columns;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
getGlobalRetentionSetting = (enabled: boolean | undefined, hours: string | undefined): JSX.Element => {
|
||||||
|
if (!enabled) {
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.data_retention.form.keepForever'
|
||||||
|
defaultMessage='Keep forever'
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const hoursInt = parseInt(hours || '', 10);
|
||||||
|
if (hoursInt && hoursInt % 8760 === 0) {
|
||||||
|
const years = hoursInt / 8760;
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.data_retention.retention_years'
|
||||||
|
defaultMessage='{count} {count, plural, one {year} other {years}}'
|
||||||
|
values={{
|
||||||
|
count: `${years}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (hoursInt && hoursInt % 24 === 0) {
|
||||||
|
const days = hoursInt / 24;
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.data_retention.retention_days'
|
||||||
|
defaultMessage='{count} {count, plural, one {day} other {days}}'
|
||||||
|
values={{
|
||||||
|
count: `${days}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<FormattedMessage
|
||||||
|
id='admin.data_retention.retention_hours'
|
||||||
|
defaultMessage='{count} {count, plural, one {hour} other {hours}}'
|
||||||
|
values={{
|
||||||
|
count: `${hours}`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
getMessageRetentionSetting = (enabled: boolean | undefined, days: number | undefined): JSX.Element => {
|
getMessageRetentionSetting = (enabled: boolean | undefined, days: number | undefined): JSX.Element => {
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
return (
|
return (
|
||||||
@@ -185,12 +233,12 @@ export default class DataRetentionSettings extends React.PureComponent<Props, St
|
|||||||
description: Utils.localizeMessage('admin.data_retention.form.text', 'Applies to all teams and channels, but does not apply to custom retention policies.'),
|
description: Utils.localizeMessage('admin.data_retention.form.text', 'Applies to all teams and channels, but does not apply to custom retention policies.'),
|
||||||
channel_messages: (
|
channel_messages: (
|
||||||
<div data-testid='global_message_retention_cell'>
|
<div data-testid='global_message_retention_cell'>
|
||||||
{this.getMessageRetentionSetting(DataRetentionSettings?.EnableMessageDeletion, DataRetentionSettings?.MessageRetentionDays)}
|
{this.getGlobalRetentionSetting(DataRetentionSettings?.EnableMessageDeletion, this.props.globalMessageRetentionHours)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
files: (
|
files: (
|
||||||
<div data-testid='global_file_retention_cell'>
|
<div data-testid='global_file_retention_cell'>
|
||||||
{this.getMessageRetentionSetting(DataRetentionSettings?.EnableFileDeletion, DataRetentionSettings?.FileRetentionDays)}
|
{this.getGlobalRetentionSetting(DataRetentionSettings?.EnableFileDeletion, this.props.globalFileRetentionHours)}
|
||||||
</div>
|
</div>
|
||||||
),
|
),
|
||||||
actions: (
|
actions: (
|
||||||
|
|||||||
@@ -8,6 +8,8 @@ import * as Utils from 'utils/utils';
|
|||||||
export const FOREVER = 'FOREVER';
|
export const FOREVER = 'FOREVER';
|
||||||
export const YEARS = 'YEARS';
|
export const YEARS = 'YEARS';
|
||||||
export const DAYS = 'DAYS';
|
export const DAYS = 'DAYS';
|
||||||
|
export const HOURS = 'HOURS';
|
||||||
export const keepForeverOption = () => ({value: FOREVER, label: <div><i className='icon icon-infinity option-icon'/><span className='option_forever'>{Utils.localizeMessage('admin.data_retention.form.keepForever', 'Keep forever')}</span></div>});
|
export const keepForeverOption = () => ({value: FOREVER, label: <div><i className='icon icon-infinity option-icon'/><span className='option_forever'>{Utils.localizeMessage('admin.data_retention.form.keepForever', 'Keep forever')}</span></div>});
|
||||||
export const yearsOption = () => ({value: YEARS, label: <span className='option_years'>{Utils.localizeMessage('admin.data_retention.form.years', 'Years')}</span>});
|
export const yearsOption = () => ({value: YEARS, label: <span className='option_years'>{Utils.localizeMessage('admin.data_retention.form.years', 'Years')}</span>});
|
||||||
export const daysOption = () => ({value: DAYS, label: <span className='option_days'>{Utils.localizeMessage('admin.data_retention.form.days', 'Days')}</span>});
|
export const daysOption = () => ({value: DAYS, label: <span className='option_days'>{Utils.localizeMessage('admin.data_retention.form.days', 'Days')}</span>});
|
||||||
|
export const hoursOption = () => ({value: HOURS, label: <span className='option_hours'>{Utils.localizeMessage('admin.data_retention.form.hours', 'Hours')}</span>});
|
||||||
|
|||||||
@@ -62,13 +62,21 @@ exports[`components/PluginManagement should match snapshot 1`] = `
|
|||||||
}
|
}
|
||||||
inputId="channel_message_retention_input"
|
inputId="channel_message_retention_input"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
inputValue="60"
|
inputValue="100"
|
||||||
legend="Channel & direct message retention"
|
legend="Channel & direct message retention"
|
||||||
name="channel_message_retention"
|
name="channel_message_retention"
|
||||||
onDropdownChange={[Function]}
|
onDropdownChange={[Function]}
|
||||||
onInputChange={[Function]}
|
onInputChange={[Function]}
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": <span
|
||||||
|
className="option_hours"
|
||||||
|
>
|
||||||
|
Hours
|
||||||
|
</span>,
|
||||||
|
"value": "HOURS",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"label": <span
|
"label": <span
|
||||||
className="option_days"
|
className="option_days"
|
||||||
@@ -141,13 +149,21 @@ exports[`components/PluginManagement should match snapshot 1`] = `
|
|||||||
}
|
}
|
||||||
inputId="file_retention_input"
|
inputId="file_retention_input"
|
||||||
inputType="number"
|
inputType="number"
|
||||||
inputValue="40"
|
inputValue="100"
|
||||||
legend="File retention"
|
legend="File retention"
|
||||||
name="file_retention"
|
name="file_retention"
|
||||||
onDropdownChange={[Function]}
|
onDropdownChange={[Function]}
|
||||||
onInputChange={[Function]}
|
onInputChange={[Function]}
|
||||||
options={
|
options={
|
||||||
Array [
|
Array [
|
||||||
|
Object {
|
||||||
|
"label": <span
|
||||||
|
className="option_hours"
|
||||||
|
>
|
||||||
|
Hours
|
||||||
|
</span>,
|
||||||
|
"value": "HOURS",
|
||||||
|
},
|
||||||
Object {
|
Object {
|
||||||
"label": <span
|
"label": <span
|
||||||
className="option_days"
|
className="option_days"
|
||||||
|
|||||||
@@ -48,6 +48,12 @@
|
|||||||
background-color: var(--sys-center-channel-bg);
|
background-color: var(--sys-center-channel-bg);
|
||||||
color: rgba(var(--sys-center-channel-color-rgb), 0.64);
|
color: rgba(var(--sys-center-channel-color-rgb), 0.64);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.alert {
|
||||||
|
&.alert-warning {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.CustomPolicy__error {
|
.CustomPolicy__error {
|
||||||
|
|||||||
@@ -12,11 +12,14 @@ describe('components/PluginManagement', () => {
|
|||||||
DataRetentionSettings: {
|
DataRetentionSettings: {
|
||||||
EnableMessageDeletion: true,
|
EnableMessageDeletion: true,
|
||||||
EnableFileDeletion: true,
|
EnableFileDeletion: true,
|
||||||
MessageRetentionDays: 60,
|
MessageRetentionHours: 1440,
|
||||||
FileRetentionDays: 40,
|
FileRetentionHours: 960,
|
||||||
DeletionJobStartTime: '10:00',
|
DeletionJobStartTime: '10:00',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
messageRetentionHours: '2400',
|
||||||
|
fileRetentionHours: '2400',
|
||||||
|
environmentConfig: {},
|
||||||
actions: {
|
actions: {
|
||||||
updateConfig: jest.fn(),
|
updateConfig: jest.fn(),
|
||||||
setNavigationBlocked: jest.fn(),
|
setNavigationBlocked: jest.fn(),
|
||||||
|
|||||||
@@ -4,12 +4,13 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {FormattedMessage} from 'react-intl';
|
import {FormattedMessage} from 'react-intl';
|
||||||
|
|
||||||
import type {AdminConfig} from '@mattermost/types/config';
|
import type {AdminConfig, EnvironmentConfig} from '@mattermost/types/config';
|
||||||
import type {ServerError} from '@mattermost/types/errors';
|
import type {ServerError} from '@mattermost/types/errors';
|
||||||
import type {DeepPartial} from '@mattermost/types/utilities';
|
import type {DeepPartial} from '@mattermost/types/utilities';
|
||||||
|
|
||||||
import BlockableLink from 'components/admin_console/blockable_link';
|
import BlockableLink from 'components/admin_console/blockable_link';
|
||||||
import {keepForeverOption, yearsOption, daysOption, FOREVER, YEARS, DAYS} from 'components/admin_console/data_retention_settings/dropdown_options/dropdown_options';
|
import {keepForeverOption, yearsOption, daysOption, FOREVER, YEARS, DAYS, hoursOption} from 'components/admin_console/data_retention_settings/dropdown_options/dropdown_options';
|
||||||
|
import SetByEnv from 'components/admin_console/set_by_env';
|
||||||
import Card from 'components/card/card';
|
import Card from 'components/card/card';
|
||||||
import SaveButton from 'components/save_button';
|
import SaveButton from 'components/save_button';
|
||||||
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
import AdminHeader from 'components/widgets/admin_console/admin_header';
|
||||||
@@ -26,6 +27,9 @@ type ValueType = {
|
|||||||
}
|
}
|
||||||
type Props = {
|
type Props = {
|
||||||
config: DeepPartial<AdminConfig>;
|
config: DeepPartial<AdminConfig>;
|
||||||
|
messageRetentionHours: string | undefined;
|
||||||
|
fileRetentionHours: string | undefined;
|
||||||
|
environmentConfig: Partial<EnvironmentConfig>;
|
||||||
actions: {
|
actions: {
|
||||||
updateConfig: (config: Record<string, any>) => Promise<{ data?: AdminConfig; error?: ServerError }>;
|
updateConfig: (config: Record<string, any>) => Promise<{ data?: AdminConfig; error?: ServerError }>;
|
||||||
setNavigationBlocked: (blocked: boolean) => void;
|
setNavigationBlocked: (blocked: boolean) => void;
|
||||||
@@ -51,30 +55,44 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
saving: false,
|
saving: false,
|
||||||
serverError: null,
|
serverError: null,
|
||||||
formErrorText: '',
|
formErrorText: '',
|
||||||
messageRetentionDropdownValue: this.getDefaultDropdownValue(DataRetentionSettings?.EnableMessageDeletion, DataRetentionSettings?.MessageRetentionDays),
|
messageRetentionDropdownValue: this.getDefaultDropdownValue(DataRetentionSettings?.EnableMessageDeletion, props.messageRetentionHours),
|
||||||
messageRetentionInputValue: this.getDefaultInputValue(DataRetentionSettings?.EnableMessageDeletion, DataRetentionSettings?.MessageRetentionDays),
|
messageRetentionInputValue: this.getDefaultInputValue(DataRetentionSettings?.EnableMessageDeletion, props.messageRetentionHours),
|
||||||
fileRetentionDropdownValue: this.getDefaultDropdownValue(DataRetentionSettings?.EnableFileDeletion, DataRetentionSettings?.FileRetentionDays),
|
fileRetentionDropdownValue: this.getDefaultDropdownValue(DataRetentionSettings?.EnableFileDeletion, props.fileRetentionHours),
|
||||||
fileRetentionInputValue: this.getDefaultInputValue(DataRetentionSettings?.EnableFileDeletion, DataRetentionSettings?.FileRetentionDays),
|
fileRetentionInputValue: this.getDefaultInputValue(DataRetentionSettings?.EnableFileDeletion, props.fileRetentionHours),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getDefaultInputValue = (isEnabled: boolean | undefined, days: number | undefined): string => {
|
getDefaultInputValue = (isEnabled: boolean | undefined, hours: string | undefined): string => {
|
||||||
if (!isEnabled || days === undefined) {
|
if (!isEnabled || hours === undefined) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
if (days % 365 === 0) {
|
const hoursInt = parseInt(hours, 10);
|
||||||
return (days / 365).toString();
|
|
||||||
|
// 8760 hours in a year
|
||||||
|
if (hoursInt % 8760 === 0) {
|
||||||
|
return (hoursInt / 8760).toString();
|
||||||
}
|
}
|
||||||
return days.toString();
|
if (hoursInt % 24 === 0) {
|
||||||
|
return (hoursInt / 24).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hours.toString();
|
||||||
};
|
};
|
||||||
getDefaultDropdownValue = (isEnabled: boolean | undefined, days: number | undefined) => {
|
getDefaultDropdownValue = (isEnabled: boolean | undefined, hours: string | undefined) => {
|
||||||
if (!isEnabled || days === undefined) {
|
if (!isEnabled || hours === undefined) {
|
||||||
return keepForeverOption();
|
return keepForeverOption();
|
||||||
}
|
}
|
||||||
if (days % 365 === 0) {
|
const hoursInt = parseInt(hours, 10);
|
||||||
|
|
||||||
|
// 8760 hours in a year
|
||||||
|
if (hoursInt % 8760 === 0) {
|
||||||
return yearsOption();
|
return yearsOption();
|
||||||
}
|
}
|
||||||
return daysOption();
|
if (hoursInt % 24 === 0) {
|
||||||
|
return daysOption();
|
||||||
|
}
|
||||||
|
|
||||||
|
return hoursOption();
|
||||||
};
|
};
|
||||||
|
|
||||||
handleSubmit = async () => {
|
handleSubmit = async () => {
|
||||||
@@ -90,16 +108,16 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
|
|
||||||
newConfig.DataRetentionSettings.EnableMessageDeletion = this.setDeletionEnabled(messageRetentionDropdownValue.value);
|
newConfig.DataRetentionSettings.EnableMessageDeletion = this.setDeletionEnabled(messageRetentionDropdownValue.value);
|
||||||
|
|
||||||
const messageDays = this.setRetentionDays(messageRetentionDropdownValue.value, messageRetentionInputValue);
|
if (!this.isMessageRetentionSetByEnv() && this.setDeletionEnabled(messageRetentionDropdownValue.value)) {
|
||||||
if (messageDays >= 1) {
|
newConfig.DataRetentionSettings.MessageRetentionDays = 0;
|
||||||
newConfig.DataRetentionSettings.MessageRetentionDays = messageDays;
|
newConfig.DataRetentionSettings.MessageRetentionHours = this.setRetentionHours(messageRetentionDropdownValue.value, messageRetentionInputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
newConfig.DataRetentionSettings.EnableFileDeletion = this.setDeletionEnabled(fileRetentionDropdownValue.value);
|
newConfig.DataRetentionSettings.EnableFileDeletion = this.setDeletionEnabled(fileRetentionDropdownValue.value);
|
||||||
|
|
||||||
const fileDays = this.setRetentionDays(fileRetentionDropdownValue.value, fileRetentionInputValue);
|
if (!this.isFileRetentionSetByEnv() && this.setDeletionEnabled(fileRetentionDropdownValue.value)) {
|
||||||
if (fileDays >= 1) {
|
newConfig.DataRetentionSettings.FileRetentionDays = 0;
|
||||||
newConfig.DataRetentionSettings.FileRetentionDays = fileDays;
|
newConfig.DataRetentionSettings.FileRetentionHours = this.setRetentionHours(fileRetentionDropdownValue.value, fileRetentionInputValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
const {error} = await this.props.actions.updateConfig(newConfig);
|
const {error} = await this.props.actions.updateConfig(newConfig);
|
||||||
@@ -119,16 +137,26 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
setRetentionDays = (dropdownValue: string, value: string): number => {
|
setRetentionHours = (dropdownValue: string, value: string): number => {
|
||||||
if (dropdownValue === YEARS) {
|
if (dropdownValue === YEARS) {
|
||||||
return parseInt(value, 10) * 365;
|
return parseInt(value, 10) * 24 * 365;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dropdownValue === DAYS) {
|
if (dropdownValue === DAYS) {
|
||||||
return parseInt(value, 10);
|
return parseInt(value, 10) * 24;
|
||||||
}
|
}
|
||||||
|
return parseInt(value, 10);
|
||||||
|
};
|
||||||
|
|
||||||
return 0;
|
isMessageRetentionSetByEnv = () => {
|
||||||
|
return (this.props.environmentConfig?.DataRetentionSettings?.MessageRetentionDays && this.props.config.DataRetentionSettings?.MessageRetentionDays && this.props.config.DataRetentionSettings.MessageRetentionDays > 0) ||
|
||||||
|
(this.props.environmentConfig?.DataRetentionSettings?.MessageRetentionHours && this.props.config.DataRetentionSettings?.MessageRetentionHours && this.props.config.DataRetentionSettings.MessageRetentionHours > 0) ||
|
||||||
|
(this.props.environmentConfig?.DataRetentionSettings?.EnableMessageDeletion && !this.props.config.DataRetentionSettings?.EnableMessageDeletion);
|
||||||
|
};
|
||||||
|
|
||||||
|
isFileRetentionSetByEnv = () => {
|
||||||
|
return (this.props.environmentConfig?.DataRetentionSettings?.FileRetentionDays && this.props.config.DataRetentionSettings?.FileRetentionDays && this.props.config.DataRetentionSettings.FileRetentionDays > 0) ||
|
||||||
|
(this.props.environmentConfig?.DataRetentionSettings?.FileRetentionHours && this.props.config.DataRetentionSettings?.FileRetentionHours && this.props.config.DataRetentionSettings.FileRetentionHours > 0) ||
|
||||||
|
(this.props.environmentConfig?.DataRetentionSettings?.EnableFileDeletion && !this.props.config.DataRetentionSettings?.EnableFileDeletion);
|
||||||
};
|
};
|
||||||
|
|
||||||
render = () => {
|
render = () => {
|
||||||
@@ -173,8 +201,9 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
inputValue={this.state.messageRetentionInputValue}
|
inputValue={this.state.messageRetentionInputValue}
|
||||||
width={90}
|
width={90}
|
||||||
exceptionToInput={[FOREVER]}
|
exceptionToInput={[FOREVER]}
|
||||||
|
disabled={this.isMessageRetentionSetByEnv()}
|
||||||
defaultValue={keepForeverOption()}
|
defaultValue={keepForeverOption()}
|
||||||
options={[daysOption(), yearsOption(), keepForeverOption()]}
|
options={[hoursOption(), daysOption(), yearsOption(), keepForeverOption()]}
|
||||||
legend={Utils.localizeMessage('admin.data_retention.form.channelAndDirectMessageRetention', 'Channel & direct message retention')}
|
legend={Utils.localizeMessage('admin.data_retention.form.channelAndDirectMessageRetention', 'Channel & direct message retention')}
|
||||||
placeholder={Utils.localizeMessage('admin.data_retention.form.channelAndDirectMessageRetention', 'Channel & direct message retention')}
|
placeholder={Utils.localizeMessage('admin.data_retention.form.channelAndDirectMessageRetention', 'Channel & direct message retention')}
|
||||||
name={'channel_message_retention'}
|
name={'channel_message_retention'}
|
||||||
@@ -182,6 +211,7 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
dropdownClassNamePrefix={'channel_message_retention_dropdown'}
|
dropdownClassNamePrefix={'channel_message_retention_dropdown'}
|
||||||
inputId={'channel_message_retention_input'}
|
inputId={'channel_message_retention_input'}
|
||||||
/>
|
/>
|
||||||
|
{this.isMessageRetentionSetByEnv() && <SetByEnv/>}
|
||||||
</div>
|
</div>
|
||||||
<div id='global_file_dropdown'>
|
<div id='global_file_dropdown'>
|
||||||
<DropdownInputHybrid
|
<DropdownInputHybrid
|
||||||
@@ -199,8 +229,9 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
inputValue={this.state.fileRetentionInputValue}
|
inputValue={this.state.fileRetentionInputValue}
|
||||||
width={90}
|
width={90}
|
||||||
exceptionToInput={[FOREVER]}
|
exceptionToInput={[FOREVER]}
|
||||||
|
disabled={this.isFileRetentionSetByEnv()}
|
||||||
defaultValue={keepForeverOption()}
|
defaultValue={keepForeverOption()}
|
||||||
options={[daysOption(), yearsOption(), keepForeverOption()]}
|
options={[hoursOption(), daysOption(), yearsOption(), keepForeverOption()]}
|
||||||
legend={Utils.localizeMessage('admin.data_retention.form.fileRetention', 'File retention')}
|
legend={Utils.localizeMessage('admin.data_retention.form.fileRetention', 'File retention')}
|
||||||
placeholder={Utils.localizeMessage('admin.data_retention.form.fileRetention', 'File retention')}
|
placeholder={Utils.localizeMessage('admin.data_retention.form.fileRetention', 'File retention')}
|
||||||
name={'file_retention'}
|
name={'file_retention'}
|
||||||
@@ -208,6 +239,7 @@ export default class GlobalPolicyForm extends React.PureComponent<Props, State>
|
|||||||
dropdownClassNamePrefix={'file_retention_dropdown'}
|
dropdownClassNamePrefix={'file_retention_dropdown'}
|
||||||
inputId={'file_retention_input'}
|
inputId={'file_retention_input'}
|
||||||
/>
|
/>
|
||||||
|
{this.isFileRetentionSetByEnv() && <SetByEnv/>}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -11,10 +11,14 @@ import type {ServerError} from '@mattermost/types/errors';
|
|||||||
import {
|
import {
|
||||||
updateConfig,
|
updateConfig,
|
||||||
} from 'mattermost-redux/actions/admin';
|
} from 'mattermost-redux/actions/admin';
|
||||||
|
import {getEnvironmentConfig} from 'mattermost-redux/selectors/entities/admin';
|
||||||
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import type {GenericAction, ActionFunc} from 'mattermost-redux/types/actions';
|
import type {GenericAction, ActionFunc} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
|
import {setNavigationBlocked} from 'actions/admin_actions.jsx';
|
||||||
|
|
||||||
|
import type {GlobalState} from 'types/store';
|
||||||
|
|
||||||
import GlobalPolicyForm from './global_policy_form';
|
import GlobalPolicyForm from './global_policy_form';
|
||||||
|
|
||||||
type Actions = {
|
type Actions = {
|
||||||
@@ -22,6 +26,17 @@ type Actions = {
|
|||||||
setNavigationBlocked: (blocked: boolean) => void;
|
setNavigationBlocked: (blocked: boolean) => void;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function mapStateToProps(state: GlobalState) {
|
||||||
|
const messageRetentionHours = getConfig(state).DataRetentionMessageRetentionHours;
|
||||||
|
const fileRetentionHours = getConfig(state).DataRetentionFileRetentionHours;
|
||||||
|
|
||||||
|
return {
|
||||||
|
messageRetentionHours,
|
||||||
|
fileRetentionHours,
|
||||||
|
environmentConfig: getEnvironmentConfig(state),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
||||||
return {
|
return {
|
||||||
actions: bindActionCreators<ActionCreatorsMapObject<ActionFunc | GenericAction>, Actions>({
|
actions: bindActionCreators<ActionCreatorsMapObject<ActionFunc | GenericAction>, Actions>({
|
||||||
@@ -31,4 +46,4 @@ function mapDispatchToProps(dispatch: Dispatch<GenericAction>) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export default connect(null, mapDispatchToProps)(GlobalPolicyForm);
|
export default connect(mapStateToProps, mapDispatchToProps)(GlobalPolicyForm);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import type {JobTypeBase, JobType} from '@mattermost/types/jobs';
|
|||||||
import {getDataRetentionCustomPolicies as fetchDataRetentionCustomPolicies, deleteDataRetentionCustomPolicy, updateConfig} from 'mattermost-redux/actions/admin';
|
import {getDataRetentionCustomPolicies as fetchDataRetentionCustomPolicies, deleteDataRetentionCustomPolicy, updateConfig} from 'mattermost-redux/actions/admin';
|
||||||
import {createJob, getJobsByType} from 'mattermost-redux/actions/jobs';
|
import {createJob, getJobsByType} from 'mattermost-redux/actions/jobs';
|
||||||
import {getDataRetentionCustomPolicies, getDataRetentionCustomPoliciesCount} from 'mattermost-redux/selectors/entities/admin';
|
import {getDataRetentionCustomPolicies, getDataRetentionCustomPoliciesCount} from 'mattermost-redux/selectors/entities/admin';
|
||||||
|
import {getConfig} from 'mattermost-redux/selectors/entities/general';
|
||||||
import type {GenericAction, ActionFunc, ActionResult} from 'mattermost-redux/types/actions';
|
import type {GenericAction, ActionFunc, ActionResult} from 'mattermost-redux/types/actions';
|
||||||
|
|
||||||
import type {GlobalState} from 'types/store';
|
import type {GlobalState} from 'types/store';
|
||||||
@@ -28,10 +29,14 @@ type Actions = {
|
|||||||
function mapStateToProps(state: GlobalState) {
|
function mapStateToProps(state: GlobalState) {
|
||||||
const customPolicies = getDataRetentionCustomPolicies(state);
|
const customPolicies = getDataRetentionCustomPolicies(state);
|
||||||
const customPoliciesCount = getDataRetentionCustomPoliciesCount(state);
|
const customPoliciesCount = getDataRetentionCustomPoliciesCount(state);
|
||||||
|
const globalMessageRetentionHours = getConfig(state).DataRetentionMessageRetentionHours;
|
||||||
|
const globalFileRetentionHours = getConfig(state).DataRetentionFileRetentionHours;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
customPolicies,
|
customPolicies,
|
||||||
customPoliciesCount,
|
customPoliciesCount,
|
||||||
|
globalMessageRetentionHours,
|
||||||
|
globalFileRetentionHours,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -44,6 +44,8 @@ const baseStyles = {
|
|||||||
boxShadow: 'none',
|
boxShadow: 'none',
|
||||||
padding: '0 2px',
|
padding: '0 2px',
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
|
minHeight: '40px',
|
||||||
|
borderRadius: '0',
|
||||||
}),
|
}),
|
||||||
indicatorSeparator: (provided: CSSProperties) => ({
|
indicatorSeparator: (provided: CSSProperties) => ({
|
||||||
...provided,
|
...provided,
|
||||||
@@ -206,6 +208,7 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
|
|||||||
className={classNames('Input form-control')}
|
className={classNames('Input form-control')}
|
||||||
ref={inputRef}
|
ref={inputRef}
|
||||||
id={inputId}
|
id={inputId}
|
||||||
|
disabled={props.disabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
@@ -232,6 +235,7 @@ const DropdownInputHybrid = <T extends OptionType = OptionType>(props: Props<T>)
|
|||||||
hideSelectedOptions={true}
|
hideSelectedOptions={true}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
menuPortalTarget={document.body}
|
menuPortalTarget={document.body}
|
||||||
|
isDisabled={props.disabled}
|
||||||
{...otherProps}
|
{...otherProps}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -722,6 +722,7 @@
|
|||||||
"admin.data_retention.form.channelAndDirectMessageRetention": "Channel & direct message retention",
|
"admin.data_retention.form.channelAndDirectMessageRetention": "Channel & direct message retention",
|
||||||
"admin.data_retention.form.days": "Days",
|
"admin.data_retention.form.days": "Days",
|
||||||
"admin.data_retention.form.fileRetention": "File retention",
|
"admin.data_retention.form.fileRetention": "File retention",
|
||||||
|
"admin.data_retention.form.hours": "Hours",
|
||||||
"admin.data_retention.form.keepForever": "Keep forever",
|
"admin.data_retention.form.keepForever": "Keep forever",
|
||||||
"admin.data_retention.form.text": "Applies to all teams and channels, but does not apply to custom retention policies.",
|
"admin.data_retention.form.text": "Applies to all teams and channels, but does not apply to custom retention policies.",
|
||||||
"admin.data_retention.form.years": "Years",
|
"admin.data_retention.form.years": "Years",
|
||||||
@@ -739,6 +740,7 @@
|
|||||||
"admin.data_retention.jobTimeAM": "{time} AM (UTC)",
|
"admin.data_retention.jobTimeAM": "{time} AM (UTC)",
|
||||||
"admin.data_retention.jobTimePM": "{time} PM (UTC)",
|
"admin.data_retention.jobTimePM": "{time} PM (UTC)",
|
||||||
"admin.data_retention.retention_days": "{count} {count, plural, one {day} other {days}}",
|
"admin.data_retention.retention_days": "{count} {count, plural, one {day} other {days}}",
|
||||||
|
"admin.data_retention.retention_hours": "{count} {count, plural, one {hour} other {hours}}",
|
||||||
"admin.data_retention.retention_years": "{count} {count, plural, one {year} other {years}}",
|
"admin.data_retention.retention_years": "{count} {count, plural, one {year} other {years}}",
|
||||||
"admin.data_retention.settings.title": "Data Retention Policies",
|
"admin.data_retention.settings.title": "Data Retention Policies",
|
||||||
"admin.data_retention.title": "Data Retention Policy",
|
"admin.data_retention.title": "Data Retention Policy",
|
||||||
|
|||||||
@@ -33,8 +33,8 @@ export type ClientConfig = {
|
|||||||
CWSMock: string;
|
CWSMock: string;
|
||||||
DataRetentionEnableFileDeletion: string;
|
DataRetentionEnableFileDeletion: string;
|
||||||
DataRetentionEnableMessageDeletion: string;
|
DataRetentionEnableMessageDeletion: string;
|
||||||
DataRetentionFileRetentionDays: string;
|
DataRetentionFileRetentionHours: string;
|
||||||
DataRetentionMessageRetentionDays: string;
|
DataRetentionMessageRetentionHours: string;
|
||||||
DefaultClientLocale: string;
|
DefaultClientLocale: string;
|
||||||
DefaultTheme: string;
|
DefaultTheme: string;
|
||||||
DiagnosticId: string;
|
DiagnosticId: string;
|
||||||
@@ -828,7 +828,9 @@ export type DataRetentionSettings = {
|
|||||||
EnableMessageDeletion: boolean;
|
EnableMessageDeletion: boolean;
|
||||||
EnableFileDeletion: boolean;
|
EnableFileDeletion: boolean;
|
||||||
MessageRetentionDays: number;
|
MessageRetentionDays: number;
|
||||||
|
MessageRetentionHours: number;
|
||||||
FileRetentionDays: number;
|
FileRetentionDays: number;
|
||||||
|
FileRetentionHours: number;
|
||||||
DeletionJobStartTime: string;
|
DeletionJobStartTime: string;
|
||||||
BatchSize: number;
|
BatchSize: number;
|
||||||
EnableBoardsDeletion: boolean,
|
EnableBoardsDeletion: boolean,
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user