Adds default team to the remote cluster entity (#27863)
* Adds default team to the remote cluster entity A new DefaultTeamId field is added to the RemoteCluster entity and its endpoints, and used when receiving channel invites to choose in which team to create a new channel. This will be later extended with the ability for the system admin to manually accept invites, choosing which team to create the channel on each. This use case will be triggered when the DefaultTeamId field is empty, which now simply chooses the first team it finds in the database as a fallback. * Fix migrations list * Fixes channelinvite test case * Fix i18n * Fix migration list
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
3d8d0f5e3e
Коммит
59873a2a50
@@ -3499,6 +3499,9 @@ components:
|
|||||||
site_url:
|
site_url:
|
||||||
description: URL of the remote cluster
|
description: URL of the remote cluster
|
||||||
type: string
|
type: string
|
||||||
|
default_team_id:
|
||||||
|
description: The team where channels from invites are created
|
||||||
|
type: string
|
||||||
create_at:
|
create_at:
|
||||||
description: Time in milliseconds that the remote cluster was created
|
description: Time in milliseconds that the remote cluster was created
|
||||||
type: integer
|
type: integer
|
||||||
|
|||||||
@@ -171,6 +171,9 @@
|
|||||||
properties:
|
properties:
|
||||||
display_name:
|
display_name:
|
||||||
type: string
|
type: string
|
||||||
|
default_team_id:
|
||||||
|
type: string
|
||||||
|
description: The team where channels from invites are created
|
||||||
responses:
|
responses:
|
||||||
"200":
|
"200":
|
||||||
description: Remote Cluster patch successful
|
description: Remote Cluster patch successful
|
||||||
|
|||||||
@@ -381,6 +381,7 @@ func createRemoteCluster(c *Context, w http.ResponseWriter, r *http.Request) {
|
|||||||
Name: rcWithTeamAndPassword.Name,
|
Name: rcWithTeamAndPassword.Name,
|
||||||
DisplayName: rcWithTeamAndPassword.DisplayName,
|
DisplayName: rcWithTeamAndPassword.DisplayName,
|
||||||
SiteURL: model.SiteURLPending + model.NewId(),
|
SiteURL: model.SiteURLPending + model.NewId(),
|
||||||
|
DefaultTeamId: rcWithTeamAndPassword.DefaultTeamId,
|
||||||
Token: model.NewId(),
|
Token: model.NewId(),
|
||||||
CreatorId: c.AppContext.Session().UserId,
|
CreatorId: c.AppContext.Session().UserId,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -160,6 +160,7 @@ func TestCreateRemoteCluster(t *testing.T) {
|
|||||||
RemoteCluster: &model.RemoteCluster{
|
RemoteCluster: &model.RemoteCluster{
|
||||||
Name: "remotecluster",
|
Name: "remotecluster",
|
||||||
SiteURL: "http://example.com",
|
SiteURL: "http://example.com",
|
||||||
|
DefaultTeamId: model.NewId(),
|
||||||
Token: model.NewId(),
|
Token: model.NewId(),
|
||||||
},
|
},
|
||||||
Password: "mysupersecret",
|
Password: "mysupersecret",
|
||||||
@@ -231,6 +232,7 @@ func TestCreateRemoteCluster(t *testing.T) {
|
|||||||
CheckCreatedStatus(t, resp)
|
CheckCreatedStatus(t, resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, rcWithTeamAndPassword.Name, rcWithInvite.RemoteCluster.Name)
|
require.Equal(t, rcWithTeamAndPassword.Name, rcWithInvite.RemoteCluster.Name)
|
||||||
|
require.Equal(t, rcWithTeamAndPassword.DefaultTeamId, rcWithInvite.RemoteCluster.DefaultTeamId)
|
||||||
require.NotZero(t, rcWithInvite.Invite)
|
require.NotZero(t, rcWithInvite.Invite)
|
||||||
require.Zero(t, rcWithInvite.RemoteCluster.Token)
|
require.Zero(t, rcWithInvite.RemoteCluster.Token)
|
||||||
require.Zero(t, rcWithInvite.RemoteCluster.RemoteToken)
|
require.Zero(t, rcWithInvite.RemoteCluster.RemoteToken)
|
||||||
@@ -431,6 +433,7 @@ func TestGetRemoteCluster(t *testing.T) {
|
|||||||
defer th.TearDown()
|
defer th.TearDown()
|
||||||
|
|
||||||
newRC.CreatorId = th.SystemAdminUser.Id
|
newRC.CreatorId = th.SystemAdminUser.Id
|
||||||
|
newRC.DefaultTeamId = th.BasicTeam.Id
|
||||||
|
|
||||||
rc, appErr := th.App.AddRemoteCluster(newRC)
|
rc, appErr := th.App.AddRemoteCluster(newRC)
|
||||||
require.Nil(t, appErr)
|
require.Nil(t, appErr)
|
||||||
@@ -455,6 +458,7 @@ func TestGetRemoteCluster(t *testing.T) {
|
|||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, rc.RemoteId, fetchedRC.RemoteId)
|
require.Equal(t, rc.RemoteId, fetchedRC.RemoteId)
|
||||||
|
require.Equal(t, th.BasicTeam.Id, fetchedRC.DefaultTeamId)
|
||||||
require.Empty(t, fetchedRC.Token)
|
require.Empty(t, fetchedRC.Token)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -509,12 +513,17 @@ func TestPatchRemoteCluster(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("should correctly patch the remote cluster", func(t *testing.T) {
|
t.Run("should correctly patch the remote cluster", func(t *testing.T) {
|
||||||
rcp := &model.RemoteClusterPatch{DisplayName: model.NewPointer("patched!")}
|
newTeamId := model.NewId()
|
||||||
|
rcp := &model.RemoteClusterPatch{
|
||||||
|
DisplayName: model.NewPointer("patched!"),
|
||||||
|
DefaultTeamId: model.NewPointer(newTeamId),
|
||||||
|
}
|
||||||
|
|
||||||
patchedRC, resp, err := th.SystemAdminClient.PatchRemoteCluster(context.Background(), rc.RemoteId, rcp)
|
patchedRC, resp, err := th.SystemAdminClient.PatchRemoteCluster(context.Background(), rc.RemoteId, rcp)
|
||||||
CheckOKStatus(t, resp)
|
CheckOKStatus(t, resp)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "patched!", patchedRC.DisplayName)
|
require.Equal(t, "patched!", patchedRC.DisplayName)
|
||||||
|
require.Equal(t, newTeamId, patchedRC.DefaultTeamId)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -246,6 +246,8 @@ channels/db/migrations/mysql/000123_remove_upload_file_permission.down.sql
|
|||||||
channels/db/migrations/mysql/000123_remove_upload_file_permission.up.sql
|
channels/db/migrations/mysql/000123_remove_upload_file_permission.up.sql
|
||||||
channels/db/migrations/mysql/000124_remove_manage_team_permission.down.sql
|
channels/db/migrations/mysql/000124_remove_manage_team_permission.down.sql
|
||||||
channels/db/migrations/mysql/000124_remove_manage_team_permission.up.sql
|
channels/db/migrations/mysql/000124_remove_manage_team_permission.up.sql
|
||||||
|
channels/db/migrations/mysql/000125_remoteclusters_add_default_team_id.down.sql
|
||||||
|
channels/db/migrations/mysql/000125_remoteclusters_add_default_team_id.up.sql
|
||||||
channels/db/migrations/postgres/000001_create_teams.down.sql
|
channels/db/migrations/postgres/000001_create_teams.down.sql
|
||||||
channels/db/migrations/postgres/000001_create_teams.up.sql
|
channels/db/migrations/postgres/000001_create_teams.up.sql
|
||||||
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
channels/db/migrations/postgres/000002_create_team_members.down.sql
|
||||||
@@ -492,3 +494,5 @@ channels/db/migrations/postgres/000123_remove_upload_file_permission.down.sql
|
|||||||
channels/db/migrations/postgres/000123_remove_upload_file_permission.up.sql
|
channels/db/migrations/postgres/000123_remove_upload_file_permission.up.sql
|
||||||
channels/db/migrations/postgres/000124_remove_manage_team_permission.down.sql
|
channels/db/migrations/postgres/000124_remove_manage_team_permission.down.sql
|
||||||
channels/db/migrations/postgres/000124_remove_manage_team_permission.up.sql
|
channels/db/migrations/postgres/000124_remove_manage_team_permission.up.sql
|
||||||
|
channels/db/migrations/postgres/000125_remoteclusters_add_default_team_id.down.sql
|
||||||
|
channels/db/migrations/postgres/000125_remoteclusters_add_default_team_id.up.sql
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
-- Skipping it because the forward migrations are destructive
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
SET @preparedStatement = (SELECT IF(
|
||||||
|
(
|
||||||
|
SELECT COUNT(*) FROM INFORMATION_SCHEMA.COLUMNS
|
||||||
|
WHERE table_name = 'RemoteClusters'
|
||||||
|
AND table_schema = DATABASE()
|
||||||
|
AND column_name = 'DefaultTeamId'
|
||||||
|
) > 0,
|
||||||
|
'SELECT 1',
|
||||||
|
'ALTER TABLE RemoteClusters ADD DefaultTeamId VARCHAR(26) DEFAULT "";'
|
||||||
|
));
|
||||||
|
|
||||||
|
PREPARE alterIfNotExists FROM @preparedStatement;
|
||||||
|
EXECUTE alterIfNotExists;
|
||||||
|
DEALLOCATE PREPARE alterIfNotExists;
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
-- Skipping it because the forward migrations are destructive
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
ALTER TABLE remoteclusters ADD COLUMN IF NOT EXISTS defaultteamid character varying(26) DEFAULT '';
|
||||||
@@ -34,6 +34,7 @@ func remoteClusterFields(prefix string) []string {
|
|||||||
prefix + "Name",
|
prefix + "Name",
|
||||||
prefix + "DisplayName",
|
prefix + "DisplayName",
|
||||||
prefix + "SiteURL",
|
prefix + "SiteURL",
|
||||||
|
prefix + "DefaultTeamId",
|
||||||
prefix + "CreateAt",
|
prefix + "CreateAt",
|
||||||
prefix + "LastPingAt",
|
prefix + "LastPingAt",
|
||||||
prefix + "Token",
|
prefix + "Token",
|
||||||
@@ -65,10 +66,10 @@ func (s sqlRemoteClusterStore) Save(remoteCluster *model.RemoteCluster) (*model.
|
|||||||
}
|
}
|
||||||
|
|
||||||
query := `INSERT INTO RemoteClusters
|
query := `INSERT INTO RemoteClusters
|
||||||
(RemoteId, RemoteTeamId, Name, DisplayName, SiteURL, CreateAt,
|
(RemoteId, RemoteTeamId, Name, DisplayName, SiteURL, DefaultTeamId, CreateAt,
|
||||||
LastPingAt, Token, RemoteToken, Topics, CreatorId, PluginID, Options)
|
LastPingAt, Token, RemoteToken, Topics, CreatorId, PluginID, Options)
|
||||||
VALUES
|
VALUES
|
||||||
(:RemoteId, :RemoteTeamId, :Name, :DisplayName, :SiteURL, :CreateAt,
|
(:RemoteId, :RemoteTeamId, :Name, :DisplayName, :SiteURL, :DefaultTeamId, :CreateAt,
|
||||||
:LastPingAt, :Token, :RemoteToken, :Topics, :CreatorId, :PluginID, :Options)`
|
:LastPingAt, :Token, :RemoteToken, :Topics, :CreatorId, :PluginID, :Options)`
|
||||||
|
|
||||||
if _, err := s.GetMasterX().NamedExec(query, remoteCluster); err != nil {
|
if _, err := s.GetMasterX().NamedExec(query, remoteCluster); err != nil {
|
||||||
@@ -93,6 +94,7 @@ func (s sqlRemoteClusterStore) Update(remoteCluster *model.RemoteCluster) (*mode
|
|||||||
CreatorId = :CreatorId,
|
CreatorId = :CreatorId,
|
||||||
DisplayName = :DisplayName,
|
DisplayName = :DisplayName,
|
||||||
SiteURL = :SiteURL,
|
SiteURL = :SiteURL,
|
||||||
|
DefaultTeamId = :DefaultTeamId,
|
||||||
Topics = :Topics,
|
Topics = :Topics,
|
||||||
PluginID = :PluginID,
|
PluginID = :PluginID,
|
||||||
Options = :Options
|
Options = :Options
|
||||||
|
|||||||
@@ -255,8 +255,8 @@ func (scs *Service) handleChannelCreation(invite channelInviteMsg, rc *model.Rem
|
|||||||
return scs.createDirectChannel(invite, rc)
|
return scs.createDirectChannel(invite, rc)
|
||||||
}
|
}
|
||||||
|
|
||||||
teamId := invite.TeamId
|
teamId := rc.DefaultTeamId
|
||||||
// if the invite doesn't have a teamId associated and until the
|
// if the remote doesn't have a teamId associated and until the
|
||||||
// acceptance of an invite includes selecting a team, we use the
|
// acceptance of an invite includes selecting a team, we use the
|
||||||
// first team of the list
|
// first team of the list
|
||||||
if teamId == "" {
|
if teamId == "" {
|
||||||
|
|||||||
@@ -62,7 +62,7 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
mockStore := &mocks.Store{}
|
mockStore := &mocks.Store{}
|
||||||
remoteCluster := &model.RemoteCluster{Name: "test"}
|
remoteCluster := &model.RemoteCluster{Name: "test", DefaultTeamId: model.NewId()}
|
||||||
invitation := channelInviteMsg{
|
invitation := channelInviteMsg{
|
||||||
ChannelId: model.NewId(),
|
ChannelId: model.NewId(),
|
||||||
TeamId: model.NewId(),
|
TeamId: model.NewId(),
|
||||||
@@ -144,9 +144,15 @@ func TestOnReceiveChannelInvite(t *testing.T) {
|
|||||||
channel := &model.Channel{
|
channel := &model.Channel{
|
||||||
Id: invitation.ChannelId,
|
Id: invitation.ChannelId,
|
||||||
}
|
}
|
||||||
|
mockTeamStore := mocks.TeamStore{}
|
||||||
|
team := &model.Team{
|
||||||
|
Id: model.NewId(),
|
||||||
|
}
|
||||||
|
|
||||||
mockChannelStore.On("Get", invitation.ChannelId, true).Return(nil, &store.ErrNotFound{})
|
mockChannelStore.On("Get", invitation.ChannelId, true).Return(nil, &store.ErrNotFound{})
|
||||||
|
mockTeamStore.On("GetAllPage", 0, 1, mock.Anything).Return([]*model.Team{team}, nil)
|
||||||
mockStore.On("Channel").Return(&mockChannelStore)
|
mockStore.On("Channel").Return(&mockChannelStore)
|
||||||
|
mockStore.On("Team").Return(&mockTeamStore)
|
||||||
|
|
||||||
mockServer = scs.server.(*MockServerIface)
|
mockServer = scs.server.(*MockServerIface)
|
||||||
mockServer.On("GetStore").Return(mockStore)
|
mockServer.On("GetStore").Return(mockStore)
|
||||||
|
|||||||
@@ -55,6 +55,7 @@ type RemoteCluster struct {
|
|||||||
Name string `json:"name"`
|
Name string `json:"name"`
|
||||||
DisplayName string `json:"display_name"`
|
DisplayName string `json:"display_name"`
|
||||||
SiteURL string `json:"site_url"`
|
SiteURL string `json:"site_url"`
|
||||||
|
DefaultTeamId string `json:"default_team_id"`
|
||||||
CreateAt int64 `json:"create_at"`
|
CreateAt int64 `json:"create_at"`
|
||||||
LastPingAt int64 `json:"last_ping_at"`
|
LastPingAt int64 `json:"last_ping_at"`
|
||||||
Token string `json:"token"`
|
Token string `json:"token"`
|
||||||
@@ -72,6 +73,7 @@ func (rc *RemoteCluster) Auditable() map[string]interface{} {
|
|||||||
"name": rc.Name,
|
"name": rc.Name,
|
||||||
"display_name": rc.DisplayName,
|
"display_name": rc.DisplayName,
|
||||||
"site_url": rc.SiteURL,
|
"site_url": rc.SiteURL,
|
||||||
|
"default_team_id": rc.DefaultTeamId,
|
||||||
"create_at": rc.CreateAt,
|
"create_at": rc.CreateAt,
|
||||||
"last_ping_at": rc.LastPingAt,
|
"last_ping_at": rc.LastPingAt,
|
||||||
"creator_id": rc.CreatorId,
|
"creator_id": rc.CreatorId,
|
||||||
@@ -123,6 +125,11 @@ func (rc *RemoteCluster) IsValid() *AppError {
|
|||||||
if !IsValidId(rc.CreatorId) {
|
if !IsValidId(rc.CreatorId) {
|
||||||
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "creator_id="+rc.CreatorId, http.StatusBadRequest)
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "creator_id="+rc.CreatorId, http.StatusBadRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if rc.DefaultTeamId != "" && !IsValidId(rc.DefaultTeamId) {
|
||||||
|
return NewAppError("RemoteCluster.IsValid", "model.cluster.is_valid.id.app_error", nil, "default_team_id="+rc.DefaultTeamId, http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -133,11 +140,13 @@ func (rc *RemoteCluster) Sanitize() {
|
|||||||
|
|
||||||
type RemoteClusterPatch struct {
|
type RemoteClusterPatch struct {
|
||||||
DisplayName *string `json:"display_name"`
|
DisplayName *string `json:"display_name"`
|
||||||
|
DefaultTeamId *string `json:"default_team_id"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rcp *RemoteClusterPatch) Auditable() map[string]interface{} {
|
func (rcp *RemoteClusterPatch) Auditable() map[string]interface{} {
|
||||||
return map[string]interface{}{
|
return map[string]interface{}{
|
||||||
"display_name": rcp.DisplayName,
|
"display_name": rcp.DisplayName,
|
||||||
|
"default_team_id": rcp.DefaultTeamId,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -145,6 +154,10 @@ func (rc *RemoteCluster) Patch(patch *RemoteClusterPatch) {
|
|||||||
if patch.DisplayName != nil {
|
if patch.DisplayName != nil {
|
||||||
rc.DisplayName = *patch.DisplayName
|
rc.DisplayName = *patch.DisplayName
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if patch.DefaultTeamId != nil {
|
||||||
|
rc.DefaultTeamId = *patch.DefaultTeamId
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
type RemoteClusterWithPassword struct {
|
type RemoteClusterWithPassword struct {
|
||||||
|
|||||||
@@ -27,6 +27,8 @@ func TestRemoteClusterIsValid(t *testing.T) {
|
|||||||
{name: "Missing create_at", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com"}, valid: false},
|
{name: "Missing create_at", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com"}, valid: false},
|
||||||
{name: "Missing last_ping_at", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreatorId: creator, CreateAt: now}, valid: true},
|
{name: "Missing last_ping_at", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreatorId: creator, CreateAt: now}, valid: true},
|
||||||
{name: "Missing creator", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now}, valid: false},
|
{name: "Missing creator", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now}, valid: false},
|
||||||
|
{name: "Bad default_team_id", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now, CreatorId: creator, DefaultTeamId: "bad-id"}, valid: false},
|
||||||
|
{name: "Valid default_team_id", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now, CreatorId: creator, DefaultTeamId: NewId()}, valid: true},
|
||||||
{name: "RemoteCluster valid", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
{name: "RemoteCluster valid", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "example.com", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
||||||
{name: "Include protocol", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "http://example.com", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
{name: "Include protocol", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "http://example.com", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
||||||
{name: "Include protocol & port", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "http://example.com:8065", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
{name: "Include protocol & port", rc: &RemoteCluster{RemoteId: id, Name: NewId(), SiteURL: "http://example.com:8065", CreateAt: now, LastPingAt: now, CreatorId: creator}, valid: true},
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user