[MM-44307] On Limits Change, archive or restore teams according to the limits (#20247)

Automatic Merge
Этот коммит содержится в:
Nick Misasi
2022-05-24 09:16:24 -04:00
коммит произвёл GitHub
родитель 380a0ef827
Коммит 50d069e86d
13 изменённых файлов: 454 добавлений и 26 удалений

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

@@ -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)

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

@@ -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")

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

@@ -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 {

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

@@ -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()