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>
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
60ffd00d30
Коммит
3dc0e63c03
@@ -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 {
|
||||
|
||||
Ссылка в новой задаче
Block a user