From 5211d5de157a8b6ba1ac091f1836995bd136a5fc Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Fri, 9 Sep 2022 07:51:17 -0400 Subject: [PATCH 01/21] Allow inline JSON in config.json for advanced logging config (#20954) * Allow embedded JSON in config.json for AdvancedLoggingConfig * fix escaped JSON case * Add unit test cases for escaped JSON Co-authored-by: Mattermod --- app/audit.go | 8 ++--- app/platform/config.go | 8 ++--- config/logconfigsrc.go | 50 ++++++++++++++++++-------- config/logconfigsrc_test.go | 23 +++++++----- config/migrate.go | 4 +-- config/utils.go | 5 +-- config/utils_test.go | 2 +- model/config.go | 64 ++++++++++++++++----------------- services/telemetry/telemetry.go | 6 ++-- 9 files changed, 98 insertions(+), 72 deletions(-) diff --git a/app/audit.go b/app/audit.go index e0e471c75e..7dfc4308f5 100644 --- a/app/audit.go +++ b/app/audit.go @@ -109,14 +109,14 @@ func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) er adt.OnError = s.onAuditError var logConfigSrc config.LogConfigSrc - dsn := *s.platform.Config().ExperimentalAuditSettings.AdvancedLoggingConfig - if bAllowAdvancedLogging && dsn != "" { + auditSettings := s.platform.Config().ExperimentalAuditSettings + if bAllowAdvancedLogging && !config.IsEmptyDSN(auditSettings.AdvancedLoggingConfig) { var err error - logConfigSrc, err = config.NewLogConfigSrc(dsn, s.platform.GetConfigStore()) + logConfigSrc, err = config.NewLogConfigSrc(auditSettings.AdvancedLoggingConfig, s.platform.GetConfigStore()) if err != nil { return fmt.Errorf("invalid config source for audit, %w", err) } - mlog.Debug("Loaded audit configuration", mlog.String("source", dsn)) + mlog.Debug("Loaded audit configuration", mlog.String("source", string(auditSettings.AdvancedLoggingConfig))) } // ExperimentalAuditSettings provides basic file audit (E0, E10); logConfigSrc provides advanced config (E20). diff --git a/app/platform/config.go b/app/platform/config.go index ab6c5e44b8..397516f7dc 100644 --- a/app/platform/config.go +++ b/app/platform/config.go @@ -122,14 +122,14 @@ func (ps *PlatformService) ConfigureLogger(name string, logger *mlog.Logger, log // file is loaded. If no valid E20 license exists then advanced logging will be // shutdown once license is loaded/checked. var err error - dsn := *logSettings.AdvancedLoggingConfig var logConfigSrc config.LogConfigSrc - if dsn != "" { - logConfigSrc, err = config.NewLogConfigSrc(dsn, ps.configStore) + + if !config.IsEmptyDSN(logSettings.AdvancedLoggingConfig) { + logConfigSrc, err = config.NewLogConfigSrc(logSettings.AdvancedLoggingConfig, ps.configStore) if err != nil { return fmt.Errorf("invalid config source for %s, %w", name, err) } - ps.logger.Info("Loaded configuration for "+name, mlog.String("source", dsn)) + ps.logger.Info("Loaded configuration for "+name, mlog.String("source", string(logSettings.AdvancedLoggingConfig))) } cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, logConfigSrc, getPath) diff --git a/config/logconfigsrc.go b/config/logconfigsrc.go index fa082caaf0..5fe57c6915 100644 --- a/config/logconfigsrc.go +++ b/config/logconfigsrc.go @@ -4,9 +4,11 @@ package config import ( + "bytes" "encoding/json" "errors" "path/filepath" + "strconv" "strings" "sync" @@ -22,16 +24,23 @@ type LogConfigSrc interface { Get() mlog.LoggerConfiguration // Set updates the dsn specifying the source and reloads - Set(dsn string, configStore *Store) (err error) + Set(dsn []byte, configStore *Store) (err error) // Close cleans up resources. Close() error } +func IsEmptyDSN(dsn json.RawMessage) bool { + if len(dsn) == 0 || bytes.Equal(dsn, []byte("{}")) || bytes.Equal(dsn, []byte("\"\"")) { + return true + } + return false +} + // NewLogConfigSrc creates an advanced logging configuration source, backed by a -// file, JSON string, or database. -func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) { - if dsn == "" { +// file, JSON, or database. +func NewLogConfigSrc(dsn json.RawMessage, configStore *Store) (LogConfigSrc, error) { + if len(dsn) == 0 { return nil, errors.New("dsn should not be empty") } @@ -39,17 +48,28 @@ func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) { return nil, errors.New("configStore should not be nil") } - dsn = strings.TrimSpace(dsn) - + // check if embedded JSON if isJSONMap(dsn) { return newJSONSrc(dsn) } - path := dsn + // Now we're treating the DSN as a string which may contain escaped JSON or be a filespec. + str := strings.TrimSpace(string(dsn)) + if s, err := strconv.Unquote(str); err == nil { + str = s + } + + // check if escaped JSON + strBytes := []byte(str) + if isJSONMap(strBytes) { + return newJSONSrc(strBytes) + } + // If this is a file based config we need the full path so it can be watched. - if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(dsn) { + path := str + if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(path) { configPath := strings.TrimPrefix(configStore.String(), "file://") - path = filepath.Join(filepath.Dir(configPath), dsn) + path = filepath.Join(filepath.Dir(configPath), path) } return newFileSrc(path, configStore) @@ -63,7 +83,7 @@ type jsonSrc struct { cfg mlog.LoggerConfiguration } -func newJSONSrc(data string) (*jsonSrc, error) { +func newJSONSrc(data json.RawMessage) (*jsonSrc, error) { src := &jsonSrc{} return src, src.Set(data, nil) } @@ -76,8 +96,8 @@ func (src *jsonSrc) Get() mlog.LoggerConfiguration { } // Set updates the JSON specifying the source and reloads -func (src *jsonSrc) Set(data string, _ *Store) error { - cfg, err := logTargetCfgFromJSON([]byte(data)) +func (src *jsonSrc) Set(data []byte, _ *Store) error { + cfg, err := logTargetCfgFromJSON(data) if err != nil { return err } @@ -112,7 +132,7 @@ func newFileSrc(path string, configStore *Store) (*fileSrc, error) { src := &fileSrc{ path: path, } - if err := src.Set(path, configStore); err != nil { + if err := src.Set([]byte(path), configStore); err != nil { return nil, err } return src, nil @@ -128,8 +148,8 @@ func (src *fileSrc) Get() mlog.LoggerConfiguration { // Set updates the dsn specifying the file source and reloads. // The file will be watched for changes and reloaded as needed, // and all listeners notified. -func (src *fileSrc) Set(path string, configStore *Store) error { - data, err := configStore.GetFile(path) +func (src *fileSrc) Set(path []byte, configStore *Store) error { + data, err := configStore.GetFile(string(path)) if err != nil { return err } diff --git a/config/logconfigsrc_test.go b/config/logconfigsrc_test.go index 50e90ec4a7..6b79b14ab3 100644 --- a/config/logconfigsrc_test.go +++ b/config/logconfigsrc_test.go @@ -4,36 +4,41 @@ package config import ( + "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -const ( - validJSON = `{"file":{ "Type":"file"}}` - badJSON = `{"file":{ Type="file"}}` +var ( + validJSON = []byte(`{"file":{ "Type":"file"}}`) + badJSON = []byte(`{"file":{ Type="file"}}`) + validEscapedJSON = []byte(`"{\"file\":{ \"Type\":\"file\"}}"`) + badEscapedJSON = []byte(`"{\"file\":{ Type:\"file\"}}"`) ) func TestNewLogConfigSrc(t *testing.T) { store := NewTestMemoryStore() require.NotNil(t, store) - err := store.SetFile("advancedlogging.conf", []byte(validJSON)) + err := store.SetFile("advancedlogging.conf", validJSON) require.NoError(t, err) tests := []struct { name string - dsn string + dsn json.RawMessage configStore *Store wantErr bool wantType LogConfigSrc }{ - {name: "empty dsn", dsn: "", configStore: store, wantErr: true, wantType: nil}, - {name: "garbage dsn", dsn: "!@wfejwcevioj", configStore: store, wantErr: true, wantType: nil}, + {name: "empty dsn", dsn: []byte(""), configStore: store, wantErr: true, wantType: nil}, + {name: "garbage dsn", dsn: []byte("!@wfejwcevioj"), configStore: store, wantErr: true, wantType: nil}, {name: "valid json dsn", dsn: validJSON, configStore: store, wantErr: false, wantType: &jsonSrc{}}, {name: "invalid json dsn", dsn: badJSON, configStore: store, wantErr: true, wantType: nil}, - {name: "valid filespec dsn", dsn: "advancedlogging.conf", configStore: store, wantErr: false, wantType: &fileSrc{}}, - {name: "invalid filespec dsn", dsn: "/nobody/here.conf", configStore: store, wantErr: true, wantType: nil}, + {name: "valid escaped json dsn", dsn: validEscapedJSON, configStore: store, wantErr: false, wantType: &jsonSrc{}}, + {name: "invalid escaped json dsn", dsn: badEscapedJSON, configStore: store, wantErr: true, wantType: nil}, + {name: "valid filespec dsn", dsn: []byte("advancedlogging.conf"), configStore: store, wantErr: false, wantType: &fileSrc{}}, + {name: "invalid filespec dsn", dsn: []byte("/nobody/here.conf"), configStore: store, wantErr: true, wantType: nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/config/migrate.go b/config/migrate.go index ac4b4e597f..5aec95b3fd 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -33,8 +33,8 @@ func Migrate(from, to string) error { } // Only migrate advanced logging config if it is not embedded JSON. - if !isJSONMap(*sourceConfig.LogSettings.AdvancedLoggingConfig) { - files = append(files, *sourceConfig.LogSettings.AdvancedLoggingConfig) + if !isJSONMap(sourceConfig.LogSettings.AdvancedLoggingConfig) { + files = append(files, string(sourceConfig.LogSettings.AdvancedLoggingConfig)) } files = append(files, sourceConfig.PluginSettings.SignaturePublicKeyFiles...) diff --git a/config/utils.go b/config/utils.go index 9faa69e738..fa7359a31f 100644 --- a/config/utils.go +++ b/config/utils.go @@ -200,9 +200,10 @@ func stripPassword(dsn, schema string) string { return prefix + dsn[:i+1] + dsn[j:] } -func isJSONMap(data string) bool { +func isJSONMap(data []byte) bool { var m map[string]any - return json.Unmarshal([]byte(data), &m) == nil + err := json.Unmarshal(data, &m) + return err == nil } func GetValueByPath(path []string, obj any) (any, bool) { diff --git a/config/utils_test.go b/config/utils_test.go index 4788fac807..ad631e205e 100644 --- a/config/utils_test.go +++ b/config/utils_test.go @@ -276,7 +276,7 @@ func TestIsJSONMap(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isJSONMap(tt.data); got != tt.want { + if got := isJSONMap([]byte(tt.data)); got != tt.want { t.Errorf("isJSONMap() = %v, want %v", got, tt.want) } }) diff --git a/model/config.go b/model/config.go index b117548a67..4de3bc6694 100644 --- a/model/config.go +++ b/model/config.go @@ -1210,18 +1210,18 @@ func (s *SqlSettings) SetDefaults(isUpdate bool) { } type LogSettings struct { - EnableConsole *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - ConsoleLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - ConsoleJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableColor *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - EnableFile *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileLocation *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableWebhookDebugging *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableDiagnostics *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - EnableSentry *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableConsole *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableColor *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + EnableFile *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileLocation *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableWebhookDebugging *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableDiagnostics *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + EnableSentry *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + AdvancedLoggingConfig json.RawMessage `access:"environment_logging,write_restrictable,cloud_restrictable"` } func NewLogSettings() *LogSettings { @@ -1276,19 +1276,19 @@ func (s *LogSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = NewString("") + s.AdvancedLoggingConfig = []byte("{}") } } type ExperimentalAuditSettings struct { - FileEnabled *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileName *string `access:"experimental_features,write_restrictable,cloud_restrictable"` // telemetry: none - FileMaxSizeMB *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxAgeDays *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxBackups *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileCompress *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxQueueSize *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - AdvancedLoggingConfig *string `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileEnabled *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileName *string `access:"experimental_features,write_restrictable,cloud_restrictable"` // telemetry: none + FileMaxSizeMB *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxAgeDays *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxBackups *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileCompress *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxQueueSize *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig json.RawMessage `access:"experimental_features,write_restrictable,cloud_restrictable"` } func (s *ExperimentalAuditSettings) SetDefaults() { @@ -1321,20 +1321,20 @@ func (s *ExperimentalAuditSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = NewString("") + s.AdvancedLoggingConfig = []byte("{}") } } type NotificationLogSettings struct { - EnableConsole *bool `access:"write_restrictable,cloud_restrictable"` - ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"` - ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"` - EnableColor *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none - EnableFile *bool `access:"write_restrictable,cloud_restrictable"` - FileLevel *string `access:"write_restrictable,cloud_restrictable"` - FileJson *bool `access:"write_restrictable,cloud_restrictable"` - FileLocation *string `access:"write_restrictable,cloud_restrictable"` - AdvancedLoggingConfig *string `access:"write_restrictable,cloud_restrictable"` + EnableConsole *bool `access:"write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"` + EnableColor *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none + EnableFile *bool `access:"write_restrictable,cloud_restrictable"` + FileLevel *string `access:"write_restrictable,cloud_restrictable"` + FileJson *bool `access:"write_restrictable,cloud_restrictable"` + FileLocation *string `access:"write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig json.RawMessage `access:"write_restrictable,cloud_restrictable"` } func (s *NotificationLogSettings) SetDefaults() { @@ -1371,7 +1371,7 @@ func (s *NotificationLogSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = NewString("") + s.AdvancedLoggingConfig = []byte("{}") } } diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index fc58db9005..cf5e92ce13 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -503,7 +503,7 @@ func (ts *TelemetryService) trackConfig() { "file_json": cfg.LogSettings.FileJson, "enable_webhook_debugging": cfg.LogSettings.EnableWebhookDebugging, "isdefault_file_location": isDefault(cfg.LogSettings.FileLocation, ""), - "advanced_logging_config": *cfg.LogSettings.AdvancedLoggingConfig != "", + "advanced_logging_config": len(cfg.LogSettings.AdvancedLoggingConfig) != 0, }) ts.SendTelemetry(TrackConfigAudit, map[string]any{ @@ -513,7 +513,7 @@ func (ts *TelemetryService) trackConfig() { "file_max_backups": *cfg.ExperimentalAuditSettings.FileMaxBackups, "file_compress": *cfg.ExperimentalAuditSettings.FileCompress, "file_max_queue_size": *cfg.ExperimentalAuditSettings.FileMaxQueueSize, - "advanced_logging_config": *cfg.ExperimentalAuditSettings.AdvancedLoggingConfig != "", + "advanced_logging_config": len(cfg.ExperimentalAuditSettings.AdvancedLoggingConfig) != 0, }) ts.SendTelemetry(TrackConfigNotificationLog, map[string]any{ @@ -524,7 +524,7 @@ func (ts *TelemetryService) trackConfig() { "file_level": *cfg.NotificationLogSettings.FileLevel, "file_json": *cfg.NotificationLogSettings.FileJson, "isdefault_file_location": isDefault(*cfg.NotificationLogSettings.FileLocation, ""), - "advanced_logging_config": *cfg.NotificationLogSettings.AdvancedLoggingConfig != "", + "advanced_logging_config": len(cfg.NotificationLogSettings.AdvancedLoggingConfig) != 0, }) ts.SendTelemetry(TrackConfigPassword, map[string]any{ From 78251a3ff1986f389c98a1e1af7943f53913a418 Mon Sep 17 00:00:00 2001 From: Tim Scheuermann Date: Fri, 9 Sep 2022 16:20:56 +0200 Subject: [PATCH 02/21] Initialize the logger for imports (#20978) --- jobs/import_process/worker.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/jobs/import_process/worker.go b/jobs/import_process/worker.go index be09177006..5747d04b93 100644 --- a/jobs/import_process/worker.go +++ b/jobs/import_process/worker.go @@ -17,6 +17,7 @@ import ( "github.com/mattermost/mattermost-server/v6/model" "github.com/mattermost/mattermost-server/v6/services/configservice" "github.com/mattermost/mattermost-server/v6/shared/filestore" + "github.com/mattermost/mattermost-server/v6/shared/mlog" ) const jobName = "ImportProcess" @@ -28,10 +29,11 @@ type AppIface interface { FileSize(path string) (int64, *model.AppError) FileReader(path string) (filestore.ReadCloseSeeker, *model.AppError) BulkImportWithPath(c *request.Context, jsonlReader io.Reader, attachmentsReader *zip.Reader, dryRun bool, workers int, importPath string) (*model.AppError, int) + Log() *mlog.Logger } func MakeWorker(jobServer *jobs.JobServer, app AppIface) model.Worker { - appContext := request.EmptyContext(nil) + appContext := request.EmptyContext(app.Log()) isEnabled := func(cfg *model.Config) bool { return true } From 8b328386c59324106ed25fb8ea5b94fff6b47206 Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Fri, 9 Sep 2022 21:04:00 +0530 Subject: [PATCH 03/21] MM-46911: P1 - Fix MySQL query to filter bots out of TopDM (#20965) Automatic Merge --- api4/insights_test.go | 2 +- store/sqlstore/post_store.go | 22 +++++++++++----------- store/storetest/post_store.go | 24 ++++++++++++++++++++++++ 3 files changed, 36 insertions(+), 12 deletions(-) diff --git a/api4/insights_test.go b/api4/insights_test.go index b0d8993779..dd1637a4a1 100644 --- a/api4/insights_test.go +++ b/api4/insights_test.go @@ -897,7 +897,6 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { } func TestGetTopDMsForUserSince(t *testing.T) { - t.Skip("MM-46911") th := Setup(t).InitBasic() defer th.TearDown() @@ -933,6 +932,7 @@ func TestGetTopDMsForUserSince(t *testing.T) { Username: GenerateTestUsername(), DisplayName: "a bot", Description: "bot", + UserId: model.NewId(), } createdBot, resp, err := th.Client.CreateBot(bot) diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index a8c8faf04b..2f3fd2b6b9 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -2999,21 +2999,21 @@ func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts } func (s *SqlPostStore) GetTopDMsForUserSince(userID string, since int64, offset int, limit int) (*model.TopDMList, error) { - var botsFilterExpr, stringSplitKeyword string - if s.DriverName() == model.DatabaseDriverPostgres { - stringSplitKeyword = "split_part" - } else if s.DriverName() == model.DatabaseDriverMysql { - stringSplitKeyword = "SUBSTRING_INDEX" - } - + var botsFilterExpr string /* Channel.Name is of the format userId1__userId2. Using this, self dms, and bot dms can be filtered. */ - botsFilterExpr = fmt.Sprintf(` - %s(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots) - AND %s(Channels.Name, '__', 2) NOT IN (SELECT UserId FROM Bots) - `, stringSplitKeyword, stringSplitKeyword) + if s.DriverName() == model.DatabaseDriverPostgres { + botsFilterExpr = `SPLIT_PART(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots) + AND SPLIT_PART(Channels.Name, '__', 2) NOT IN (SELECT UserId FROM Bots) + ` + } else if s.DriverName() == model.DatabaseDriverMysql { + botsFilterExpr = `SUBSTRING_INDEX(Channels.Name, '__', 1) NOT IN (SELECT UserId FROM Bots) + AND SUBSTRING_INDEX(Channels.Name, '__', -1) NOT IN (SELECT UserId FROM Bots) + ` + } + channelSelector := s.getQueryBuilder().Select("Id", "TotalMsgCount").From("Channels").Join("ChannelMembers as cm on cm.ChannelId = Channels.Id"). Where(sq.And{ sq.Expr("Channels.Type = 'D'"), diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index b2214d83c8..41b8c138d3 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -4067,6 +4067,8 @@ func testGetTopDMsForUserSince(t *testing.T, ss store.Store, s SqlStore) { u2 := model.User{Email: MakeEmail(), Username: model.NewId()} u3 := model.User{Email: MakeEmail(), Username: model.NewId()} u4 := model.User{Email: MakeEmail(), Username: model.NewId()} + u5 := model.User{Email: MakeEmail(), Username: model.NewId()} + _, err := ss.User().Save(&user) require.NoError(t, err) _, err = ss.User().Save(&u1) @@ -4077,6 +4079,17 @@ func testGetTopDMsForUserSince(t *testing.T, ss store.Store, s SqlStore) { require.NoError(t, err) _, err = ss.User().Save(&u4) require.NoError(t, err) + _, err = ss.User().Save(&u5) + require.NoError(t, err) + bot := &model.Bot{ + Username: "bot_user", + Description: "bot", + OwnerId: model.NewId(), + UserId: u5.Id, + } + + savedBot, nErr := ss.Bot().Save(bot) + require.NoError(t, nErr) // user direct messages chUser1, nErr := ss.Channel().CreateDirectChannel(&u1, &user) require.NoError(t, nErr) @@ -4088,6 +4101,17 @@ func testGetTopDMsForUserSince(t *testing.T, ss store.Store, s SqlStore) { chUser3User4, nErr := ss.Channel().CreateDirectChannel(&u3, &u4) require.NoError(t, nErr) + // bot direct message - should be ignored by top DMs + botUser, err := ss.User().Get(context.Background(), savedBot.UserId) + require.NoError(t, err) + chBot, nErr := ss.Channel().CreateDirectChannel(&user, botUser) + require.NoError(t, nErr) + _, err = ss.Post().Save(&model.Post{ + ChannelId: chBot.Id, + UserId: botUser.Id, + }) + require.NoError(t, err) + // sample post data // for u1 _, err = ss.Post().Save(&model.Post{ From 8797bfcde76c686390cf6f66127c20392ab77bdd Mon Sep 17 00:00:00 2001 From: Agniva De Sarker Date: Fri, 9 Sep 2022 21:21:34 +0530 Subject: [PATCH 04/21] MM-46871: Add remining search parameters to be escaped (#20963) The first try wasn't exhaustive. I was planning to use https://github.com/mattermost/mattermost-server/blob/1f933263e76739e4e3f089b6cefbdbeb41dd29e4/store/sqlstore/post_store.go#L1738-L1748 for this but it also contained `@` which we don't want. In the end, I just used a separate slice for all the characters to be escaped. https://mattermost.atlassian.net/browse/MM-46871 ```release-note NONE ``` Co-authored-by: Mattermod --- store/sqlstore/user_store.go | 7 +++++-- store/storetest/user_store.go | 14 ++++++++++++++ 2 files changed, 19 insertions(+), 2 deletions(-) diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index b5b60c3d57..8a359ed870 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -1503,8 +1503,11 @@ func generateSearchQuery(query sq.SelectBuilder, terms []string, fields []string var dbSpecificTerm string if isPostgreSQL { - // Escaping the : in case of a Postgres search. - term = strings.ReplaceAll(term, ":", "\\:") + // Refer to https://www.postgresql.org/docs/current/functions-textsearch.html for the list of operators. + for _, c := range []string{":", "(", ")", "<", "!", "|"} { + // Escaping the special chars in case of a Postgres search. + term = strings.ReplaceAll(term, c, "\\"+c) + } } for _, field := range fields { diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index 14cde98222..cef2ce02e8 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -2807,6 +2807,20 @@ func testUserStoreSearch(t *testing.T, ss store.Store) { &model.UserSearchOptions{}, []*model.User{}, }, + { + "escape ( and )", + t1id, + "ji(bah)", + &model.UserSearchOptions{}, + []*model.User{}, + }, + { + "escape <", + t1id, + "ji(bah<", + &model.UserSearchOptions{}, + []*model.User{}, + }, { "wildcard search", t1id, From 1d7822d1548c331919fe7d1d328993ca950641e1 Mon Sep 17 00:00:00 2001 From: Doug Lauder Date: Fri, 9 Sep 2022 12:56:05 -0400 Subject: [PATCH 05/21] Revert "Allow inline JSON in config.json for advanced logging config (#20954)" (#20983) This reverts commit 5211d5de157a8b6ba1ac091f1836995bd136a5fc. --- app/audit.go | 8 ++--- app/platform/config.go | 8 ++--- config/logconfigsrc.go | 50 ++++++++------------------ config/logconfigsrc_test.go | 23 +++++------- config/migrate.go | 4 +-- config/utils.go | 5 ++- config/utils_test.go | 2 +- model/config.go | 64 ++++++++++++++++----------------- services/telemetry/telemetry.go | 6 ++-- 9 files changed, 72 insertions(+), 98 deletions(-) diff --git a/app/audit.go b/app/audit.go index 7dfc4308f5..e0e471c75e 100644 --- a/app/audit.go +++ b/app/audit.go @@ -109,14 +109,14 @@ func (s *Server) configureAudit(adt *audit.Audit, bAllowAdvancedLogging bool) er adt.OnError = s.onAuditError var logConfigSrc config.LogConfigSrc - auditSettings := s.platform.Config().ExperimentalAuditSettings - if bAllowAdvancedLogging && !config.IsEmptyDSN(auditSettings.AdvancedLoggingConfig) { + dsn := *s.platform.Config().ExperimentalAuditSettings.AdvancedLoggingConfig + if bAllowAdvancedLogging && dsn != "" { var err error - logConfigSrc, err = config.NewLogConfigSrc(auditSettings.AdvancedLoggingConfig, s.platform.GetConfigStore()) + logConfigSrc, err = config.NewLogConfigSrc(dsn, s.platform.GetConfigStore()) if err != nil { return fmt.Errorf("invalid config source for audit, %w", err) } - mlog.Debug("Loaded audit configuration", mlog.String("source", string(auditSettings.AdvancedLoggingConfig))) + mlog.Debug("Loaded audit configuration", mlog.String("source", dsn)) } // ExperimentalAuditSettings provides basic file audit (E0, E10); logConfigSrc provides advanced config (E20). diff --git a/app/platform/config.go b/app/platform/config.go index 397516f7dc..ab6c5e44b8 100644 --- a/app/platform/config.go +++ b/app/platform/config.go @@ -122,14 +122,14 @@ func (ps *PlatformService) ConfigureLogger(name string, logger *mlog.Logger, log // file is loaded. If no valid E20 license exists then advanced logging will be // shutdown once license is loaded/checked. var err error + dsn := *logSettings.AdvancedLoggingConfig var logConfigSrc config.LogConfigSrc - - if !config.IsEmptyDSN(logSettings.AdvancedLoggingConfig) { - logConfigSrc, err = config.NewLogConfigSrc(logSettings.AdvancedLoggingConfig, ps.configStore) + if dsn != "" { + logConfigSrc, err = config.NewLogConfigSrc(dsn, ps.configStore) if err != nil { return fmt.Errorf("invalid config source for %s, %w", name, err) } - ps.logger.Info("Loaded configuration for "+name, mlog.String("source", string(logSettings.AdvancedLoggingConfig))) + ps.logger.Info("Loaded configuration for "+name, mlog.String("source", dsn)) } cfg, err := config.MloggerConfigFromLoggerConfig(logSettings, logConfigSrc, getPath) diff --git a/config/logconfigsrc.go b/config/logconfigsrc.go index 5fe57c6915..fa082caaf0 100644 --- a/config/logconfigsrc.go +++ b/config/logconfigsrc.go @@ -4,11 +4,9 @@ package config import ( - "bytes" "encoding/json" "errors" "path/filepath" - "strconv" "strings" "sync" @@ -24,23 +22,16 @@ type LogConfigSrc interface { Get() mlog.LoggerConfiguration // Set updates the dsn specifying the source and reloads - Set(dsn []byte, configStore *Store) (err error) + Set(dsn string, configStore *Store) (err error) // Close cleans up resources. Close() error } -func IsEmptyDSN(dsn json.RawMessage) bool { - if len(dsn) == 0 || bytes.Equal(dsn, []byte("{}")) || bytes.Equal(dsn, []byte("\"\"")) { - return true - } - return false -} - // NewLogConfigSrc creates an advanced logging configuration source, backed by a -// file, JSON, or database. -func NewLogConfigSrc(dsn json.RawMessage, configStore *Store) (LogConfigSrc, error) { - if len(dsn) == 0 { +// file, JSON string, or database. +func NewLogConfigSrc(dsn string, configStore *Store) (LogConfigSrc, error) { + if dsn == "" { return nil, errors.New("dsn should not be empty") } @@ -48,28 +39,17 @@ func NewLogConfigSrc(dsn json.RawMessage, configStore *Store) (LogConfigSrc, err return nil, errors.New("configStore should not be nil") } - // check if embedded JSON + dsn = strings.TrimSpace(dsn) + if isJSONMap(dsn) { return newJSONSrc(dsn) } - // Now we're treating the DSN as a string which may contain escaped JSON or be a filespec. - str := strings.TrimSpace(string(dsn)) - if s, err := strconv.Unquote(str); err == nil { - str = s - } - - // check if escaped JSON - strBytes := []byte(str) - if isJSONMap(strBytes) { - return newJSONSrc(strBytes) - } - + path := dsn // If this is a file based config we need the full path so it can be watched. - path := str - if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(path) { + if strings.HasPrefix(configStore.String(), "file://") && !filepath.IsAbs(dsn) { configPath := strings.TrimPrefix(configStore.String(), "file://") - path = filepath.Join(filepath.Dir(configPath), path) + path = filepath.Join(filepath.Dir(configPath), dsn) } return newFileSrc(path, configStore) @@ -83,7 +63,7 @@ type jsonSrc struct { cfg mlog.LoggerConfiguration } -func newJSONSrc(data json.RawMessage) (*jsonSrc, error) { +func newJSONSrc(data string) (*jsonSrc, error) { src := &jsonSrc{} return src, src.Set(data, nil) } @@ -96,8 +76,8 @@ func (src *jsonSrc) Get() mlog.LoggerConfiguration { } // Set updates the JSON specifying the source and reloads -func (src *jsonSrc) Set(data []byte, _ *Store) error { - cfg, err := logTargetCfgFromJSON(data) +func (src *jsonSrc) Set(data string, _ *Store) error { + cfg, err := logTargetCfgFromJSON([]byte(data)) if err != nil { return err } @@ -132,7 +112,7 @@ func newFileSrc(path string, configStore *Store) (*fileSrc, error) { src := &fileSrc{ path: path, } - if err := src.Set([]byte(path), configStore); err != nil { + if err := src.Set(path, configStore); err != nil { return nil, err } return src, nil @@ -148,8 +128,8 @@ func (src *fileSrc) Get() mlog.LoggerConfiguration { // Set updates the dsn specifying the file source and reloads. // The file will be watched for changes and reloaded as needed, // and all listeners notified. -func (src *fileSrc) Set(path []byte, configStore *Store) error { - data, err := configStore.GetFile(string(path)) +func (src *fileSrc) Set(path string, configStore *Store) error { + data, err := configStore.GetFile(path) if err != nil { return err } diff --git a/config/logconfigsrc_test.go b/config/logconfigsrc_test.go index 6b79b14ab3..50e90ec4a7 100644 --- a/config/logconfigsrc_test.go +++ b/config/logconfigsrc_test.go @@ -4,41 +4,36 @@ package config import ( - "encoding/json" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) -var ( - validJSON = []byte(`{"file":{ "Type":"file"}}`) - badJSON = []byte(`{"file":{ Type="file"}}`) - validEscapedJSON = []byte(`"{\"file\":{ \"Type\":\"file\"}}"`) - badEscapedJSON = []byte(`"{\"file\":{ Type:\"file\"}}"`) +const ( + validJSON = `{"file":{ "Type":"file"}}` + badJSON = `{"file":{ Type="file"}}` ) func TestNewLogConfigSrc(t *testing.T) { store := NewTestMemoryStore() require.NotNil(t, store) - err := store.SetFile("advancedlogging.conf", validJSON) + err := store.SetFile("advancedlogging.conf", []byte(validJSON)) require.NoError(t, err) tests := []struct { name string - dsn json.RawMessage + dsn string configStore *Store wantErr bool wantType LogConfigSrc }{ - {name: "empty dsn", dsn: []byte(""), configStore: store, wantErr: true, wantType: nil}, - {name: "garbage dsn", dsn: []byte("!@wfejwcevioj"), configStore: store, wantErr: true, wantType: nil}, + {name: "empty dsn", dsn: "", configStore: store, wantErr: true, wantType: nil}, + {name: "garbage dsn", dsn: "!@wfejwcevioj", configStore: store, wantErr: true, wantType: nil}, {name: "valid json dsn", dsn: validJSON, configStore: store, wantErr: false, wantType: &jsonSrc{}}, {name: "invalid json dsn", dsn: badJSON, configStore: store, wantErr: true, wantType: nil}, - {name: "valid escaped json dsn", dsn: validEscapedJSON, configStore: store, wantErr: false, wantType: &jsonSrc{}}, - {name: "invalid escaped json dsn", dsn: badEscapedJSON, configStore: store, wantErr: true, wantType: nil}, - {name: "valid filespec dsn", dsn: []byte("advancedlogging.conf"), configStore: store, wantErr: false, wantType: &fileSrc{}}, - {name: "invalid filespec dsn", dsn: []byte("/nobody/here.conf"), configStore: store, wantErr: true, wantType: nil}, + {name: "valid filespec dsn", dsn: "advancedlogging.conf", configStore: store, wantErr: false, wantType: &fileSrc{}}, + {name: "invalid filespec dsn", dsn: "/nobody/here.conf", configStore: store, wantErr: true, wantType: nil}, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/config/migrate.go b/config/migrate.go index 5aec95b3fd..ac4b4e597f 100644 --- a/config/migrate.go +++ b/config/migrate.go @@ -33,8 +33,8 @@ func Migrate(from, to string) error { } // Only migrate advanced logging config if it is not embedded JSON. - if !isJSONMap(sourceConfig.LogSettings.AdvancedLoggingConfig) { - files = append(files, string(sourceConfig.LogSettings.AdvancedLoggingConfig)) + if !isJSONMap(*sourceConfig.LogSettings.AdvancedLoggingConfig) { + files = append(files, *sourceConfig.LogSettings.AdvancedLoggingConfig) } files = append(files, sourceConfig.PluginSettings.SignaturePublicKeyFiles...) diff --git a/config/utils.go b/config/utils.go index fa7359a31f..9faa69e738 100644 --- a/config/utils.go +++ b/config/utils.go @@ -200,10 +200,9 @@ func stripPassword(dsn, schema string) string { return prefix + dsn[:i+1] + dsn[j:] } -func isJSONMap(data []byte) bool { +func isJSONMap(data string) bool { var m map[string]any - err := json.Unmarshal(data, &m) - return err == nil + return json.Unmarshal([]byte(data), &m) == nil } func GetValueByPath(path []string, obj any) (any, bool) { diff --git a/config/utils_test.go b/config/utils_test.go index ad631e205e..4788fac807 100644 --- a/config/utils_test.go +++ b/config/utils_test.go @@ -276,7 +276,7 @@ func TestIsJSONMap(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := isJSONMap([]byte(tt.data)); got != tt.want { + if got := isJSONMap(tt.data); got != tt.want { t.Errorf("isJSONMap() = %v, want %v", got, tt.want) } }) diff --git a/model/config.go b/model/config.go index 4de3bc6694..b117548a67 100644 --- a/model/config.go +++ b/model/config.go @@ -1210,18 +1210,18 @@ func (s *SqlSettings) SetDefaults(isUpdate bool) { } type LogSettings struct { - EnableConsole *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - ConsoleLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - ConsoleJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableColor *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - EnableFile *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - FileLocation *string `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableWebhookDebugging *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` - EnableDiagnostics *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - EnableSentry *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none - AdvancedLoggingConfig json.RawMessage `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableConsole *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableColor *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + EnableFile *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileLevel *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileJson *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + FileLocation *string `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableWebhookDebugging *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` + EnableDiagnostics *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + EnableSentry *bool `access:"environment_logging,write_restrictable,cloud_restrictable"` // telemetry: none + AdvancedLoggingConfig *string `access:"environment_logging,write_restrictable,cloud_restrictable"` } func NewLogSettings() *LogSettings { @@ -1276,19 +1276,19 @@ func (s *LogSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = []byte("{}") + s.AdvancedLoggingConfig = NewString("") } } type ExperimentalAuditSettings struct { - FileEnabled *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileName *string `access:"experimental_features,write_restrictable,cloud_restrictable"` // telemetry: none - FileMaxSizeMB *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxAgeDays *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxBackups *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileCompress *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` - FileMaxQueueSize *int `access:"experimental_features,write_restrictable,cloud_restrictable"` - AdvancedLoggingConfig json.RawMessage `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileEnabled *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileName *string `access:"experimental_features,write_restrictable,cloud_restrictable"` // telemetry: none + FileMaxSizeMB *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxAgeDays *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxBackups *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileCompress *bool `access:"experimental_features,write_restrictable,cloud_restrictable"` + FileMaxQueueSize *int `access:"experimental_features,write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig *string `access:"experimental_features,write_restrictable,cloud_restrictable"` } func (s *ExperimentalAuditSettings) SetDefaults() { @@ -1321,20 +1321,20 @@ func (s *ExperimentalAuditSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = []byte("{}") + s.AdvancedLoggingConfig = NewString("") } } type NotificationLogSettings struct { - EnableConsole *bool `access:"write_restrictable,cloud_restrictable"` - ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"` - ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"` - EnableColor *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none - EnableFile *bool `access:"write_restrictable,cloud_restrictable"` - FileLevel *string `access:"write_restrictable,cloud_restrictable"` - FileJson *bool `access:"write_restrictable,cloud_restrictable"` - FileLocation *string `access:"write_restrictable,cloud_restrictable"` - AdvancedLoggingConfig json.RawMessage `access:"write_restrictable,cloud_restrictable"` + EnableConsole *bool `access:"write_restrictable,cloud_restrictable"` + ConsoleLevel *string `access:"write_restrictable,cloud_restrictable"` + ConsoleJson *bool `access:"write_restrictable,cloud_restrictable"` + EnableColor *bool `access:"write_restrictable,cloud_restrictable"` // telemetry: none + EnableFile *bool `access:"write_restrictable,cloud_restrictable"` + FileLevel *string `access:"write_restrictable,cloud_restrictable"` + FileJson *bool `access:"write_restrictable,cloud_restrictable"` + FileLocation *string `access:"write_restrictable,cloud_restrictable"` + AdvancedLoggingConfig *string `access:"write_restrictable,cloud_restrictable"` } func (s *NotificationLogSettings) SetDefaults() { @@ -1371,7 +1371,7 @@ func (s *NotificationLogSettings) SetDefaults() { } if s.AdvancedLoggingConfig == nil { - s.AdvancedLoggingConfig = []byte("{}") + s.AdvancedLoggingConfig = NewString("") } } diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index cf5e92ce13..fc58db9005 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -503,7 +503,7 @@ func (ts *TelemetryService) trackConfig() { "file_json": cfg.LogSettings.FileJson, "enable_webhook_debugging": cfg.LogSettings.EnableWebhookDebugging, "isdefault_file_location": isDefault(cfg.LogSettings.FileLocation, ""), - "advanced_logging_config": len(cfg.LogSettings.AdvancedLoggingConfig) != 0, + "advanced_logging_config": *cfg.LogSettings.AdvancedLoggingConfig != "", }) ts.SendTelemetry(TrackConfigAudit, map[string]any{ @@ -513,7 +513,7 @@ func (ts *TelemetryService) trackConfig() { "file_max_backups": *cfg.ExperimentalAuditSettings.FileMaxBackups, "file_compress": *cfg.ExperimentalAuditSettings.FileCompress, "file_max_queue_size": *cfg.ExperimentalAuditSettings.FileMaxQueueSize, - "advanced_logging_config": len(cfg.ExperimentalAuditSettings.AdvancedLoggingConfig) != 0, + "advanced_logging_config": *cfg.ExperimentalAuditSettings.AdvancedLoggingConfig != "", }) ts.SendTelemetry(TrackConfigNotificationLog, map[string]any{ @@ -524,7 +524,7 @@ func (ts *TelemetryService) trackConfig() { "file_level": *cfg.NotificationLogSettings.FileLevel, "file_json": *cfg.NotificationLogSettings.FileJson, "isdefault_file_location": isDefault(*cfg.NotificationLogSettings.FileLocation, ""), - "advanced_logging_config": len(cfg.NotificationLogSettings.AdvancedLoggingConfig) != 0, + "advanced_logging_config": *cfg.NotificationLogSettings.AdvancedLoggingConfig != "", }) ts.SendTelemetry(TrackConfigPassword, map[string]any{ From 42e03e18b990fe66f49cd2286d5fb6bb3c7f44ee Mon Sep 17 00:00:00 2001 From: Cass C Date: Fri, 9 Sep 2022 13:00:10 -0400 Subject: [PATCH 06/21] Prepackage Playbooks v1.32.2 (#20979) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 0fe01db270..a95dd79fdd 100644 --- a/Makefile +++ b/Makefile @@ -154,7 +154,7 @@ PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-custom-attributes-v1.3.0 PLUGIN_PACKAGES += mattermost-plugin-github-v2.0.1 PLUGIN_PACKAGES += mattermost-plugin-gitlab-v1.3.0 -PLUGIN_PACKAGES += mattermost-plugin-playbooks-v1.32.1 +PLUGIN_PACKAGES += mattermost-plugin-playbooks-v1.32.2 PLUGIN_PACKAGES += mattermost-plugin-jenkins-v1.1.0 PLUGIN_PACKAGES += mattermost-plugin-jira-v2.4.0 PLUGIN_PACKAGES += mattermost-plugin-nps-v1.2.0 From 38aaa9e3d33786dd75f15955d483b3b918ddac72 Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Fri, 9 Sep 2022 23:04:00 +0530 Subject: [PATCH 07/21] [MM-46603] P1 - Improvements in handling '0 posts' channels (#20905) Automatic Merge --- api4/insights_test.go | 64 ++++++++++++++++++++----- app/channel_test.go | 82 +++++++++++++++++++------------- app/helper_test.go | 10 +++- model/channel.go | 5 +- store/sqlstore/channel_store.go | 60 +++++++++++------------ store/storetest/channel_store.go | 11 +++-- 6 files changed, 147 insertions(+), 85 deletions(-) diff --git a/api4/insights_test.go b/api4/insights_test.go index dd1637a4a1..1eca69c72c 100644 --- a/api4/insights_test.go +++ b/api4/insights_test.go @@ -821,25 +821,68 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - // delete offtopic channel - which interferes with 'least' active channel results + // delete offtopic, town-square, th.basicchannel channel - which interferes with 'least' active channel results offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) require.Nil(t, appErr, "Expected nil, didn't receive nil") appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) require.Nil(t, appErr) + townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) + require.Nil(t, appErr) + basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) + require.Nil(t, appErr) + basicChannel2, appErr := th.App.GetChannel(th.Context, th.BasicChannel2.Id) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel2) + require.Nil(t, appErr) + basicPrivateChannel, appErr := th.App.GetChannel(th.Context, th.BasicPrivateChannel.Id) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, basicPrivateChannel) + require.Nil(t, appErr) th.App.Srv().SetLicense(model.NewTestLicenseSKU(model.LicenseShortSkuProfessional)) client := th.Client userId := th.BasicUser.Id - channel4 := th.CreatePublicChannel() - channel5 := th.CreatePrivateChannel() - channel6 := th.CreatePrivateChannel() + channel4Req := &model.Channel{ + DisplayName: "channel4", + Name: GenerateTestChannelName(), + Type: model.ChannelTypeOpen, + TeamId: th.BasicTeam.Id, + CreateAt: 1, + } + channel4, _, err := client.CreateChannel(channel4Req) + require.NoError(t, err) + + channel5Req := &model.Channel{ + DisplayName: "channel4", + Name: GenerateTestChannelName(), + Type: model.ChannelTypePrivate, + TeamId: th.BasicTeam.Id, + CreateAt: 1, + } + channel5, _, err := client.CreateChannel(channel5Req) + require.NoError(t, err) + + channel6Req := &model.Channel{ + DisplayName: "channel4", + Name: GenerateTestChannelName(), + Type: model.ChannelTypePrivate, + TeamId: th.BasicTeam.Id, + CreateAt: 1, + } + channel6, _, err := client.CreateChannel(channel6Req) + require.NoError(t, err) + th.App.AddUserToChannel(th.Context, th.BasicUser, channel4, false) th.App.AddUserToChannel(th.Context, th.BasicUser, channel5, false) th.App.AddUserToChannel(th.Context, th.BasicUser, channel6, false) - channelIDs := [6]string{th.BasicChannel.Id, th.BasicChannel2.Id, th.BasicPrivateChannel.Id, channel4.Id, channel5.Id, channel6.Id} + channelIDs := [3]string{channel4.Id, channel5.Id, channel6.Id} i := len(channelIDs) for _, channelID := range channelIDs { @@ -859,22 +902,19 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { ID: channel6.Id, MessageCount: 1}, {ID: channel5.Id, MessageCount: 2}, {ID: channel4.Id, MessageCount: 3}, - {ID: th.BasicPrivateChannel.Id, MessageCount: 4}, - {ID: th.BasicChannel2.Id, MessageCount: 5}, - {ID: th.BasicChannel.Id, MessageCount: 7}, } t.Run("get-top-inactive-channels-for-team-since", func(t *testing.T) { - topInactiveChannels, _, err := client.GetTopInactiveChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 5) + topInactiveChannels, _, err := client.GetTopInactiveChannelsForTeamSince(teamId, model.TimeRangeToday, 0, 2) require.NoError(t, err) for i, channel := range topInactiveChannels.Items { assert.Equal(t, expectedTopChannels[i].ID, channel.ID) } - topInactiveChannels, _, err = client.GetTopInactiveChannelsForTeamSince(teamId, model.TimeRangeToday, 1, 5) + topInactiveChannels, _, err = client.GetTopInactiveChannelsForTeamSince(teamId, model.TimeRangeToday, 1, 2) require.NoError(t, err) - assert.Equal(t, th.BasicChannel.Id, topInactiveChannels.Items[0].ID) + assert.Equal(t, channel4.Id, topInactiveChannels.Items[0].ID) }) t.Run("get-top-channels-for-user-since exclude channels user is not member of", func(t *testing.T) { @@ -887,7 +927,7 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { th.RemoveUserFromChannel(th.BasicUser, excludedChannel) - topInactiveChannels, _, err := client.GetTopInactiveChannelsForUserSince(teamId, model.TimeRangeToday, 0, 5) + topInactiveChannels, _, err := client.GetTopInactiveChannelsForUserSince(teamId, model.TimeRangeToday, 0, 3) require.NoError(t, err) for i, channel := range topInactiveChannels.Items { diff --git a/app/channel_test.go b/app/channel_test.go index c4525ae706..93ce63dfa9 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -2685,13 +2685,30 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - channel2 := th.CreateChannel(th.Context, th.BasicTeam) + channel2 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel3 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel5 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + th.AddUserToChannel(th.BasicUser, channel2) + th.AddUserToChannel(th.BasicUser, channel3) + th.AddUserToChannel(th.BasicUser, channel4) + th.AddUserToChannel(th.BasicUser, channel5) + th.AddUserToChannel(th.BasicUser, channel6) - // delete offtopic channel - which interferes with 'least' active channel results + // delete offtopic, town square, basicChannel channel - which interferes with 'least' active channel results offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) require.Nil(t, appErr, "Expected nil, didn't receive nil") appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) require.Nil(t, appErr) + townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) + require.Nil(t, appErr) + basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) + require.Nil(t, appErr) // add a bot post to ensure it's counted _, err := th.Server.Store.Post().Save(&model.Post{ @@ -2704,8 +2721,6 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { }) require.NoError(t, err) - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - // add a webhook post to ensure it's counted _, err = th.Server.Store.Post().Save(&model.Post{ Message: "hello from a webhook", @@ -2717,16 +2732,7 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { }) require.NoError(t, err) - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel5 := th.CreateChannel(th.Context, th.BasicTeam) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - th.AddUserToChannel(th.BasicUser, channel2) - th.AddUserToChannel(th.BasicUser, channel3) - th.AddUserToChannel(th.BasicUser, channel4) - th.AddUserToChannel(th.BasicUser, channel5) - th.AddUserToChannel(th.BasicUser, channel6) - - channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6} + channels := [5]*model.Channel{channel2, channel3, channel4, channel5, channel6} i := len(channels) for _, channel := range channels { @@ -2745,13 +2751,12 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { {ID: channel4.Id, MessageCount: 3}, {ID: channel3.Id, MessageCount: 5}, {ID: channel2.Id, MessageCount: 6}, - {ID: th.BasicChannel.Id, MessageCount: 7}, } timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-team-since", func(t *testing.T) { - topChannels, err := th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 6}) + topChannels, err := th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) require.Nil(t, err) for i, channel := range topChannels.Items { @@ -2759,10 +2764,16 @@ func TestGetTopInactiveChannelsForTeamSince(t *testing.T) { assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) } - topChannels, err = th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) + topChannels, err = th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 4}) require.Nil(t, err) - assert.Equal(t, th.BasicChannel.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(7), topChannels.Items[0].MessageCount) + assert.Equal(t, channel2.Id, topChannels.Items[0].ID) + assert.Equal(t, int64(6), topChannels.Items[0].MessageCount) + + // it simulates channel being created recently + _ = th.CreatePrivateChannel(th.Context, th.BasicTeam) + topChannels, err = th.App.GetTopInactiveChannelsForTeamSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 6}) + require.Nil(t, err) + assert.Equal(t, 5, len(topChannels.Items)) }) } @@ -2770,13 +2781,21 @@ func TestGetTopInactiveChannelsForUserSince(t *testing.T) { th := Setup(t).InitBasic() defer th.TearDown() - // delete offtopic channel - which interferes with 'least' active channel results + // delete offtopic, town-square, th.basicchannel channels - which interferes with 'least' active channel results offTopicChannel, appErr := th.App.GetChannelByName(th.Context, "off-topic", th.BasicTeam.Id, false) require.Nil(t, appErr, "Expected nil, didn't receive nil") appErr = th.App.PermanentDeleteChannel(th.Context, offTopicChannel) require.Nil(t, appErr) + townSquareChannel, appErr := th.App.GetChannelByName(th.Context, "town-square", th.BasicTeam.Id, false) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, townSquareChannel) + require.Nil(t, appErr) + basicChannel, appErr := th.App.GetChannel(th.Context, th.BasicChannel.Id) + require.Nil(t, appErr, "Expected nil, didn't receive nil") + appErr = th.App.PermanentDeleteChannel(th.Context, basicChannel) + require.Nil(t, appErr) - channel2 := th.CreateChannel(th.Context, th.BasicTeam) + channel2 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) // add a bot post to ensure it's counted _, err := th.Server.Store.Post().Save(&model.Post{ @@ -2789,7 +2808,7 @@ func TestGetTopInactiveChannelsForUserSince(t *testing.T) { }) require.NoError(t, err) - channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam) + channel3 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) // add a webhook post to ensure it's counted _, err = th.Server.Store.Post().Save(&model.Post{ @@ -2802,16 +2821,16 @@ func TestGetTopInactiveChannelsForUserSince(t *testing.T) { }) require.NoError(t, err) - channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam) - channel5 := th.CreateChannel(th.Context, th.BasicTeam) - channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam) + channel4 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel5 := th.CreateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) + channel6 := th.CreatePrivateChannel(th.Context, th.BasicTeam, WithCreateAt(1)) th.AddUserToChannel(th.BasicUser, channel2) th.AddUserToChannel(th.BasicUser, channel3) th.AddUserToChannel(th.BasicUser, channel4) th.AddUserToChannel(th.BasicUser, channel5) th.AddUserToChannel(th.BasicUser, channel6) - channels := [6]*model.Channel{th.BasicChannel, channel2, channel3, channel4, channel5, channel6} + channels := [5]*model.Channel{channel2, channel3, channel4, channel5, channel6} i := len(channels) for _, channel := range channels { @@ -2830,24 +2849,23 @@ func TestGetTopInactiveChannelsForUserSince(t *testing.T) { {ID: channel4.Id, MessageCount: 3}, {ID: channel3.Id, MessageCount: 5}, {ID: channel2.Id, MessageCount: 6}, - {ID: th.BasicChannel.Id, MessageCount: 7}, } timeRange := model.StartOfDayForTimeRange(model.TimeRangeToday, time.Now().Location()) t.Run("get-top-channels-for-user-since", func(t *testing.T) { - topChannels, err := th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 5}) + topChannels, err := th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 0, PerPage: 4}) require.Nil(t, err) - require.Equal(t, len(topChannels.Items), 5) + require.Equal(t, len(topChannels.Items), 4) for i, channel := range topChannels.Items { assert.Equal(t, expectedTopChannels[i].ID, channel.ID) assert.Equal(t, expectedTopChannels[i].MessageCount, channel.MessageCount) } - topChannels, err = th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 5}) + topChannels, err = th.App.GetTopInactiveChannelsForUserSince(th.Context, th.BasicChannel.TeamId, th.BasicUser.Id, &model.InsightsOpts{StartUnixMilli: timeRange.UnixMilli(), Page: 1, PerPage: 4}) require.Nil(t, err) require.Equal(t, len(topChannels.Items), 1) - assert.Equal(t, th.BasicChannel.Id, topChannels.Items[0].ID) - assert.Equal(t, int64(7), topChannels.Items[0].MessageCount) + assert.Equal(t, channel2.Id, topChannels.Items[0].ID) + assert.Equal(t, int64(6), topChannels.Items[0].MessageCount) }) } diff --git a/app/helper_test.go b/app/helper_test.go index 5b15d3f519..7de517ea58 100644 --- a/app/helper_test.go +++ b/app/helper_test.go @@ -327,12 +327,18 @@ func WithShared(v bool) ChannelOption { } } +func WithCreateAt(v int64) ChannelOption { + return func(channel *model.Channel) { + channel.CreateAt = *model.NewInt64(v) + } +} + func (th *TestHelper) CreateChannel(c request.CTX, team *model.Team, options ...ChannelOption) *model.Channel { return th.createChannel(c, team, model.ChannelTypeOpen, options...) } -func (th *TestHelper) CreatePrivateChannel(c request.CTX, team *model.Team) *model.Channel { - return th.createChannel(c, team, model.ChannelTypePrivate) +func (th *TestHelper) CreatePrivateChannel(c request.CTX, team *model.Team, options ...ChannelOption) *model.Channel { + return th.createChannel(c, team, model.ChannelTypePrivate, options...) } func (th *TestHelper) createChannel(c request.CTX, team *model.Team, channelType model.ChannelType, options ...ChannelOption) *model.Channel { diff --git a/model/channel.go b/model/channel.go index e66c30faef..e900011cc9 100644 --- a/model/channel.go +++ b/model/channel.go @@ -296,8 +296,9 @@ func (o *Channel) PreSave() { o.Name = SanitizeUnicode(o.Name) o.DisplayName = SanitizeUnicode(o.DisplayName) - - o.CreateAt = GetMillis() + if o.CreateAt == 0 { + o.CreateAt = GetMillis() + } o.UpdateAt = o.CreateAt o.ExtraUpdateAt = 0 } diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index e772f2706d..19686d967d 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -4337,48 +4337,45 @@ func (s SqlChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userI LastActivityAt FROM ((SELECT - Posts.ChannelId AS ID, + PublicChannels.Id AS ID, 'O' AS Type, PublicChannels.DisplayName AS DisplayName, PublicChannels.Name AS Name, - count(Posts.Id) AS MessageCount, - max(Posts.CreateAt) AS LastActivityAt + COALESCE(count(Posts.Id), 0) AS MessageCount, + COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt FROM - Posts - LEFT JOIN PublicChannels on Posts.ChannelId = PublicChannels.Id + PublicChannels + LEFT JOIN Posts on Posts.ChannelId = PublicChannels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 + LEFT JOIN Channels on Channels.Id = PublicChannels.Id WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND (Posts.Type = '' OR Posts.Type = 'system_join_channel') - AND PublicChannels.TeamId = ? + PublicChannels.TeamId = ? AND PublicChannels.DeleteAt = 0 + AND Channels.CreateAt < ? GROUP BY - Posts.ChannelId, + PublicChannels.Id, PublicChannels.DisplayName, PublicChannels.Name, PublicChannels.TeamId) UNION ALL (SELECT - Posts.ChannelId AS ID, + Channels.Id AS ID, Channels.Type AS Type, Channels.DisplayName AS DisplayName, Channels.Name AS Name, - count(Posts.Id) AS MessageCount, - max(Posts.CreateAt) AS LastActivityAt + COALESCE(count(Posts.Id), 0) AS MessageCount, + COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt FROM - Posts - LEFT JOIN Channels on Posts.ChannelId = Channels.Id + Channels + LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND (Posts.Type = '' OR Posts.Type = 'system_join_channel') - AND Channels.TeamId = ? + Channels.TeamId = ? + AND Channels.CreateAt < ? AND Channels.Type = 'P' AND Channels.DeleteAt = 0 AND ChannelMembers.UserId = ? GROUP BY - Posts.ChannelId, + Channels.Id, Channels.Type, Channels.DisplayName, Channels.Name)) AS A @@ -4387,8 +4384,7 @@ func (s SqlChannelStore) GetTopInactiveChannelsForTeamSince(teamID string, userI Name ASC LIMIT ? OFFSET ?` - args = append(args, since, teamID, since, teamID, userID, limit+1, offset) - + args = append(args, since, teamID, since, since, teamID, since, userID, limit+1, offset) if err := s.GetReplicaX().Select(&channels, query, args...); err != nil { return nil, errors.Wrap(err, "failed to get top Channels") } @@ -4411,25 +4407,23 @@ func (s SqlChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userI query = ` SELECT - Posts.ChannelId AS ID, + Channels.Id AS ID, Channels.Type AS Type, Channels.DisplayName AS DisplayName, Channels.Name AS Name, - count(Posts.Id) AS MessageCount, - max(Posts.CreateAt) AS LastActivityAt + COALESCE(count(Posts.Id), 0) AS MessageCount, + COALESCE(max(Posts.CreateAt), 0) AS LastActivityAt FROM - Posts - LEFT JOIN Channels on Posts.ChannelId = Channels.Id + Channels + LEFT JOIN Posts on Posts.ChannelId = Channels.Id AND Posts.Type = '' AND Posts.CreateAt > ? AND Posts.DeleteAt = 0 LEFT JOIN ChannelMembers on Posts.ChannelId = ChannelMembers.ChannelId WHERE - Posts.DeleteAt = 0 - AND Posts.CreateAt > ? - AND (Posts.Type = '' OR Posts.Type = 'system_join_channel') - AND Channels.DeleteAt = 0 + Channels.DeleteAt = 0 + AND Channels.CreateAt < ? AND (Channels.Type = 'O' OR Channels.Type = 'P') AND ChannelMembers.UserId = ? ` - args = []any{since, userID} + args = []any{since, since, userID} if teamID != "" { query += ` @@ -4439,7 +4433,7 @@ func (s SqlChannelStore) GetTopInactiveChannelsForUserSince(teamID string, userI query += ` Group By - Posts.ChannelId, + Channels.Id, Channels.Type, Channels.DisplayName, Channels.Name diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index 913ac7e08a..7b2757ad14 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -7978,6 +7978,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { DisplayName: "test_share_flag asdf", Name: "test_share_flag_public0", Type: model.ChannelTypeOpen, + CreateAt: 1, } channelSaved0, err := ss.Channel().Save(channelPublic0, 999) @@ -7989,6 +7990,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { DisplayName: "test_share_flag", Name: "test_share_flag", Type: model.ChannelTypeOpen, + CreateAt: 1, } channelSaved1, err := ss.Channel().Save(channelPublic1, 999) @@ -8001,6 +8003,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { c3.DisplayName = "Channel3" + model.NewId() c3.Name = NewTestId() c3.Type = model.ChannelTypePrivate + c3.CreateAt = 1 channelPrivate, nErr := ss.Channel().Save(&c3, -1) require.NoError(t, nErr) @@ -8080,7 +8083,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { // for u1 t.Run("top inactive channels for team - u1 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u1.Id, 0, 0, 10) + topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u1.Id, 2, 0, 10) require.NoError(t, err) require.Len(t, topInactiveChannels.Items, 3) require.Equal(t, topInactiveChannels.Items[0].ID, channelSaved0.Id) @@ -8096,7 +8099,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { }) t.Run("top inactive channels for user - u1 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u1.Id, 0, 0, 10) + topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u1.Id, 2, 0, 10) require.NoError(t, err) require.Len(t, topInactiveChannels.Items, 2) require.Equal(t, topInactiveChannels.Items[0].ID, channelPrivate.Id) @@ -8105,7 +8108,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { // for u2 t.Run("top inactive channels for team - u2 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u2.Id, 0, 0, 10) + topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForTeamSince(team.Id, u2.Id, 2, 0, 10) require.NoError(t, err) require.Len(t, topInactiveChannels.Items, 2) require.Equal(t, topInactiveChannels.Items[0].ID, channelSaved0.Id) @@ -8114,7 +8117,7 @@ func testGetTopInactiveChannels(t *testing.T, ss store.Store) { }) t.Run("top inactive channels for user - u2 ", func(t *testing.T) { - topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u2.Id, 0, 0, 10) + topInactiveChannels, err := ss.Channel().GetTopInactiveChannelsForUserSince(team.Id, u2.Id, 2, 0, 10) require.NoError(t, err) require.Len(t, topInactiveChannels.Items, 1) require.Equal(t, topInactiveChannels.Items[0].ID, channelPublic0.Id) From 93174fbe56f3d3dd16b97c84656c4a46305f31cd Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Sun, 11 Sep 2022 20:45:20 +0300 Subject: [PATCH 08/21] MM-46408: adds PostPriority feature flag and admin setting (#20875) * MM-46408: adds PostPriority feature flag Adds the PostPriority feature flag. You can enable it by setting the env variable `MM_FEATUREFLAGS_POSTPRIORITY=true`. * Adds support for admin setting * empty * Uses post priority in telemetry Co-authored-by: Mattermod --- config/client.go | 1 + model/config.go | 5 +++++ model/feature_flags.go | 3 +++ services/telemetry/telemetry.go | 1 + 4 files changed, 10 insertions(+) diff --git a/config/client.go b/config/client.go index c8e9eedc52..7315ab6898 100644 --- a/config/client.go +++ b/config/client.go @@ -131,6 +131,7 @@ func GenerateClientConfig(c *model.Config, telemetryID string, license *model.Li props["CollapsedThreads"] = *c.ServiceSettings.CollapsedThreads props["EnableCustomGroups"] = "false" props["InsightsEnabled"] = "false" + props["PostPriority"] = strconv.FormatBool(*c.ServiceSettings.PostPriority) if license != nil { props["ExperimentalEnableAuthenticationTransfer"] = strconv.FormatBool(*c.ServiceSettings.ExperimentalEnableAuthenticationTransfer) diff --git a/model/config.go b/model/config.go index b117548a67..967cff1dc8 100644 --- a/model/config.go +++ b/model/config.go @@ -371,6 +371,7 @@ type ServiceSettings struct { EnableSVGs *bool `access:"site_posts"` EnableLatex *bool `access:"site_posts"` EnableInlineLatex *bool `access:"site_posts"` + PostPriority *bool `access:"site_posts"` EnableAPIChannelDeletion *bool EnableLocalMode *bool LocalModeSocketLocation *string // telemetry: none @@ -842,6 +843,10 @@ func (s *ServiceSettings) SetDefaults(isUpdate bool) { if s.EnableCustomGroups == nil { s.EnableCustomGroups = NewBool(true) } + + if s.PostPriority == nil { + s.PostPriority = NewBool(false) + } } type ClusterSettings struct { diff --git a/model/feature_flags.go b/model/feature_flags.go index 257b6e1a6d..209e7a9f99 100644 --- a/model/feature_flags.go +++ b/model/feature_flags.go @@ -73,6 +73,8 @@ type FeatureFlags struct { BoardsProduct bool PlanUpgradeButtonText string + + PostPriority bool } func (f *FeatureFlags) SetDefaults() { @@ -100,6 +102,7 @@ func (f *FeatureFlags) SetDefaults() { f.CallsEnabled = true f.BoardsProduct = false f.PlanUpgradeButtonText = "upgrade" + f.PostPriority = false } func (f *FeatureFlags) Plugins() map[string]string { diff --git a/services/telemetry/telemetry.go b/services/telemetry/telemetry.go index fc58db9005..3168f61e35 100644 --- a/services/telemetry/telemetry.go +++ b/services/telemetry/telemetry.go @@ -448,6 +448,7 @@ func (ts *TelemetryService) trackConfig() { "enable_file_search": *cfg.ServiceSettings.EnableFileSearch, "restrict_link_previews": isDefault(*cfg.ServiceSettings.RestrictLinkPreviews, ""), "enable_custom_groups": *cfg.ServiceSettings.EnableCustomGroups, + "post_priority": *cfg.ServiceSettings.PostPriority, }) ts.SendTelemetry(TrackConfigTeam, map[string]any{ From 8edd351f27dc382b253899b155f4d96d0ab23c3a Mon Sep 17 00:00:00 2001 From: Kyriakos Z <3829551+koox00@users.noreply.github.com> Date: Mon, 12 Sep 2022 11:30:02 +0300 Subject: [PATCH 09/21] Fixes kerberos version not found (#20998) * Fixes kerberos version not found docker-build fails in CI because the following versions are not found. - libkrb5support0=1.17-3+deb10u3 - libk5crypto3=1.17-3+deb10u3 - libkrb5-3=1.17-3+deb10u3 - libgssapi-krb5-2=1.17-3+deb10u3 --- build/Dockerfile | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/build/Dockerfile b/build/Dockerfile index 9ad11d9272..90c15906c4 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -31,10 +31,10 @@ RUN apt-get update \ ucf=3.0038+nmu1 \ openssl=1.1.1n-0+deb10u3 \ libkeyutils1=1.6-6 \ - libkrb5support0=1.17-3+deb10u3 \ - libk5crypto3=1.17-3+deb10u3 \ - libkrb5-3=1.17-3+deb10u3 \ - libgssapi-krb5-2=1.17-3+deb10u3 \ + libkrb5support0=1.17-3+deb10u4 \ + libk5crypto3=1.17-3+deb10u4 \ + libkrb5-3=1.17-3+deb10u4 \ + libgssapi-krb5-2=1.17-3+deb10u4 \ libnghttp2-14=1.36.0-2+deb10u1 \ libpsl5=0.20.2-2 \ librtmp1=2.4+20151223.gitfa8646d.1-2 \ From 203c6a50137cb31d628d936a39a2611a9aea5db0 Mon Sep 17 00:00:00 2001 From: Anurag Shivarathri Date: Mon, 12 Sep 2022 14:51:33 +0530 Subject: [PATCH 10/21] Badge count fix for push notifications when CRT is enabled (#20898) * Fix * Fixed other cases and tests Co-authored-by: Mattermod --- app/notification_push.go | 67 +++++++++++++--------- app/notification_push_test.go | 9 +-- app/web_hub_test.go | 2 +- i18n/en.json | 4 ++ store/opentracinglayer/opentracinglayer.go | 4 +- store/retrylayer/retrylayer.go | 4 +- store/sqlstore/user_store.go | 13 ++++- store/store.go | 2 +- store/storetest/mocks/UserStore.go | 14 ++--- store/storetest/user_store.go | 13 ++++- store/timerlayer/timerlayer.go | 4 +- 11 files changed, 85 insertions(+), 51 deletions(-) diff --git a/app/notification_push.go b/app/notification_push.go index 757764c613..a50b16ceab 100644 --- a/app/notification_push.go +++ b/app/notification_push.go @@ -219,30 +219,42 @@ func (a *App) getPushNotificationMessage(contentsConfig, postMessage string, exp return senderName + userLocale("api.post.send_notifications_and_forget.push_general_message") } +func (a *App) getUserBadgeCount(userID string, isCRTEnabled bool) (int, *model.AppError) { + unreadCount, err := a.Srv().Store.User().GetUnreadCount(userID, isCRTEnabled) + if err != nil { + return 0, model.NewAppError("getUserBadgeCount", "app.user.get_unread_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + badgeCount := int(unreadCount) + + if isCRTEnabled { + threadUnreadMentions, err := a.Srv().Store.Thread().GetTotalUnreadMentions(userID, "", model.GetUserThreadsOpts{}) + if err != nil { + return 0, model.NewAppError("getUserBadgeCount", "app.user.get_thread_count_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + badgeCount += int(threadUnreadMentions) + } + + return badgeCount, nil +} + func (a *App) clearPushNotificationSync(c request.CTX, currentSessionId, userID, channelID, rootID string) *model.AppError { + isCRTEnabled := a.IsCRTEnabledForUser(c, userID) + + badgeCount, err := a.getUserBadgeCount(userID, isCRTEnabled) + if err != nil { + return model.NewAppError("clearPushNotificationSync", "app.user.get_badge_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + msg := &model.PushNotification{ Type: model.PushTypeClear, Version: model.PushMessageV2, ChannelId: channelID, RootId: rootID, ContentAvailable: 1, - Badge: 0, - IsCRTEnabled: a.IsCRTEnabledForUser(c, userID), + Badge: badgeCount, + IsCRTEnabled: isCRTEnabled, } - unreadCount, err := a.Srv().Store.User().GetUnreadCount(userID) - if err != nil { - return model.NewAppError("clearPushNotificationSync", "app.user.get_unread_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - msg.Badge = int(unreadCount) - - if msg.IsCRTEnabled { - totalUnreadMentions, err := a.Srv().Store.Thread().GetTotalUnreadMentions(userID, "", model.GetUserThreadsOpts{}) - if err != nil { - return model.NewAppError("clearPushNotificationSync", "app.user.get_thread_count_for_user.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - msg.Badge += int(totalUnreadMentions) - } return a.sendPushNotificationToAllSessions(msg, userID, currentSessionId) } @@ -260,21 +272,19 @@ func (a *App) clearPushNotification(currentSessionId, userID, channelID, rootID } } -func (a *App) updateMobileAppBadgeSync(userID string) *model.AppError { +func (a *App) updateMobileAppBadgeSync(c request.CTX, userID string) *model.AppError { + badgeCount, err := a.getUserBadgeCount(userID, a.IsCRTEnabledForUser(c, userID)) + if err != nil { + return model.NewAppError("updateMobileAppBadgeSync", "app.user.get_badge_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + } + msg := &model.PushNotification{ Type: model.PushTypeUpdateBadge, Version: model.PushMessageV2, Sound: "none", ContentAvailable: 1, + Badge: badgeCount, } - - unreadCount, err := a.Srv().Store.User().GetUnreadCount(userID) - if err != nil { - return model.NewAppError("updateMobileAppBadgeSync", "app.user.get_unread_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) - } - - msg.Badge = int(unreadCount) - return a.sendPushNotificationToAllSessions(msg, userID, "") } @@ -345,7 +355,7 @@ func (hub *PushNotificationsHub) start(c request.CTX) { notification.replyToThreadType, ) case notificationTypeUpdateBadge: - err = hub.app.updateMobileAppBadgeSync(notification.userID) + err = hub.app.updateMobileAppBadgeSync(c, notification.userID) default: mlog.Debug("Invalid notification type", mlog.String("notification_type", string(notification.notificationType))) } @@ -566,11 +576,12 @@ func (a *App) BuildPushNotificationMessage(c request.CTX, contentsConfig string, msg = a.buildFullPushNotificationMessage(c, contentsConfig, post, user, channel, channelName, senderName, explicitMention, channelWideMention, replyToThreadType) } - unreadCount, err := a.Srv().Store.User().GetUnreadCount(user.Id) + badgeCount, err := a.getUserBadgeCount(user.Id, a.IsCRTEnabledForUser(c, user.Id)) if err != nil { - return nil, model.NewAppError("BuildPushNotificationMessage", "app.user.get_unread_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) + return nil, model.NewAppError("BuildPushNotificationMessage", "app.user.get_badge_count.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } - msg.Badge = int(unreadCount) + + msg.Badge = badgeCount return msg, nil } diff --git a/app/notification_push_test.go b/app/notification_push_test.go index 0f3e8a157f..dfb226573e 100644 --- a/app/notification_push_test.go +++ b/app/notification_push_test.go @@ -1136,7 +1136,7 @@ func TestClearPushNotificationSync(t *testing.T) { mockStore := th.App.Srv().Store.(*mocks.Store) mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) - mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string")).Return(int64(1), nil) + mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} @@ -1212,7 +1212,7 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) { mockStore := th.App.Srv().Store.(*mocks.Store) mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) - mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string")).Return(int64(1), nil) + mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} @@ -1231,9 +1231,10 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) { th.App.UpdateConfig(func(cfg *model.Config) { *cfg.EmailSettings.PushNotificationServer = pushServer.URL + *cfg.ServiceSettings.CollapsedThreads = model.CollapsedThreadsDisabled }) - err := th.App.updateMobileAppBadgeSync("user1") + err := th.App.updateMobileAppBadgeSync(th.Context, "user1") require.Nil(t, err) // Server side verification. // We verify that 2 requests have been sent, and also check the message contents. @@ -1529,7 +1530,7 @@ func BenchmarkPushNotificationThroughput(b *testing.B) { mockStore := th.App.Srv().Store.(*mocks.Store) mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) - mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string")).Return(int64(1), nil) + mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} diff --git a/app/web_hub_test.go b/app/web_hub_test.go index f4f347cb7f..e53a2b51d0 100644 --- a/app/web_hub_test.go +++ b/app/web_hub_test.go @@ -135,7 +135,7 @@ func TestHubSessionRevokeRace(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) - mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string")).Return(int64(1), nil) + mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} mockPostStore.On("GetMaxPostSize").Return(65535, nil) mockSystemStore := mocks.SystemStore{} diff --git a/i18n/en.json b/i18n/en.json index abf4c2280a..93666a3143 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -6591,6 +6591,10 @@ "id": "app.user.get.app_error", "translation": "We encountered an error finding the account." }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "We could not get the badge count for the user." + }, { "id": "app.user.get_by_auth.missing_account.app_error", "translation": "Unable to find an existing account matching your authentication type for this team. This team may require an invite from the team owner to join." diff --git a/store/opentracinglayer/opentracinglayer.go b/store/opentracinglayer/opentracinglayer.go index 51a2dd1055..45b6277728 100644 --- a/store/opentracinglayer/opentracinglayer.go +++ b/store/opentracinglayer/opentracinglayer.go @@ -11089,7 +11089,7 @@ func (s *OpenTracingLayerUserStore) GetTeamGroupUsers(teamID string) ([]*model.U return result, err } -func (s *OpenTracingLayerUserStore) GetUnreadCount(userID string) (int64, error) { +func (s *OpenTracingLayerUserStore) GetUnreadCount(userID string, isCRTEnabled bool) (int64, error) { origCtx := s.Root.Store.Context() span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "UserStore.GetUnreadCount") s.Root.Store.SetContext(newCtx) @@ -11098,7 +11098,7 @@ func (s *OpenTracingLayerUserStore) GetUnreadCount(userID string) (int64, error) }() defer span.Finish() - result, err := s.UserStore.GetUnreadCount(userID) + result, err := s.UserStore.GetUnreadCount(userID, isCRTEnabled) if err != nil { span.LogFields(spanlog.Error(err)) ext.Error.Set(span, true) diff --git a/store/retrylayer/retrylayer.go b/store/retrylayer/retrylayer.go index 2506d610f9..6e544138a2 100644 --- a/store/retrylayer/retrylayer.go +++ b/store/retrylayer/retrylayer.go @@ -12660,11 +12660,11 @@ func (s *RetryLayerUserStore) GetTeamGroupUsers(teamID string) ([]*model.User, e } -func (s *RetryLayerUserStore) GetUnreadCount(userID string) (int64, error) { +func (s *RetryLayerUserStore) GetUnreadCount(userID string, isCRTEnabled bool) (int64, error) { tries := 0 for { - result, err := s.UserStore.GetUnreadCount(userID) + result, err := s.UserStore.GetUnreadCount(userID, isCRTEnabled) if err == nil { return result, nil } diff --git a/store/sqlstore/user_store.go b/store/sqlstore/user_store.go index 8a359ed870..201aac0ca3 100644 --- a/store/sqlstore/user_store.go +++ b/store/sqlstore/user_store.go @@ -1371,9 +1371,18 @@ func (us SqlUserStore) AnalyticsActiveCountForPeriod(startTime int64, endTime in return v, nil } -func (us SqlUserStore) GetUnreadCount(userId string) (int64, error) { +func (us SqlUserStore) GetUnreadCount(userId string, isCRTEnabled bool) (int64, error) { + var totalMsgCountColumn = "c.TotalMsgCount" + var msgCountColumn = "cm.MsgCount" + var mentionCountColumn = "cm.MentionCount" + if isCRTEnabled { + totalMsgCountColumn = "c.TotalMsgCountRoot" + msgCountColumn = "cm.MsgCountRoot" + mentionCountColumn = "cm.MentionCountRoot" + } + query := ` - SELECT SUM(CASE WHEN c.Type = ? THEN (c.TotalMsgCount - cm.MsgCount) ELSE cm.MentionCount END) + SELECT SUM(CASE WHEN c.Type = ? THEN (` + totalMsgCountColumn + ` - ` + msgCountColumn + `) ELSE ` + mentionCountColumn + ` END) FROM Channels c INNER JOIN ChannelMembers cm ON cm.ChannelId = c.Id diff --git a/store/store.go b/store/store.go index 0e392bf861..df2473b701 100644 --- a/store/store.go +++ b/store/store.go @@ -447,7 +447,7 @@ type UserStore interface { PermanentDelete(userID string) error AnalyticsActiveCount(timestamp int64, options model.UserCountOptions) (int64, error) AnalyticsActiveCountForPeriod(startTime int64, endTime int64, options model.UserCountOptions) (int64, error) - GetUnreadCount(userID string) (int64, error) + GetUnreadCount(userID string, isCRTEnabled bool) (int64, error) GetUnreadCountForChannel(userID string, channelID string) (int64, error) GetAnyUnreadPostCountForChannel(userID string, channelID string) (int64, error) GetRecentlyActiveUsersForTeam(teamID string, offset, limit int, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, error) diff --git a/store/storetest/mocks/UserStore.go b/store/storetest/mocks/UserStore.go index afb1ae0c9a..ea0fdcfcc0 100644 --- a/store/storetest/mocks/UserStore.go +++ b/store/storetest/mocks/UserStore.go @@ -960,20 +960,20 @@ func (_m *UserStore) GetTeamGroupUsers(teamID string) ([]*model.User, error) { return r0, r1 } -// GetUnreadCount provides a mock function with given fields: userID -func (_m *UserStore) GetUnreadCount(userID string) (int64, error) { - ret := _m.Called(userID) +// GetUnreadCount provides a mock function with given fields: userID, isCRTEnabled +func (_m *UserStore) GetUnreadCount(userID string, isCRTEnabled bool) (int64, error) { + ret := _m.Called(userID, isCRTEnabled) var r0 int64 - if rf, ok := ret.Get(0).(func(string) int64); ok { - r0 = rf(userID) + if rf, ok := ret.Get(0).(func(string, bool) int64); ok { + r0 = rf(userID, isCRTEnabled) } else { r0 = ret.Get(0).(int64) } var r1 error - if rf, ok := ret.Get(1).(func(string) error); ok { - r1 = rf(userID) + if rf, ok := ret.Get(1).(func(string, bool) error); ok { + r1 = rf(userID, isCRTEnabled) } else { r1 = ret.Error(1) } diff --git a/store/storetest/user_store.go b/store/storetest/user_store.go index cef2ce02e8..fd2d8746d9 100644 --- a/store/storetest/user_store.go +++ b/store/storetest/user_store.go @@ -2440,14 +2440,23 @@ func testUserUnreadCount(t *testing.T, ss store.Store) { nErr = ss.Channel().IncrementMentionCount(c2.Id, []string{u2.Id}, false) require.NoError(t, nErr) - badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id) + badge, unreadCountErr := ss.User().GetUnreadCount(u2.Id, false) require.NoError(t, unreadCountErr) require.Equal(t, int64(3), badge, "should have 3 unread messages") - badge, unreadCountErr = ss.User().GetUnreadCount(u3.Id) + badge, unreadCountErr = ss.User().GetUnreadCount(u3.Id, false) require.NoError(t, unreadCountErr) require.Equal(t, int64(1), badge, "should have 1 unread message") + // Increment root mentions by 1 + nErr = ss.Channel().IncrementMentionCount(c1.Id, []string{u3.Id}, true) + require.NoError(t, nErr) + + // CRT is enabled, only root mentions are counted + badge, unreadCountErr = ss.User().GetUnreadCount(u3.Id, true) + require.NoError(t, unreadCountErr) + require.Equal(t, int64(1), badge, "should have 1 unread message with CRT") + badge, unreadCountErr = ss.User().GetUnreadCountForChannel(u2.Id, c1.Id) require.NoError(t, unreadCountErr) require.Equal(t, int64(1), badge, "should have 1 unread messages for that channel") diff --git a/store/timerlayer/timerlayer.go b/store/timerlayer/timerlayer.go index 6835edbb68..3a4e40ed39 100644 --- a/store/timerlayer/timerlayer.go +++ b/store/timerlayer/timerlayer.go @@ -9983,10 +9983,10 @@ func (s *TimerLayerUserStore) GetTeamGroupUsers(teamID string) ([]*model.User, e return result, err } -func (s *TimerLayerUserStore) GetUnreadCount(userID string) (int64, error) { +func (s *TimerLayerUserStore) GetUnreadCount(userID string, isCRTEnabled bool) (int64, error) { start := time.Now() - result, err := s.UserStore.GetUnreadCount(userID) + result, err := s.UserStore.GetUnreadCount(userID, isCRTEnabled) elapsed := float64(time.Since(start)) / float64(time.Second) if s.Root.Metrics != nil { From 098873b58f1b9b6df0d34e5dcfd8c6f114ef7081 Mon Sep 17 00:00:00 2001 From: Shivashis Padhi Date: Mon, 12 Sep 2022 16:02:35 +0530 Subject: [PATCH 11/21] [MM-31745] Add translations for enterprise PR#1266 (#20872) * Add certificate_parse_error's translation from enterprise * Change translation text Co-authored-by: Mattermod --- i18n/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/i18n/en.json b/i18n/en.json index 93666a3143..aeeca11e0a 100644 --- a/i18n/en.json +++ b/i18n/en.json @@ -7691,6 +7691,10 @@ "id": "ent.saml.build_request.encoding.app_error", "translation": "An error occurred while encoding the request for the Identity Provider. Please contact your System Administrator." }, + { + "id": "ent.saml.configure.certificate_parse_error.app_error", + "translation": "SAML could not load Identity Provider Public Certificate successfully, please contact your system administrator." + }, { "id": "ent.saml.configure.encryption_not_enabled.app_error", "translation": "SAML login was unsuccessful because encryption is not enabled. Please contact your System Administrator." From e6a42ceb70375dffa6020a238acdd4f065f86bd0 Mon Sep 17 00:00:00 2001 From: Tom De Moor Date: Mon, 12 Sep 2022 18:03:34 +0200 Subject: [PATCH 12/21] Translated using Weblate (Dutch) Currently translated at 99.4% (2366 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ Translated using Weblate (Dutch) Currently translated at 99.0% (2356 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/nl/ --- i18n/nl.json | 160 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 160 insertions(+) diff --git a/i18n/nl.json b/i18n/nl.json index 42c9e5d8af..91b4a89878 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -9317,5 +9317,165 @@ { "id": "api.cloud.delinquency_email.missing_email_to_trigger", "translation": "Verplichte velden ontbreken voor het versturen van een e-mail over wanbetaling." + }, + { + "id": "api.templates.delinquency_7.title", + "translation": "Jouw betaling is niet voltooid" + }, + { + "id": "api.templates.delinquency_7.subtitle2", + "translation": "Om jouw {{.Plan}} plan actief te houden, dien je zo snel mogelijk contact op te nemen met jouw financiële instelling. Werk vervolgens jouw betalingsgegevens zo nodig bij." + }, + { + "id": "api.templates.delinquency_7.subtitle1", + "translation": "We konden jouw laatste betaling niet verwerken" + }, + { + "id": "api.templates.delinquency_7.button", + "translation": "Betalingsgegevens bijwerken" + }, + { + "id": "api.templates.delinquency_60.title", + "translation": "Jouw Mattermost-werkruimte wordt na 30 dagen gedowngraded" + }, + { + "id": "api.templates.delinquency_60.subtitle3", + "translation": "Werk nu jouw betalingsgegevens bij of downgrade naar Cloud Starter hieronder." + }, + { + "id": "api.templates.delinquency_60.subtitle2", + "translation": "Wij zullen jouw werkruimte automatisch downgraden na 30 dagen als wij niet in staat zijn om jouw betaling te verwerken." + }, + { + "id": "api.templates.delinquency_60.subtitle1", + "translation": "Gelieve jouw betalingsgegevens spoedig bij te werken om jouw openstaande facturen te verwerken." + }, + { + "id": "api.templates.delinquency_60.subject", + "translation": "Actie vereist: Werkruimte zal binnen 30 dagen gedowngraded worden" + }, + { + "id": "api.templates.delinquency_60.downgrade_to_starter", + "translation": "Downgraden naar Cloud Starter" + }, + { + "id": "api.templates.delinquency_60.button", + "translation": "Betalingsgegevens bijwerken" + }, + { + "id": "api.templates.delinquency_45.title", + "translation": "Jouw werkruimte zal binnenkort gedowngraded worden" + }, + { + "id": "api.templates.delinquency_45.subtitle3", + "translation": "Werk nu jouw creditcardgegevens bij." + }, + { + "id": "api.templates.delinquency_45.subtitle2", + "translation": "Een gedowngradede workspace kan een negatieve invloed hebben op kritische workflows, integraties en andere bedrijfskritische activiteiten die in jouw workspace worden uitgevoerd." + }, + { + "id": "api.templates.delinquency_45.subtitle1", + "translation": "We hebben geen betaling kunnen innen voor openstaande facturen van {{.DelinquencyDate}}. Jouw werkruimte loopt het risico om gedowngraded te worden." + }, + { + "id": "api.templates.delinquency_45.subject", + "translation": "Melding: Jouw Mattermost {{.Plan}} zal binnenkort gedowngraded worden" + }, + { + "id": "api.templates.delinquency_45.button", + "translation": "Betalingsgegevens bijwerken" + }, + { + "id": "api.templates.delinquency_30.title", + "translation": "Jouw werkruimte zal binnenkort gedowngraded worden" + }, + { + "id": "api.templates.delinquency_30.subtitle2", + "translation": "als geen actie wordt ondernomen, zal jouw werkruimte worden gedowngraded en kunnen de volgende gegevens worden gearchiveerd:" + }, + { + "id": "api.templates.delinquency_30.subtitle1", + "translation": "Je hebt tijd om jouw Mattermost {{.Plan}} actief te houden, maar je moet de problemen met jouw betalingsmethode oplossen." + }, + { + "id": "api.templates.delinquency_30.subject", + "translation": "Handel om jouw Mattermost {{.Plan}} eigenschappen te behouden" + }, + { + "id": "api.templates.delinquency_30.limits_documentation", + "translation": "Bekijk alle documentatie rond beperkingen." + }, + { + "id": "api.templates.delinquency_30.button", + "translation": "Betalingsgegevens bijwerken" + }, + { + "id": "api.templates.delinquency_30.bullet.plugins", + "translation": "Actieve plugins en integraties" + }, + { + "id": "api.templates.delinquency_30.bullet.message_history", + "translation": "Berichtengeschiedenis" + }, + { + "id": "api.templates.delinquency_30.bullet.files", + "translation": "Bestanden" + }, + { + "id": "api.templates.delinquency_30.bullet.cards", + "translation": "Kaarten van jouw Boards" + }, + { + "id": "api.templates.delinquency_14.title", + "translation": "Betaling niet ontvangen" + }, + { + "id": "api.templates.delinquency_14.subtitle2", + "translation": "Neem contact op met jouw financiële instelling om eventuele problemen op te lossen. Werk vervolgens jouw betalingsgegevens bij indien nodig." + }, + { + "id": "api.templates.delinquency_14.subtitle1", + "translation": "We waren niet in staat om de kredietkaart die we in ons bestand hebben in rekening te brengen. Dit betekent dat jouw werkruimte het risico loopt te worden gedegradeerd naar Cloud Starter." + }, + { + "id": "api.templates.delinquency_90.subject", + "translation": "Jouw Mattermost Cloud-werkruimte werd gedowngraded" + }, + { + "id": "api.templates.delinquency_90.secondary_action_button", + "translation": "Plannen en prijzen bekijken" + }, + { + "id": "api.templates.delinquency_90.button", + "translation": "Betalingsgegevens bijwerken" + }, + { + "id": "api.templates.delinquency_75.title", + "translation": "Over 15 dagen zal jouw werkruimte wordeng edowngrade" + }, + { + "id": "api.templates.delinquency_75.subtitle3", + "translation": "Werk nu jouw betalingsgegevens bij, of downgrade naar Cloud Starter." + }, + { + "id": "api.templates.delinquency_75.subtitle2", + "translation": "Jouw werkruimte zal worden gedowngrade naar Cloud Starter. Jouw {{.Plan}} functies zullen worden vergrendeld en sommige van jouw werkruimtegegevens kunnen worden gearchiveerd totdat je jouw volledige uitstaande saldo hebt voldaan." + }, + { + "id": "api.templates.delinquency_75.subtitle1", + "translation": "Dit is de laatste herinnering dat we geen betaling hebben ontvangen voor jouw Mattermost Cloud-werkruimte sinds {{.DelinquencyDate}}" + }, + { + "id": "api.templates.delinquency_75.subject", + "translation": "Jouw Mattermost {{.Plan}} zal over 15 dagen worden gedowngraded" + }, + { + "id": "api.templates.delinquency_75.downgrade_to_starter", + "translation": "Downgraden naar Cloud Starter" + }, + { + "id": "api.templates.delinquency_75.button", + "translation": "Betalingsgegevens bijwerken" } ] From 0478377c36daff821e1a6490e18501117a5e5ec6 Mon Sep 17 00:00:00 2001 From: Matthew Williams Date: Mon, 12 Sep 2022 18:03:34 +0200 Subject: [PATCH 13/21] Translated using Weblate (English (Australia)) Currently translated at 100.0% (2379 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/en_AU/ --- i18n/en_AU.json | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/i18n/en_AU.json b/i18n/en_AU.json index ce533a1723..c47c88b664 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -9505,5 +9505,29 @@ { "id": "api.cloud.delinquency_email.missing_email_to_trigger", "translation": "Missing required fields to send delinquency email." + }, + { + "id": "app.notify_admin.send_notification_post.app_error", + "translation": "Unable to send notification post." + }, + { + "id": "app.notify_admin.save.app_error", + "translation": "Unable to save notify data." + }, + { + "id": "app.cloud.upgrade_plan_bot_message_single", + "translation": "{{.UsersNum}} member of the {{.WorkspaceName}} workspace has requested a workspace upgrade for: " + }, + { + "id": "app.cloud.upgrade_plan_bot_message", + "translation": "{{.UsersNum}} members of the {{.WorkspaceName}} workspace have requested a workspace upgrade for: " + }, + { + "id": "app.cloud.trial_plan_bot_message_single", + "translation": "{{.UsersNum}} member of the {{.WorkspaceName}} workspace has requested starting the Enterprise trial for access to: " + }, + { + "id": "app.cloud.trial_plan_bot_message", + "translation": "{{.UsersNum}} members of the {{.WorkspaceName}} workspace have requested starting the Enterprise trial for access to: " } ] From ab3f25c966cfe59d55e35ae876f090f711c97d65 Mon Sep 17 00:00:00 2001 From: jprusch Date: Mon, 12 Sep 2022 18:03:34 +0200 Subject: [PATCH 14/21] Translated using Weblate (German) Currently translated at 100.0% (2381 of 2381 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2379 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ Translated using Weblate (German) Currently translated at 100.0% (2379 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/de/ --- i18n/de.json | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/i18n/de.json b/i18n/de.json index 35d6d194d4..3d10154f54 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -4569,7 +4569,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "Die Nutzungsbedingungen von GitLab haben sich geändert. Bitte gehe zu gitlab.com um sie zu akzeptieren und versuche dann, dich erneut an Mattermost anzumelden." + "translation": "Die Nutzungsbedingungen von GitLab haben sich geändert. Bitte gehe zu {{.URL}} um sie zu akzeptieren und versuche dann, dich erneut an Mattermost anzumelden." }, { "id": "plugin.api.update_user_status.bad_status", @@ -8008,15 +8008,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Fehlgeschlagene Zahlung" + "translation": "Die Zahlung war nicht erfolgreich" }, { "id": "api.templates.payment_failed.subject", - "translation": "Aktion notwendig: Zahlung für Mattermost Cloud fehlgeschlagen" + "translation": "Aktion notwendig: Zahlung für Mattermost {{.Plan}} fehlgeschlagen" }, { "id": "api.templates.payment_failed.info3", - "translation": "Um einen unterbrechungsfreien Betrieb deines Mattermost Cloud Abonnements zu gewährleisten, kontaktiere bitte dein Finanzinstitut um das Problem zu lösen oder aktualisiere deine Zahlungsinformationen. Sobald die Zahlungsinformationen aktualisiert wurden, wird Mattermost versuchen den Außenstand auszugleichen." + "translation": "Um einen unterbrechungsfreien Zugriff auf Mattermost {{.Plan}} zu gewährleisten, kontaktiere bitte dein Finanzinstitut um das Problem zu lösen oder aktualisiere deine Zahlungsinformationen. Sobald die Zahlungsinformationen aktualisiert wurden, wird Mattermost versuchen den Außenstand auszugleichen." }, { "id": "api.templates.payment_failed.info2", @@ -9529,5 +9529,17 @@ { "id": "app.cloud.trial_plan_bot_message", "translation": "{{.UsersNum}} Mitglieder des {{.WorkspaceName}} Arbeitsbereichs haben den Start des Enterprise-Tests angefragt für Zugriff auf: " + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Abrufen des aktuellen Plan Namens nicht möglich" + }, + { + "id": "ent.saml.configure.certificate_parse_error.app_error", + "translation": "SAML konnte das öffentliche Zertifikat des Identity Providers nicht erfolgreich laden. Bitte kontaktiere deinen Systemadmin." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "Wir konnten den Nachrichtenzähler für den Benutzer nicht abfragen." } ] From 699f94c2cacc7635092904b23322c1e4b08fb19a Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 12 Sep 2022 18:03:35 +0200 Subject: [PATCH 15/21] Update translation files Updated by "Cleanup translation files" hook in Weblate. Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/ --- i18n/bg.json | 4 ---- i18n/de.json | 4 ---- i18n/en_AU.json | 4 ---- i18n/es.json | 4 ---- i18n/fa.json | 4 ---- i18n/fr.json | 4 ---- i18n/hu.json | 4 ---- i18n/it.json | 4 ---- i18n/ja.json | 4 ---- i18n/nl.json | 4 ---- i18n/pl.json | 4 ---- i18n/pt-BR.json | 4 ---- i18n/ro.json | 4 ---- i18n/ru.json | 4 ---- i18n/sv.json | 4 ---- i18n/tr.json | 4 ---- i18n/zh-CN.json | 4 ---- 17 files changed, 68 deletions(-) diff --git a/i18n/bg.json b/i18n/bg.json index e3d1ea8a2f..7913a2775f 100644 --- a/i18n/bg.json +++ b/i18n/bg.json @@ -4023,10 +4023,6 @@ "id": "api.templates.password_change_body.info", "translation": "Вашата парола е актуализирана за {{.TeamDisplayName}} на {{.TeamURL}} от {{.Method}}." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Оправи сега" - }, { "id": "api.templates.mfa_deactivated_body.title", "translation": "Многофакторното удостоверяване бе премахнато" diff --git a/i18n/de.json b/i18n/de.json index 3d10154f54..ae6d93035c 100644 --- a/i18n/de.json +++ b/i18n/de.json @@ -8026,10 +8026,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Dein Finanzinstitut hat ein Zahlung mit deiner {{.CardBrand}} Kreditkarte mit der Nummer ****{{.LastFour}}, die für deinen Mattermost Cloud Arbeitsbereich hinterlegt ist, abgelehnt." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Jetzt beheben" - }, { "id": "api.upload.upload_data.multipart_error", "translation": "Verarbeitung der Multipart-Daten fehlgeschlagen." diff --git a/i18n/en_AU.json b/i18n/en_AU.json index c47c88b664..eafc534942 100644 --- a/i18n/en_AU.json +++ b/i18n/en_AU.json @@ -5019,10 +5019,6 @@ "id": "api.templates.password_change_body.info", "translation": "Your password has been updated for {{.TeamDisplayName}} on {{ .TeamURL }} by {{.Method}}." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Fix Now" - }, { "id": "ent.compliance.csv.metadata.export.appError", "translation": "Unable to add metadata file to the zip file." diff --git a/i18n/es.json b/i18n/es.json index c3ce264e1a..97cec02eba 100644 --- a/i18n/es.json +++ b/i18n/es.json @@ -7663,10 +7663,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Su institución financiera rechazó un pago de su {{.CardBrand}} ****{{.LastFour}} asociado a su espacio de trabajo de Mattermost Cloud." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Arreglar ahora" - }, { "id": "api.oauth.redirecting_back", "translation": "Redirigiéndote de nuevo a la aplicación." diff --git a/i18n/fa.json b/i18n/fa.json index 4bd2716514..b71235d64b 100644 --- a/i18n/fa.json +++ b/i18n/fa.json @@ -4895,10 +4895,6 @@ "id": "api.templates.password_change_body.info", "translation": "رمز ورود شما برای {{.TeamDisplayName}} در {{.TeamURL}} توسط {{.Method}} به روز شده است." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "اکنون رفع کنید" - }, { "id": "api.templates.mfa_deactivated_body.title", "translation": "احراز هویت چند عاملی حذف شد" diff --git a/i18n/fr.json b/i18n/fr.json index b173cc06bc..043cda6743 100644 --- a/i18n/fr.json +++ b/i18n/fr.json @@ -7927,10 +7927,6 @@ "id": "api.templates.payment_failed.info2", "translation": "La raison suivante a été fournie :" }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Résoudre maintenant" - }, { "id": "api.templates.license_up_for_renewal_title", "translation": "Votre abonnement à Mattermost doit être renouvelé" diff --git a/i18n/hu.json b/i18n/hu.json index 14893d7263..b10693e9f8 100644 --- a/i18n/hu.json +++ b/i18n/hu.json @@ -5499,10 +5499,6 @@ "id": "api.templates.password_change_body.info", "translation": "Jelszava frissítésre került a {{.TeamDisplayName}} ({{ .TeamURL }}) csapatban {{.Method}} által." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Javítás most" - }, { "id": "api.templates.mfa_deactivated_body.title", "translation": "A többtényezős hitelesítést eltávolítottuk" diff --git a/i18n/it.json b/i18n/it.json index 020ceeea2e..2b4114bad9 100644 --- a/i18n/it.json +++ b/i18n/it.json @@ -9107,10 +9107,6 @@ "id": "api.templates.verify_body.info1", "translation": " " }, - { - "id": "api.templates.over_limit_fix_now", - "translation": " " - }, { "id": "api.templates.cloud_welcome_email.signin_sub_info", "translation": " " diff --git a/i18n/ja.json b/i18n/ja.json index 50b6de4219..743dfa5997 100644 --- a/i18n/ja.json +++ b/i18n/ja.json @@ -7723,10 +7723,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Mattermost Cloudワークスペースに関連付けられたあなたの {{.CardBrand}} ****{{.LastFour}} からの支払いを金融機関が拒否しました。" }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "今すぐ対応する" - }, { "id": "api.system.update_viewed_notices.failed", "translation": "閲覧済みのお知らせを更新できませんでした" diff --git a/i18n/nl.json b/i18n/nl.json index 91b4a89878..08e471d2cc 100644 --- a/i18n/nl.json +++ b/i18n/nl.json @@ -7671,10 +7671,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Jouw financiële instelling weigerde een betaling van uw {{.CardBrand}} ****{{.LastFour}} geassocieerd met uw Mattermost Cloud werkruimte." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Los het nu op" - }, { "id": "api.roles.patch_roles.not_allowed_permission.error", "translation": "Een of meer van de volgende rechten die je probeert toe te voegen of te verwijderen zijn niet toegestaan" diff --git a/i18n/pl.json b/i18n/pl.json index a405acb796..c2df88466d 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -8167,10 +8167,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Twoja instytucja finansowa odrzuciła płatność z Twojej {{.CardBrand}} ****{{.LastFour}} powiązanej z obszarem roboczym Mattermost Cloud." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Napraw teraz" - }, { "id": "store.sql_file_info.search.disabled", "translation": "Wyszukiwanie plików zostało wyłączone na tym serwerze. Skontaktuj się z Administratorem Systemu." diff --git a/i18n/pt-BR.json b/i18n/pt-BR.json index 97ba1d0bdb..94b78b3e58 100644 --- a/i18n/pt-BR.json +++ b/i18n/pt-BR.json @@ -7663,10 +7663,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Sua instituição financeira recusou um pagamento de seu {{.CardBrand}} ****{{.LastFour}} associado ao seu espaço de trabalho Mattermost Cloud." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Corrigir Agora" - }, { "id": "model.config.is_valid.import.retention_days_too_low.app_error", "translation": "Valor inválido para RetentionDays. O valor é muito baixo." diff --git a/i18n/ro.json b/i18n/ro.json index 999a2c9fc8..c51b4666e0 100644 --- a/i18n/ro.json +++ b/i18n/ro.json @@ -7283,10 +7283,6 @@ "id": "api.cloud.app_error", "translation": "Eroare internă la solicitarea api cloud." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Repară acum" - }, { "id": "api.templates.email_us_anytime_at", "translation": "Trimiteți-ne un e-mail oricând la " diff --git a/i18n/ru.json b/i18n/ru.json index 20751ec396..3d371f6a36 100644 --- a/i18n/ru.json +++ b/i18n/ru.json @@ -7947,10 +7947,6 @@ "id": "api.templates.payment_failed.info1", "translation": "Ваше финансовое учреждение отклонило платёж вашей карты {{.CardBrand}}. ****{{.LastFour}}, связанный с вашим рабочим пространством Mattermost Cloud." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Исправить сейчас" - }, { "id": "api.templates.invite_body_guest.subTitle", "translation": "Вы были приглашены в качестве гостя в команду" diff --git a/i18n/sv.json b/i18n/sv.json index b73d75fb5c..ac1f2c05e5 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -4955,10 +4955,6 @@ "id": "api.templates.password_change_body.info", "translation": "Ditt lösenord har uppdaterats av {{.Method}} för {{.TeamDisplayName}} på {{ .TeamURL }}." }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Fixa nu" - }, { "id": "api.templates.mfa_deactivated_body.title", "translation": "Flerfaktorauthentisering är borttagen" diff --git a/i18n/tr.json b/i18n/tr.json index a20d37adb2..d55e2d186a 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -7635,10 +7635,6 @@ "id": "app.user.get_threads_for_user.app_error", "translation": "Kullanıcı konuları alınamadı" }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "Şimdi düzelt" - }, { "id": "api.roles.patch_roles.not_allowed_permission.error", "translation": "Eklemek ya da silmek istediğiniz bir ya da bir kaç yetkiye izin verilmiyor" diff --git a/i18n/zh-CN.json b/i18n/zh-CN.json index e72cc7d333..4dba1375ff 100644 --- a/i18n/zh-CN.json +++ b/i18n/zh-CN.json @@ -7459,10 +7459,6 @@ "id": "ent.message_export.global_relay_export.get_attachment_error", "translation": "无法获取帖子的文件信息。" }, - { - "id": "api.templates.over_limit_fix_now", - "translation": "立刻修复" - }, { "id": "api.templates.email_us_anytime_at", "translation": "随时通过电子邮件发送给我们 " From 177c7b5cd476048d3c05f2c8635689aa416033f8 Mon Sep 17 00:00:00 2001 From: Kaya Zeren Date: Mon, 12 Sep 2022 18:03:35 +0200 Subject: [PATCH 16/21] Translated using Weblate (Turkish) Currently translated at 100.0% (2379 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/tr/ --- i18n/tr.json | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/i18n/tr.json b/i18n/tr.json index d55e2d186a..05ef8c4e65 100644 --- a/i18n/tr.json +++ b/i18n/tr.json @@ -4569,7 +4569,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "GitLab hizmet koşulları güncellendi. Lütfen gitlab.com adresine giderek yeni hizmet koşullarını onayladıktan sonra yeniden Mattermost oturumu açmayı deneyin." + "translation": "GitLab hizmet koşulları güncellendi. Lütfen {{.URL}} adresine giderek yeni hizmet koşullarını onayladıktan sonra Mattermost oturumunu yeniden açmayı deneyin." }, { "id": "plugin.api.update_user_status.bad_status", @@ -7697,11 +7697,11 @@ }, { "id": "api.templates.payment_failed.subject", - "translation": "İşlem gerekli: Mattermost Cloud ödemesi alınamadı" + "translation": "İşlem gerekli: Mattermost {{.Plan}} ödemesi alınamadı" }, { "id": "api.templates.payment_failed.info3", - "translation": "Mattermost Cloud aboneliğinizin kesintiye uğramaması için sorunu çözmesi için bankanızla görüşün ya da ödeme bilgilerinizi güncelleyin. Ödeme bilgileri güncellendikten sonra Mattermost kalan ödemeyi almayı deneyecek." + "translation": "Mattermost {{.Plan}} erişiminizin kesintiye uğramaması için sorunu çözmek amacıyla bankanızla görüşün ya da ödeme bilgilerinizi güncelleyin. Ödeme bilgileri güncellendikten sonra Mattermost kalan ödemeyi almayı deneyecek." }, { "id": "api.templates.payment_failed.info2", @@ -9525,5 +9525,9 @@ { "id": "app.cloud.trial_plan_bot_message", "translation": "{{.WorkspaceName}} çalışma alanının {{.UsersNum}} üyesi şuraya erişmek için Enterprise sürümü deneme süresinin başlatılmasını istedi: " + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Geçerli tarifenin adı alınamadı" } ] From eba83d578020923cf2ede5ab8736b7c11a2f9128 Mon Sep 17 00:00:00 2001 From: master7 Date: Mon, 12 Sep 2022 18:03:36 +0200 Subject: [PATCH 17/21] Translated using Weblate (Polish) Currently translated at 100.0% (2381 of 2381 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ Translated using Weblate (Polish) Currently translated at 100.0% (2379 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/pl/ --- i18n/pl.json | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/i18n/pl.json b/i18n/pl.json index c2df88466d..a5593cfa3c 100644 --- a/i18n/pl.json +++ b/i18n/pl.json @@ -4573,7 +4573,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "Zaktualizowano Warunki usługi GitLab. Przejdź na stronę gitlab.com, aby je zaakceptować, a następnie spróbuj zalogować się ponownie do Mattermost." + "translation": "Warunki korzystania z usług GitLab zostały zaktualizowane. Proszę przejść do {{.URL}}, aby je zaakceptować, a następnie spróbować ponownie zalogować się do Mattermost." }, { "id": "plugin.api.update_user_status.bad_status", @@ -8149,15 +8149,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Nieudana płatność" + "translation": "Płatność nie powiodła się" }, { "id": "api.templates.payment_failed.subject", - "translation": "Wymagane działanie: Nieudana płatność za Mattermost Cloud" + "translation": "Wymagane działanie: Płatność nie powiodła się dla Mattermost {{.Plan}}" }, { "id": "api.templates.payment_failed.info3", - "translation": "Aby zapewnić nieprzerwaną subskrypcję Mattermost Cloud, należy skontaktować się ze swoją instytucją finansową w celu rozwiązania problemu lub zaktualizować informacje dotyczące płatności. Po zaktualizowaniu informacji o płatności, Mattermost podejmie próbę uregulowania wszelkich zaległości." + "translation": "Aby zapewnić nieprzerwany dostęp do Mattermost {{.Plan}}, należy skontaktować się ze swoją instytucją finansową w celu rozwiązania problemu lub zaktualizować informacje dotyczące płatności. Po zaktualizowaniu informacji o płatności, Mattermost podejmie próbę uregulowania wszelkich zaległości." }, { "id": "api.templates.payment_failed.info2", @@ -9526,5 +9526,17 @@ { "id": "app.cloud.trial_plan_bot_message", "translation": "{{.UsersNum}} członkowie obszaru roboczego {{.WorkspaceName}} zażądali rozpoczęcia wersji próbnej Enterprise w celu uzyskania dostępu do: " + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Nie można uzyskać nazwy bieżącego planu" + }, + { + "id": "ent.saml.configure.certificate_parse_error.app_error", + "translation": "SAML nie mógł pomyślnie załadować Identity Provider Public Certificate, skontaktuj się z administratorem systemu." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "Nie mogliśmy uzyskać liczby odznak dla użytkownika." } ] From 4d7a2233974d06b5183a8e7edd7561717f450005 Mon Sep 17 00:00:00 2001 From: MArtin Johnson Date: Mon, 12 Sep 2022 18:03:36 +0200 Subject: [PATCH 18/21] Translated using Weblate (Swedish) Currently translated at 100.0% (2381 of 2381 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ Translated using Weblate (Swedish) Currently translated at 98.4% (2341 of 2379 strings) Translation: mattermost-languages-shipped/mattermost-server Translate-URL: https://translate.mattermost.com/projects/mattermost/mattermost-server_master/sv/ --- i18n/sv.json | 168 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 164 insertions(+), 4 deletions(-) diff --git a/i18n/sv.json b/i18n/sv.json index ac1f2c05e5..e3ede34fd8 100644 --- a/i18n/sv.json +++ b/i18n/sv.json @@ -241,7 +241,7 @@ }, { "id": "oauth.gitlab.tos.error", - "translation": "GitLab's användningsvillkor har uppdaterats. Logga in på gitlab.com för att acceptera dem och logga sedan in i Mattermost igen." + "translation": "GitLab's användningsvillkor har uppdaterats. Gå till {{.URL}} för att acceptera dem och logga sedan in i Mattermost igen." }, { "id": "model.group_syncable.syncable_id.app_error", @@ -4925,15 +4925,15 @@ }, { "id": "api.templates.payment_failed.title", - "translation": "Misslyckad betalning" + "translation": "Betalningen gick inte bra" }, { "id": "api.templates.payment_failed.subject", - "translation": "Åtgärd krävs: Betalning för Mattermost Cloud misslyckades" + "translation": "Åtgärd krävs: Betalning för Mattermost {{.Plan}} misslyckades" }, { "id": "api.templates.payment_failed.info3", - "translation": "För att säkerställa en fortsatt prenumeration på Mattermost Cloud bör du kontakta din kortutgivare för att åtgärda problemet, alternativt uppdatera dina betaluppgifter. När betalinformationen är uppdaterad kommer Mattermost försöka reglera eventuellt utestående saldo." + "translation": "För att säkerställa en fortsatt tillgång på Mattermost {{.Plan}} bör du kontakta din kortutgivare för att åtgärda problemet, alternativt uppdatera dina betaluppgifter. När betalinformationen är uppdaterad kommer Mattermost försöka reglera eventuellt utestående saldo." }, { "id": "api.templates.payment_failed.info2", @@ -9377,5 +9377,165 @@ { "id": "api.templates.delinquency_14.subject", "translation": "Betalningen för din Mattermost {{.Plan}} är försenad." + }, + { + "id": "ent.saml.configure.certificate_parse_error.app_error", + "translation": "SAML kunde inte ladda din Identity Providers Publika Certificat. Kontakta din systemadministratör." + }, + { + "id": "app.user.get_badge_count.app_error", + "translation": "Vi kunde inte få fram användarens antal märken." + }, + { + "id": "app.notify_admin.send_notification_post.app_error", + "translation": "Det går inte att skicka notifieringsmeddelande." + }, + { + "id": "app.notify_admin.save.app_error", + "translation": "Det går inte att spara uppgifter om notifiering." + }, + { + "id": "app.cloud.upgrade_plan_bot_message_single", + "translation": "{{.UsersNum}} medlemmar i arbetsytan {{.WorkspaceName}} har önskat en uppgradering för att få tillgång till: " + }, + { + "id": "app.cloud.upgrade_plan_bot_message", + "translation": "{{.UsersNum}} medlemmar i arbetsytan {{.WorkspaceName}} har önskat en uppgradering för att få tillgång till: " + }, + { + "id": "app.cloud.trial_plan_bot_message_single", + "translation": "{{.UsersNum}} medlemmar i arbetsytan {{.WorkspaceName}} har önskat starta Enterprise trial för att få tillgång till: " + }, + { + "id": "app.cloud.trial_plan_bot_message", + "translation": "{{.UsersNum}} medlemmar i arbetsytan {{.WorkspaceName}} har önskat starta Enterprise trial för att få tillgång till: " + }, + { + "id": "app.cloud.get_subscription_delinquency_date.app_error", + "translation": "Abonnemanget är inte försenat" + }, + { + "id": "app.cloud.get_subscription.app_error", + "translation": "Kunde inte hämta molnprenumeration" + }, + { + "id": "app.cloud.get_current_plan_name.app_error", + "translation": "Det går inte att hämta fram namnet på den aktuella planen" + }, + { + "id": "app.cloud.get_cloud_products.app_error", + "translation": "Kunde inte hämta molnprodukter" + }, + { + "id": "api.templates.delinquency_90.title", + "translation": "Din Mattermost-arbetsyta har nedgraderats" + }, + { + "id": "api.templates.delinquency_90.subtitle3", + "translation": "Uppdatera betalningsinformationen om du vill ta tillbaka ditt data från arkivet och behålla betal-funktioner." + }, + { + "id": "api.templates.delinquency_90.subtitle2", + "translation": "Dessutom kan dina data ha arkiverats på grund av begränsningar i Cloud Starter." + }, + { + "id": "api.templates.delinquency_90.subtitle1", + "translation": "Om du använder Cloud Professional- eller Enterprise-funktioner för viktiga affärsaktiviteter kommer dessa inte längre att vara tillgängliga och du kommer att uppleva försämrad prestanda." + }, + { + "id": "api.templates.delinquency_90.subject", + "translation": "Din Mattermost Cloud-arbetsyta har nedgraderats" + }, + { + "id": "api.templates.delinquency_90.secondary_action_button", + "translation": "Visa abonnemang och priser" + }, + { + "id": "api.templates.delinquency_90.button", + "translation": "Uppdatera betalningsinformation" + }, + { + "id": "api.templates.delinquency_75.title", + "translation": "Din arbetsyta kommer att nedgraderas om 15 dagar" + }, + { + "id": "api.templates.delinquency_75.subtitle3", + "translation": "Uppdatera din betalningsinformation nu, eller nedgradera till Cloud Starter." + }, + { + "id": "api.templates.delinquency_75.subtitle2", + "translation": "Din arbetsplats kommer att nedgraderas till Cloud Starter. Dina {{.Plan}}-funktioner kommer att spärras och delar av dina arbetsytedata kan komma att arkiveras tills hela ditt utestående belopp är betalt." + }, + { + "id": "api.templates.delinquency_75.subtitle1", + "translation": "Detta är en sista påminnelse. Vi har inte mottagit betalning för din Mattermost Cloud-arbetsyta sedan {{.DelinquencyDate}}" + }, + { + "id": "api.templates.delinquency_75.subject", + "translation": "Din Mattermost {{.Plan}} kommer att nedgraderas om 15 dagar" + }, + { + "id": "api.templates.delinquency_75.downgrade_to_starter", + "translation": "Nedgradera till Cloud Starter" + }, + { + "id": "api.templates.delinquency_75.button", + "translation": "Uppdatera betalningsinformation" + }, + { + "id": "api.templates.delinquency_7.title", + "translation": "Din betalning slutfördes inte" + }, + { + "id": "api.templates.delinquency_7.subtitle2", + "translation": "För att hålla din {{.Plan}}-plan aktiv, kontakta din bank eller kortutgivare så snart som möjligt. Uppdatera din betalningsinformation vid behov." + }, + { + "id": "api.templates.delinquency_7.subtitle1", + "translation": "Vi kunde inte behandla din senaste betalning" + }, + { + "id": "api.templates.delinquency_7.button", + "translation": "Uppdatera betalningsinformation" + }, + { + "id": "api.templates.delinquency_60.title", + "translation": "Din Mattermost-arbetsyta kommer att nedgraderas om 30 dagar" + }, + { + "id": "api.templates.delinquency_60.subtitle3", + "translation": "Uppdatera din betalningsinformation nu eller nedgradera till Cloud Starter nedan." + }, + { + "id": "api.templates.delinquency_60.subtitle2", + "translation": "Vi nedgraderar din arbetsyta automatiskt om 30 dagar om vi inte kan behandla din betalning." + }, + { + "id": "api.templates.delinquency_60.subtitle1", + "translation": "Uppdatera din betalningsinformation snarast så att utestående fakturor kan hanteras." + }, + { + "id": "api.templates.delinquency_60.subject", + "translation": "Åtgärder krävs: Arbetsytan kommer att nedgraderas inom 30 dagar" + }, + { + "id": "api.templates.delinquency_60.downgrade_to_starter", + "translation": "Nedgradera till Cloud Starter" + }, + { + "id": "api.templates.delinquency_60.button", + "translation": "Uppdatera betalningsinformation" + }, + { + "id": "api.templates.delinquency_45.title", + "translation": "Din arbetsyta kommer snart att nedgraderas" + }, + { + "id": "api.templates.delinquency_45.subtitle3", + "translation": "Uppdatera din kreditkortsinformation nu." + }, + { + "id": "api.cloud.delinquency_email.missing_email_to_trigger", + "translation": "Information i obligatoriska fält saknas för att kunna skicka e-postmeddelanden om utebliven betalning." } ] From 54df69e57ab0ca5fc1102b4e1ecca95e2c189b9b Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Mon, 12 Sep 2022 14:35:33 -0400 Subject: [PATCH 19/21] MM-46947 - Fix: Manifest parsing won't allow placeholders for custom pluginSettingType (#21004) * allow custom pluginSettingTypes to have a placeholder * i18n * lock it down with a test case Co-authored-by: Mattermod --- model/manifest.go | 5 +++-- model/manifest_test.go | 7 +++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/model/manifest.go b/model/manifest.go index 9b1551e921..9c5047a887 100644 --- a/model/manifest.go +++ b/model/manifest.go @@ -364,8 +364,9 @@ func (s *PluginSetting) isValid() error { pluginSettingType == Text || pluginSettingType == LongText || pluginSettingType == Number || - pluginSettingType == Username) { - return errors.New("should not set Placeholder for setting type not in text, generated or username") + pluginSettingType == Username || + pluginSettingType == Custom) { + return errors.New("should not set Placeholder for setting type not in text, generated, number, username, or custom") } if s.Options != nil { diff --git a/model/manifest_test.go b/model/manifest_test.go index de93569b53..8f42ab5f4b 100644 --- a/model/manifest_test.go +++ b/model/manifest_test.go @@ -184,6 +184,13 @@ func TestSettingIsValid(t *testing.T) { }, false, }, + "Placeholder is allowed for custom settings": { + PluginSetting{ + Type: "custom", + Placeholder: "some Text", + }, + false, + }, } { t.Run(name, func(t *testing.T) { err := test.Setting.isValid() From 393c46c2d8e0d8df24562ea72966de732f18ba10 Mon Sep 17 00:00:00 2001 From: Christopher Poile Date: Mon, 12 Sep 2022 16:42:22 -0400 Subject: [PATCH 20/21] calls v0.8.1 (#21006) --- Makefile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Makefile b/Makefile index a95dd79fdd..6ccc076dde 100644 --- a/Makefile +++ b/Makefile @@ -149,7 +149,7 @@ TEMPLATES_DIR=templates PLUGIN_PACKAGES ?= mattermost-plugin-antivirus-v0.1.2 PLUGIN_PACKAGES += mattermost-plugin-autolink-v1.2.2 PLUGIN_PACKAGES += mattermost-plugin-aws-SNS-v1.2.0 -PLUGIN_PACKAGES += mattermost-plugin-calls-v0.7.1 +PLUGIN_PACKAGES += mattermost-plugin-calls-v0.8.1 PLUGIN_PACKAGES += mattermost-plugin-channel-export-v1.0.0 PLUGIN_PACKAGES += mattermost-plugin-custom-attributes-v1.3.0 PLUGIN_PACKAGES += mattermost-plugin-github-v2.0.1 From e0a5b3620da0dd0e5c7d1f424033d2e847be84fc Mon Sep 17 00:00:00 2001 From: Harrison Healey Date: Mon, 12 Sep 2022 16:59:54 -0400 Subject: [PATCH 21/21] MM-46004 Add Focalboard webpack dev server to dev CSP policy (#20888) * MM-46004 Add Focalboard webpack dev server to dev CSP policy * Fix tests and prevent duplicated CSP values --- web/handlers.go | 31 ++++++++++++++++++++----------- web/handlers_test.go | 8 ++++---- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/web/handlers.go b/web/handlers.go index 777a8ebcad..f0faa7abe3 100644 --- a/web/handlers.go +++ b/web/handlers.go @@ -87,16 +87,17 @@ type Handler struct { } func generateDevCSP(c Context) string { + var devCSP []string + // Add unsafe-eval to the content security policy for faster source maps in development mode - devCSPMap := make(map[string]bool) if model.BuildNumber == "dev" { - devCSPMap["unsafe-eval"] = true + devCSP = append(devCSP, "'unsafe-eval'") } // Add unsafe-inline to unlock extensions like React & Redux DevTools in Firefox // see https://github.com/reduxjs/redux-devtools/issues/380 if model.BuildNumber == "dev" { - devCSPMap["unsafe-inline"] = true + devCSP = append(devCSP, "'unsafe-inline'") } // Add supported flags for debugging during development, even if not on a dev build. @@ -118,21 +119,29 @@ func generateDevCSP(c Context) string { // Honour only supported keys switch devFlagKey { case "unsafe-eval", "unsafe-inline": - devCSPMap[devFlagKey] = true + if model.BuildNumber == "dev" { + // These flags are added automatically for dev builds + continue + } + + devCSP = append(devCSP, "'"+devFlagKey+"'") default: c.Logger.Warn("Unrecognized developer flag", mlog.String("developer_flag", devFlagKVStr)) } } } - var devCSP string - supportedCSPFlags := []string{"unsafe-eval", "unsafe-inline"} - for _, devCSPFlag := range supportedCSPFlags { - if devCSPMap[devCSPFlag] { - devCSP += fmt.Sprintf(" '%s'", devCSPFlag) - } + + // Add flags for Webpack dev servers used by other products during development + if model.BuildNumber == "dev" { + // Focalboard runs on http://localhost:9006 + devCSP = append(devCSP, "http://localhost:9006") } - return devCSP + if len(devCSP) == 0 { + return "" + } + + return " " + strings.Join(devCSP, " ") } func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { diff --git a/web/handlers_test.go b/web/handlers_test.go index 4651bb99e6..906a464b39 100644 --- a/web/handlers_test.go +++ b/web/handlers_test.go @@ -388,7 +388,7 @@ func TestHandlerServeCSPHeader(t *testing.T) { response := httptest.NewRecorder() handler.ServeHTTP(response, request) assert.Equal(t, 200, response.Code) - assert.Equal(t, []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com 'unsafe-eval' 'unsafe-inline'"}, response.Header()["Content-Security-Policy"]) + assert.Equal(t, []string{"frame-ancestors 'self'; script-src 'self' cdn.rudderlabs.com 'unsafe-eval' 'unsafe-inline' http://localhost:9006"}, response.Header()["Content-Security-Policy"]) }) } @@ -411,9 +411,9 @@ func TestGenerateDevCSP(t *testing.T) { devCSP := generateDevCSP(*c) - assert.Equal(t, " 'unsafe-eval' 'unsafe-inline'", devCSP) - + assert.Equal(t, " 'unsafe-eval' 'unsafe-inline' http://localhost:9006", devCSP) }) + t.Run("allowed dev flags", func(t *testing.T) { th := Setup(t) defer th.TearDown() @@ -436,7 +436,7 @@ func TestGenerateDevCSP(t *testing.T) { devCSP := generateDevCSP(*c) - assert.Equal(t, " 'unsafe-eval' 'unsafe-inline'", devCSP) + assert.Equal(t, " 'unsafe-inline' 'unsafe-eval'", devCSP) }) t.Run("partial dev flags", func(t *testing.T) {