From f2e5e1562dbc71766d5ac96e9c11929b312a8cca Mon Sep 17 00:00:00 2001 From: Eli Yukelzon Date: Wed, 7 Oct 2020 17:46:07 +0300 Subject: [PATCH] MM-29400 Mark all current notices as viewed for newly created users (#15840) * Added clearing of notices for new users * ci kick --- app/app_iface.go | 12 +++++++++--- app/opentracing/opentracing_layer.go | 15 +++++++++++++++ app/product_notices.go | 15 +++++++++++++++ app/user.go | 1 + 4 files changed, 40 insertions(+), 3 deletions(-) diff --git a/app/app_iface.go b/app/app_iface.go index 019c2cfb6d..12dd59bfa5 100644 --- a/app/app_iface.go +++ b/app/app_iface.go @@ -177,6 +177,8 @@ type AppIface interface { // To get the plugins environment when the plugins are disabled, manually acquire the plugins // lock instead. GetPluginsEnvironment() *plugin.Environment + // GetProductNotices is called from the frontend to fetch the product notices that are relevant to the caller + GetProductNotices(userId, teamId string, client model.NoticeClientType, clientVersion string, locale string) (model.NoticeMessages, *model.AppError) // GetPublicKey will return the actual public key saved in the `name` file. GetPublicKey(name string) ([]byte, *model.AppError) // GetSanitizedConfig gets the configuration for a system admin without any secrets. @@ -314,6 +316,13 @@ type AppIface interface { UpdateChannel(channel *model.Channel) (*model.Channel, *model.AppError) // UpdateChannelScheme saves the new SchemeId of the channel passed. UpdateChannelScheme(channel *model.Channel) (*model.Channel, *model.AppError) + // UpdateProductNotices is called periodically from a scheduled worker to fetch new notices and update the cache + UpdateProductNotices() *model.AppError + // UpdateViewedProductNotices is called from the frontend to mark a set of notices as 'viewed' by user + UpdateViewedProductNotices(userId string, noticeIds []string) *model.AppError + // UpdateViewedProductNoticesForNewUser is called when new user is created to mark all current notices for this + // user as viewed in order to avoid showing them imminently on first login + UpdateViewedProductNoticesForNewUser(userId string) // UpdateWebConnUserActivity sets the LastUserActivityAt of the hub for the given session. UpdateWebConnUserActivity(session model.Session, activityAt int64) // UploadFile uploads a single file in form of a completely constructed byte array for a channel. @@ -629,7 +638,6 @@ type AppIface interface { GetPreferencesForUser(userId string) (model.Preferences, *model.AppError) GetPrevPostIdFromPostList(postList *model.PostList) string GetPrivateChannelsForTeam(teamId string, offset int, limit int) (*model.ChannelList, *model.AppError) - GetProductNotices(userId, teamId string, client model.NoticeClientType, clientVersion string, locale string) (model.NoticeMessages, *model.AppError) GetProfileImage(user *model.User) ([]byte, bool, *model.AppError) GetPublicChannelsByIdsForTeam(teamId string, channelIds []string) (*model.ChannelList, *model.AppError) GetPublicChannelsForTeam(teamId string, offset int, limit int) (*model.ChannelList, *model.AppError) @@ -973,7 +981,6 @@ type AppIface interface { UpdatePasswordSendEmail(user *model.User, newPassword, method string) *model.AppError UpdatePost(post *model.Post, safeUpdate bool) (*model.Post, *model.AppError) UpdatePreferences(userId string, preferences model.Preferences) *model.AppError - UpdateProductNotices() *model.AppError UpdateRole(role *model.Role) (*model.Role, *model.AppError) UpdateScheme(scheme *model.Scheme) (*model.Scheme, *model.AppError) UpdateSessionsIsGuest(userId string, isGuest bool) @@ -990,7 +997,6 @@ type AppIface interface { UpdateUserAuth(userId string, userAuth *model.UserAuth) (*model.UserAuth, *model.AppError) UpdateUserNotifyProps(userId string, props map[string]string) (*model.User, *model.AppError) UpdateUserRoles(userId string, newRoles string, sendWebSocketEvent bool) (*model.User, *model.AppError) - UpdateViewedProductNotices(userId string, noticeIds []string) *model.AppError UploadData(us *model.UploadSession, rd io.Reader) (*model.FileInfo, *model.AppError) UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError UploadMultipartFiles(teamId string, channelId string, userId string, fileHeaders []*multipart.FileHeader, clientIds []string, now time.Time) (*model.FileUploadResponse, *model.AppError) diff --git a/app/opentracing/opentracing_layer.go b/app/opentracing/opentracing_layer.go index d000fa2365..64a28c3d58 100644 --- a/app/opentracing/opentracing_layer.go +++ b/app/opentracing/opentracing_layer.go @@ -15248,6 +15248,21 @@ func (a *OpenTracingAppLayer) UpdateViewedProductNotices(userId string, noticeId return resultVar0 } +func (a *OpenTracingAppLayer) UpdateViewedProductNoticesForNewUser(userId string) { + origCtx := a.ctx + span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateViewedProductNoticesForNewUser") + + a.ctx = newCtx + a.app.Srv().Store.SetContext(newCtx) + defer func() { + a.app.Srv().Store.SetContext(origCtx) + a.ctx = origCtx + }() + + defer span.Finish() + a.app.UpdateViewedProductNoticesForNewUser(userId) +} + func (a *OpenTracingAppLayer) UpdateWebConnUserActivity(session model.Session, activityAt int64) { origCtx := a.ctx span, newCtx := tracing.StartSpanWithParentByContext(a.ctx, "app.UpdateWebConnUserActivity") diff --git a/app/product_notices.go b/app/product_notices.go index 25af87bfa0..cd26d8cce0 100644 --- a/app/product_notices.go +++ b/app/product_notices.go @@ -200,6 +200,7 @@ func validateConfigEntry(conf *model.Config, path string, expectedValue interfac return val == expectedValue } +// GetProductNotices is called from the frontend to fetch the product notices that are relevant to the caller func (a *App) GetProductNotices(userId, teamId string, client model.NoticeClientType, clientVersion string, locale string) (model.NoticeMessages, *model.AppError) { isSystemAdmin := a.SessionHasPermissionTo(*a.Session(), model.PERMISSION_MANAGE_SYSTEM) isTeamAdmin := a.SessionHasPermissionToTeam(*a.Session(), teamId, model.PERMISSION_MANAGE_TEAM) @@ -275,6 +276,7 @@ func (a *App) GetProductNotices(userId, teamId string, client model.NoticeClient return filteredNotices, nil } +// UpdateViewedProductNotices is called from the frontend to mark a set of notices as 'viewed' by user func (a *App) UpdateViewedProductNotices(userId string, noticeIds []string) *model.AppError { if err := a.Srv().Store.ProductNotices().View(userId, noticeIds); err != nil { return model.NewAppError("UpdateViewedProductNotices", "api.system.update_viewed_notices.failed", nil, err.Error(), http.StatusBadRequest) @@ -282,6 +284,19 @@ func (a *App) UpdateViewedProductNotices(userId string, noticeIds []string) *mod return nil } +// UpdateViewedProductNoticesForNewUser is called when new user is created to mark all current notices for this +// user as viewed in order to avoid showing them imminently on first login +func (a *App) UpdateViewedProductNoticesForNewUser(userId string) { + var noticeIds []string + for _, notice := range cachedNotices { + noticeIds = append(noticeIds, notice.ID) + } + if err := a.Srv().Store.ProductNotices().View(userId, noticeIds); err != nil { + mlog.Error("Cannot update product notices viewed state for user", mlog.String("userId", userId)) + } +} + +// UpdateProductNotices is called periodically from a scheduled worker to fetch new notices and update the cache func (a *App) UpdateProductNotices() *model.AppError { url := *a.Srv().Config().AnnouncementSettings.NoticesURL skip := *a.Srv().Config().AnnouncementSettings.NoticesSkipCache diff --git a/app/user.go b/app/user.go index e9d6fd35b3..3f3460273e 100644 --- a/app/user.go +++ b/app/user.go @@ -314,6 +314,7 @@ func (a *App) createUser(user *model.User) (*model.User, *model.AppError) { mlog.Error("Encountered error saving tutorial preference", mlog.Err(err)) } + go a.UpdateViewedProductNoticesForNewUser(ruser.Id) ruser.Sanitize(map[string]bool{}) return ruser, nil }