From 5872bf9c2f9b81c7aad761d40a6970f6267f1424 Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Mon, 30 Jul 2018 15:06:08 -0400 Subject: Pr 9039 (#9187) * MM-11065: Allow to search and get archived channels from the API * Fixing more tests * Add some unit tests * Add includeDeleted parameter to session permissions check function * More test fixing * Adding archive channels list in channels search * Add restriction for archived channel edition * Reverting permissions checks modification * Changed the query parameter to include_deleted * Enable search archive channels as true by default * Adding tests for verify search on deleted channels * Allowing to override archive channels during the imports * Fixed test * Search in archive channels from the API must be explicitly requested * Removing includeDeleted parameter from GetChannelByName and GetChannelByNameForTeam * Back to ViewArchivedChannels config * Fixing tests * Reverting GetChannelByName parameter * Add include deleted parameter on GetChannel functions in plugins api * Fixing tests --- api4/apitestlib.go | 5 + api4/channel.go | 10 +- api4/channel_test.go | 28 ++++- api4/post.go | 4 +- api4/post_test.go | 11 +- api4/reaction_test.go | 4 +- app/authorization.go | 2 +- app/channel.go | 53 ++++++--- app/channel_test.go | 2 +- app/command_invite.go | 2 +- app/import.go | 4 +- app/import_test.go | 12 +-- app/plugin_api.go | 8 +- app/post.go | 8 +- app/team.go | 2 +- app/user.go | 2 +- app/web_conn.go | 2 +- config/default.json | 1 + manualtesting/manual_testing.go | 2 +- model/client4.go | 29 +++++ model/config.go | 4 + model/search_params.go | 11 +- plugin/api.go | 4 +- plugin/client_rpc_generated.go | 18 ++-- plugin/example_help_test.go | 2 +- plugin/plugintest/api.go | 28 ++--- store/sqlstore/channel_store.go | 48 +++++++-- store/sqlstore/post_store.go | 7 +- store/store.go | 8 +- store/storetest/channel_store.go | 45 ++++---- store/storetest/mocks/ChannelStore.go | 24 ++--- store/storetest/post_store.go | 197 +++++++++++++++++++++++----------- utils/config.go | 1 + 33 files changed, 401 insertions(+), 187 deletions(-) diff --git a/api4/apitestlib.go b/api4/apitestlib.go index fce44cfa1..652a8f882 100644 --- a/api4/apitestlib.go +++ b/api4/apitestlib.go @@ -44,6 +44,7 @@ type TestHelper struct { BasicTeam *model.Team BasicChannel *model.Channel BasicPrivateChannel *model.Channel + BasicDeletedChannel *model.Channel BasicChannel2 *model.Channel BasicPost *model.Post @@ -250,6 +251,7 @@ func (me *TestHelper) InitBasic() *TestHelper { me.BasicTeam = me.CreateTeam() me.BasicChannel = me.CreatePublicChannel() me.BasicPrivateChannel = me.CreatePrivateChannel() + me.BasicDeletedChannel = me.CreatePublicChannel() me.BasicChannel2 = me.CreatePublicChannel() me.BasicPost = me.CreatePost() me.BasicUser = me.CreateUser() @@ -262,7 +264,10 @@ func (me *TestHelper) InitBasic() *TestHelper { me.App.AddUserToChannel(me.BasicUser2, me.BasicChannel2) me.App.AddUserToChannel(me.BasicUser, me.BasicPrivateChannel) me.App.AddUserToChannel(me.BasicUser2, me.BasicPrivateChannel) + me.App.AddUserToChannel(me.BasicUser, me.BasicDeletedChannel) + me.App.AddUserToChannel(me.BasicUser2, me.BasicDeletedChannel) me.App.UpdateUserRoles(me.BasicUser.Id, model.SYSTEM_USER_ROLE_ID, false) + me.Client.DeleteChannel(me.BasicDeletedChannel.Id) me.LoginBasic() return me diff --git a/api4/channel.go b/api4/channel.go index db34bf03c..0b8470975 100644 --- a/api4/channel.go +++ b/api4/channel.go @@ -589,7 +589,7 @@ func getChannelsForTeamForUser(c *Context, w http.ResponseWriter, r *http.Reques return } - channels, err := c.App.GetChannelsForUser(c.Params.TeamId, c.Params.UserId) + channels, err := c.App.GetChannelsForUser(c.Params.TeamId, c.Params.UserId, false) if err != nil { c.Err = err return @@ -709,7 +709,9 @@ func getChannelByName(c *Context, w http.ResponseWriter, r *http.Request) { var channel *model.Channel var err *model.AppError - if channel, err = c.App.GetChannelByName(c.Params.ChannelName, c.Params.TeamId); err != nil { + includeDeleted := r.URL.Query().Get("include_deleted") == "true" + + if channel, err = c.App.GetChannelByName(c.Params.ChannelName, c.Params.TeamId, includeDeleted); err != nil { c.Err = err return } @@ -744,7 +746,9 @@ func getChannelByNameForTeamName(c *Context, w http.ResponseWriter, r *http.Requ var channel *model.Channel var err *model.AppError - if channel, err = c.App.GetChannelByNameForTeamName(c.Params.ChannelName, c.Params.TeamName); err != nil { + includeDeleted := r.URL.Query().Get("include_deleted") == "true" + + if channel, err = c.App.GetChannelByNameForTeamName(c.Params.ChannelName, c.Params.TeamName, includeDeleted); err != nil { c.Err = err return } diff --git a/api4/channel_test.go b/api4/channel_test.go index 0d8fbe4d5..4645a0ab6 100644 --- a/api4/channel_test.go +++ b/api4/channel_test.go @@ -867,7 +867,7 @@ func TestDeleteChannel(t *testing.T) { CheckNoError(t, resp) // default channel cannot be deleted. - defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, team.Id) + defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, team.Id, false) pass, resp = Client.DeleteChannel(defaultChannel.Id) CheckBadRequestStatus(t, resp) @@ -998,7 +998,7 @@ func TestConvertChannelToPrivate(t *testing.T) { defer th.TearDown() Client := th.Client - defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id) + defaultChannel, _ := th.App.GetChannelByName(model.DEFAULT_CHANNEL, th.BasicTeam.Id, false) _, resp := Client.ConvertChannelToPrivate(defaultChannel.Id) CheckForbiddenStatus(t, resp) @@ -1115,6 +1115,16 @@ func TestGetChannelByName(t *testing.T) { _, resp = Client.GetChannelByName(strings.ToUpper(th.BasicPrivateChannel.Name), th.BasicTeam.Id, "") CheckNoError(t, resp) + _, resp = Client.GetChannelByName(th.BasicDeletedChannel.Name, th.BasicTeam.Id, "") + CheckNotFoundStatus(t, resp) + + channel, resp = Client.GetChannelByNameIncludeDeleted(th.BasicDeletedChannel.Name, th.BasicTeam.Id, "") + CheckNoError(t, resp) + + if channel.Name != th.BasicDeletedChannel.Name { + t.Fatal("names did not match") + } + Client.RemoveUserFromChannel(th.BasicChannel.Id, th.BasicUser.Id) _, resp = Client.GetChannelByName(th.BasicChannel.Name, th.BasicTeam.Id, "") CheckNoError(t, resp) @@ -1157,6 +1167,16 @@ func TestGetChannelByNameForTeamName(t *testing.T) { _, resp = Client.GetChannelByNameForTeamName(th.BasicChannel.Name, th.BasicTeam.Name, "") CheckNoError(t, resp) + _, resp = Client.GetChannelByNameForTeamName(th.BasicDeletedChannel.Name, th.BasicTeam.Name, "") + CheckNotFoundStatus(t, resp) + + channel, resp = Client.GetChannelByNameForTeamNameIncludeDeleted(th.BasicDeletedChannel.Name, th.BasicTeam.Name, "") + CheckNoError(t, resp) + + if channel.Name != th.BasicDeletedChannel.Name { + t.Fatal("names did not match") + } + _, resp = Client.GetChannelByNameForTeamName(th.BasicChannel.Name, model.NewRandomString(15), "") CheckNotFoundStatus(t, resp) @@ -1330,8 +1350,8 @@ func TestGetChannelMembersForUser(t *testing.T) { members, resp := Client.GetChannelMembersForUser(th.BasicUser.Id, th.BasicTeam.Id, "") CheckNoError(t, resp) - if len(*members) != 5 { - t.Fatal("should have 5 members on team") + if len(*members) != 6 { + t.Fatal("should have 6 members on team") } _, resp = Client.GetChannelMembersForUser("", th.BasicTeam.Id, "") diff --git a/api4/post.go b/api4/post.go index b76e89964..12664cc24 100644 --- a/api4/post.go +++ b/api4/post.go @@ -330,6 +330,8 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) { return } + includeDeletedChannels := r.URL.Query().Get("include_deleted_channels") == "true" + props := model.StringInterfaceFromJson(r.Body) terms, ok := props["terms"].(string) if !ok || len(terms) == 0 { @@ -341,7 +343,7 @@ func searchPosts(c *Context, w http.ResponseWriter, r *http.Request) { startTime := time.Now() - results, err := c.App.SearchPostsInTeam(terms, c.Session.UserId, c.Params.TeamId, isOrSearch) + results, err := c.App.SearchPostsInTeam(terms, c.Session.UserId, c.Params.TeamId, isOrSearch, includeDeletedChannels) elapsedTime := float64(time.Since(startTime)) / float64(time.Second) metrics := c.App.Metrics diff --git a/api4/post_test.go b/api4/post_test.go index 720fdc410..036a64fc7 100644 --- a/api4/post_test.go +++ b/api4/post_test.go @@ -1288,6 +1288,10 @@ func TestSearchPosts(t *testing.T) { message = "hashtag for post4" _ = th.CreateMessagePost(message) + archivedChannel := th.CreatePublicChannel() + _ = th.CreateMessagePostWithClient(th.Client, archivedChannel, "#hashtag for post3") + th.Client.DeleteChannel(archivedChannel.Id) + posts, resp := Client.SearchPosts(th.BasicTeam.Id, "search", false) CheckNoError(t, resp) if len(posts.Order) != 3 { @@ -1306,6 +1310,12 @@ func TestSearchPosts(t *testing.T) { t.Fatal("wrong search") } + posts, resp = Client.SearchPostsIncludeDeletedChannels(th.BasicTeam.Id, "#hashtag", false) + CheckNoError(t, resp) + if len(posts.Order) != 2 { + t.Fatal("wrong search") + } + if posts, resp = Client.SearchPosts(th.BasicTeam.Id, "*", false); len(posts.Order) != 0 { t.Fatal("searching for just * shouldn't return any results") } @@ -1328,7 +1338,6 @@ func TestSearchPosts(t *testing.T) { Client.Logout() _, resp = Client.SearchPosts(th.BasicTeam.Id, "#sgtitlereview", false) CheckUnauthorizedStatus(t, resp) - } func TestSearchHashtagPosts(t *testing.T) { diff --git a/api4/reaction_test.go b/api4/reaction_test.go index dda9a578a..ca2c49808 100644 --- a/api4/reaction_test.go +++ b/api4/reaction_test.go @@ -176,7 +176,7 @@ func TestSaveReaction(t *testing.T) { t.Run("unable-to-react-in-read-only-town-square", func(t *testing.T) { th.LoginBasic() - channel, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id) + channel, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, true) assert.Nil(t, err) post := th.CreatePostWithClient(th.Client, channel) @@ -482,7 +482,7 @@ func TestDeleteReaction(t *testing.T) { t.Run("unable-to-delete-reactions-in-read-only-town-square", func(t *testing.T) { th.LoginBasic() - channel, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id) + channel, err := th.App.GetChannelByName("town-square", th.BasicTeam.Id, true) assert.Nil(t, err) post := th.CreatePostWithClient(th.Client, channel) diff --git a/app/authorization.go b/app/authorization.go index 3de50e27b..0955cb90c 100644 --- a/app/authorization.go +++ b/app/authorization.go @@ -37,7 +37,7 @@ func (a *App) SessionHasPermissionToChannel(session model.Session, channelId str return false } - cmc := a.Srv.Store.Channel().GetAllChannelMembersForUser(session.UserId, true) + cmc := a.Srv.Store.Channel().GetAllChannelMembersForUser(session.UserId, true, true) var channelRoles []string if cmcresult := <-cmc; cmcresult.Err == nil { diff --git a/app/channel.go b/app/channel.go index 619cc09eb..830dbb8b7 100644 --- a/app/channel.go +++ b/app/channel.go @@ -393,7 +393,7 @@ func (a *App) GetGroupChannel(userIds []string) (*model.Channel, *model.AppError return nil, model.NewAppError("GetGroupChannel", "api.channel.create_group.bad_user.app_error", nil, "user_ids="+model.ArrayToJson(userIds), http.StatusBadRequest) } - channel, err := a.GetChannelByName(model.GetGroupNameFromUserIds(userIds), "") + channel, err := a.GetChannelByName(model.GetGroupNameFromUserIds(userIds), "", true) if err != nil { return nil, err } @@ -1011,16 +1011,26 @@ func (a *App) GetChannel(channelId string) (*model.Channel, *model.AppError) { } } -func (a *App) GetChannelByName(channelName, teamId string) (*model.Channel, *model.AppError) { - if result := <-a.Srv.Store.Channel().GetByName(teamId, channelName, true); result.Err != nil && result.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { +func (a *App) GetChannelByName(channelName, teamId string, includeDeleted bool) (*model.Channel, *model.AppError) { + var result store.StoreResult + + if includeDeleted { + result = <-a.Srv.Store.Channel().GetByNameIncludeDeleted(teamId, channelName, false) + } else { + result = <-a.Srv.Store.Channel().GetByName(teamId, channelName, false) + } + + if result.Err != nil && result.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { result.Err.StatusCode = http.StatusNotFound return nil, result.Err - } else if result.Err != nil { + } + + if result.Err != nil { result.Err.StatusCode = http.StatusBadRequest return nil, result.Err - } else { - return result.Data.(*model.Channel), nil } + + return result.Data.(*model.Channel), nil } func (a *App) GetChannelsByNames(channelNames []string, teamId string) ([]*model.Channel, *model.AppError) { @@ -1035,7 +1045,7 @@ func (a *App) GetChannelsByNames(channelNames []string, teamId string) ([]*model } } -func (a *App) GetChannelByNameForTeamName(channelName, teamName string) (*model.Channel, *model.AppError) { +func (a *App) GetChannelByNameForTeamName(channelName, teamName string, includeDeleted bool) (*model.Channel, *model.AppError) { var team *model.Team if result := <-a.Srv.Store.Team().GetByName(teamName); result.Err != nil { @@ -1045,19 +1055,28 @@ func (a *App) GetChannelByNameForTeamName(channelName, teamName string) (*model. team = result.Data.(*model.Team) } - if result := <-a.Srv.Store.Channel().GetByName(team.Id, channelName, true); result.Err != nil && result.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { + var result store.StoreResult + if includeDeleted { + result = <-a.Srv.Store.Channel().GetByNameIncludeDeleted(team.Id, channelName, false) + } else { + result = <-a.Srv.Store.Channel().GetByName(team.Id, channelName, false) + } + + if result.Err != nil && result.Err.Id == "store.sql_channel.get_by_name.missing.app_error" { result.Err.StatusCode = http.StatusNotFound return nil, result.Err - } else if result.Err != nil { + } + + if result.Err != nil { result.Err.StatusCode = http.StatusBadRequest return nil, result.Err - } else { - return result.Data.(*model.Channel), nil } + + return result.Data.(*model.Channel), nil } -func (a *App) GetChannelsForUser(teamId string, userId string) (*model.ChannelList, *model.AppError) { - if result := <-a.Srv.Store.Channel().GetChannels(teamId, userId); result.Err != nil { +func (a *App) GetChannelsForUser(teamId string, userId string, includeDeleted bool) (*model.ChannelList, *model.AppError) { + if result := <-a.Srv.Store.Channel().GetChannels(teamId, userId, includeDeleted); result.Err != nil { return nil, result.Err } else { return result.Data.(*model.ChannelList), nil @@ -1494,7 +1513,9 @@ func (a *App) UpdateChannelLastViewedAt(channelIds []string, userId string) *mod } func (a *App) AutocompleteChannels(teamId string, term string) (*model.ChannelList, *model.AppError) { - if result := <-a.Srv.Store.Channel().AutocompleteInTeam(teamId, term); result.Err != nil { + includeDeleted := *a.Config().TeamSettings.ViewArchivedChannels + + if result := <-a.Srv.Store.Channel().AutocompleteInTeam(teamId, term, includeDeleted); result.Err != nil { return nil, result.Err } else { return result.Data.(*model.ChannelList), nil @@ -1502,7 +1523,9 @@ func (a *App) AutocompleteChannels(teamId string, term string) (*model.ChannelLi } func (a *App) SearchChannels(teamId string, term string) (*model.ChannelList, *model.AppError) { - if result := <-a.Srv.Store.Channel().SearchInTeam(teamId, term); result.Err != nil { + includeDeleted := *a.Config().TeamSettings.ViewArchivedChannels + + if result := <-a.Srv.Store.Channel().SearchInTeam(teamId, term, includeDeleted); result.Err != nil { return nil, result.Err } else { return result.Data.(*model.ChannelList), nil diff --git a/app/channel_test.go b/app/channel_test.go index fa9520b52..ba5f3eb58 100644 --- a/app/channel_test.go +++ b/app/channel_test.go @@ -175,7 +175,7 @@ func TestJoinDefaultChannelsExperimentalDefaultChannels(t *testing.T) { th.App.JoinDefaultChannels(th.BasicTeam.Id, user, false, "") for _, channelName := range defaultChannelList { - channel, err := th.App.GetChannelByName(channelName, th.BasicTeam.Id) + channel, err := th.App.GetChannelByName(channelName, th.BasicTeam.Id, false) if err != nil { t.Errorf("Expected nil, got %s", err) diff --git a/app/command_invite.go b/app/command_invite.go index 54cf2da02..86cc5fdbb 100644 --- a/app/command_invite.go +++ b/app/command_invite.go @@ -59,7 +59,7 @@ func (me *InviteProvider) DoCommand(a *App, args *model.CommandArgs, message str if len(splitMessage) > 1 && splitMessage[1] != "" { targetChannelName := strings.TrimPrefix(strings.TrimSpace(splitMessage[1]), "~") - if channelToJoin, err = a.GetChannelByName(targetChannelName, args.TeamId); err != nil { + if channelToJoin, err = a.GetChannelByName(targetChannelName, args.TeamId, false); err != nil { return &model.CommandResponse{Text: args.T("api.command_invite.channel.error", map[string]interface{}{"Channel": targetChannelName}), ResponseType: model.COMMAND_RESPONSE_TYPE_EPHEMERAL} } } else { diff --git a/app/import.go b/app/import.go index dc4b65396..50973d504 100644 --- a/app/import.go +++ b/app/import.go @@ -1116,7 +1116,7 @@ func (a *App) ImportUserTeams(user *model.User, data *[]UserTeamImportData) *mod a.UpdateTeamMemberSchemeRoles(team.Id, user.Id, isSchemeUser, isSchemeAdmin) } - if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id); err != nil { + if defaultChannel, err := a.GetChannelByName(model.DEFAULT_CHANNEL, team.Id, true); err != nil { return err } else if _, err = a.addUserToChannel(user, defaultChannel, member); err != nil { return err @@ -1139,7 +1139,7 @@ func (a *App) ImportUserChannels(user *model.User, team *model.Team, teamMember // Loop through all channels. for _, cdata := range *data { - channel, err := a.GetChannelByName(*cdata.Name, team.Id) + channel, err := a.GetChannelByName(*cdata.Name, team.Id, true) if err != nil { return err } diff --git a/app/import_test.go b/app/import_test.go index f99e100f1..a8e5a65a8 100644 --- a/app/import_test.go +++ b/app/import_test.go @@ -1898,7 +1898,7 @@ func TestImportImportChannel(t *testing.T) { th.CheckChannelsCount(t, channelCount+1) // Get the Channel and check all the fields are correct. - if channel, err := th.App.GetChannelByName(*data.Name, team.Id); err != nil { + if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { t.Fatalf("Failed to get channel from database.") } else { assert.Equal(t, *data.Name, channel.Name) @@ -1923,7 +1923,7 @@ func TestImportImportChannel(t *testing.T) { th.CheckChannelsCount(t, channelCount) // Get the Channel and check all the fields are correct. - if channel, err := th.App.GetChannelByName(*data.Name, team.Id); err != nil { + if channel, err := th.App.GetChannelByName(*data.Name, team.Id, false); err != nil { t.Fatalf("Failed to get channel from database.") } else { assert.Equal(t, *data.Name, channel.Name) @@ -2157,7 +2157,7 @@ func TestImportImportUser(t *testing.T) { DisplayName: ptrStr("Display Name"), Type: ptrStr("O"), }, false) - channel, err := th.App.GetChannelByName(channelName, team.Id) + channel, err := th.App.GetChannelByName(channelName, team.Id, false) if err != nil { t.Fatalf("Failed to get channel from database.") } @@ -2671,7 +2671,7 @@ func TestImportImportUser(t *testing.T) { if err := th.App.ImportChannel(channelData, false); err != nil { t.Fatalf("Import should have succeeded.") } - channel, err = th.App.GetChannelByName(*channelData.Name, team.Id) + channel, err = th.App.GetChannelByName(*channelData.Name, team.Id, false) if err != nil { t.Fatalf("Failed to get channel from database: %v", err.Error()) } @@ -2755,7 +2755,7 @@ func TestImportImportPost(t *testing.T) { DisplayName: ptrStr("Display Name"), Type: ptrStr("O"), }, false) - channel, err := th.App.GetChannelByName(channelName, team.Id) + channel, err := th.App.GetChannelByName(channelName, team.Id, false) if err != nil { t.Fatalf("Failed to get channel from database.") } @@ -3959,7 +3959,7 @@ func TestImportPostAndRepliesWithAttachments(t *testing.T) { DisplayName: ptrStr("Display Name"), Type: ptrStr("O"), }, false) - _, err = th.App.GetChannelByName(channelName, team.Id) + _, err = th.App.GetChannelByName(channelName, team.Id, false) if err != nil { t.Fatalf("Failed to get channel from database.") } diff --git a/app/plugin_api.go b/app/plugin_api.go index 414ce4d6e..66f17bdfb 100644 --- a/app/plugin_api.go +++ b/app/plugin_api.go @@ -195,12 +195,12 @@ func (api *PluginAPI) GetChannel(channelId string) (*model.Channel, *model.AppEr return api.app.GetChannel(channelId) } -func (api *PluginAPI) GetChannelByName(teamId, name string) (*model.Channel, *model.AppError) { - return api.app.GetChannelByName(name, teamId) +func (api *PluginAPI) GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError) { + return api.app.GetChannelByName(name, teamId, includeDeleted) } -func (api *PluginAPI) GetChannelByNameForTeamName(teamName, channelName string) (*model.Channel, *model.AppError) { - return api.app.GetChannelByNameForTeamName(channelName, teamName) +func (api *PluginAPI) GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) { + return api.app.GetChannelByNameForTeamName(channelName, teamName, includeDeleted) } func (api *PluginAPI) GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) { diff --git a/app/post.go b/app/post.go index 0344d8f0d..8c44436aa 100644 --- a/app/post.go +++ b/app/post.go @@ -638,8 +638,9 @@ func (a *App) DeletePostFiles(post *model.Post) { } } -func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool) (*model.PostSearchResults, *model.AppError) { +func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOrSearch bool, includeDeletedChannels bool) (*model.PostSearchResults, *model.AppError) { paramsList := model.ParseSearchParams(terms) + includeDeleted := includeDeletedChannels && *a.Config().TeamSettings.ViewArchivedChannels esInterface := a.Elasticsearch if license := a.License(); esInterface != nil && *a.Config().ElasticsearchSettings.EnableSearching && license != nil && *license.Features.Elasticsearch { @@ -651,7 +652,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr if params.Terms != "*" { // Convert channel names to channel IDs for idx, channelName := range params.InChannels { - if channel, err := a.GetChannelByName(channelName, teamId); err != nil { + if channel, err := a.GetChannelByName(channelName, teamId, includeDeleted); err != nil { mlog.Error(fmt.Sprint(err)) } else { params.InChannels[idx] = channel.Id @@ -677,7 +678,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr } // We only allow the user to search in channels they are a member of. - userChannels, err := a.GetChannelsForUser(teamId, userId) + userChannels, err := a.GetChannelsForUser(teamId, userId, includeDeleted) if err != nil { mlog.Error(fmt.Sprint(err)) return nil, err @@ -710,6 +711,7 @@ func (a *App) SearchPostsInTeam(terms string, userId string, teamId string, isOr channels := []store.StoreChannel{} for _, params := range paramsList { + params.IncludeDeletedChannels = includeDeleted params.OrTerms = isOrSearch // don't allow users to search for everything if params.Terms != "*" { diff --git a/app/team.go b/app/team.go index ff1037b7c..8d1331823 100644 --- a/app/team.go +++ b/app/team.go @@ -715,7 +715,7 @@ func (a *App) LeaveTeam(team *model.Team, user *model.User, requestorId string) var channelList *model.ChannelList - if result := <-a.Srv.Store.Channel().GetChannels(team.Id, user.Id); result.Err != nil { + if result := <-a.Srv.Store.Channel().GetChannels(team.Id, user.Id, true); result.Err != nil { if result.Err.Id == "store.sql_channel.get_channels.not_found.app_error" { channelList = &model.ChannelList{} } else { diff --git a/app/user.go b/app/user.go index b9a97b2a9..fa4f36ff1 100644 --- a/app/user.go +++ b/app/user.go @@ -901,7 +901,7 @@ func (a *App) UpdateActive(user *model.User, active bool) (*model.User, *model.A } for _, team := range teamsForUser { - channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id) + channelsForUser, err := a.GetChannelsForUser(team.Id, user.Id, false) if err != nil { return nil, err } diff --git a/app/web_conn.go b/app/web_conn.go index 47fae24c3..1c314bb11 100644 --- a/app/web_conn.go +++ b/app/web_conn.go @@ -334,7 +334,7 @@ func (webCon *WebConn) ShouldSendEvent(msg *model.WebSocketEvent) bool { } if webCon.AllChannelMembers == nil { - if result := <-webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true); result.Err != nil { + if result := <-webCon.App.Srv.Store.Channel().GetAllChannelMembersForUser(webCon.UserId, true, false); result.Err != nil { mlog.Error("webhub.shouldSendEvent: " + result.Err.Error()) return false } else { diff --git a/config/default.json b/config/default.json index d0c18e60c..22f94d8f0 100644 --- a/config/default.json +++ b/config/default.json @@ -101,6 +101,7 @@ "MaxNotificationsPerChannel": 1000, "EnableConfirmNotificationsToChannel": true, "TeammateNameDisplay": "username", + "ViewArchivedChannels": true, "ExperimentalEnableAutomaticReplies": false, "ExperimentalHideTownSquareinLHS": false, "ExperimentalTownSquareIsReadOnly": false, diff --git a/manualtesting/manual_testing.go b/manualtesting/manual_testing.go index 2dbe61343..df72fbb57 100644 --- a/manualtesting/manual_testing.go +++ b/manualtesting/manual_testing.go @@ -154,7 +154,7 @@ func manualTest(c *web.Context, w http.ResponseWriter, r *http.Request) { func getChannelID(a *app.App, channelname string, teamid string, userid string) (id string, err bool) { // Grab all the channels - result := <-a.Srv.Store.Channel().GetChannels(teamid, userid) + result := <-a.Srv.Store.Channel().GetChannels(teamid, userid, false) if result.Err != nil { mlog.Debug("Unable to get channels") return "", false diff --git a/model/client4.go b/model/client4.go index d34354715..48627c4b0 100644 --- a/model/client4.go +++ b/model/client4.go @@ -1791,6 +1791,15 @@ func (c *Client4) GetChannelByName(channelName, teamId string, etag string) (*Ch } } +func (c *Client4) GetChannelByNameIncludeDeleted(channelName, teamId string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameRoute(channelName, teamId)+"?include_deleted=true", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + // GetChannelByNameForTeamName returns a channel based on the provided channel name and team name strings. func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag string) (*Channel, *Response) { if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName), etag); err != nil { @@ -1801,6 +1810,15 @@ func (c *Client4) GetChannelByNameForTeamName(channelName, teamName string, etag } } +func (c *Client4) GetChannelByNameForTeamNameIncludeDeleted(channelName, teamName string, etag string) (*Channel, *Response) { + if r, err := c.DoApiGet(c.GetChannelByNameForTeamNameRoute(channelName, teamName)+"?include_deleted=true", etag); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return ChannelFromJson(r.Body), BuildResponse(r) + } +} + // GetChannelMembers gets a page of channel members. func (c *Client4) GetChannelMembers(channelId string, page, perPage int, etag string) (*ChannelMembers, *Response) { query := fmt.Sprintf("?page=%v&per_page=%v", page, perPage) @@ -2127,6 +2145,17 @@ func (c *Client4) SearchPosts(teamId string, terms string, isOrSearch bool) (*Po } } +// SearchPosts returns any posts with matching terms string including deleted channels. +func (c *Client4) SearchPostsIncludeDeletedChannels(teamId string, terms string, isOrSearch bool) (*PostList, *Response) { + requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch} + if r, err := c.DoApiPost(c.GetTeamRoute(teamId)+"/posts/search?include_deleted_channels=true", StringInterfaceToJson(requestBody)); err != nil { + return nil, BuildErrorResponse(r, err) + } else { + defer closeBody(r) + return PostListFromJson(r.Body), BuildResponse(r) + } +} + // SearchPosts returns any posts with matching terms string, including . func (c *Client4) SearchPostsWithMatches(teamId string, terms string, isOrSearch bool) (*PostSearchResults, *Response) { requestBody := map[string]interface{}{"terms": terms, "is_or_search": isOrSearch} diff --git a/model/config.go b/model/config.go index aa7900279..e38c993a8 100644 --- a/model/config.go +++ b/model/config.go @@ -1125,6 +1125,7 @@ type TeamSettings struct { MaxNotificationsPerChannel *int64 EnableConfirmNotificationsToChannel *bool TeammateNameDisplay *string + ViewArchivedChannels *bool ExperimentalEnableAutomaticReplies *bool ExperimentalHideTownSquareinLHS *bool ExperimentalTownSquareIsReadOnly *bool @@ -1254,6 +1255,9 @@ func (s *TeamSettings) SetDefaults() { s.EnableUserCreation = NewBool(true) } + if s.ViewArchivedChannels == nil { + s.ViewArchivedChannels = NewBool(true) + } } type ClientRequirements struct { diff --git a/model/search_params.go b/model/search_params.go index 481671ab5..21aa69a0d 100644 --- a/model/search_params.go +++ b/model/search_params.go @@ -12,11 +12,12 @@ var searchTermPuncStart = regexp.MustCompile(`^[^\pL\d\s#"]+`) var searchTermPuncEnd = regexp.MustCompile(`[^\pL\d\s*"]+$`) type SearchParams struct { - Terms string - IsHashtag bool - InChannels []string - FromUsers []string - OrTerms bool + Terms string + IsHashtag bool + InChannels []string + FromUsers []string + OrTerms bool + IncludeDeletedChannels bool } var searchFlags = [...]string{"from", "channel", "in"} diff --git a/plugin/api.go b/plugin/api.go index 6d4976c4f..0b413d4d1 100644 --- a/plugin/api.go +++ b/plugin/api.go @@ -108,10 +108,10 @@ type API interface { GetChannel(channelId string) (*model.Channel, *model.AppError) // GetChannelByName gets a channel by its name, given a team id. - GetChannelByName(teamId, name string) (*model.Channel, *model.AppError) + GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError) // GetChannelByNameForTeamName gets a channel by its name, given a team name. - GetChannelByNameForTeamName(teamName, channelName string) (*model.Channel, *model.AppError) + GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) // GetDirectChannel gets a direct message channel. GetDirectChannel(userId1, userId2 string) (*model.Channel, *model.AppError) diff --git a/plugin/client_rpc_generated.go b/plugin/client_rpc_generated.go index 9eac71be4..98b906186 100644 --- a/plugin/client_rpc_generated.go +++ b/plugin/client_rpc_generated.go @@ -1349,6 +1349,7 @@ func (s *apiRPCServer) GetChannel(args *Z_GetChannelArgs, returns *Z_GetChannelR type Z_GetChannelByNameArgs struct { A string B string + C bool } type Z_GetChannelByNameReturns struct { @@ -1356,8 +1357,8 @@ type Z_GetChannelByNameReturns struct { B *model.AppError } -func (g *apiRPCClient) GetChannelByName(teamId, name string) (*model.Channel, *model.AppError) { - _args := &Z_GetChannelByNameArgs{teamId, name} +func (g *apiRPCClient) GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError) { + _args := &Z_GetChannelByNameArgs{teamId, name, includeDeleted} _returns := &Z_GetChannelByNameReturns{} if err := g.client.Call("Plugin.GetChannelByName", _args, _returns); err != nil { log.Printf("RPC call to GetChannelByName API failed: %s", err.Error()) @@ -1367,9 +1368,9 @@ func (g *apiRPCClient) GetChannelByName(teamId, name string) (*model.Channel, *m func (s *apiRPCServer) GetChannelByName(args *Z_GetChannelByNameArgs, returns *Z_GetChannelByNameReturns) error { if hook, ok := s.impl.(interface { - GetChannelByName(teamId, name string) (*model.Channel, *model.AppError) + GetChannelByName(teamId, name string, includeDeleted bool) (*model.Channel, *model.AppError) }); ok { - returns.A, returns.B = hook.GetChannelByName(args.A, args.B) + returns.A, returns.B = hook.GetChannelByName(args.A, args.B, args.C) } else { return fmt.Errorf("API GetChannelByName called but not implemented.") } @@ -1379,6 +1380,7 @@ func (s *apiRPCServer) GetChannelByName(args *Z_GetChannelByNameArgs, returns *Z type Z_GetChannelByNameForTeamNameArgs struct { A string B string + C bool } type Z_GetChannelByNameForTeamNameReturns struct { @@ -1386,8 +1388,8 @@ type Z_GetChannelByNameForTeamNameReturns struct { B *model.AppError } -func (g *apiRPCClient) GetChannelByNameForTeamName(teamName, channelName string) (*model.Channel, *model.AppError) { - _args := &Z_GetChannelByNameForTeamNameArgs{teamName, channelName} +func (g *apiRPCClient) GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) { + _args := &Z_GetChannelByNameForTeamNameArgs{teamName, channelName, includeDeleted} _returns := &Z_GetChannelByNameForTeamNameReturns{} if err := g.client.Call("Plugin.GetChannelByNameForTeamName", _args, _returns); err != nil { log.Printf("RPC call to GetChannelByNameForTeamName API failed: %s", err.Error()) @@ -1397,9 +1399,9 @@ func (g *apiRPCClient) GetChannelByNameForTeamName(teamName, channelName string) func (s *apiRPCServer) GetChannelByNameForTeamName(args *Z_GetChannelByNameForTeamNameArgs, returns *Z_GetChannelByNameForTeamNameReturns) error { if hook, ok := s.impl.(interface { - GetChannelByNameForTeamName(teamName, channelName string) (*model.Channel, *model.AppError) + GetChannelByNameForTeamName(teamName, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) }); ok { - returns.A, returns.B = hook.GetChannelByNameForTeamName(args.A, args.B) + returns.A, returns.B = hook.GetChannelByNameForTeamName(args.A, args.B, args.C) } else { return fmt.Errorf("API GetChannelByNameForTeamName called but not implemented.") } diff --git a/plugin/example_help_test.go b/plugin/example_help_test.go index 3f9be9a20..175b6588e 100644 --- a/plugin/example_help_test.go +++ b/plugin/example_help_test.go @@ -30,7 +30,7 @@ func (p *HelpPlugin) OnConfigurationChange() error { return nil } - channel, err := p.API.GetChannelByName(p.ChannelName, team.Id) + channel, err := p.API.GetChannelByName(p.ChannelName, team.Id, false) if err != nil { p.API.LogError("failed to find channel", "channel_name", p.ChannelName) return nil diff --git a/plugin/plugintest/api.go b/plugin/plugintest/api.go index 473ef6d2b..70e90df4b 100644 --- a/plugin/plugintest/api.go +++ b/plugin/plugintest/api.go @@ -308,13 +308,13 @@ func (_m *API) GetChannel(channelId string) (*model.Channel, *model.AppError) { return r0, r1 } -// GetChannelByName provides a mock function with given fields: teamId, name -func (_m *API) GetChannelByName(teamId string, name string) (*model.Channel, *model.AppError) { - ret := _m.Called(teamId, name) +// GetChannelByName provides a mock function with given fields: teamId, name, includeDeleted +func (_m *API) GetChannelByName(teamId string, name string, includeDeleted bool) (*model.Channel, *model.AppError) { + ret := _m.Called(teamId, name, includeDeleted) var r0 *model.Channel - if rf, ok := ret.Get(0).(func(string, string) *model.Channel); ok { - r0 = rf(teamId, name) + if rf, ok := ret.Get(0).(func(string, string, bool) *model.Channel); ok { + r0 = rf(teamId, name, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*model.Channel) @@ -322,8 +322,8 @@ func (_m *API) GetChannelByName(teamId string, name string) (*model.Channel, *mo } var r1 *model.AppError - if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok { - r1 = rf(teamId, name) + if rf, ok := ret.Get(1).(func(string, string, bool) *model.AppError); ok { + r1 = rf(teamId, name, includeDeleted) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*model.AppError) @@ -333,13 +333,13 @@ func (_m *API) GetChannelByName(teamId string, name string) (*model.Channel, *mo return r0, r1 } -// GetChannelByNameForTeamName provides a mock function with given fields: teamName, channelName -func (_m *API) GetChannelByNameForTeamName(teamName string, channelName string) (*model.Channel, *model.AppError) { - ret := _m.Called(teamName, channelName) +// GetChannelByNameForTeamName provides a mock function with given fields: teamName, channelName, includeDeleted +func (_m *API) GetChannelByNameForTeamName(teamName string, channelName string, includeDeleted bool) (*model.Channel, *model.AppError) { + ret := _m.Called(teamName, channelName, includeDeleted) var r0 *model.Channel - if rf, ok := ret.Get(0).(func(string, string) *model.Channel); ok { - r0 = rf(teamName, channelName) + if rf, ok := ret.Get(0).(func(string, string, bool) *model.Channel); ok { + r0 = rf(teamName, channelName, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(*model.Channel) @@ -347,8 +347,8 @@ func (_m *API) GetChannelByNameForTeamName(teamName string, channelName string) } var r1 *model.AppError - if rf, ok := ret.Get(1).(func(string, string) *model.AppError); ok { - r1 = rf(teamName, channelName) + if rf, ok := ret.Get(1).(func(string, string, bool) *model.AppError); ok { + r1 = rf(teamName, channelName, includeDeleted) } else { if ret.Get(1) != nil { r1 = ret.Get(1).(*model.AppError) diff --git a/store/sqlstore/channel_store.go b/store/sqlstore/channel_store.go index 63e924e21..c073680f4 100644 --- a/store/sqlstore/channel_store.go +++ b/store/sqlstore/channel_store.go @@ -303,6 +303,11 @@ func (s SqlChannelStore) CreateIndexesIfNotExists() { func (s SqlChannelStore) Save(channel *model.Channel, maxChannelsPerTeam int64) store.StoreChannel { return store.Do(func(result *store.StoreResult) { + if channel.DeleteAt != 0 { + result.Err = model.NewAppError("SqlChannelStore.Save", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest) + return + } + if channel.Type == model.CHANNEL_DIRECT { result.Err = model.NewAppError("SqlChannelStore.Save", "store.sql_channel.save.direct_channel.app_error", nil, "", http.StatusBadRequest) return @@ -352,6 +357,11 @@ func (s SqlChannelStore) CreateDirectChannel(userId string, otherUserId string) func (s SqlChannelStore) SaveDirectChannel(directchannel *model.Channel, member1 *model.ChannelMember, member2 *model.ChannelMember) store.StoreChannel { return store.Do(func(result *store.StoreResult) { + if directchannel.DeleteAt != 0 { + result.Err = model.NewAppError("SqlChannelStore.Save", "store.sql_channel.save.archived_channel.app_error", nil, "", http.StatusBadRequest) + return + } + if directchannel.Type != model.CHANNEL_DIRECT { result.Err = model.NewAppError("SqlChannelStore.SaveDirectChannel", "store.sql_channel.save_direct_channel.not_direct.app_error", nil, "", http.StatusBadRequest) return @@ -629,10 +639,14 @@ func (s SqlChannelStore) PermanentDeleteMembersByChannel(channelId string) store }) } -func (s SqlChannelStore) GetChannels(teamId string, userId string) store.StoreChannel { +func (s SqlChannelStore) GetChannels(teamId string, userId string, includeDeleted bool) store.StoreChannel { return store.Do(func(result *store.StoreResult) { + query := "SELECT Channels.* FROM Channels, ChannelMembers WHERE Id = ChannelId AND UserId = :UserId AND DeleteAt = 0 AND (TeamId = :TeamId OR TeamId = '') ORDER BY DisplayName" + if includeDeleted { + query = "SELECT Channels.* FROM Channels, ChannelMembers WHERE Id = ChannelId AND UserId = :UserId AND (TeamId = :TeamId OR TeamId = '') ORDER BY DisplayName" + } data := &model.ChannelList{} - _, err := s.GetReplica().Select(data, "SELECT Channels.* FROM Channels, ChannelMembers WHERE Id = ChannelId AND UserId = :UserId AND DeleteAt = 0 AND (TeamId = :TeamId OR TeamId = '') ORDER BY DisplayName", map[string]interface{}{"TeamId": teamId, "UserId": userId}) + _, err := s.GetReplica().Select(data, query, map[string]interface{}{"TeamId": teamId, "UserId": userId}) if err != nil { result.Err = model.NewAppError("SqlChannelStore.GetChannels", "store.sql_channel.get_channels.get.app_error", nil, "teamId="+teamId+", userId="+userId+", err="+err.Error(), http.StatusInternalServerError) @@ -1100,7 +1114,7 @@ func (s SqlChannelStore) IsUserInChannelUseCache(userId string, channelId string s.metrics.IncrementMemCacheMissCounter("All Channel Members for User") } - result := <-s.GetAllChannelMembersForUser(userId, true) + result := <-s.GetAllChannelMembersForUser(userId, true, false) if result.Err != nil { mlog.Error("SqlChannelStore.IsUserInChannelUseCache: " + result.Err.Error()) return false @@ -1147,7 +1161,7 @@ func (s SqlChannelStore) GetMemberForPost(postId string, userId string) store.St }) } -func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool) store.StoreChannel { +func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool, includeDeleted bool) store.StoreChannel { return store.Do(func(result *store.StoreResult) { if allowFromCache { if cacheItem, ok := allChannelMembersForUserCache.Get(userId); ok { @@ -1163,6 +1177,11 @@ func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCac s.metrics.IncrementMemCacheMissCounter("All Channel Members for User") } + var deletedClause string + if !includeDeleted { + deletedClause = "Channels.DeleteAt = 0 AND" + } + var data allChannelMembers _, err := s.GetReplica().Select(&data, ` SELECT @@ -1182,8 +1201,8 @@ func (s SqlChannelStore) GetAllChannelMembersForUser(userId string, allowFromCac LEFT JOIN Schemes TeamScheme ON Teams.SchemeId = TeamScheme.Id WHERE - Channels.DeleteAt = 0 - AND ChannelMembers.UserId = :UserId`, map[string]interface{}{"UserId": userId}) + `+deletedClause+` + ChannelMembers.UserId = :UserId`, map[string]interface{}{"UserId": userId}) if err != nil { result.Err = model.NewAppError("SqlChannelStore.GetAllChannelMembersForUser", "store.sql_channel.get_channels.get.app_error", nil, "userId="+userId+", err="+err.Error(), http.StatusInternalServerError) @@ -1515,8 +1534,13 @@ func (s SqlChannelStore) GetMembersForUser(teamId string, userId string) store.S }) } -func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string) store.StoreChannel { +func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { return store.Do(func(result *store.StoreResult) { + deleteFilter := "AND DeleteAt = 0" + if includeDeleted { + deleteFilter = "" + } + queryFormat := ` SELECT * @@ -1525,7 +1549,7 @@ func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string) store.St WHERE TeamId = :TeamId AND Type = 'O' - AND DeleteAt = 0 + ` + deleteFilter + ` %v LIMIT 50` @@ -1555,8 +1579,12 @@ func (s SqlChannelStore) AutocompleteInTeam(teamId string, term string) store.St }) } -func (s SqlChannelStore) SearchInTeam(teamId string, term string) store.StoreChannel { +func (s SqlChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { return store.Do(func(result *store.StoreResult) { + deleteFilter := "AND DeleteAt = 0" + if includeDeleted { + deleteFilter = "" + } searchQuery := ` SELECT * @@ -1565,7 +1593,7 @@ func (s SqlChannelStore) SearchInTeam(teamId string, term string) store.StoreCha WHERE TeamId = :TeamId AND Type = 'O' - AND DeleteAt = 0 + ` + deleteFilter + ` SEARCH_CLAUSE ORDER BY DisplayName LIMIT 100` diff --git a/store/sqlstore/post_store.go b/store/sqlstore/post_store.go index 229005b73..90db80796 100644 --- a/store/sqlstore/post_store.go +++ b/store/sqlstore/post_store.go @@ -803,6 +803,11 @@ func (s *SqlPostStore) Search(teamId string, userId string, params *model.Search var posts []*model.Post + deletedQueryPart := "AND DeleteAt = 0" + if params.IncludeDeletedChannels { + deletedQueryPart = "" + } + searchQuery := ` SELECT * @@ -822,7 +827,7 @@ func (s *SqlPostStore) Search(teamId string, userId string, params *model.Search Id = ChannelId AND (TeamId = :TeamId OR TeamId = '') AND UserId = :UserId - AND DeleteAt = 0 + ` + deletedQueryPart + ` CHANNEL_FILTER) SEARCH_CLAUSE ORDER BY CreateAt DESC diff --git a/store/store.go b/store/store.go index 203c637ff..2f18a0d8f 100644 --- a/store/store.go +++ b/store/store.go @@ -131,7 +131,7 @@ type ChannelStore interface { GetByNameIncludeDeleted(team_id string, name string, allowFromCache bool) StoreChannel GetDeletedByName(team_id string, name string) StoreChannel GetDeleted(team_id string, offset int, limit int) StoreChannel - GetChannels(teamId string, userId string) StoreChannel + GetChannels(teamId string, userId string, includeDeleted bool) StoreChannel GetMoreChannels(teamId string, userId string, offset int, limit int) StoreChannel GetPublicChannelsForTeam(teamId string, offset int, limit int) StoreChannel GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) StoreChannel @@ -143,7 +143,7 @@ type ChannelStore interface { UpdateMember(member *model.ChannelMember) StoreChannel GetMembers(channelId string, offset, limit int) StoreChannel GetMember(channelId string, userId string) StoreChannel - GetAllChannelMembersForUser(userId string, allowFromCache bool) StoreChannel + GetAllChannelMembersForUser(userId string, allowFromCache bool, includeDeleted bool) StoreChannel InvalidateAllChannelMembersForUser(userId string) IsUserInChannelUseCache(userId string, channelId string) bool GetAllChannelMembersNotifyPropsForChannel(channelId string, allowFromCache bool) StoreChannel @@ -160,8 +160,8 @@ type ChannelStore interface { IncrementMentionCount(channelId string, userId string) StoreChannel AnalyticsTypeCount(teamId string, channelType string) StoreChannel GetMembersForUser(teamId string, userId string) StoreChannel - AutocompleteInTeam(teamId string, term string) StoreChannel - SearchInTeam(teamId string, term string) StoreChannel + AutocompleteInTeam(teamId string, term string, includeDeleted bool) StoreChannel + SearchInTeam(teamId string, term string, includeDeleted bool) StoreChannel SearchMore(userId string, teamId string, term string) StoreChannel GetMembersByIds(channelId string, userIds []string) StoreChannel AnalyticsDeletedTypeCount(teamId string, channelType string) StoreChannel diff --git a/store/storetest/channel_store.go b/store/storetest/channel_store.go index ccf4b1c59..9b5ac9e38 100644 --- a/store/storetest/channel_store.go +++ b/store/storetest/channel_store.go @@ -493,7 +493,7 @@ func testChannelStoreDelete(t *testing.T, ss store.Store) { t.Fatal(r.Err) } - cresult := <-ss.Channel().GetChannels(o1.TeamId, m1.UserId) + cresult := <-ss.Channel().GetChannels(o1.TeamId, m1.UserId, false) list := cresult.Data.(*model.ChannelList) if len(*list) != 1 { @@ -509,7 +509,7 @@ func testChannelStoreDelete(t *testing.T, ss store.Store) { <-ss.Channel().PermanentDelete(o2.Id) - cresult = <-ss.Channel().GetChannels(o1.TeamId, m1.UserId) + cresult = <-ss.Channel().GetChannels(o1.TeamId, m1.UserId, false) t.Log(cresult.Err) if cresult.Err.Id != "store.sql_channel.get_channels.not_found.app_error" { t.Fatal("no channels should be found") @@ -555,7 +555,7 @@ func testChannelStoreGetByName(t *testing.T, ss store.Store) { store.Must(ss.Channel().Delete(r1.Data.(*model.Channel).Id, model.GetMillis())) - if err := (<-ss.Channel().GetByName(o1.TeamId, "", false)).Err; err == nil { + if err := (<-ss.Channel().GetByName(o1.TeamId, r1.Data.(*model.Channel).Name, false)).Err; err == nil { t.Fatal("Deleted channel should not be returned by GetByName()") } } @@ -617,8 +617,11 @@ func testChannelStoreGetDeletedByName(t *testing.T, ss store.Store) { o1.DisplayName = "Name" o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_OPEN - o1.DeleteAt = model.GetMillis() store.Must(ss.Channel().Save(&o1, -1)) + now := model.GetMillis() + store.Must(ss.Channel().Delete(o1.Id, now)) + o1.DeleteAt = now + o1.UpdateAt = now if r1 := <-ss.Channel().GetDeletedByName(o1.TeamId, o1.Name); r1.Err != nil { t.Fatal(r1.Err) @@ -639,8 +642,8 @@ func testChannelStoreGetDeleted(t *testing.T, ss store.Store) { o1.DisplayName = "Channel1" o1.Name = "zz" + model.NewId() + "b" o1.Type = model.CHANNEL_OPEN - o1.DeleteAt = model.GetMillis() store.Must(ss.Channel().Save(&o1, -1)) + store.Must(ss.Channel().Delete(o1.Id, model.GetMillis())) cresult := <-ss.Channel().GetDeleted(o1.TeamId, 0, 100) if cresult.Err != nil { @@ -678,8 +681,8 @@ func testChannelStoreGetDeleted(t *testing.T, ss store.Store) { o3.DisplayName = "Channel3" o3.Name = "zz" + model.NewId() + "b" o3.Type = model.CHANNEL_OPEN - o3.DeleteAt = model.GetMillis() store.Must(ss.Channel().Save(&o3, -1)) + store.Must(ss.Channel().SetDeleteAt(o3.Id, model.GetMillis(), model.GetMillis())) cresult = <-ss.Channel().GetDeleted(o1.TeamId, 0, 100) if cresult.Err != nil { @@ -890,26 +893,26 @@ func testChannelStoreGetChannels(t *testing.T, ss store.Store) { m3.NotifyProps = model.GetDefaultChannelNotifyProps() store.Must(ss.Channel().SaveMember(&m3)) - cresult := <-ss.Channel().GetChannels(o1.TeamId, m1.UserId) + cresult := <-ss.Channel().GetChannels(o1.TeamId, m1.UserId, false) list := cresult.Data.(*model.ChannelList) if (*list)[0].Id != o1.Id { t.Fatal("missing channel") } - acresult := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, false) + acresult := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, false, false) ids := acresult.Data.(map[string]string) if _, ok := ids[o1.Id]; !ok { t.Fatal("missing channel") } - acresult2 := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, true) + acresult2 := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, true, false) ids2 := acresult2.Data.(map[string]string) if _, ok := ids2[o1.Id]; !ok { t.Fatal("missing channel") } - acresult3 := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, true) + acresult3 := <-ss.Channel().GetAllChannelMembersForUser(m1.UserId, true, false) ids3 := acresult3.Data.(map[string]string) if _, ok := ids3[o1.Id]; !ok { t.Fatal("missing channel") @@ -1913,12 +1916,12 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { o12.Type = model.CHANNEL_OPEN store.Must(ss.Channel().Save(&o12, -1)) - for name, search := range map[string]func(teamId string, term string) store.StoreChannel{ + for name, search := range map[string]func(teamId string, term string, includeDeleted bool) store.StoreChannel{ "AutocompleteInTeam": ss.Channel().AutocompleteInTeam, "SearchInTeam": ss.Channel().SearchInTeam, } { t.Run(name, func(t *testing.T) { - if result := <-search(o1.TeamId, "ChannelA"); result.Err != nil { + if result := <-search(o1.TeamId, "ChannelA", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1927,7 +1930,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, ""); result.Err != nil { + if result := <-search(o1.TeamId, "", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1936,7 +1939,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "blargh"); result.Err != nil { + if result := <-search(o1.TeamId, "blargh", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1945,7 +1948,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "off-"); result.Err != nil { + if result := <-search(o1.TeamId, "off-", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1962,7 +1965,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "off-topic"); result.Err != nil { + if result := <-search(o1.TeamId, "off-topic", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1975,7 +1978,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "town square"); result.Err != nil { + if result := <-search(o1.TeamId, "town square", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -1988,7 +1991,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "the"); result.Err != nil { + if result := <-search(o1.TeamId, "the", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -2002,7 +2005,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "Mobile"); result.Err != nil { + if result := <-search(o1.TeamId, "Mobile", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -2016,7 +2019,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "now searchable"); result.Err != nil { + if result := <-search(o1.TeamId, "now searchable", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) @@ -2029,7 +2032,7 @@ func testChannelStoreSearchInTeam(t *testing.T, ss store.Store) { } } - if result := <-search(o1.TeamId, "town square |"); result.Err != nil { + if result := <-search(o1.TeamId, "town square |", false); result.Err != nil { t.Fatal(result.Err) } else { channels := result.Data.(*model.ChannelList) diff --git a/store/storetest/mocks/ChannelStore.go b/store/storetest/mocks/ChannelStore.go index 4929557ea..4cbf9cb77 100644 --- a/store/storetest/mocks/ChannelStore.go +++ b/store/storetest/mocks/ChannelStore.go @@ -46,12 +46,12 @@ func (_m *ChannelStore) AnalyticsTypeCount(teamId string, channelType string) st } // AutocompleteInTeam provides a mock function with given fields: teamId, term -func (_m *ChannelStore) AutocompleteInTeam(teamId string, term string) store.StoreChannel { +func (_m *ChannelStore) AutocompleteInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { ret := _m.Called(teamId, term) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok { - r0 = rf(teamId, term) + if rf, ok := ret.Get(0).(func(string, string, bool) store.StoreChannel); ok { + r0 = rf(teamId, term, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) @@ -147,12 +147,12 @@ func (_m *ChannelStore) GetAll(teamId string) store.StoreChannel { } // GetAllChannelMembersForUser provides a mock function with given fields: userId, allowFromCache -func (_m *ChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool) store.StoreChannel { +func (_m *ChannelStore) GetAllChannelMembersForUser(userId string, allowFromCache bool, includeDeleted bool) store.StoreChannel { ret := _m.Called(userId, allowFromCache) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, bool) store.StoreChannel); ok { - r0 = rf(userId, allowFromCache) + if rf, ok := ret.Get(0).(func(string, bool, bool) store.StoreChannel); ok { + r0 = rf(userId, allowFromCache, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) @@ -259,12 +259,12 @@ func (_m *ChannelStore) GetChannelUnread(channelId string, userId string) store. } // GetChannels provides a mock function with given fields: teamId, userId -func (_m *ChannelStore) GetChannels(teamId string, userId string) store.StoreChannel { +func (_m *ChannelStore) GetChannels(teamId string, userId string, includeDeleted bool) store.StoreChannel { ret := _m.Called(teamId, userId) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok { - r0 = rf(teamId, userId) + if rf, ok := ret.Get(0).(func(string, string, bool) store.StoreChannel); ok { + r0 = rf(teamId, userId, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) @@ -776,12 +776,12 @@ func (_m *ChannelStore) SaveMember(member *model.ChannelMember) store.StoreChann } // SearchInTeam provides a mock function with given fields: teamId, term -func (_m *ChannelStore) SearchInTeam(teamId string, term string) store.StoreChannel { +func (_m *ChannelStore) SearchInTeam(teamId string, term string, includeDeleted bool) store.StoreChannel { ret := _m.Called(teamId, term) var r0 store.StoreChannel - if rf, ok := ret.Get(0).(func(string, string) store.StoreChannel); ok { - r0 = rf(teamId, term) + if rf, ok := ret.Get(0).(func(string, string, bool) store.StoreChannel); ok { + r0 = rf(teamId, term, includeDeleted) } else { if ret.Get(0) != nil { r0 = ret.Get(0).(store.StoreChannel) diff --git a/store/storetest/post_store.go b/store/storetest/post_store.go index 1159af37f..da1b069a2 100644 --- a/store/storetest/post_store.go +++ b/store/storetest/post_store.go @@ -13,6 +13,7 @@ import ( "github.com/mattermost/mattermost-server/store" "github.com/mattermost/mattermost-server/utils" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestPostStore(t *testing.T, ss store.Store) { @@ -839,6 +840,20 @@ func testPostStoreSearch(t *testing.T, ss store.Store) { c2.Type = model.CHANNEL_OPEN c2 = (<-ss.Channel().Save(c2, -1)).Data.(*model.Channel) + c3 := &model.Channel{} + c3.TeamId = teamId + c3.DisplayName = "Channel1" + c3.Name = "zz" + model.NewId() + "b" + c3.Type = model.CHANNEL_OPEN + c3 = (<-ss.Channel().Save(c3, -1)).Data.(*model.Channel) + <-ss.Channel().Delete(c3.Id, model.GetMillis()) + + m3 := model.ChannelMember{} + m3.ChannelId = c3.Id + m3.UserId = userId + m3.NotifyProps = model.GetDefaultChannelNotifyProps() + store.Must(ss.Channel().SaveMember(&m3)) + o1 := &model.Post{} o1.ChannelId = c1.Id o1.UserId = model.NewId() @@ -877,69 +892,129 @@ func testPostStoreSearch(t *testing.T, ss store.Store) { o5.Hashtags = "#secret #howdy" o5 = (<-ss.Post().Save(o5)).Data.(*model.Post) - r1 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "corey", IsHashtag: false})).Data.(*model.PostList) - if len(r1.Order) != 1 || r1.Order[0] != o1.Id { - t.Fatal("returned wrong search result") - } - - r3 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "new", IsHashtag: false})).Data.(*model.PostList) - if len(r3.Order) != 2 || (r3.Order[0] != o1.Id && r3.Order[1] != o1.Id) { - t.Fatal("returned wrong search result") - } - - r4 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "john", IsHashtag: false})).Data.(*model.PostList) - if len(r4.Order) != 1 || r4.Order[0] != o2.Id { - t.Fatal("returned wrong search result") - } - - r5 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "matter*", IsHashtag: false})).Data.(*model.PostList) - if len(r5.Order) != 1 || r5.Order[0] != o1.Id { - t.Fatal("returned wrong search result") - } - - r6 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "#hashtag", IsHashtag: true})).Data.(*model.PostList) - if len(r6.Order) != 1 || r6.Order[0] != o4.Id { - t.Fatal("returned wrong search result") - } - - r7 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "#secret", IsHashtag: true})).Data.(*model.PostList) - if len(r7.Order) != 1 || r7.Order[0] != o5.Id { - t.Fatal("returned wrong search result") - } - - r8 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "@thisshouldmatchnothing", IsHashtag: true})).Data.(*model.PostList) - if len(r8.Order) != 0 { - t.Fatal("returned wrong search result") - } - - r9 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "mattermost jersey", IsHashtag: false})).Data.(*model.PostList) - if len(r9.Order) != 0 { - t.Fatal("returned wrong search result") - } - - r9a := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "corey new york", IsHashtag: false})).Data.(*model.PostList) - if len(r9a.Order) != 1 { - t.Fatal("returned wrong search result") - } - - r10 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "matter* jer*", IsHashtag: false})).Data.(*model.PostList) - if len(r10.Order) != 0 { - t.Fatal("returned wrong search result") - } - - r11 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "message blargh", IsHashtag: false})).Data.(*model.PostList) - if len(r11.Order) != 1 { - t.Fatal("returned wrong search result") - } + o6 := &model.Post{} + o6.ChannelId = c3.Id + o6.UserId = model.NewId() + o6.Hashtags = "#hashtag" + o6 = (<-ss.Post().Save(o6)).Data.(*model.Post) - r12 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "blargh>", IsHashtag: false})).Data.(*model.PostList) - if len(r12.Order) != 1 { - t.Fatal("returned wrong search result") + o7 := &model.Post{} + o7.ChannelId = c3.Id + o7.UserId = model.NewId() + o7.Message = "New Jersey is where John is from corey new york" + o7 = (<-ss.Post().Save(o7)).Data.(*model.Post) + + o8 := &model.Post{} + o8.ChannelId = c3.Id + o8.UserId = model.NewId() + o8.Message = "Deleted" + o8 = (<-ss.Post().Save(o8)).Data.(*model.Post) + + tt := []struct { + name string + searchParams *model.SearchParams + extectedResultsCount int + expectedMessageResultIds []string + }{ + { + "normal-search-1", + &model.SearchParams{Terms: "corey"}, + 1, + []string{o1.Id}, + }, + { + "normal-search-2", + &model.SearchParams{Terms: "new"}, + 2, + []string{o1.Id, o2.Id}, + }, + { + "normal-search-3", + &model.SearchParams{Terms: "john"}, + 1, + []string{o2.Id}, + }, + { + "wildcard-search", + &model.SearchParams{Terms: "matter*"}, + 1, + []string{o1.Id}, + }, + { + "hashtag-search", + &model.SearchParams{Terms: "#hashtag", IsHashtag: true}, + 1, + []string{o4.Id}, + }, + { + "hashtag-search-2", + &model.SearchParams{Terms: "#secret", IsHashtag: true}, + 1, + []string{o5.Id}, + }, + { + "no-match-mention", + &model.SearchParams{Terms: "@thisshouldmatchnothing", IsHashtag: true}, + 0, + []string{}, + }, + { + "no-results-search", + &model.SearchParams{Terms: "mattermost jersey"}, + 0, + []string{}, + }, + { + "multiple-words-search", + &model.SearchParams{Terms: "corey new york"}, + 1, + []string{o1.Id}, + }, + { + "multiple-wildcard-search", + &model.SearchParams{Terms: "matter* jer*"}, + 0, + []string{}, + }, + { + "search-with-work-next-to-a-symbol", + &model.SearchParams{Terms: "message blargh"}, + 1, + []string{o4.Id}, + }, + { + "search-with-or", + &model.SearchParams{Terms: "Jersey corey", OrTerms: true}, + 2, + []string{o1.Id, o2.Id}, + }, + { + "search-with-or-and-deleted", + &model.SearchParams{Terms: "Jersey corey", OrTerms: true, IncludeDeletedChannels: true}, + 3, + []string{o1.Id, o2.Id, o7.Id}, + }, + { + "search-hashtag-deleted", + &model.SearchParams{Terms: "#hashtag", IsHashtag: true, IncludeDeletedChannels: true}, + 2, + []string{o4.Id, o6.Id}, + }, + { + "search-deleted-only", + &model.SearchParams{Terms: "Deleted", IncludeDeletedChannels: true}, + 1, + []string{o8.Id}, + }, } - - r13 := (<-ss.Post().Search(teamId, userId, &model.SearchParams{Terms: "Jersey corey", IsHashtag: false, OrTerms: true})).Data.(*model.PostList) - if len(r13.Order) != 2 { - t.Fatal("returned wrong search result") + for _, tc := range tt { + t.Run(tc.name, func(t *testing.T) { + result := (<-ss.Post().Search(teamId, userId, tc.searchParams)).Data.(*model.PostList) + require.Len(t, result.Order, tc.extectedResultsCount) + for _, expectedMessageResultId := range tc.expectedMessageResultIds { + assert.Contains(t, result.Order, expectedMessageResultId) + } + }) } } diff --git a/utils/config.go b/utils/config.go index 0a20c1723..4bbe4cd15 100644 --- a/utils/config.go +++ b/utils/config.go @@ -512,6 +512,7 @@ func GenerateClientConfig(c *model.Config, diagnosticId string, license *model.L props["EnableXToLeaveChannelsFromLHS"] = strconv.FormatBool(*c.TeamSettings.EnableXToLeaveChannelsFromLHS) props["TeammateNameDisplay"] = *c.TeamSettings.TeammateNameDisplay props["ExperimentalPrimaryTeam"] = *c.TeamSettings.ExperimentalPrimaryTeam + props["ViewArchivedChannels"] = strconv.FormatBool(*c.TeamSettings.ViewArchivedChannels) props["EnableOAuthServiceProvider"] = strconv.FormatBool(c.ServiceSettings.EnableOAuthServiceProvider) props["GoogleDeveloperKey"] = c.ServiceSettings.GoogleDeveloperKey -- cgit v1.2.3-1-g7c22