Excludes remote channels from channel search (#28708)

* Excludes remote channels from channel search

This is done through a new body parameter in the SearchAllChannels
endpoint that allows to search for local only channels, which are
either channels that are shared but marked as homed locally, or
channels that are not shared at all.

* fix lint

* Fix tests

---------

Co-authored-by: Caleb Roseland <caleb@calebroseland.com>
This commit is contained in:
Miguel de la Cruz 2024-10-18 15:41:24 +02:00 committed by GitHub
parent f06742c7e2
commit b4d8b6239c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 82 additions and 8 deletions

View File

@ -287,7 +287,6 @@
__Minimum server version__: 5.26
deleted:
type: boolean
description: >
@ -314,7 +313,6 @@
required to use this parameter.
__Minimum server version__: 5.35
include_search_by_id:
type: boolean
default: false
@ -322,6 +320,13 @@
If set to true, returns channels where given search 'term' matches channel ID.
__Minimum server version__: 5.35
exclude_remote:
type: boolean
default: false
description: >
If set to true, only returns channels that are local to this server.
__Minimum server version__: 10.2
description: The search terms and logic to use in the search.
required: true
responses:

View File

@ -1299,6 +1299,7 @@ func searchAllChannels(c *Context, w http.ResponseWriter, r *http.Request) {
ExcludeGroupConstrained: props.ExcludeGroupConstrained,
ExcludePolicyConstrained: props.ExcludePolicyConstrained,
IncludeSearchById: props.IncludeSearchById,
ExcludeRemote: props.ExcludeRemote,
Public: props.Public,
Private: props.Private,
IncludeDeleted: includeDeleted,

View File

@ -1696,7 +1696,7 @@ func TestSearchArchivedChannels(t *testing.T) {
}
func TestSearchAllChannels(t *testing.T) {
th := Setup(t).InitBasic()
th := setupForSharedChannels(t).InitBasic()
th.LoginSystemManager()
defer th.TearDown()
client := th.Client
@ -1737,6 +1737,29 @@ func TestSearchAllChannels(t *testing.T) {
})
require.NoError(t, err)
// share the open and private channels, one homed locally and the
// other remotely
sco := &model.SharedChannel{
ChannelId: openChannel.Id,
TeamId: openChannel.TeamId,
Home: true,
ShareName: "testsharelocal",
CreatorId: th.BasicChannel.CreatorId,
}
_, scoErr := th.App.ShareChannel(th.Context, sco)
require.NoError(t, scoErr)
scp := &model.SharedChannel{
ChannelId: privateChannel.Id,
TeamId: privateChannel.TeamId,
Home: false,
RemoteId: model.NewId(),
ShareName: "testshareremote",
CreatorId: th.BasicChannel.CreatorId,
}
_, scpErr := th.App.ShareChannel(th.Context, scp)
require.NoError(t, scpErr)
testCases := []struct {
Description string
Search *model.ChannelSearch
@ -1847,6 +1870,11 @@ func TestSearchAllChannels(t *testing.T) {
&model.ChannelSearch{Term: "SearchAllChannels", ExcludeGroupConstrained: true},
[]string{openChannel.Id, privateChannel.Id},
},
{
"Search for local only channels",
&model.ChannelSearch{Term: "SearchAllChannels", ExcludeRemote: true},
[]string{openChannel.Id, groupConstrainedChannel.Id},
},
}
for _, testCase := range testCases {
t.Run(testCase.Description, func(t *testing.T) {

View File

@ -2923,6 +2923,7 @@ func (a *App) SearchAllChannels(c request.CTX, term string, opts model.ChannelSe
PolicyID: opts.PolicyID,
IncludePolicyID: opts.IncludePolicyID,
IncludeSearchById: opts.IncludeSearchById,
ExcludeRemote: opts.ExcludeRemote,
ExcludePolicyConstrained: opts.ExcludePolicyConstrained,
Public: opts.Public,
Private: opts.Private,

View File

@ -3411,6 +3411,16 @@ func (s SqlChannelStore) channelSearchQuery(opts *store.ChannelSearchOpts) sq.Se
})
}
if opts.ExcludeRemote {
// local channels either have a SharedChannels record with
// home set to true, or don't have a SharedChannels record at all
query = query.LeftJoin("SharedChannels ON c.Id = SharedChannels.ChannelId").
Where(sq.Or{
sq.Eq{"SharedChannels.Home": true},
sq.Eq{"SharedChannels.ChannelId": nil},
})
}
return query
}

View File

@ -1074,6 +1074,7 @@ type ChannelSearchOpts struct {
IncludePolicyID bool
IncludeTeamInfo bool
IncludeSearchById bool
ExcludeRemote bool
CountOnly bool
Public bool
Private bool

View File

@ -6506,6 +6506,29 @@ func testChannelStoreSearchAllChannels(t *testing.T, rctx request.CTX, ss store.
})
require.NoError(t, nErr)
// Mark o12 and o14 as shared, o13 will be homed locally and o14
// will be homed remotely
sc12 := &model.SharedChannel{
ChannelId: o12.Id,
TeamId: o12.TeamId,
CreatorId: model.NewId(),
ShareName: "testsharelocal",
Home: true,
}
_, scErr := ss.SharedChannel().Save(sc12)
require.NoError(t, scErr)
sc14 := &model.SharedChannel{
ChannelId: o14.Id,
TeamId: o14.TeamId,
CreatorId: model.NewId(),
ShareName: "testshareremote",
Home: false,
RemoteId: model.NewId(),
}
_, sc2Err := ss.SharedChannel().Save(sc14)
require.NoError(t, sc2Err)
testCases := []struct {
Description string
Term string
@ -6550,6 +6573,7 @@ func testChannelStoreSearchAllChannels(t *testing.T, rctx request.CTX, ss store.
{"Filter team 1 and team 2, public and private and exclude group constrained", "", store.ChannelSearchOpts{IncludeDeleted: false, TeamIds: []string{t1.Id, t2.Id}, Public: true, Private: true, ExcludeGroupConstrained: true, Page: model.NewPointer(0), PerPage: model.NewPointer(5)}, model.ChannelList{&o1, &o2, &o3, &o4, &o6}, 12},
{"Filter deleted returns only deleted channels", "", store.ChannelSearchOpts{Deleted: true, Page: model.NewPointer(0), PerPage: model.NewPointer(5)}, model.ChannelList{&o13}, 1},
{"Search ChannelA by id", o1.Id, store.ChannelSearchOpts{IncludeDeleted: false, Page: model.NewPointer(0), PerPage: model.NewPointer(5), IncludeSearchById: true}, model.ChannelList{&o1}, 1},
{"Filter excluding remote channels", "", store.ChannelSearchOpts{IncludeDeleted: false, ExcludeRemote: true}, model.ChannelList{&o1, &o2, &o3, &o4, &o5, &o6, &o7, &o8, &o9, &o10, &o11, &o12}, 0},
}
for _, testCase := range testCases {

View File

@ -180,6 +180,7 @@ type ChannelSearchOpts struct {
ExcludePolicyConstrained bool
IncludePolicyID bool
IncludeSearchById bool
ExcludeRemote bool
Public bool
Private bool
Page *int

View File

@ -17,6 +17,7 @@ type ChannelSearch struct {
Private bool `json:"private"`
IncludeDeleted bool `json:"include_deleted"`
IncludeSearchById bool `json:"include_search_by_id"`
ExcludeRemote bool `json:"exclude_remote"`
Deleted bool `json:"deleted"`
Page *int `json:"page,omitempty"`
PerPage *int `json:"per_page,omitempty"`

View File

@ -79,7 +79,7 @@ function SharedChannelsAddModal({
return [];
}
const {data} = await dispatch(searchAllChannels(query, {page: 0, per_page: 20, signal}));
const {data} = await dispatch(searchAllChannels(query, {page: 0, per_page: 20, exclude_remote: true, signal}));
if (data) {
return data.channels.filter(({id}) => {
const remote = remotesByChannelId?.[id];

View File

@ -1132,7 +1132,7 @@ describe('Actions.Channels', () => {
);
nock(Client4.getBaseRoute()).
post('/channels/search?include_deleted=false').
post('/channels/search?include_deleted=false&exclude_remote=false').
reply(200, [TestHelper.basicChannel, userChannel]);
await store.dispatch(Actions.searchAllChannels('test', {}));
@ -1143,7 +1143,7 @@ describe('Actions.Channels', () => {
}
nock(Client4.getBaseRoute()).
post('/channels/search?include_deleted=false').
post('/channels/search?include_deleted=false&exclude_remote=false').
reply(200, {channels: [TestHelper.basicChannel, userChannel], total_count: 2});
let response = await store.dispatch(Actions.searchAllChannels('test', {exclude_default_channels: false, page: 0, per_page: 100}));
@ -1156,7 +1156,7 @@ describe('Actions.Channels', () => {
expect(response.data.channels.length === 2).toBeTruthy();
nock(Client4.getBaseRoute()).
post('/channels/search?include_deleted=true').
post('/channels/search?include_deleted=true&exclude_remote=false').
reply(200, {channels: [TestHelper.basicChannel, userChannel], total_count: 2});
response = await store.dispatch(Actions.searchAllChannels('test', {exclude_default_channels: false, page: 0, per_page: 100, include_deleted: true}));

View File

@ -1961,7 +1961,8 @@ export default class Client4 {
};
const includeDeleted = Boolean(opts.include_deleted);
const nonAdminSearch = Boolean(opts.nonAdminSearch);
let queryParams: {include_deleted?: boolean; system_console?: boolean} = {include_deleted: includeDeleted};
const excludeRemote = Boolean(opts.exclude_remote);
let queryParams: {include_deleted?: boolean; system_console?: boolean; exclude_remote?: boolean} = {include_deleted: includeDeleted, exclude_remote: excludeRemote};
if (nonAdminSearch) {
queryParams = {system_console: false};
delete body.nonAdminSearch;

View File

@ -211,6 +211,7 @@ export type ChannelSearchOpts = {
private?: boolean;
include_deleted?: boolean;
include_search_by_id?: boolean;
exclude_remote?: boolean;
deleted?: boolean;
page?: number;
per_page?: number;