summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.vscode/launch.json18
-rw-r--r--Makefile10
-rw-r--r--api4/channel.go8
-rw-r--r--api4/channel_test.go12
-rw-r--r--api4/system_test.go17
-rw-r--r--app/analytics.go18
-rw-r--r--app/app.go8
-rw-r--r--app/app_test.go28
-rw-r--r--app/command_invite.go16
-rw-r--r--app/diagnostics.go24
-rw-r--r--app/email_batching.go2
-rw-r--r--app/file.go67
-rw-r--r--app/notification_test.go2
-rw-r--r--einterfaces/metrics.go1
-rw-r--r--model/client4.go2
-rw-r--r--model/version.go1
-rw-r--r--store/sqlstore/upgrade.go32
-rw-r--r--utils/config.go15
-rw-r--r--utils/config_test.go22
-rw-r--r--utils/utils.go9
20 files changed, 214 insertions, 98 deletions
diff --git a/.vscode/launch.json b/.vscode/launch.json
deleted file mode 100644
index 0a367a572..000000000
--- a/.vscode/launch.json
+++ /dev/null
@@ -1,18 +0,0 @@
-{
- "version": "0.2.0",
- "configurations": [
- {
- "name": "Launch",
- "type": "go",
- "request": "launch",
- "mode": "debug",
- "remotePath": "",
- "port": 2345,
- "host": "127.0.0.1",
- "program": "${workspaceRoot}/cmd/platform",
- "env": {},
- "args": [],
- "showLog": true
- }
- ]
-} \ No newline at end of file
diff --git a/Makefile b/Makefile
index 8f4b70fac..bff03b1ad 100644
--- a/Makefile
+++ b/Makefile
@@ -43,7 +43,7 @@ else
endif
# Golang Flags
-GOPATH ?= $(GOPATH:):./vendor
+GOPATH ?= $(shell go env GOPATH)
GOFLAGS ?= $(GOFLAGS:)
GO=go
GO_LINKER_FLAGS ?= -ldflags \
@@ -198,9 +198,9 @@ stop-docker: ## Stops the docker containers for local development.
fi
@if [ $(shell docker ps -a | grep -ci mattermost-minio) -eq 1 ]; then \
- echo stopping mattermost-minio; \
- docker stop mattermost-minio > /dev/null; \
- fi
+ echo stopping mattermost-minio; \
+ docker stop mattermost-minio > /dev/null; \
+ fi
@if [ $(shell docker ps -a | grep -ci mattermost-elasticsearch) -eq 1 ]; then \
echo stopping mattermost-elasticsearch; \
@@ -273,7 +273,7 @@ gofmt: ## Runs gofmt against all packages.
store-mocks: ## Creates mock files.
go get github.com/vektra/mockery/...
- GOPATH=$(shell go env GOPATH) $(shell go env GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.'
+ $(GOPATH)/bin/mockery -dir store -all -output store/storetest/mocks -note 'Regenerate this file using `make store-mocks`.'
update-jira-plugin: ## Updates Jira plugin.
go get github.com/mattermost/go-bindata/...
diff --git a/api4/channel.go b/api4/channel.go
index a19a1b094..e5101ada8 100644
--- a/api4/channel.go
+++ b/api4/channel.go
@@ -154,15 +154,13 @@ func convertChannelToPrivate(c *Context, w http.ResponseWriter, r *http.Request)
return
}
- if !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
- c.SetPermissionError(model.PERMISSION_MANAGE_SYSTEM)
- return
- }
-
oldPublicChannel, err := c.App.GetChannel(c.Params.ChannelId)
if err != nil {
c.Err = err
return
+ } else if !c.App.SessionHasPermissionToTeam(c.Session, oldPublicChannel.TeamId, model.PERMISSION_MANAGE_TEAM) {
+ c.SetPermissionError(model.PERMISSION_MANAGE_TEAM)
+ return
} else if oldPublicChannel.Type == model.CHANNEL_PRIVATE {
c.Err = model.NewAppError("convertChannelToPrivate", "api.channel.convert_channel_to_private.private_channel_error", nil, "", http.StatusBadRequest)
return
diff --git a/api4/channel_test.go b/api4/channel_test.go
index 7618b22d9..11d313291 100644
--- a/api4/channel_test.go
+++ b/api4/channel_test.go
@@ -915,10 +915,13 @@ func TestConvertChannelToPrivate(t *testing.T) {
CheckForbiddenStatus(t, resp)
th.LoginTeamAdmin()
- _, resp = Client.ConvertChannelToPrivate(publicChannel.Id)
- CheckForbiddenStatus(t, resp)
+ rchannel, resp := Client.ConvertChannelToPrivate(publicChannel.Id)
+ CheckOKStatus(t, resp)
+ if rchannel.Type != model.CHANNEL_PRIVATE {
+ t.Fatal("channel should be converted from public to private")
+ }
- rchannel, resp := th.SystemAdminClient.ConvertChannelToPrivate(privateChannel.Id)
+ rchannel, resp = th.SystemAdminClient.ConvertChannelToPrivate(privateChannel.Id)
CheckBadRequestStatus(t, resp)
if rchannel != nil {
t.Fatal("should not return a channel")
@@ -930,7 +933,8 @@ func TestConvertChannelToPrivate(t *testing.T) {
t.Fatal("should not return a channel")
}
- rchannel, resp = th.SystemAdminClient.ConvertChannelToPrivate(publicChannel.Id)
+ publicChannel2 := th.CreatePublicChannel()
+ rchannel, resp = th.SystemAdminClient.ConvertChannelToPrivate(publicChannel2.Id)
CheckOKStatus(t, resp)
if rchannel.Type != model.CHANNEL_PRIVATE {
t.Fatal("channel should be converted from public to private")
diff --git a/api4/system_test.go b/api4/system_test.go
index c0fde6c39..d4134f8e2 100644
--- a/api4/system_test.go
+++ b/api4/system_test.go
@@ -512,15 +512,18 @@ func TestGetAnalyticsOld(t *testing.T) {
CheckNoError(t, resp)
found := false
+ found2 := false
for _, row := range rows {
if row.Name == "unique_user_count" {
found = true
+ } else if row.Name == "inactive_user_count" {
+ found2 = true
+ assert.True(t, row.Value >= 0)
}
}
- if !found {
- t.Fatal("should return unique user count")
- }
+ assert.True(t, found, "should return unique user count")
+ assert.True(t, found2, "should return inactive user count")
_, resp = th.SystemAdminClient.GetAnalyticsOld("post_counts_day", "")
CheckNoError(t, resp)
@@ -531,9 +534,15 @@ func TestGetAnalyticsOld(t *testing.T) {
_, resp = th.SystemAdminClient.GetAnalyticsOld("extra_counts", "")
CheckNoError(t, resp)
- _, resp = th.SystemAdminClient.GetAnalyticsOld("", th.BasicTeam.Id)
+ rows, resp = th.SystemAdminClient.GetAnalyticsOld("", th.BasicTeam.Id)
CheckNoError(t, resp)
+ for _, row := range rows {
+ if row.Name == "inactive_user_count" {
+ assert.Equal(t, float64(-1), row.Value, "inactive user count should be -1 when team specified")
+ }
+ }
+
rows2, resp2 := th.SystemAdminClient.GetAnalyticsOld("standard", "")
CheckNoError(t, resp2)
assert.Equal(t, "total_websocket_connections", rows2[5].Name)
diff --git a/app/analytics.go b/app/analytics.go
index 7a32e78c1..a09b56538 100644
--- a/app/analytics.go
+++ b/app/analytics.go
@@ -30,7 +30,7 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo
}
if name == "standard" {
- var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 10)
+ var rows model.AnalyticsRows = make([]*model.AnalyticsRow, 11)
rows[0] = &model.AnalyticsRow{Name: "channel_open_count", Value: 0}
rows[1] = &model.AnalyticsRow{Name: "channel_private_count", Value: 0}
rows[2] = &model.AnalyticsRow{Name: "post_count", Value: 0}
@@ -41,13 +41,17 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo
rows[7] = &model.AnalyticsRow{Name: "total_read_db_connections", Value: 0}
rows[8] = &model.AnalyticsRow{Name: "daily_active_users", Value: 0}
rows[9] = &model.AnalyticsRow{Name: "monthly_active_users", Value: 0}
+ rows[10] = &model.AnalyticsRow{Name: "inactive_user_count", Value: 0}
openChan := a.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_OPEN)
privateChan := a.Srv.Store.Channel().AnalyticsTypeCount(teamId, model.CHANNEL_PRIVATE)
teamChan := a.Srv.Store.Team().AnalyticsTeamCount()
var userChan store.StoreChannel
- if teamId != "" {
+ var userInactiveChan store.StoreChannel
+ if teamId == "" {
+ userInactiveChan = a.Srv.Store.User().AnalyticsGetInactiveUsersCount()
+ } else {
userChan = a.Srv.Store.User().AnalyticsUniqueUserCount(teamId)
}
@@ -91,6 +95,16 @@ func (a *App) GetAnalytics(name string, teamId string) (model.AnalyticsRows, *mo
}
}
+ if userInactiveChan == nil {
+ rows[10].Value = -1
+ } else {
+ if r := <-userInactiveChan; r.Err != nil {
+ return nil, r.Err
+ } else {
+ rows[10].Value = float64(r.Data.(int64))
+ }
+ }
+
if r := <-teamChan; r.Err != nil {
return nil, r.Err
} else {
diff --git a/app/app.go b/app/app.go
index b31f67d6b..2cdf333c1 100644
--- a/app/app.go
+++ b/app/app.go
@@ -560,6 +560,14 @@ func (a *App) DoAdvancedPermissionsMigration() {
return
}
+ config := a.Config()
+ if *config.ServiceSettings.AllowEditPost == model.ALLOW_EDIT_POST_ALWAYS {
+ *config.ServiceSettings.PostEditTimeLimit = -1
+ if err := a.SaveConfig(config, true); err != nil {
+ mlog.Error("Failed to update config in Advanced Permissions Phase 1 Migration.", mlog.String("error", err.Error()))
+ }
+ }
+
system := model.System{
Name: ADVANCED_PERMISSIONS_MIGRATION_KEY,
Value: "true",
diff --git a/app/app_test.go b/app/app_test.go
index ccf7faeeb..cb6917073 100644
--- a/app/app_test.go
+++ b/app/app_test.go
@@ -426,4 +426,32 @@ func TestDoAdvancedPermissionsMigration(t *testing.T) {
assert.Nil(t, err)
assert.Equal(t, permissions, role.Permissions)
}
+
+ // Check that the config setting for "always" and "time_limit" edit posts is updated correctly.
+ th.ResetRoleMigration()
+
+ config := th.App.GetConfig()
+ *config.ServiceSettings.AllowEditPost = "always"
+ *config.ServiceSettings.PostEditTimeLimit = 300
+ th.App.SaveConfig(config, false)
+
+ th.App.DoAdvancedPermissionsMigration()
+ config = th.App.GetConfig()
+ assert.Equal(t, -1, *config.ServiceSettings.PostEditTimeLimit)
+
+ th.ResetRoleMigration()
+
+ config = th.App.GetConfig()
+ *config.ServiceSettings.AllowEditPost = "time_limit"
+ *config.ServiceSettings.PostEditTimeLimit = 300
+ th.App.SaveConfig(config, false)
+
+ th.App.DoAdvancedPermissionsMigration()
+ config = th.App.GetConfig()
+ assert.Equal(t, 300, *config.ServiceSettings.PostEditTimeLimit)
+
+ config = th.App.GetConfig()
+ *config.ServiceSettings.AllowEditPost = "always"
+ *config.ServiceSettings.PostEditTimeLimit = 300
+ th.App.SaveConfig(config, false)
}
diff --git a/app/command_invite.go b/app/command_invite.go
index 9045365ad..4b76c0c45 100644
--- a/app/command_invite.go
+++ b/app/command_invite.go
@@ -4,7 +4,6 @@
package app
import (
- "fmt"
"strings"
"github.com/mattermost/mattermost-server/mlog"
@@ -42,8 +41,6 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str
return &model.CommandResponse{Text: args.T("api.command_invite.missing_message.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
- mlog.Debug(fmt.Sprint(message))
-
splitMessage := strings.SplitN(message, " ", 2)
targetUsername := splitMessage[0]
targetUsername = strings.TrimPrefix(targetUsername, "@")
@@ -77,12 +74,7 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str
return &model.CommandResponse{Text: args.T("api.command_invite.directchannel.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
- // Check if user is already in the channel
- _, err = a.GetChannelMember(channelToJoin.Id, userProfile.Id)
- if err == nil {
- return &model.CommandResponse{Text: args.T("api.command_invite.user_already_in_channel.app_error", map[string]interface{}{"User": userProfile.Username}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
- }
-
+ // Check Permissions
if channelToJoin.Type == model.CHANNEL_OPEN && !a.SessionHasPermissionToChannel(args.Session, channelToJoin.Id, model.PERMISSION_MANAGE_PUBLIC_CHANNEL_MEMBERS) {
return &model.CommandResponse{Text: args.T("api.command_invite.permission.app_error", map[string]interface{}{"User": userProfile.Username, "Channel": channelToJoin.Name}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
@@ -91,6 +83,12 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str
return &model.CommandResponse{Text: args.T("api.command_invite.permission.app_error", map[string]interface{}{"User": userProfile.Username, "Channel": channelToJoin.Name}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
+ // Check if user is already in the channel
+ _, err = a.GetChannelMember(channelToJoin.Id, userProfile.Id)
+ if err == nil {
+ return &model.CommandResponse{Text: args.T("api.command_invite.user_already_in_channel.app_error", map[string]interface{}{"User": userProfile.Username}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
+ }
+
if _, err := a.AddChannelMember(userProfile.Id, channelToJoin, args.Session.UserId, ""); err != nil {
return &model.CommandResponse{Text: args.T("api.command_invite.fail.app_error"), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL}
}
diff --git a/app/diagnostics.go b/app/diagnostics.go
index 6237f5f83..7dcea839e 100644
--- a/app/diagnostics.go
+++ b/app/diagnostics.go
@@ -138,7 +138,7 @@ func (a *App) trackActivity() {
activeUserCount = ucr.Data.(int64)
}
- if iucr := <-a.Srv.Store.Status().GetTotalActiveUsersCount(); iucr.Err == nil {
+ if iucr := <-a.Srv.Store.User().AnalyticsGetInactiveUsersCount(); iucr.Err == nil {
inactiveUserCount = iucr.Data.(int64)
}
@@ -171,17 +171,17 @@ func (a *App) trackActivity() {
}
a.SendDiagnostic(TRACK_ACTIVITY, map[string]interface{}{
- "registered_users": userCount,
- "active_users": activeUserCount,
- "registered_inactive_users": inactiveUserCount,
- "teams": teamCount,
- "public_channels": publicChannelCount,
- "private_channels": privateChannelCount,
- "direct_message_channels": directChannelCount,
- "public_channels_deleted": deletedPublicChannelCount,
- "private_channels_deleted": deletedPrivateChannelCount,
- "posts": postsCount,
- "used_apiv3": atomic.LoadInt32(model.UsedApiV3) == 1,
+ "registered_users": userCount,
+ "active_users": activeUserCount,
+ "registered_deactivated_users": inactiveUserCount,
+ "teams": teamCount,
+ "public_channels": publicChannelCount,
+ "private_channels": privateChannelCount,
+ "direct_message_channels": directChannelCount,
+ "public_channels_deleted": deletedPublicChannelCount,
+ "private_channels_deleted": deletedPrivateChannelCount,
+ "posts": postsCount,
+ "used_apiv3": atomic.LoadInt32(model.UsedApiV3) == 1,
})
atomic.StoreInt32(model.UsedApiV3, 0)
diff --git a/app/email_batching.go b/app/email_batching.go
index e1ea7abb5..e75979bca 100644
--- a/app/email_batching.go
+++ b/app/email_batching.go
@@ -250,7 +250,7 @@ func (a *App) sendBatchedEmailNotification(userId string, notifications []*batch
body.Props["BodyText"] = translateFunc("api.email_batching.send_batched_email_notification.body_text", len(notifications))
if err := a.SendMail(user.Email, subject, body.Render()); err != nil {
- mlog.Warn(fmt.Sprint("api.email_batchings.send_batched_email_notification.send.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", user.Email, err))
+ mlog.Warn(fmt.Sprintf("Unable to send batched email notification err=%v", err), mlog.String("email", user.Email))
}
}
diff --git a/app/file.go b/app/file.go
index a1addd7ac..87e1986a2 100644
--- a/app/file.go
+++ b/app/file.go
@@ -98,7 +98,7 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin
// Find the path from the Filename of the form /{channelId}/{userId}/{uid}/{nameWithExtension}
split := strings.SplitN(filename, "/", 5)
if len(split) < 5 {
- mlog.Error(fmt.Sprintf("Unable to decipher filename when migrating post to use FileInfos, post_id=%v, filename=%v", post.Id, filename), mlog.String("post_id", post.Id))
+ mlog.Error("Unable to decipher filename when migrating post to use FileInfos", mlog.String("post_id", post.Id), mlog.String("filename", filename))
return nil
}
@@ -108,7 +108,13 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin
name, _ := url.QueryUnescape(split[4])
if split[0] != "" || split[1] != post.ChannelId || split[2] != post.UserId || strings.Contains(split[4], "/") {
- mlog.Warn(fmt.Sprintf("Found an unusual filename when migrating post to use FileInfos, post_id=%v, channel_id=%v, user_id=%v, filename=%v", post.Id, post.ChannelId, post.UserId, filename), mlog.String("post_id", post.Id))
+ mlog.Warn(
+ "Found an unusual filename when migrating post to use FileInfos",
+ mlog.String("post_id", post.Id),
+ mlog.String("channel_id", post.ChannelId),
+ mlog.String("user_id", post.UserId),
+ mlog.String("filename", filename),
+ )
}
pathPrefix := fmt.Sprintf("teams/%s/channels/%s/users/%s/%s/", teamId, channelId, userId, oldId)
@@ -117,13 +123,22 @@ func (a *App) GetInfoForFilename(post *model.Post, teamId string, filename strin
// Open the file and populate the fields of the FileInfo
var info *model.FileInfo
if data, err := a.ReadFile(path); err != nil {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.file_not_found.error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, filename, path, err), mlog.String("post_id", post.Id))
+ mlog.Error(
+ fmt.Sprintf("File not found when migrating post to use FileInfos, err=%v", err),
+ mlog.String("post_id", post.Id),
+ mlog.String("filename", filename),
+ mlog.String("path", path),
+ )
return nil
} else {
var err *model.AppError
info, err = model.GetInfoForBytes(name, data)
if err != nil {
- mlog.Warn(fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, post_id=%v, filename=%v, err=%v", post.Id, filename, err), mlog.String("post_id", post.Id))
+ mlog.Warn(
+ fmt.Sprintf("Unable to fully decode file info when migrating post to use FileInfos, err=%v", err),
+ mlog.String("post_id", post.Id),
+ mlog.String("filename", filename),
+ )
}
}
@@ -151,7 +166,7 @@ func (a *App) FindTeamIdForFilename(post *model.Post, filename string) string {
// This post is in a direct channel so we need to figure out what team the files are stored under.
if result := <-a.Srv.Store.Team().GetTeamsByUserId(post.UserId); result.Err != nil {
- mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfos, post_id=%v, err=%v", post.Id, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(fmt.Sprintf("Unable to get teams when migrating post to use FileInfo, err=%v", result.Err), mlog.String("post_id", post.Id))
} else if teams := result.Data.([]*model.Team); len(teams) == 1 {
// The user has only one team so the post must've been sent from it
return teams[0].Id
@@ -173,7 +188,7 @@ var fileMigrationLock sync.Mutex
// Creates and stores FileInfos for a post created before the FileInfos table existed.
func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
if len(post.Filenames) == 0 {
- mlog.Warn(fmt.Sprintf("Unable to migrate post to use FileInfos with an empty Filenames field, post_id=%v", post.Id), mlog.String("post_id", post.Id))
+ mlog.Warn("Unable to migrate post to use FileInfos with an empty Filenames field", mlog.String("post_id", post.Id))
return []*model.FileInfo{}
}
@@ -184,7 +199,11 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
var channel *model.Channel
if result := <-cchan; result.Err != nil {
- mlog.Error(fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, post_id=%v, channel_id=%v, err=%v", post.Id, post.ChannelId, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(
+ fmt.Sprintf("Unable to get channel when migrating post to use FileInfos, err=%v", result.Err),
+ mlog.String("post_id", post.Id),
+ mlog.String("channel_id", post.ChannelId),
+ )
return []*model.FileInfo{}
} else {
channel = result.Data.(*model.Channel)
@@ -202,7 +221,10 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
// Create FileInfo objects for this post
infos := make([]*model.FileInfo, 0, len(filenames))
if teamId == "" {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.team_id.error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, filenames), mlog.String("post_id", post.Id))
+ mlog.Error(
+ fmt.Sprintf("Unable to find team id for files when migrating post to use FileInfos, filenames=%v", filenames),
+ mlog.String("post_id", post.Id),
+ )
} else {
for _, filename := range filenames {
info := a.GetInfoForFilename(post, teamId, filename)
@@ -219,26 +241,31 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
defer fileMigrationLock.Unlock()
if result := <-a.Srv.Store.Post().Get(post.Id); result.Err != nil {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.get_post_again.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(fmt.Sprintf("Unable to get post when migrating post to use FileInfos, err=%v", result.Err), mlog.String("post_id", post.Id))
return []*model.FileInfo{}
} else if newPost := result.Data.(*model.PostList).Posts[post.Id]; len(newPost.Filenames) != len(post.Filenames) {
// Another thread has already created FileInfos for this post, so just return those
if result := <-a.Srv.Store.FileInfo().GetForPost(post.Id, true, false); result.Err != nil {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.get_post_file_infos_again.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(fmt.Sprintf("Unable to get FileInfos for migrated post, err=%v", result.Err), mlog.String("post_id", post.Id))
return []*model.FileInfo{}
} else {
- mlog.Debug(fmt.Sprintf("Post already migrated to use FileInfos, post_id=%v", post.Id), mlog.String("post_id", post.Id))
+ mlog.Debug("Post already migrated to use FileInfos", mlog.String("post_id", post.Id))
return result.Data.([]*model.FileInfo)
}
}
- mlog.Debug(fmt.Sprintf("Migrating post to use FileInfos, post_id=%v", post.Id), mlog.String("post_id", post.Id))
+ mlog.Debug("Migrating post to use FileInfos", mlog.String("post_id", post.Id))
savedInfos := make([]*model.FileInfo, 0, len(infos))
fileIds := make([]string, 0, len(filenames))
for _, info := range infos {
if result := <-a.Srv.Store.FileInfo().Save(info); result.Err != nil {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.save_file_info.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, info.Id, info.Path, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(
+ fmt.Sprintf("Unable to save file info when migrating post to use FileInfos, err=%v", result.Err),
+ mlog.String("post_id", post.Id),
+ mlog.String("file_info_id", info.Id),
+ mlog.String("file_info_path", info.Path),
+ )
continue
}
@@ -255,7 +282,7 @@ func (a *App) MigrateFilenamesToFileInfos(post *model.Post) []*model.FileInfo {
// Update Posts to clear Filenames and set FileIds
if result := <-a.Srv.Store.Post().Update(newPost, post); result.Err != nil {
- mlog.Error(fmt.Sprint("api.file.migrate_filenames_to_file_infos.save_post.app_error FIXME: NOT FOUND IN TRANSLATIONS FILE", post.Id, newPost.FileIds, post.Filenames, result.Err), mlog.String("post_id", post.Id))
+ mlog.Error(fmt.Sprintf("Unable to save migrated post when migrating to use FileInfos, new_file_ids=%v, old_filenames=%v, err=%v", newPost.FileIds, post.Filenames, result.Err), mlog.String("post_id", post.Id))
return []*model.FileInfo{}
} else {
return savedInfos
@@ -361,6 +388,14 @@ func (a *App) DoUploadFile(now time.Time, rawTeamId string, rawChannelId string,
return nil, err
}
+ if orientation, err := getImageOrientation(bytes.NewReader(data)); err == nil &&
+ (orientation == RotatedCWMirrored ||
+ orientation == RotatedCCW ||
+ orientation == RotatedCCWMirrored ||
+ orientation == RotatedCW) {
+ info.Width, info.Height = info.Height, info.Width
+ }
+
info.Id = model.NewId()
info.CreatorId = userId
@@ -514,12 +549,12 @@ func (a *App) generatePreviewImage(img image.Image, previewPath string, width in
buf := new(bytes.Buffer)
if err := jpeg.Encode(buf, preview, &jpeg.Options{Quality: 90}); err != nil {
- mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg path=%v err=%v", previewPath, err))
+ mlog.Error(fmt.Sprintf("Unable to encode image as preview jpg err=%v", err), mlog.String("path", previewPath))
return
}
if err := a.WriteFile(buf.Bytes(), previewPath); err != nil {
- mlog.Error(fmt.Sprintf("Unable to upload preview path=%v err=%v", previewPath, err))
+ mlog.Error(fmt.Sprintf("Unable to upload preview err=%v", err), mlog.String("path", previewPath))
return
}
}
diff --git a/app/notification_test.go b/app/notification_test.go
index 24784940e..3b8b4adf5 100644
--- a/app/notification_test.go
+++ b/app/notification_test.go
@@ -37,7 +37,7 @@ func TestSendNotifications(t *testing.T) {
} else if mentions == nil {
t.Log(mentions)
t.Fatal("user should have been mentioned")
- } else if mentions[0] != th.BasicUser2.Id {
+ } else if !utils.StringInSlice(th.BasicUser2.Id, mentions) {
t.Log(mentions)
t.Fatal("user should have been mentioned")
}
diff --git a/einterfaces/metrics.go b/einterfaces/metrics.go
index 3f709eb99..7c2c60a14 100644
--- a/einterfaces/metrics.go
+++ b/einterfaces/metrics.go
@@ -20,6 +20,7 @@ type MetricsInterface interface {
IncrementClusterRequest()
ObserveClusterRequestDuration(elapsed float64)
+ IncrementClusterEventType(eventType string)
IncrementLogin()
IncrementLoginFail()
diff --git a/model/client4.go b/model/client4.go
index d4410a5c3..2bfdd049f 100644
--- a/model/client4.go
+++ b/model/client4.go
@@ -2375,7 +2375,7 @@ func (c *Client4) RemoveLicenseFile() (bool, *Response) {
// and defaults to "standard". The "teamId" argument is optional and will limit results
// to a specific team.
func (c *Client4) GetAnalyticsOld(name, teamId string) (AnalyticsRows, *Response) {
- query := fmt.Sprintf("?name=%v&teamId=%v", name, teamId)
+ query := fmt.Sprintf("?name=%v&team_id=%v", name, teamId)
if r, err := c.DoApiGet(c.GetAnalyticsRoute()+"/old"+query, ""); err != nil {
return nil, BuildErrorResponse(r, err)
} else {
diff --git a/model/version.go b/model/version.go
index a58ca4df6..581d03dea 100644
--- a/model/version.go
+++ b/model/version.go
@@ -13,6 +13,7 @@ import (
// It should be maintained in chronological order with most current
// release at the front of the list.
var versions = []string{
+ "4.10.0",
"4.9.0",
"4.8.1",
"4.8.0",
diff --git a/store/sqlstore/upgrade.go b/store/sqlstore/upgrade.go
index 3fc3ba79b..45515178d 100644
--- a/store/sqlstore/upgrade.go
+++ b/store/sqlstore/upgrade.go
@@ -15,6 +15,7 @@ import (
)
const (
+ VERSION_5_0_0 = "5.0.0"
VERSION_4_10_0 = "4.10.0"
VERSION_4_9_0 = "4.9.0"
VERSION_4_8_1 = "4.8.1"
@@ -76,6 +77,7 @@ func UpgradeDatabase(sqlStore SqlStore) {
UpgradeDatabaseToVersion481(sqlStore)
UpgradeDatabaseToVersion49(sqlStore)
UpgradeDatabaseToVersion410(sqlStore)
+ UpgradeDatabaseToVersion50(sqlStore)
// If the SchemaVersion is empty this this is the first time it has ran
// so lets set it to the current version.
@@ -411,26 +413,20 @@ func UpgradeDatabaseToVersion49(sqlStore SqlStore) {
}
func UpgradeDatabaseToVersion410(sqlStore SqlStore) {
- // TODO: Uncomment following condition when version 4.10.0 is released
- //if shouldPerformUpgrade(sqlStore, VERSION_4_9_0, VERSION_4_10_0) {
+ if shouldPerformUpgrade(sqlStore, VERSION_4_9_0, VERSION_4_10_0) {
- sqlStore.RemoveIndexIfExists("Name_2", "Channels")
- sqlStore.RemoveIndexIfExists("Name_2", "Emoji")
- sqlStore.RemoveIndexIfExists("ClientId_2", "OAuthAccessData")
+ sqlStore.RemoveIndexIfExists("Name_2", "Channels")
+ sqlStore.RemoveIndexIfExists("Name_2", "Emoji")
+ sqlStore.RemoveIndexIfExists("ClientId_2", "OAuthAccessData")
- // saveSchemaVersion(sqlStore, VERSION_4_10_0)
- sqlStore.CreateColumnIfNotExistsNoDefault("Teams", "SchemeId", "varchar(26)", "varchar(26)")
- sqlStore.CreateColumnIfNotExistsNoDefault("Channels", "SchemeId", "varchar(26)", "varchar(26)")
-
- sqlStore.CreateColumnIfNotExistsNoDefault("TeamMembers", "SchemeUser", "boolean", "boolean")
- sqlStore.CreateColumnIfNotExistsNoDefault("TeamMembers", "SchemeAdmin", "boolean", "boolean")
- sqlStore.CreateColumnIfNotExistsNoDefault("ChannelMembers", "SchemeUser", "boolean", "boolean")
- sqlStore.CreateColumnIfNotExistsNoDefault("ChannelMembers", "SchemeAdmin", "boolean", "boolean")
-
- sqlStore.CreateColumnIfNotExists("Roles", "BuiltIn", "boolean", "boolean", "0")
- sqlStore.GetMaster().Exec("UPDATE Roles SET BuiltIn=true")
- sqlStore.GetMaster().Exec("UPDATE Roles SET SchemeManaged=false WHERE Name NOT IN ('system_user', 'system_admin', 'team_user', 'team_admin', 'channel_user', 'channel_admin')")
+ saveSchemaVersion(sqlStore, VERSION_4_10_0)
+ sqlStore.GetMaster().Exec("UPDATE Users SET AuthData=LOWER(AuthData) WHERE AuthService = 'saml'")
+ }
+}
- // saveSchemaVersion(sqlStore, VERSION_4_9_0)
+func UpgradeDatabaseToVersion50(sqlStore SqlStore) {
+ // TODO: Uncomment following condition when version 3.10.0 is released
+ //if shouldPerformUpgrade(sqlStore, VERSION_4_10_0, VERSION_5_0_0) {
+ // saveSchemaVersion(sqlStore, VERSION_5_0_0)
//}
}
diff --git a/utils/config.go b/utils/config.go
index 34cd0ed9f..3827cf4ee 100644
--- a/utils/config.go
+++ b/utils/config.go
@@ -217,7 +217,7 @@ func newViper(allowEnvironmentOverrides bool) *viper.Viper {
// Set zeroed defaults for all the config settings so that Viper knows what environment variables
// it needs to be looking for. The correct defaults will later be applied using Config.SetDefaults.
- defaults := flattenStructToMap(structToMap(reflect.TypeOf(model.Config{})))
+ defaults := getDefaultsFromStruct(model.Config{})
for key, value := range defaults {
v.SetDefault(key, value)
@@ -226,6 +226,10 @@ func newViper(allowEnvironmentOverrides bool) *viper.Viper {
return v
}
+func getDefaultsFromStruct(s interface{}) map[string]interface{} {
+ return flattenStructToMap(structToMap(reflect.TypeOf(s)))
+}
+
// Converts a struct type into a nested map with keys matching the struct's fields and values
// matching the zeroed value of the corresponding field.
func structToMap(t reflect.Type) (out map[string]interface{}) {
@@ -251,7 +255,14 @@ func structToMap(t reflect.Type) (out map[string]interface{}) {
case reflect.Struct:
value = structToMap(field.Type)
case reflect.Ptr:
- value = nil
+ indirectType := field.Type.Elem()
+
+ if indirectType.Kind() == reflect.Struct {
+ // Follow pointers to structs since we need to define defaults for their fields
+ value = structToMap(indirectType)
+ } else {
+ value = nil
+ }
default:
value = reflect.Zero(field.Type).Interface()
}
diff --git a/utils/config_test.go b/utils/config_test.go
index ec66a30f0..75bbc420f 100644
--- a/utils/config_test.go
+++ b/utils/config_test.go
@@ -396,3 +396,25 @@ func sToP(s string) *string {
func bToP(b bool) *bool {
return &b
}
+
+func TestGetDefaultsFromStruct(t *testing.T) {
+ s := struct {
+ TestSettings struct {
+ IntValue int
+ BoolValue bool
+ StringValue string
+ }
+ PointerToTestSettings *struct {
+ Value int
+ }
+ }{}
+
+ defaults := getDefaultsFromStruct(s)
+
+ assert.Equal(t, defaults["TestSettings.IntValue"], 0)
+ assert.Equal(t, defaults["TestSettings.BoolValue"], false)
+ assert.Equal(t, defaults["TestSettings.StringValue"], "")
+ assert.Equal(t, defaults["PointerToTestSettings.Value"], 0)
+ assert.NotContains(t, defaults, "PointerToTestSettings")
+ assert.Len(t, defaults, 4)
+}
diff --git a/utils/utils.go b/utils/utils.go
index 595a9d2ba..b156f9934 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -13,6 +13,15 @@ import (
"github.com/mattermost/mattermost-server/model"
)
+func StringInSlice(a string, slice []string) bool {
+ for _, b := range slice {
+ if b == a {
+ return true
+ }
+ }
+ return false
+}
+
func StringArrayIntersection(arr1, arr2 []string) []string {
arrMap := map[string]bool{}
result := []string{}