Adds Shared Channel related API endpoints (#27436)

* Adds Shared Channel management API endpoints

New endpoints for the following routes are added:

- Get Shared Channel Remotes by Remote Cluster at `GET
/api/v4/remotecluster/{remote_id}/sharedchannelremotes`
- Invite Remote Cluster to Channel at `POST
/api/v4/remotecluster/{remote_id}/channels/invite`
- Uninvite Remote Cluster to Channel at `POST
/api/v4/remotecluster/{remote_id}/channels/uninvite`

These endpoints are planned to be used from the system console, and
gated through the `manage_secure_connections` permission.

* Adds i18n messages for API errors

* Fix pagination flaky test

* Fix linter

* Adds the posibility of filtering shared channel remotes by home

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Miguel de la Cruz
2024-08-29 12:46:37 +02:00
коммит произвёл GitHub
родитель 60ffd00d30
Коммит 3dc0e63c03
20 изменённых файлов: 819 добавлений и 54 удалений

Просмотреть файл

@@ -9106,7 +9106,7 @@ func (s *OpenTracingLayerSharedChannelStore) GetRemoteForUser(remoteId string, u
return result, err
}
func (s *OpenTracingLayerSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
func (s *OpenTracingLayerSharedChannelStore) GetRemotes(offset int, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "SharedChannelStore.GetRemotes")
s.Root.Store.SetContext(newCtx)
@@ -9115,7 +9115,7 @@ func (s *OpenTracingLayerSharedChannelStore) GetRemotes(opts model.SharedChannel
}()
defer span.Finish()
result, err := s.SharedChannelStore.GetRemotes(opts)
result, err := s.SharedChannelStore.GetRemotes(offset, limit, opts)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)

Просмотреть файл

@@ -10400,11 +10400,11 @@ func (s *RetryLayerSharedChannelStore) GetRemoteForUser(remoteId string, userId
}
func (s *RetryLayerSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
func (s *RetryLayerSharedChannelStore) GetRemotes(offset int, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
tries := 0
for {
result, err := s.SharedChannelStore.GetRemotes(opts)
result, err := s.SharedChannelStore.GetRemotes(offset, limit, opts)
if err == nil {
return result, nil
}

Просмотреть файл

@@ -159,8 +159,10 @@ func (s SqlSharedChannelStore) GetAll(offset, limit int, opts model.SharedChanne
return nil, err
}
query := s.getSharedChannelsQuery(opts, false)
query = query.OrderBy("sc.ShareDisplayName, sc.ShareName").Limit(safeLimit).Offset(safeOffset)
query := s.getSharedChannelsQuery(opts, false).
OrderBy("sc.ShareDisplayName, sc.ShareName").
Limit(safeLimit).
Offset(safeOffset)
squery, args, err := query.ToSql()
if err != nil {
@@ -459,25 +461,49 @@ func (s SqlSharedChannelStore) GetRemoteByIds(channelId string, remoteId string)
}
// GetRemotes fetches all shared channel remotes associated with channel_id.
func (s SqlSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
func (s SqlSharedChannelStore) GetRemotes(offset, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
if opts.ExcludeHome && opts.ExcludeRemote {
return nil, errors.New("cannot exclude home and remote shared channel remotes")
}
if offset < 0 {
return nil, errors.New("offset must be a positive integer")
}
if limit < 0 {
return nil, errors.New("limit must be a positive integer")
}
remotes := []*model.SharedChannelRemote{}
query := s.getQueryBuilder().
Select(sharedChannelRemoteFields("")...).
From("SharedChannelRemotes")
Select(sharedChannelRemoteFields("scr")...).
From("SharedChannelRemotes scr").
OrderBy("scr.Id")
if opts.ChannelId != "" {
query = query.Where(sq.Eq{"ChannelId": opts.ChannelId})
query = query.Where(sq.Eq{"scr.ChannelId": opts.ChannelId})
}
if opts.RemoteId != "" {
query = query.Where(sq.Eq{"RemoteId": opts.RemoteId})
query = query.Where(sq.Eq{"scr.RemoteId": opts.RemoteId})
}
if !opts.InclUnconfirmed {
query = query.Where(sq.Eq{"IsInviteConfirmed": true})
query = query.Where(sq.Eq{"scr.IsInviteConfirmed": true})
}
if opts.ExcludeHome {
query = query.Join("SharedChannels sc ON (scr.ChannelId = sc.ChannelId)").
Where(sq.Eq{"sc.Home": false})
}
if opts.ExcludeRemote {
query = query.Join("SharedChannels sc ON (scr.ChannelId = sc.ChannelId)").
Where(sq.Eq{"sc.Home": true})
}
query = query.Offset(uint64(offset)).Limit(uint64(limit))
squery, args, err := query.ToSql()
if err != nil {
return nil, errors.Wrapf(err, "get_shared_channel_remotes_tosql")

Просмотреть файл

@@ -980,7 +980,7 @@ type SharedChannelStore interface {
HasRemote(channelID string, remoteId string) (bool, error)
GetRemoteForUser(remoteId string, userId string) (*model.RemoteCluster, error)
GetRemoteByIds(channelId string, remoteId string) (*model.SharedChannelRemote, error)
GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
GetRemotes(offset, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
UpdateRemoteCursor(id string, cursor model.GetPostsSinceForSyncCursor) error
DeleteRemote(remoteId string) (bool, error)
GetRemotesStatus(channelId string) ([]*model.SharedChannelRemoteStatus, error)

Просмотреть файл

@@ -278,9 +278,9 @@ func (_m *SharedChannelStore) GetRemoteForUser(remoteId string, userId string) (
return r0, r1
}
// GetRemotes provides a mock function with given fields: opts
func (_m *SharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
ret := _m.Called(opts)
// GetRemotes provides a mock function with given fields: offset, limit, opts
func (_m *SharedChannelStore) GetRemotes(offset int, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
ret := _m.Called(offset, limit, opts)
if len(ret) == 0 {
panic("no return value specified for GetRemotes")
@@ -288,19 +288,19 @@ func (_m *SharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpt
var r0 []*model.SharedChannelRemote
var r1 error
if rf, ok := ret.Get(0).(func(model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)); ok {
return rf(opts)
if rf, ok := ret.Get(0).(func(int, int, model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)); ok {
return rf(offset, limit, opts)
}
if rf, ok := ret.Get(0).(func(model.SharedChannelRemoteFilterOpts) []*model.SharedChannelRemote); ok {
r0 = rf(opts)
if rf, ok := ret.Get(0).(func(int, int, model.SharedChannelRemoteFilterOpts) []*model.SharedChannelRemote); ok {
r0 = rf(offset, limit, opts)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.SharedChannelRemote)
}
}
if rf, ok := ret.Get(1).(func(model.SharedChannelRemoteFilterOpts) error); ok {
r1 = rf(opts)
if rf, ok := ret.Get(1).(func(int, int, model.SharedChannelRemoteFilterOpts) error); ok {
r1 = rf(offset, limit, opts)
} else {
r1 = ret.Error(1)
}

Просмотреть файл

@@ -191,16 +191,17 @@ func testGetSharedChannels(t *testing.T, rctx request.CTX, ss store.Store) {
creator := model.NewId()
team1 := model.NewId()
team2 := model.NewId()
rid := model.NewId()
rid1 := model.NewId()
rid2 := model.NewId()
data := []model.SharedChannel{
{CreatorId: creator, TeamId: team1, ShareName: "test1", Home: true},
{CreatorId: creator, TeamId: team1, ShareName: "test2", Home: false, RemoteId: rid},
{CreatorId: creator, TeamId: team1, ShareName: "test3", Home: false, RemoteId: rid},
{CreatorId: creator, TeamId: team1, ShareName: "test2", Home: false, RemoteId: rid1},
{CreatorId: creator, TeamId: team1, ShareName: "test3", Home: false, RemoteId: rid2},
{CreatorId: creator, TeamId: team1, ShareName: "test4", Home: true},
{CreatorId: creator, TeamId: team2, ShareName: "test5", Home: true},
{CreatorId: creator, TeamId: team2, ShareName: "test6", Home: false, RemoteId: rid},
{CreatorId: creator, TeamId: team2, ShareName: "test7", Home: false, RemoteId: rid},
{CreatorId: creator, TeamId: team2, ShareName: "test6", Home: false, RemoteId: rid1},
{CreatorId: creator, TeamId: team2, ShareName: "test7", Home: false, RemoteId: rid2},
{CreatorId: creator, TeamId: team2, ShareName: "test8", Home: true},
{CreatorId: creator, TeamId: team2, ShareName: "test9", Home: true},
}
@@ -407,7 +408,7 @@ func testDeleteSharedChannel(t *testing.T, rctx request.CTX, ss store.Store) {
require.Nil(t, sc)
// make sure the remotes were deleted.
remotes, err := ss.SharedChannel().GetRemotes(model.SharedChannelRemoteFilterOpts{ChannelId: channel.Id})
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, model.SharedChannelRemoteFilterOpts{ChannelId: channel.Id})
require.NoError(t, err)
require.Len(t, remotes, 0, "expected empty remotes list")
@@ -578,21 +579,29 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
creator := model.NewId()
remoteId := model.NewId()
remoteId2 := model.NewId()
data := []model.SharedChannelRemote{
{ChannelId: channel.Id, CreatorId: creator, RemoteId: model.NewId(), IsInviteConfirmed: true},
{ChannelId: channel.Id, CreatorId: creator, RemoteId: model.NewId(), IsInviteConfirmed: true},
{ChannelId: channel.Id, CreatorId: creator, RemoteId: model.NewId(), IsInviteConfirmed: true},
{ChannelId: channel.Id, CreatorId: creator, RemoteId: remoteId2, IsInviteConfirmed: true},
{CreatorId: creator, RemoteId: remoteId, IsInviteConfirmed: true},
{CreatorId: creator, RemoteId: remoteId, IsInviteConfirmed: true},
{CreatorId: creator, RemoteId: remoteId},
}
// first three remotes are homed locally
_, scErr := shareChannel(ss, channel, true, "")
require.NoError(t, scErr)
for i, r := range data {
if r.ChannelId == "" {
c, err := createTestChannel(ss, rctx, "test_remotes_get2_"+strconv.Itoa(i))
require.NoError(t, err)
r.ChannelId = c.Id
// next three remotes are homed outside
shareChannel(ss, c, false, r.RemoteId)
}
_, err := ss.SharedChannel().SaveRemote(&r)
require.NoError(t, err, "error saving shared channel remote")
@@ -602,7 +611,7 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
opts := model.SharedChannelRemoteFilterOpts{
ChannelId: channel.Id,
}
remotes, err := ss.SharedChannel().GetRemotes(opts)
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 3)
for _, r := range remotes {
@@ -614,7 +623,7 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
opts := model.SharedChannelRemoteFilterOpts{
ChannelId: model.NewId(),
}
remotes, err := ss.SharedChannel().GetRemotes(opts)
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 0)
})
@@ -623,7 +632,7 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
opts := model.SharedChannelRemoteFilterOpts{
RemoteId: remoteId,
}
remotes, err := ss.SharedChannel().GetRemotes(opts)
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 2) // only confirmed invitations
for _, r := range remotes {
@@ -636,7 +645,7 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
opts := model.SharedChannelRemoteFilterOpts{
RemoteId: model.NewId(),
}
remotes, err := ss.SharedChannel().GetRemotes(opts)
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 0)
})
@@ -646,13 +655,61 @@ func testGetSharedChannelRemotes(t *testing.T, rctx request.CTX, ss store.Store)
RemoteId: remoteId,
InclUnconfirmed: true,
}
remotes, err := ss.SharedChannel().GetRemotes(opts)
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 3)
for _, r := range remotes {
require.Equal(t, remoteId, r.RemoteId)
}
})
t.Run("Get shared channel remotes with bad options", func(t *testing.T) {
opts := model.SharedChannelRemoteFilterOpts{
ExcludeHome: true,
ExcludeRemote: true,
}
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.Error(t, err, "error expected")
require.Empty(t, remotes)
})
t.Run("Get shared channel remotes excluding shared from outside", func(t *testing.T) {
opts := model.SharedChannelRemoteFilterOpts{
ExcludeRemote: true,
}
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 3)
})
t.Run("Get shared channel remotes excluding shared from home", func(t *testing.T) {
opts := model.SharedChannelRemoteFilterOpts{
ExcludeHome: true,
}
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 2)
})
t.Run("Get shared channel remotes excluding shared from outside and by remote_id", func(t *testing.T) {
opts := model.SharedChannelRemoteFilterOpts{
ExcludeRemote: true,
RemoteId: remoteId2,
}
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 1)
})
t.Run("Get shared channel remotes excluding shared from home including unconfirmed", func(t *testing.T) {
opts := model.SharedChannelRemoteFilterOpts{
ExcludeHome: true,
InclUnconfirmed: true,
}
remotes, err := ss.SharedChannel().GetRemotes(0, 999999, opts)
require.NoError(t, err, "should not error", err)
require.Len(t, remotes, 3)
})
}
func testHasRemote(t *testing.T, rctx request.CTX, ss store.Store) {
@@ -892,21 +949,26 @@ func createSharedTestChannel(ss store.Store, rctx request.CTX, name string, shar
}
if shared {
sc := &model.SharedChannel{
ChannelId: channel.Id,
TeamId: channel.TeamId,
CreatorId: channel.CreatorId,
ShareName: channel.Name,
Home: true,
}
_, err = ss.SharedChannel().Save(sc)
if err != nil {
if _, err := shareChannel(ss, channel, true, ""); err != nil {
return nil, err
}
}
return channel, nil
}
func shareChannel(ss store.Store, channel *model.Channel, home bool, remoteId string) (*model.SharedChannel, error) {
sc := &model.SharedChannel{
ChannelId: channel.Id,
TeamId: channel.TeamId,
CreatorId: channel.CreatorId,
ShareName: channel.Name,
Home: home,
RemoteId: remoteId,
}
return ss.SharedChannel().Save(sc)
}
func clearSharedChannels(ss store.Store) error {
opts := model.SharedChannelFilterOpts{}
all, err := ss.SharedChannel().GetAll(0, 1000, opts)

Просмотреть файл

@@ -8201,10 +8201,10 @@ func (s *TimerLayerSharedChannelStore) GetRemoteForUser(remoteId string, userId
return result, err
}
func (s *TimerLayerSharedChannelStore) GetRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
func (s *TimerLayerSharedChannelStore) GetRemotes(offset int, limit int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
start := time.Now()
result, err := s.SharedChannelStore.GetRemotes(opts)
result, err := s.SharedChannelStore.GetRemotes(offset, limit, opts)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {