[MM-44307] On Limits Change, archive or restore teams according to the limits (#20247)
Automatic Merge
Этот коммит содержится в:
@@ -387,6 +387,8 @@ type AppIface interface {
|
||||
AddUserToTeamByTeamId(c *request.Context, teamID string, user *model.User) *model.AppError
|
||||
AddUserToTeamByToken(c *request.Context, userID string, tokenID string) (*model.Team, *model.TeamMember, *model.AppError)
|
||||
AdjustImage(file io.Reader) (*bytes.Buffer, *model.AppError)
|
||||
AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError
|
||||
AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError
|
||||
AllowOAuthAppAccessToUser(userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError)
|
||||
AppendFile(fr io.Reader, path string) (int64, *model.AppError)
|
||||
AsymmetricSigningKey() *ecdsa.PrivateKey
|
||||
@@ -1022,6 +1024,7 @@ type AppIface interface {
|
||||
SetTeamIconFromFile(team *model.Team, file io.Reader) *model.AppError
|
||||
SetTeamIconFromMultiPartFile(teamID string, file multipart.File) *model.AppError
|
||||
SlackImport(c *request.Context, fileData multipart.File, fileSize int64, teamID string) (*model.AppError, *bytes.Buffer)
|
||||
SoftDeleteAllTeamsExcept(teamID string) *model.AppError
|
||||
SoftDeleteTeam(teamID string) *model.AppError
|
||||
Srv() *Server
|
||||
SubmitInteractiveDialog(c *request.Context, request model.SubmitDialogRequest) (*model.SubmitDialogResponse, *model.AppError)
|
||||
|
||||
11
app/cloud.go
11
app/cloud.go
@@ -37,6 +37,17 @@ func (a *App) SendPaymentFailedEmail(failedPayment *model.FailedPayment) *model.
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError {
|
||||
if limits.Teams != nil && limits.Teams.Active != nil && *limits.Teams.Active > 0 {
|
||||
err := a.AdjustTeamsFromProductLimits(limits.Teams)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SendUpgradeConfirmationEmail() *model.AppError {
|
||||
sysAdmins, e := a.getSysAdminsEmailRecipients()
|
||||
if e != nil {
|
||||
|
||||
@@ -625,6 +625,50 @@ func (a *OpenTracingAppLayer) AdjustImage(file io.Reader) (*bytes.Buffer, *model
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AdjustInProductLimits(limits *model.ProductLimits, subscription *model.Subscription) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AdjustInProductLimits")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.AdjustInProductLimits(limits, subscription)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AdjustTeamsFromProductLimits")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.AdjustTeamsFromProductLimits(teamLimits)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) AllowOAuthAppAccessToUser(userID string, authRequest *model.AuthorizeRequest) (string, *model.AppError) {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.AllowOAuthAppAccessToUser")
|
||||
@@ -15583,6 +15627,28 @@ func (a *OpenTracingAppLayer) SlackImport(c *request.Context, fileData multipart
|
||||
return resultVar0, resultVar1
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SoftDeleteAllTeamsExcept(teamID string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SoftDeleteAllTeamsExcept")
|
||||
|
||||
a.ctx = newCtx
|
||||
a.app.Srv().Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
a.app.Srv().Store.SetContext(origCtx)
|
||||
a.ctx = origCtx
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
resultVar0 := a.app.SoftDeleteAllTeamsExcept(teamID)
|
||||
|
||||
if resultVar0 != nil {
|
||||
span.LogFields(spanlog.Error(resultVar0))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return resultVar0
|
||||
}
|
||||
|
||||
func (a *OpenTracingAppLayer) SoftDeleteTeam(teamID string) *model.AppError {
|
||||
origCtx := a.ctx
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.SoftDeleteTeam")
|
||||
|
||||
101
app/team.go
101
app/team.go
@@ -14,6 +14,7 @@ import (
|
||||
"mime/multipart"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v6/app/email"
|
||||
@@ -29,6 +30,106 @@ import (
|
||||
"github.com/mattermost/mattermost-server/v6/store/sqlstore"
|
||||
)
|
||||
|
||||
func (a *App) AdjustTeamsFromProductLimits(teamLimits *model.TeamsLimits) *model.AppError {
|
||||
maxActiveTeams := *teamLimits.Active
|
||||
teams, appErr := a.GetAllTeams()
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
if teams == nil {
|
||||
return nil
|
||||
}
|
||||
// Sort the list of teams based on their creation date
|
||||
sort.Slice(teams, func(i, j int) bool {
|
||||
return teams[i].CreateAt < teams[j].CreateAt
|
||||
})
|
||||
|
||||
var activeTeams []*model.Team
|
||||
var cloudArchivedTeams []*model.Team
|
||||
for _, team := range teams {
|
||||
if team.DeleteAt == 0 {
|
||||
activeTeams = append(activeTeams, team)
|
||||
}
|
||||
if team.DeleteAt > 0 && team.CloudLimitsArchived {
|
||||
cloudArchivedTeams = append(cloudArchivedTeams, team)
|
||||
}
|
||||
}
|
||||
|
||||
if len(activeTeams) > maxActiveTeams {
|
||||
// If there are more active teams than allowed, we must archive them
|
||||
// Remove the first n elements (where n is the allowed number of teams) so they aren't archived
|
||||
|
||||
teamsToArchive := activeTeams[maxActiveTeams:]
|
||||
|
||||
for _, team := range teamsToArchive {
|
||||
cloudLimitsArchived := true
|
||||
// Archive the remainder
|
||||
patch := model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived}
|
||||
_, err := a.PatchTeam(team.Id, &patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = a.SoftDeleteTeam(team.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
} else if len(activeTeams) < maxActiveTeams && len(cloudArchivedTeams) > 0 {
|
||||
// If the number of activeTeams is less than the allowed limit, and there are some cloudArchivedTeams, we can restore these cloudArchivedTeams
|
||||
activeTeamsBeforeLimit := maxActiveTeams - len(activeTeams)
|
||||
teamsToRestore := cloudArchivedTeams
|
||||
// If the number of active teams remaining before the limit is hit is fewer than the number of cloudArchivedTeams, trim the list (still according to CreateAt)
|
||||
// Otherwise, we can restore all of the cloudArchivedTeams without hitting the limit, so don't filter the list
|
||||
if activeTeamsBeforeLimit < len(cloudArchivedTeams) {
|
||||
teamsToRestore = cloudArchivedTeams[:(activeTeamsBeforeLimit)]
|
||||
}
|
||||
|
||||
cloudLimitsArchived := false
|
||||
patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived}
|
||||
for _, team := range teamsToRestore {
|
||||
err := a.RestoreTeam(team.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
_, err = a.PatchTeam(team.Id, patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) SoftDeleteAllTeamsExcept(teamID string) *model.AppError {
|
||||
teams, appErr := a.GetAllTeams()
|
||||
if appErr != nil {
|
||||
return appErr
|
||||
}
|
||||
|
||||
if teams == nil {
|
||||
return nil
|
||||
}
|
||||
cloudLimitsArchived := true
|
||||
patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived}
|
||||
for _, team := range teams {
|
||||
if team.Id != teamID {
|
||||
_, err := a.PatchTeam(team.Id, patch)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = a.SoftDeleteTeam(team.Id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (a *App) CreateTeam(c *request.Context, team *model.Team) (*model.Team, *model.AppError) {
|
||||
rteam, err := a.ch.srv.teamService.CreateTeam(team)
|
||||
if err != nil {
|
||||
|
||||
191
app/team_test.go
191
app/team_test.go
@@ -466,6 +466,197 @@ func TestAddUserToTeamByTeamId(t *testing.T) {
|
||||
|
||||
}
|
||||
|
||||
func TestSoftDeleteAllTeamsExcept(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
teams := []*model.Team{
|
||||
{
|
||||
DisplayName: "team-1",
|
||||
Name: "team-1",
|
||||
Email: "foo@foo.com",
|
||||
Type: model.TeamOpen,
|
||||
},
|
||||
}
|
||||
teamId := ""
|
||||
for _, create := range teams {
|
||||
team, err := th.App.CreateTeam(th.Context, create)
|
||||
require.Nil(t, err)
|
||||
teamId = team.Id
|
||||
}
|
||||
|
||||
err := th.App.SoftDeleteAllTeamsExcept(teamId)
|
||||
assert.Nil(t, err)
|
||||
allTeams, err := th.App.GetAllTeams()
|
||||
require.Nil(t, err)
|
||||
for _, team := range allTeams {
|
||||
if team.Id == teamId {
|
||||
require.Equal(t, int64(0), team.DeleteAt)
|
||||
require.Equal(t, false, team.CloudLimitsArchived)
|
||||
} else {
|
||||
require.NotEqual(t, int64(0), team.DeleteAt)
|
||||
require.Equal(t, true, team.CloudLimitsArchived)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestAdjustTeamsFromProductLimits(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
teams := []*model.Team{
|
||||
{
|
||||
DisplayName: "team-1",
|
||||
Name: "team-1",
|
||||
Email: "foo@foo.com",
|
||||
Type: model.TeamOpen,
|
||||
},
|
||||
{
|
||||
DisplayName: "team-2",
|
||||
Name: "team-2",
|
||||
Email: "foo@foo.com",
|
||||
Type: model.TeamOpen,
|
||||
},
|
||||
{
|
||||
DisplayName: "team-3",
|
||||
Name: "team-3",
|
||||
Email: "foo@foo.com",
|
||||
Type: model.TeamOpen,
|
||||
},
|
||||
}
|
||||
teamIds := []string{}
|
||||
for _, create := range teams {
|
||||
team, err := th.App.CreateTeam(th.Context, create)
|
||||
require.Nil(t, err)
|
||||
teamIds = append(teamIds, team.Id)
|
||||
}
|
||||
t.Run("Should soft delete teams if there are more teams than the limit", func(t *testing.T) {
|
||||
activeLimit := 1
|
||||
teamLimits := &model.TeamsLimits{Active: &activeLimit}
|
||||
|
||||
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
|
||||
teamsList, err := th.App.GetTeams(teamIds)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
// Sort the list of teams based on their creation date
|
||||
sort.Slice(teamsList, func(i, j int) bool {
|
||||
return teamsList[i].CreateAt < teamsList[j].CreateAt
|
||||
})
|
||||
|
||||
for i := range teamsList {
|
||||
require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName)
|
||||
require.NotEqual(t, 0, teamsList[i].DeleteAt)
|
||||
require.Equal(t, true, teamsList[i].CloudLimitsArchived)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Should not do anything if the amount of teams is equal to the limit", func(t *testing.T) {
|
||||
|
||||
expectedTeamsList, err := th.App.GetAllTeams()
|
||||
|
||||
var expectedActiveTeams []*model.Team
|
||||
var expectedCloudArchivedTeams []*model.Team
|
||||
for _, team := range expectedTeamsList {
|
||||
if team.DeleteAt == 0 {
|
||||
expectedActiveTeams = append(expectedActiveTeams, team)
|
||||
}
|
||||
if team.DeleteAt > 0 && team.CloudLimitsArchived {
|
||||
expectedCloudArchivedTeams = append(expectedCloudArchivedTeams, team)
|
||||
}
|
||||
}
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
activeLimit := len(expectedActiveTeams)
|
||||
teamLimits := &model.TeamsLimits{Active: &activeLimit}
|
||||
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
|
||||
actualTeamsList, err := th.App.GetAllTeams()
|
||||
|
||||
require.Nil(t, err)
|
||||
var actualActiveTeams []*model.Team
|
||||
var actualCloudArchivedTeams []*model.Team
|
||||
for _, team := range actualTeamsList {
|
||||
if team.DeleteAt == 0 {
|
||||
actualActiveTeams = append(actualActiveTeams, team)
|
||||
}
|
||||
if team.DeleteAt > 0 && team.CloudLimitsArchived {
|
||||
actualCloudArchivedTeams = append(actualCloudArchivedTeams, team)
|
||||
}
|
||||
}
|
||||
|
||||
require.Equal(t, len(expectedActiveTeams), len(actualActiveTeams))
|
||||
require.Equal(t, len(expectedCloudArchivedTeams), len(actualCloudArchivedTeams))
|
||||
})
|
||||
|
||||
t.Run("Should restore archived teams if limit increases", func(t *testing.T) {
|
||||
activeLimit := 1
|
||||
teamLimits := &model.TeamsLimits{Active: &activeLimit}
|
||||
|
||||
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
activeLimit = 10000 // make the limit extremely high so all teams are enabled
|
||||
teamLimits = &model.TeamsLimits{Active: &activeLimit}
|
||||
|
||||
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
|
||||
teamsList, err := th.App.GetTeams(teamIds)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
// Sort the list of teams based on their creation date
|
||||
sort.Slice(teamsList, func(i, j int) bool {
|
||||
return teamsList[i].CreateAt < teamsList[j].CreateAt
|
||||
})
|
||||
|
||||
for i := range teamsList {
|
||||
require.Equal(t, teamsList[i].DisplayName, teams[i].DisplayName)
|
||||
require.Equal(t, int64(0), teamsList[i].DeleteAt)
|
||||
require.Equal(t, false, teamsList[i].CloudLimitsArchived)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("Should only restore teams that were archived by cloud limits", func(t *testing.T) {
|
||||
|
||||
activeLimit := 1
|
||||
teamLimits := &model.TeamsLimits{Active: &activeLimit}
|
||||
|
||||
err := th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
|
||||
cloudLimitsArchived := false
|
||||
patch := &model.TeamPatch{CloudLimitsArchived: &cloudLimitsArchived}
|
||||
team, err := th.App.PatchTeam(teamIds[0], patch)
|
||||
require.Nil(t, err)
|
||||
require.Equal(t, false, team.CloudLimitsArchived)
|
||||
|
||||
activeLimit = 10000 // make the limit extremely high so all teams are enabled
|
||||
teamLimits = &model.TeamsLimits{Active: &activeLimit}
|
||||
|
||||
err = th.App.AdjustTeamsFromProductLimits(teamLimits)
|
||||
require.Nil(t, err)
|
||||
|
||||
teamsList, err := th.App.GetTeams(teamIds)
|
||||
|
||||
require.Nil(t, err)
|
||||
|
||||
// Sort the list of teams based on their creation date
|
||||
sort.Slice(teamsList, func(i, j int) bool {
|
||||
return teamsList[i].CreateAt < teamsList[j].CreateAt
|
||||
})
|
||||
|
||||
require.NotEqual(t, int64(0), teamsList[0].DeleteAt)
|
||||
require.Equal(t, int64(0), teamsList[1].DeleteAt)
|
||||
require.Equal(t, int64(0), teamsList[2].DeleteAt)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
func TestPermanentDeleteTeam(t *testing.T) {
|
||||
th := Setup(t).InitBasic()
|
||||
defer th.TearDown()
|
||||
|
||||
Ссылка в новой задаче
Block a user