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
@@ -3535,6 +3535,44 @@ components:
|
||||
description: The time in milliseconds a remote cluster was last pinged successfully
|
||||
type: integer
|
||||
format: int64
|
||||
SharedChannelRemote:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
description: The id of the shared channel remote
|
||||
type: string
|
||||
channel_id:
|
||||
description: The id of the channel
|
||||
type: string
|
||||
creator_id:
|
||||
description: Id of the user that invited the remote to share the channel
|
||||
type: string
|
||||
create_at:
|
||||
description: Time in milliseconds that the remote was invited to the channel
|
||||
type: integer
|
||||
update_at:
|
||||
description: Time in milliseconds that the shared channel remote record was last updated
|
||||
type: integer
|
||||
is_invite_accepted:
|
||||
description: Indicates if the invite has been accepted by the remote
|
||||
type: boolean
|
||||
is_invite_confirmed:
|
||||
description: Indicates if the invite has been confirmed by the remote
|
||||
type: boolean
|
||||
remote_id:
|
||||
description: Id of the remote cluster that the channel is shared with
|
||||
type: string
|
||||
last_post_update_at:
|
||||
description: Time in milliseconds of the last post in the channel that was synchronized with the remote update_at
|
||||
type: integer
|
||||
last_post_id:
|
||||
description: Id of the last post in the channel that was synchronized with the remote
|
||||
type: string
|
||||
last_post_create_at:
|
||||
description: Time in milliseconds of the last post in the channel that was synchronized with the remote create_at
|
||||
type: string
|
||||
last_post_create_id:
|
||||
type: string
|
||||
SystemStatusResponse:
|
||||
type: object
|
||||
properties:
|
||||
|
||||
@@ -45,6 +45,61 @@
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
"/api/v4/remotecluster/{remote_id}/sharedchannelremotes":
|
||||
get:
|
||||
tags:
|
||||
- shared channels
|
||||
summary: Get shared channel remotes by remote cluster.
|
||||
description: |
|
||||
Get a list of the channels shared with a given remote cluster
|
||||
and their status.
|
||||
|
||||
##### Permissions
|
||||
`manage_secure_connections`
|
||||
operationId: GetSharedChannelRemotesByRemoteCluster
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: The remote cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: exclude_home
|
||||
in: query
|
||||
description: Show only those Shared channel remotes that were shared with this server
|
||||
schema:
|
||||
type: boolean
|
||||
- name: exclude_remote
|
||||
in: query
|
||||
description: Show only those Shared channel remotes that were shared from this server
|
||||
schema:
|
||||
type: boolean
|
||||
- name: page
|
||||
in: query
|
||||
description: The page to select
|
||||
schema:
|
||||
type: integer
|
||||
- name: per_page
|
||||
in: query
|
||||
description: The number of shared channels per page
|
||||
schema:
|
||||
type: integer
|
||||
responses:
|
||||
"200":
|
||||
description: Shared channel remotes fetch successful. Result might be empty.
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: array
|
||||
items:
|
||||
$ref: "#/components/schemas/SharedChannelRemote"
|
||||
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
"/api/v4/sharedchannels/remote_info/{remote_id}":
|
||||
get:
|
||||
tags:
|
||||
@@ -80,3 +135,71 @@
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
"404":
|
||||
$ref: "#/components/responses/NotFound"
|
||||
|
||||
"/api/v4/remotecluster/{remote_id}/channels/{channel_id}/invite":
|
||||
post:
|
||||
tags:
|
||||
- shared channels
|
||||
summary: Invites a remote cluster to a channel.
|
||||
description: |
|
||||
Invites a remote cluster to a channel, sharing the channel if
|
||||
needed. If the remote cluster was already invited to the
|
||||
channel, calling this endpoint will have no effect.
|
||||
|
||||
##### Permissions
|
||||
`manage_shared_channels`
|
||||
operationId: InviteRemoteClusterToChannel
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: The remote cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: channel_id
|
||||
in: path
|
||||
description: The channel GUID to invite the remote cluster to
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: Remote cluster invited successfully
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
"/api/v4/remotecluster/{remote_id}/channels/{channel_id}/uninvite":
|
||||
post:
|
||||
tags:
|
||||
- shared channels
|
||||
summary: Uninvites a remote cluster to a channel.
|
||||
description: |
|
||||
Stops sharing a channel with a remote cluster. If the channel
|
||||
was not shared with the remote, calling this endpoint will
|
||||
have no effect.
|
||||
|
||||
##### Permissions
|
||||
`manage_shared_channels`
|
||||
operationId: UninviteRemoteClusterToChannel
|
||||
parameters:
|
||||
- name: remote_id
|
||||
in: path
|
||||
description: The remote cluster GUID
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
- name: channel_id
|
||||
in: path
|
||||
description: The channel GUID to uninvite the remote cluster to
|
||||
required: true
|
||||
schema:
|
||||
type: string
|
||||
responses:
|
||||
"204":
|
||||
description: Remote cluster uninvited successfully
|
||||
"401":
|
||||
$ref: "#/components/responses/Unauthorized"
|
||||
"403":
|
||||
$ref: "#/components/responses/Forbidden"
|
||||
|
||||
@@ -129,8 +129,10 @@ type Routes struct {
|
||||
Exports *mux.Router // 'api/v4/exports'
|
||||
Export *mux.Router // 'api/v4/exports/{export_name:.+\\.zip}'
|
||||
|
||||
RemoteCluster *mux.Router // 'api/v4/remotecluster'
|
||||
SharedChannels *mux.Router // 'api/v4/sharedchannels'
|
||||
RemoteCluster *mux.Router // 'api/v4/remotecluster'
|
||||
SharedChannels *mux.Router // 'api/v4/sharedchannels'
|
||||
ChannelForRemote *mux.Router // 'api/v4/remotecluster/{remote_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}'
|
||||
SharedChannelRemotes *mux.Router // 'api/v4/remotecluster/{remote_id:[A-Za-z0-9]+}/sharedchannelremotes'
|
||||
|
||||
Permissions *mux.Router // 'api/v4/permissions'
|
||||
|
||||
@@ -265,6 +267,8 @@ func Init(srv *app.Server) (*API, error) {
|
||||
|
||||
api.BaseRoutes.RemoteCluster = api.BaseRoutes.APIRoot.PathPrefix("/remotecluster").Subrouter()
|
||||
api.BaseRoutes.SharedChannels = api.BaseRoutes.APIRoot.PathPrefix("/sharedchannels").Subrouter()
|
||||
api.BaseRoutes.SharedChannelRemotes = api.BaseRoutes.RemoteCluster.PathPrefix("/{remote_id:[A-Za-z0-9]+}/sharedchannelremotes").Subrouter()
|
||||
api.BaseRoutes.ChannelForRemote = api.BaseRoutes.RemoteCluster.PathPrefix("/{remote_id:[A-Za-z0-9]+}/channels/{channel_id:[A-Za-z0-9]+}").Subrouter()
|
||||
|
||||
api.BaseRoutes.Permissions = api.BaseRoutes.APIRoot.PathPrefix("/permissions").Subrouter()
|
||||
|
||||
|
||||
@@ -8,11 +8,17 @@ import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mattermost/mattermost/server/public/model"
|
||||
"github.com/mattermost/mattermost/server/public/shared/mlog"
|
||||
"github.com/mattermost/mattermost/server/v8/channels/audit"
|
||||
)
|
||||
|
||||
func (api *API) InitSharedChannels() {
|
||||
api.BaseRoutes.SharedChannels.Handle("/{team_id:[A-Za-z0-9]+}", api.APISessionRequired(getSharedChannels)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.SharedChannels.Handle("/remote_info/{remote_id:[A-Za-z0-9]+}", api.APISessionRequired(getRemoteClusterInfo)).Methods(http.MethodGet)
|
||||
|
||||
api.BaseRoutes.SharedChannelRemotes.Handle("", api.APISessionRequired(getSharedChannelRemotesByRemoteCluster)).Methods(http.MethodGet)
|
||||
api.BaseRoutes.ChannelForRemote.Handle("/invite", api.APISessionRequired(inviteRemoteClusterToChannel)).Methods(http.MethodPost)
|
||||
api.BaseRoutes.ChannelForRemote.Handle("/uninvite", api.APISessionRequired(uninviteRemoteClusterToChannel)).Methods(http.MethodPost)
|
||||
}
|
||||
|
||||
func getSharedChannels(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
@@ -86,3 +92,146 @@ func getRemoteClusterInfo(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
w.Write(b)
|
||||
}
|
||||
|
||||
func getSharedChannelRemotesByRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSecureConnections)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if _, appErr := c.App.GetRemoteCluster(c.Params.RemoteId); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
filter := model.SharedChannelRemoteFilterOpts{
|
||||
RemoteId: c.Params.RemoteId,
|
||||
ExcludeHome: c.Params.ExcludeHome,
|
||||
ExcludeRemote: c.Params.ExcludeRemote,
|
||||
}
|
||||
sharedChannelRemotes, err := c.App.GetSharedChannelRemotes(c.Params.Page, c.Params.PerPage, filter)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("getSharedChannelRemotesByRemoteCluster", "api.shared_channel.get_shared_channel_remotes_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
if err := json.NewEncoder(w).Encode(sharedChannelRemotes); err != nil {
|
||||
c.Logger.Warn("Error while writing response", mlog.Err(err))
|
||||
}
|
||||
}
|
||||
|
||||
func inviteRemoteClusterToChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSharedChannels)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if _, appErr := c.App.GetRemoteCluster(c.Params.RemoteId); appErr != nil {
|
||||
c.SetInvalidRemoteIdError(c.Params.RemoteId)
|
||||
return
|
||||
}
|
||||
|
||||
if _, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId); appErr != nil {
|
||||
c.SetInvalidURLParam("channel_id")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("inviteRemoteClusterToChannel", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "remote_id", c.Params.RemoteId)
|
||||
audit.AddEventParameter(auditRec, "channel_id", c.Params.ChannelId)
|
||||
audit.AddEventParameter(auditRec, "user_id", c.AppContext.Session().UserId)
|
||||
|
||||
if err := c.App.InviteRemoteToChannel(c.Params.ChannelId, c.Params.RemoteId, c.AppContext.Session().UserId, true); err != nil {
|
||||
c.Err = model.NewAppError("inviteRemoteClusterToChannel", "api.shared_channel.invite_remote_to_channel_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
func uninviteRemoteClusterToChannel(c *Context, w http.ResponseWriter, r *http.Request) {
|
||||
c.RequireRemoteId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
c.RequireChannelId()
|
||||
if c.Err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
if !c.App.SessionHasPermissionTo(*c.AppContext.Session(), model.PermissionManageSecureConnections) {
|
||||
c.SetPermissionError(model.PermissionManageSharedChannels)
|
||||
return
|
||||
}
|
||||
|
||||
// make sure remote cluster service is enabled.
|
||||
if _, appErr := c.App.GetRemoteClusterService(); appErr != nil {
|
||||
c.Err = appErr
|
||||
return
|
||||
}
|
||||
|
||||
if _, appErr := c.App.GetRemoteCluster(c.Params.RemoteId); appErr != nil {
|
||||
c.SetInvalidRemoteIdError(c.Params.RemoteId)
|
||||
return
|
||||
}
|
||||
|
||||
if _, appErr := c.App.GetChannel(c.AppContext, c.Params.ChannelId); appErr != nil {
|
||||
c.SetInvalidURLParam("channel_id")
|
||||
return
|
||||
}
|
||||
|
||||
auditRec := c.MakeAuditRecord("uninviteRemoteClusterToChannel", audit.Fail)
|
||||
defer c.LogAuditRec(auditRec)
|
||||
audit.AddEventParameter(auditRec, "remote_id", c.Params.RemoteId)
|
||||
audit.AddEventParameter(auditRec, "channel_id", c.Params.ChannelId)
|
||||
|
||||
hasRemote, err := c.App.HasRemote(c.Params.ChannelId, c.Params.RemoteId)
|
||||
if err != nil {
|
||||
c.Err = model.NewAppError("uninviteRemoteClusterToChannel", "api.shared_channel.has_remote_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
// if the channel is not shared with the remote, we return early
|
||||
if !hasRemote {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
return
|
||||
}
|
||||
|
||||
if err := c.App.UninviteRemoteFromChannel(c.Params.ChannelId, c.Params.RemoteId); err != nil {
|
||||
c.Err = model.NewAppError("uninviteRemoteClusterToChannel", "api.shared_channel.uninvite_remote_to_channel_error", nil, "", http.StatusInternalServerError).Wrap(err)
|
||||
return
|
||||
}
|
||||
|
||||
auditRec.Success()
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"sort"
|
||||
"testing"
|
||||
"time"
|
||||
@@ -22,10 +23,16 @@ var (
|
||||
)
|
||||
|
||||
func setupForSharedChannels(tb testing.TB) *TestHelper {
|
||||
return SetupConfig(tb, func(cfg *model.Config) {
|
||||
th := SetupConfig(tb, func(cfg *model.Config) {
|
||||
*cfg.ExperimentalSettings.EnableRemoteClusterService = true
|
||||
*cfg.ExperimentalSettings.EnableSharedChannels = true
|
||||
})
|
||||
|
||||
th.App.UpdateConfig(func(cfg *model.Config) {
|
||||
*cfg.ServiceSettings.SiteURL = fmt.Sprintf("http://localhost:%d", th.Server.ListenAddr.Port)
|
||||
})
|
||||
|
||||
return th
|
||||
}
|
||||
|
||||
func TestGetAllSharedChannels(t *testing.T) {
|
||||
@@ -232,3 +239,278 @@ func TestCreateDirectChannelWithRemoteUser(t *testing.T) {
|
||||
require.True(t, dm.IsShared())
|
||||
})
|
||||
}
|
||||
|
||||
func TestGetSharedChannelRemotesByRemoteCluster(t *testing.T) {
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
resp, err := th.SystemAdminClient.DeleteRemoteCluster(context.Background(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC1 := &model.RemoteCluster{Name: "rc1", SiteURL: "http://example1.com", CreatorId: th.SystemAdminUser.Id}
|
||||
newRC2 := &model.RemoteCluster{Name: "rc2", SiteURL: "http://example2.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc1, appErr := th.App.AddRemoteCluster(newRC1)
|
||||
require.Nil(t, appErr)
|
||||
rc2, appErr := th.App.AddRemoteCluster(newRC2)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
c1 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc1 := &model.SharedChannel{
|
||||
ChannelId: c1.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_1",
|
||||
ShareDisplayName: "Shared Channel 1", // for sorting purposes
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: true,
|
||||
}
|
||||
_, err := th.App.ShareChannel(th.Context, sc1)
|
||||
require.NoError(t, err)
|
||||
|
||||
c2 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc2 := &model.SharedChannel{
|
||||
ChannelId: c2.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_2",
|
||||
ShareDisplayName: "Shared Channel 2",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Home: false,
|
||||
}
|
||||
|
||||
_, err = th.App.ShareChannel(th.Context, sc2)
|
||||
require.NoError(t, err)
|
||||
|
||||
c3 := th.CreateChannelWithClientAndTeam(th.Client, model.ChannelTypeOpen, th.BasicTeam.Id)
|
||||
sc3 := &model.SharedChannel{
|
||||
ChannelId: c3.Id,
|
||||
TeamId: th.BasicTeam.Id,
|
||||
ShareName: "shared_3",
|
||||
CreatorId: th.BasicUser.Id,
|
||||
RemoteId: rc2.RemoteId,
|
||||
}
|
||||
_, err = th.App.ShareChannel(th.Context, sc3)
|
||||
require.NoError(t, err)
|
||||
|
||||
// for the pagination test, we need to get the channelId of the
|
||||
// second SharedChannelRemote that belongs to RC1, sorted by ID,
|
||||
// so we accumulate those SharedChannelRemotes on creation and
|
||||
// later sort them to be able to get the right one for the test
|
||||
// result
|
||||
sharedChannelRemotesFromRC1 := []*model.SharedChannelRemote{}
|
||||
|
||||
// create the shared channel remotes
|
||||
for _, sc := range []*model.SharedChannel{sc1, sc2, sc3} {
|
||||
scr := &model.SharedChannelRemote{
|
||||
Id: model.NewId(),
|
||||
ChannelId: sc.ChannelId,
|
||||
CreatorId: sc.CreatorId,
|
||||
IsInviteAccepted: true,
|
||||
IsInviteConfirmed: true,
|
||||
RemoteId: sc.RemoteId,
|
||||
}
|
||||
_, err = th.App.SaveSharedChannelRemote(scr)
|
||||
require.NoError(t, err)
|
||||
|
||||
if scr.RemoteId == rc1.RemoteId {
|
||||
sharedChannelRemotesFromRC1 = append(sharedChannelRemotesFromRC1, scr)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(sharedChannelRemotesFromRC1, func(i, j int) bool {
|
||||
return sharedChannelRemotesFromRC1[i].Id < sharedChannelRemotesFromRC1[j].Id
|
||||
})
|
||||
|
||||
t.Run("should return the expected shared channels", func(t *testing.T) {
|
||||
testCases := []struct {
|
||||
Name string
|
||||
Client *model.Client4
|
||||
RemoteId string
|
||||
ExcludeHome bool
|
||||
ExcludeRemote bool
|
||||
Page int
|
||||
PerPage int
|
||||
ExpectedStatusCode int
|
||||
ExpectedError bool
|
||||
ExpectedIds []string
|
||||
}{
|
||||
{
|
||||
Name: "should not work if the user doesn't have the right permissions",
|
||||
Client: th.Client,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusForbidden,
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "should not work if the remote cluster is nonexistent",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: model.NewId(),
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusNotFound,
|
||||
ExpectedError: true,
|
||||
},
|
||||
{
|
||||
Name: "should return the complete list of shared channel remotes for a remote cluster",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId, sc2.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return only the shared channel remotes homed localy",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
ExcludeRemote: true,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc1.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should return only the shared channel remotes homed remotely",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
ExcludeHome: true,
|
||||
Page: 0,
|
||||
PerPage: 100,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sc2.ChannelId},
|
||||
},
|
||||
{
|
||||
Name: "should correctly paginate the results",
|
||||
Client: th.SystemAdminClient,
|
||||
RemoteId: rc1.RemoteId,
|
||||
Page: 1,
|
||||
PerPage: 1,
|
||||
ExpectedStatusCode: http.StatusOK,
|
||||
ExpectedError: false,
|
||||
ExpectedIds: []string{sharedChannelRemotesFromRC1[1].ChannelId},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.Name, func(t *testing.T) {
|
||||
scrs, resp, err := tc.Client.GetSharedChannelRemotesByRemoteCluster(context.Background(), tc.RemoteId, tc.ExcludeHome, tc.ExcludeRemote, tc.Page, tc.PerPage)
|
||||
checkHTTPStatus(t, resp, tc.ExpectedStatusCode)
|
||||
if tc.ExpectedError {
|
||||
require.Error(t, err)
|
||||
} else {
|
||||
require.NoError(t, err)
|
||||
}
|
||||
require.Len(t, scrs, len(tc.ExpectedIds))
|
||||
|
||||
foundIds := []string{}
|
||||
for _, scr := range scrs {
|
||||
require.Equal(t, tc.RemoteId, scr.RemoteId)
|
||||
foundIds = append(foundIds, scr.ChannelId)
|
||||
}
|
||||
require.ElementsMatch(t, tc.ExpectedIds, foundIds)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestInviteRemoteClusterToChannel(t *testing.T) {
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
resp, err := th.Client.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.InviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should correctly invite the remote cluster to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
|
||||
t.Run("should do nothing but return 204 if the remote cluster is already invited to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
}
|
||||
|
||||
func TestUninviteRemoteClusterToChannel(t *testing.T) {
|
||||
t.Run("Should not work if the remote cluster service is not enabled", func(t *testing.T) {
|
||||
th := Setup(t)
|
||||
defer th.TearDown()
|
||||
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckNotImplementedStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
th := setupForSharedChannels(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
newRC := &model.RemoteCluster{Name: "rc", SiteURL: "http://example.com", CreatorId: th.SystemAdminUser.Id}
|
||||
|
||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||
require.Nil(t, appErr)
|
||||
|
||||
t.Run("Should not work if the user doesn't have the right permissions", func(t *testing.T) {
|
||||
resp, err := th.Client.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckForbiddenStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the remote cluster is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), model.NewId(), model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should not work if the channel is nonexistent", func(t *testing.T) {
|
||||
resp, err := th.SystemAdminClient.UninviteRemoteClusterToChannel(context.Background(), rc.RemoteId, model.NewId())
|
||||
CheckBadRequestStatus(t, resp)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
t.Run("should correctly uninvite the remote cluster to the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
|
||||
t.Run("should do nothing but return 204 if the remote cluster is not sharing the channel", func(t *testing.T) {
|
||||
t.Skip("Requires server2server communication: ToBeImplemented")
|
||||
})
|
||||
}
|
||||
|
||||
@@ -817,7 +817,7 @@ type AppIface interface {
|
||||
GetSharedChannel(channelID string) (*model.SharedChannel, error)
|
||||
GetSharedChannelRemote(id string) (*model.SharedChannelRemote, error)
|
||||
GetSharedChannelRemoteByIds(channelID string, remoteID string) (*model.SharedChannelRemote, error)
|
||||
GetSharedChannelRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
|
||||
GetSharedChannelRemotes(page, perPage int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error)
|
||||
GetSharedChannelRemotesStatus(channelID string) ([]*model.SharedChannelRemoteStatus, error)
|
||||
GetSharedChannels(page int, perPage int, opts model.SharedChannelFilterOpts) ([]*model.SharedChannel, *model.AppError)
|
||||
GetSharedChannelsCount(opts model.SharedChannelFilterOpts) (int64, error)
|
||||
|
||||
@@ -9744,7 +9744,7 @@ func (a *OpenTracingAppLayer) GetSharedChannelRemoteByIds(channelID string, remo
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) GetSharedChannelRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
|
||||
func (a *OpenTracingAppLayer) GetSharedChannelRemotes(page int, perPage int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetSharedChannelRemotes")
|
||||
|
||||
@@ -9756,7 +9756,7 @@ func (a *OpenTracingAppLayer) GetSharedChannelRemotes(opts model.SharedChannelRe
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0, resultVar1 := a.app.GetSharedChannelRemotes(opts)
|
||||
resultVar0, resultVar1 := a.app.GetSharedChannelRemotes(page, perPage, opts)
|
||||
|
||||
if resultVar1 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar1))
|
||||
|
||||
@@ -137,8 +137,8 @@ func (a *App) GetSharedChannelRemoteByIds(channelID string, remoteID string) (*m
|
||||
return a.Srv().Store().SharedChannel().GetRemoteByIds(channelID, remoteID)
|
||||
}
|
||||
|
||||
func (a *App) GetSharedChannelRemotes(opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
|
||||
return a.Srv().Store().SharedChannel().GetRemotes(opts)
|
||||
func (a *App) GetSharedChannelRemotes(page, perPage int, opts model.SharedChannelRemoteFilterOpts) ([]*model.SharedChannelRemote, error) {
|
||||
return a.Srv().Store().SharedChannel().GetRemotes(page*perPage, perPage, opts)
|
||||
}
|
||||
|
||||
// HasRemote returns whether a given channelID is present in the channel remotes or not.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -100,6 +100,8 @@ type Params struct {
|
||||
OnlyConfirmed bool
|
||||
OnlyPlugins bool
|
||||
ExcludePlugins bool
|
||||
ExcludeHome bool
|
||||
ExcludeRemote bool
|
||||
|
||||
//Bookmarks
|
||||
ChannelBookmarkId string
|
||||
@@ -169,6 +171,8 @@ func ParamsFromRequest(r *http.Request) *Params {
|
||||
params.OnlyConfirmed, _ = strconv.ParseBool(query.Get("only_confirmed"))
|
||||
params.OnlyPlugins, _ = strconv.ParseBool(query.Get("only_plugins"))
|
||||
params.ExcludePlugins, _ = strconv.ParseBool(query.Get("exclude_plugins"))
|
||||
params.ExcludeHome, _ = strconv.ParseBool(query.Get("exclude_home"))
|
||||
params.ExcludeRemote, _ = strconv.ParseBool(query.Get("exclude_remote"))
|
||||
params.ChannelBookmarkId = props["bookmark_id"]
|
||||
params.Scope = query.Get("scope")
|
||||
|
||||
|
||||
@@ -2874,6 +2874,22 @@
|
||||
"id": "api.server.start_server.starting.critical",
|
||||
"translation": "Error starting server, err:%v"
|
||||
},
|
||||
{
|
||||
"id": "api.shared_channel.get_shared_channel_remotes_error",
|
||||
"translation": "Could not fetch shared channel remotes"
|
||||
},
|
||||
{
|
||||
"id": "api.shared_channel.has_remote_error",
|
||||
"translation": "Could not determine if channel is shared with the remote"
|
||||
},
|
||||
{
|
||||
"id": "api.shared_channel.invite_remote_to_channel_error",
|
||||
"translation": "Could not invite remote to channel"
|
||||
},
|
||||
{
|
||||
"id": "api.shared_channel.uninvite_remote_to_channel_error",
|
||||
"translation": "Could not uninvite remote to channel"
|
||||
},
|
||||
{
|
||||
"id": "api.slackimport.slack_add_bot_user.email_pwd",
|
||||
"translation": "The Integration/Slack Bot user with email {{.Email}} and password {{.Password}} has been imported.\r\n"
|
||||
|
||||
@@ -104,7 +104,7 @@ func (scs *Service) ForceSyncForRemote(rc *model.RemoteCluster) {
|
||||
opts := model.SharedChannelRemoteFilterOpts{
|
||||
RemoteId: rc.RemoteId,
|
||||
}
|
||||
scrs, err := scs.server.GetStore().SharedChannel().GetRemotes(opts)
|
||||
scrs, err := scs.server.GetStore().SharedChannel().GetRemotes(0, 999999, opts)
|
||||
if err != nil {
|
||||
scs.server.Log().Log(mlog.LvlSharedChannelServiceError, "Failed to fetch shared channel remotes",
|
||||
mlog.String("remote", rc.DisplayName),
|
||||
|
||||
@@ -572,6 +572,14 @@ func (c *Client4) remoteClusterRoute() string {
|
||||
return "/remotecluster"
|
||||
}
|
||||
|
||||
func (c *Client4) sharedChannelRemotesRoute(remoteId string) string {
|
||||
return fmt.Sprintf("%s/%s/sharedchannelremotes", c.remoteClusterRoute(), remoteId)
|
||||
}
|
||||
|
||||
func (c *Client4) channelRemoteRoute(remoteId, channelId string) string {
|
||||
return fmt.Sprintf("%s/%s/channels/%s", c.remoteClusterRoute(), remoteId, channelId)
|
||||
}
|
||||
|
||||
func (c *Client4) sharedChannelsRoute() string {
|
||||
return "/sharedchannels"
|
||||
}
|
||||
@@ -8887,6 +8895,57 @@ func (c *Client4) DeleteRemoteCluster(ctx context.Context, remoteClusterId strin
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetSharedChannelRemotesByRemoteCluster(ctx context.Context, remoteId string, excludeHome, excludeRemote bool, page, perPage int) ([]*SharedChannelRemote, *Response, error) {
|
||||
v := url.Values{}
|
||||
if excludeHome {
|
||||
v.Set("exclude_home", "true")
|
||||
}
|
||||
if excludeRemote {
|
||||
v.Set("exclude_remote", "true")
|
||||
}
|
||||
if page != 0 {
|
||||
v.Set("page", fmt.Sprintf("%d", page))
|
||||
}
|
||||
if perPage != 0 {
|
||||
v.Set("per_page", fmt.Sprintf("%d", perPage))
|
||||
}
|
||||
url := c.sharedChannelRemotesRoute(remoteId)
|
||||
if len(v) > 0 {
|
||||
url += "?" + v.Encode()
|
||||
}
|
||||
|
||||
r, err := c.DoAPIGet(ctx, url, "")
|
||||
if err != nil {
|
||||
return nil, BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
|
||||
var scs []*SharedChannelRemote
|
||||
json.NewDecoder(r.Body).Decode(&scs)
|
||||
|
||||
return scs, BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) InviteRemoteClusterToChannel(ctx context.Context, remoteId, channelId string) (*Response, error) {
|
||||
url := fmt.Sprintf("%s/invite", c.channelRemoteRoute(remoteId, channelId))
|
||||
r, err := c.DoAPIPost(ctx, url, "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) UninviteRemoteClusterToChannel(ctx context.Context, remoteId, channelId string) (*Response, error) {
|
||||
url := fmt.Sprintf("%s/uninvite", c.channelRemoteRoute(remoteId, channelId))
|
||||
r, err := c.DoAPIPost(ctx, url, "")
|
||||
if err != nil {
|
||||
return BuildResponse(r), err
|
||||
}
|
||||
defer closeBody(r)
|
||||
return BuildResponse(r), nil
|
||||
}
|
||||
|
||||
func (c *Client4) GetAncillaryPermissions(ctx context.Context, subsectionPermissions []string) ([]string, *Response, error) {
|
||||
var returnedPermissions []string
|
||||
url := fmt.Sprintf("%s/ancillary", c.permissionsRoute())
|
||||
|
||||
@@ -263,6 +263,8 @@ type SharedChannelRemoteFilterOpts struct {
|
||||
ChannelId string
|
||||
RemoteId string
|
||||
InclUnconfirmed bool
|
||||
ExcludeHome bool
|
||||
ExcludeRemote bool
|
||||
}
|
||||
|
||||
// SyncMsg represents a change in content (post add/edit/delete, reaction add/remove, users).
|
||||
|
||||
Ссылка в новой задаче
Block a user