MM-45120: Track team join times. Add API to retrieve new team members since a given time. (#20708)

* MM-45120: Starts tracking team join time. Adds API to retrieve team members who joined after a given time.

* MM-45120: Updates json casing to match model.User.
Этот коммит содержится в:
Martin Kraft
2022-08-11 10:38:52 -04:00
коммит произвёл GitHub
родитель 1738bd6e92
Коммит 55b3961b98
27 изменённых файлов: 630 добавлений и 13 удалений

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

@@ -670,6 +670,7 @@ type AppIface interface {
GetMemberCountsByGroup(ctx context.Context, channelID string, includeTimezones bool) ([]*model.ChannelMemberCountByGroup, *model.AppError)
GetMessageForNotification(post *model.Post, translateFunc i18n.TranslateFunc) string
GetMultipleEmojiByName(names []string) ([]*model.Emoji, *model.AppError)
GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError)
GetNewUsersForTeamPage(teamID string, page, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError)
GetNextPostIdFromPostList(postList *model.PostList, collapsedThreads bool) string
GetNotificationNameFormat(user *model.User) string

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

@@ -805,6 +805,7 @@ func (a *App) importUserTeams(c request.CTX, user *model.User, data *[]UserTeamI
SchemeGuest: user.IsGuest(),
SchemeUser: !user.IsGuest(),
SchemeAdmin: team.Email == user.Email && !user.IsGuest(),
CreateAt: model.GetMillis(),
}
if !user.IsGuest() {
var userShouldBeAdmin bool

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

@@ -7045,6 +7045,28 @@ func (a *OpenTracingAppLayer) GetMultipleEmojiByName(names []string) ([]*model.E
return resultVar0, resultVar1
}
func (a *OpenTracingAppLayer) GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetNewTeamMembersSince")
a.ctx = newCtx
a.app.Srv().Store.SetContext(newCtx)
defer func() {
a.app.Srv().Store.SetContext(origCtx)
a.ctx = origCtx
}()
defer span.Finish()
resultVar0, resultVar1, resultVar2 := a.app.GetNewTeamMembersSince(c, teamID, opts)
if resultVar2 != nil {
span.LogFields(spanlog.Error(resultVar2))
ext.Error.Set(span, true)
}
return resultVar0, resultVar1, resultVar2
}
func (a *OpenTracingAppLayer) GetNewUsersForTeamPage(teamID string, page int, perPage int, asAdmin bool, viewRestrictions *model.ViewUsersRestrictions) ([]*model.User, *model.AppError) {
origCtx := a.ctx
span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.GetNewUsersForTeamPage")

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

@@ -59,7 +59,7 @@ func CreateBasicUser(a *app.App, client *model.Client4) error {
if err != nil {
return model.NewAppError("CreateBasicUser", "app.user.verify_email.app_error", nil, err.Error(), http.StatusInternalServerError)
}
if _, nErr := a.Srv().Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id}, *a.Config().TeamSettings.MaxUsersPerTeam); nErr != nil {
if _, nErr := a.Srv().Store.Team().SaveMember(&model.TeamMember{TeamId: basicteam.Id, UserId: ruser.Id, CreateAt: model.GetMillis()}, *a.Config().TeamSettings.MaxUsersPerTeam); nErr != nil {
var appErr *model.AppError
var conflictErr *store.ErrConflict
var limitExceededErr *store.ErrLimitExceeded

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

@@ -2133,3 +2133,16 @@ func (a *App) ClearTeamMembersCache(teamID string) error {
}
return nil
}
func (a *App) GetNewTeamMembersSince(c request.CTX, teamID string, opts *model.InsightsOpts) (*model.NewTeamMembersList, int64, *model.AppError) {
if !a.Config().FeatureFlags.InsightsEnabled {
return nil, 0, model.NewAppError("GetNewTeamMembersSince", "app.insights.feature_disabled", nil, "", http.StatusNotImplemented)
}
ntms, count, err := a.Srv().Store.Team().GetNewTeamMembersSince(teamID, opts.StartUnixMilli, opts.Page*opts.PerPage, opts.PerPage)
if err != nil {
return nil, 0, model.NewAppError("GetNewTeamMembersSince", "app.post.get_new_team_members_since.app_error", nil, err.Error(), http.StatusInternalServerError)
}
return ntms, count, nil
}

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

@@ -1594,3 +1594,193 @@ func TestInviteGuestsToChannelsGracefully(t *testing.T) {
require.NotNil(t, res[0].Error)
})
}
func TestGetNewTeamMembersSince(t *testing.T) {
th := Setup(t).InitBasic()
defer th.TearDown()
team := th.CreateTeam()
t.Run("counts team members", func(t *testing.T) {
var originalExpectedCount int64
var newTeamMemberJoinTime int64
var anotherUser *model.User
t.Run("since time 0", func(t *testing.T) {
teamMembers, err := th.App.Srv().Store.Team().GetMembers(team.Id, 0, 1000, nil)
require.NoError(t, err)
originalExpectedCount = int64(len(teamMembers))
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, originalExpectedCount, actualCount)
})
t.Run("after a new team member was added", func(t *testing.T) {
anotherUser = th.CreateUser()
newTeamMember, appErr := th.App.JoinUserToTeam(th.Context, team, anotherUser, "")
newTeamMemberJoinTime = newTeamMember.CreateAt
require.Nil(t, appErr)
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, originalExpectedCount+1, actualCount)
})
t.Run("after a team member was added to a different team, ensuring the wrong team's member count isn't incremented", func(t *testing.T) {
anotherUser2 := th.CreateUser()
anotherTeam := th.CreateTeam()
_, appErr := th.App.JoinUserToTeam(th.Context, anotherTeam, anotherUser2, "")
require.Nil(t, appErr)
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, originalExpectedCount+1, actualCount)
})
t.Run("since a given time", func(t *testing.T) {
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: newTeamMemberJoinTime, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, int64(1), actualCount)
})
t.Run("after a team member was removed", func(t *testing.T) {
th.RemoveUserFromTeam(anotherUser, team)
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, originalExpectedCount, actualCount)
})
t.Run("after a user was deactivated", func(t *testing.T) {
_, appErr := th.App.JoinUserToTeam(th.Context, team, anotherUser, "")
require.Nil(t, appErr)
_, beforeCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
_, appErr = th.App.UpdateActive(th.Context, anotherUser, false)
defer th.App.UpdateActive(th.Context, anotherUser, true)
require.Nil(t, appErr)
_, afterCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, beforeCount-1, afterCount)
})
t.Run("after a user was permanently deleted", func(t *testing.T) {
_, beforeCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
appErr = th.App.PermanentDeleteUser(th.Context, anotherUser)
require.Nil(t, appErr)
_, afterCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, beforeCount-1, afterCount)
})
t.Run("exclude bots", func(t *testing.T) {
user := th.CreateUser()
_, appErr := th.App.ConvertUserToBot(user)
require.Nil(t, appErr)
_, appErr = th.App.JoinUserToTeam(th.Context, team, user, "")
require.Nil(t, appErr)
_, actualCount, appErr := th.App.GetNewTeamMembersSince(th.Context, team.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Equal(t, originalExpectedCount, actualCount)
})
})
t.Run("returns the correct team members", func(t *testing.T) {
var originalExpectedMembers []*model.TeamMember
var newTeamMemberJoinTime int64
var anotherUser *model.User
uIDs := func(members []*model.TeamMember) []string {
ids := []string{}
for _, member := range members {
ids = append(ids, member.UserId)
}
return ids
}
nUIDs := func(members []*model.NewTeamMember) []string {
ids := []string{}
for _, member := range members {
ids = append(ids, member.Id)
}
return ids
}
t.Run("since time 0", func(t *testing.T) {
var err error
originalExpectedMembers, err = th.App.Srv().Store.Team().GetMembers(th.BasicTeam.Id, 0, 1000, nil)
require.NoError(t, err)
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items))
})
t.Run("after a new team member was added", func(t *testing.T) {
anotherUser = th.CreateUser()
newTeamMember, appErr := th.App.JoinUserToTeam(th.Context, th.BasicTeam, anotherUser, "")
newTeamMemberJoinTime = newTeamMember.CreateAt
require.Nil(t, appErr)
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.ElementsMatch(t, append(uIDs(originalExpectedMembers), anotherUser.Id), nUIDs(actualMembersList.Items))
})
t.Run("after a team member was added to a different team, ensuring the wrong team's member count isn't incremented", func(t *testing.T) {
anotherUser2 := th.CreateUser()
anotherTeam := th.CreateTeam()
_, appErr := th.App.JoinUserToTeam(th.Context, anotherTeam, anotherUser2, "")
require.Nil(t, appErr)
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.ElementsMatch(t, append(uIDs(originalExpectedMembers), anotherUser.Id), nUIDs(actualMembersList.Items))
})
t.Run("since a given time", func(t *testing.T) {
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: newTeamMemberJoinTime, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Len(t, actualMembersList.Items, 1)
require.Equal(t, anotherUser.Id, actualMembersList.Items[0].Id)
})
t.Run("after a team member was removed", func(t *testing.T) {
th.RemoveUserFromTeam(anotherUser, th.BasicTeam)
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items))
})
t.Run("after a user was deactivated", func(t *testing.T) {
_, appErr := th.App.JoinUserToTeam(th.Context, th.BasicTeam, anotherUser, "")
require.Nil(t, appErr)
beforeMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Contains(t, nUIDs(beforeMembersList.Items), anotherUser.Id)
_, appErr = th.App.UpdateActive(th.Context, anotherUser, false)
defer th.App.UpdateActive(th.Context, anotherUser, true)
require.Nil(t, appErr)
afterMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.NotContains(t, nUIDs(afterMembersList.Items), anotherUser.Id)
})
t.Run("after a user was permanently deleted", func(t *testing.T) {
beforeMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.Contains(t, nUIDs(beforeMembersList.Items), anotherUser.Id)
appErr = th.App.PermanentDeleteUser(th.Context, anotherUser)
require.Nil(t, appErr)
afterMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.NotContains(t, nUIDs(afterMembersList.Items), anotherUser.Id)
})
t.Run("exclude bots", func(t *testing.T) {
user := th.CreateUser()
_, appErr := th.App.ConvertUserToBot(user)
require.Nil(t, appErr)
_, appErr = th.App.JoinUserToTeam(th.Context, th.BasicTeam, user, "")
require.Nil(t, appErr)
actualMembersList, _, appErr := th.App.GetNewTeamMembersSince(th.Context, th.BasicTeam.Id, &model.InsightsOpts{StartUnixMilli: 0, Page: 0, PerPage: 1000})
require.Nil(t, appErr)
require.ElementsMatch(t, uIDs(originalExpectedMembers), nUIDs(actualMembersList.Items))
})
})
}

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

@@ -141,6 +141,7 @@ func (ts *TeamService) JoinUserToTeam(team *model.Team, user *model.User) (*mode
UserId: user.Id,
SchemeGuest: user.IsGuest(),
SchemeUser: !user.IsGuest(),
CreateAt: model.GetMillis(),
}
if !user.IsGuest() {