[MM-47751][MM-48102] MPA: Send Persistent Notifications (#21619)

* MM-46410: adds urgency on mention counts

We have introduced priority for posts in
https://github.com/mattermost/mattermost-webapp/pull/10951.
We do need to color the mention badges in the webapp with a prominent
color when a mention is posted in an urgent message.
A thread has urgent mentions if the root post is marked as urgent, and
the replies contain mentions to the user viewing the thread.

This PR adds two columns, urgentmentioncount, and isurgent, in
channelmembers, and threads tables respectively.
Furthermore when asking for team/thread mention counts, we also return
urgent mention counts for the user.

* Adds PostAcknowledgements table and apis

* job init and fetch mentions

* add-migrations

* delete-expired

* send-notifications

* Fetches post priority in batches

* stop-notifications

* stop-notification-on-reply

* MM-47750: Adds PostAcknowledgements table and apis

- Adds post acknowledgement api/app/store methods to be able to save and
delete post acknowledgements by users.
- Adds wesbsocket events for acknowledgement created/deleted
- Returns post acknowledgements in the post's metadata

* add-license-check

* add-pagination

* delete on channel and team

* validate guests

* add configs

* move create priority post check from app to api

* Add desktop notifications

* check status

* use config in job

* add IsUrgent check

* Add last-sent-at

* validate max recipients

* Update lastSentAt

* Validate min. recipient

* send email notification only once

* remove email notifications

* use latest time from config to run job

* Add notifications counter

* publish events to mentioned users only

* pickup license updates in scheduler

* don't allow post owner to stop notifications

* follow normal notifications behaviour

* Validates persistent notifications interval

* move logic of handling valid and expired posts into sql

* Adds persistent notifications in the webapp

---------

Co-authored-by: koox00 <3829551+koox00@users.noreply.github.com>
Co-authored-by: Mattermod <mattermod@users.noreply.github.com>
Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Vishal
2023-05-18 23:44:12 +05:30
коммит произвёл GitHub
родитель ce165302cf
Коммит 9399ce8637
74 изменённых файлов: 4052 добавлений и 888 удалений

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

@@ -19,48 +19,49 @@ import (
type OpenTracingLayer struct {
store.Store
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPersistentNotificationStore store.PostPersistentNotificationStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
}
func (s *OpenTracingLayer) Audit() store.AuditStore {
@@ -143,6 +144,10 @@ func (s *OpenTracingLayer) PostAcknowledgement() store.PostAcknowledgementStore
return s.PostAcknowledgementStore
}
func (s *OpenTracingLayer) PostPersistentNotification() store.PostPersistentNotificationStore {
return s.PostPersistentNotificationStore
}
func (s *OpenTracingLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
@@ -331,6 +336,11 @@ type OpenTracingLayerPostAcknowledgementStore struct {
Root *OpenTracingLayer
}
type OpenTracingLayerPostPersistentNotificationStore struct {
store.PostPersistentNotificationStore
Root *OpenTracingLayer
}
type OpenTracingLayerPostPriorityStore struct {
store.PostPriorityStore
Root *OpenTracingLayer
@@ -6839,6 +6849,132 @@ func (s *OpenTracingLayerPostAcknowledgementStore) Save(postID string, userID st
return result, err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) Delete(postIds []string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.Delete")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostPersistentNotificationStore.Delete(postIds)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) DeleteByChannel(channelIds []string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.DeleteByChannel")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostPersistentNotificationStore.DeleteByChannel(channelIds)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) DeleteByTeam(teamIds []string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.DeleteByTeam")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostPersistentNotificationStore.DeleteByTeam(teamIds)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) DeleteExpired(maxSentCount int16) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.DeleteExpired")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostPersistentNotificationStore.DeleteExpired(maxSentCount)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.Get")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostPersistentNotificationStore.Get(params)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) GetSingle(postID string) (*model.PostPersistentNotifications, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.GetSingle")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
result, err := s.PostPersistentNotificationStore.GetSingle(postID)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return result, err
}
func (s *OpenTracingLayerPostPersistentNotificationStore) UpdateLastActivity(postIds []string) error {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPersistentNotificationStore.UpdateLastActivity")
s.Root.Store.SetContext(newCtx)
defer func() {
s.Root.Store.SetContext(origCtx)
}()
defer span.Finish()
err := s.PostPersistentNotificationStore.UpdateLastActivity(postIds)
if err != nil {
span.LogFields(spanlog.Error(err))
ext.Error.Set(span, true)
}
return err
}
func (s *OpenTracingLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
origCtx := s.Root.Store.Context()
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "PostPriorityStore.GetForPost")
@@ -12942,6 +13078,7 @@ func New(childStore store.Store, ctx context.Context) *OpenTracingLayer {
newStore.PluginStore = &OpenTracingLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &OpenTracingLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostAcknowledgementStore = &OpenTracingLayerPostAcknowledgementStore{PostAcknowledgementStore: childStore.PostAcknowledgement(), Root: &newStore}
newStore.PostPersistentNotificationStore = &OpenTracingLayerPostPersistentNotificationStore{PostPersistentNotificationStore: childStore.PostPersistentNotification(), Root: &newStore}
newStore.PostPriorityStore = &OpenTracingLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &OpenTracingLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}

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

@@ -22,48 +22,49 @@ const mySQLDeadlockCode = uint16(1213)
type RetryLayer struct {
store.Store
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPersistentNotificationStore store.PostPersistentNotificationStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
}
func (s *RetryLayer) Audit() store.AuditStore {
@@ -146,6 +147,10 @@ func (s *RetryLayer) PostAcknowledgement() store.PostAcknowledgementStore {
return s.PostAcknowledgementStore
}
func (s *RetryLayer) PostPersistentNotification() store.PostPersistentNotificationStore {
return s.PostPersistentNotificationStore
}
func (s *RetryLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
@@ -334,6 +339,11 @@ type RetryLayerPostAcknowledgementStore struct {
Root *RetryLayer
}
type RetryLayerPostPersistentNotificationStore struct {
store.PostPersistentNotificationStore
Root *RetryLayer
}
type RetryLayerPostPriorityStore struct {
store.PostPriorityStore
Root *RetryLayer
@@ -7750,6 +7760,153 @@ func (s *RetryLayerPostAcknowledgementStore) Save(postID string, userID string,
}
func (s *RetryLayerPostPersistentNotificationStore) Delete(postIds []string) error {
tries := 0
for {
err := s.PostPersistentNotificationStore.Delete(postIds)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) DeleteByChannel(channelIds []string) error {
tries := 0
for {
err := s.PostPersistentNotificationStore.DeleteByChannel(channelIds)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) DeleteByTeam(teamIds []string) error {
tries := 0
for {
err := s.PostPersistentNotificationStore.DeleteByTeam(teamIds)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) DeleteExpired(maxSentCount int16) error {
tries := 0
for {
err := s.PostPersistentNotificationStore.DeleteExpired(maxSentCount)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error) {
tries := 0
for {
result, err := s.PostPersistentNotificationStore.Get(params)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) GetSingle(postID string) (*model.PostPersistentNotifications, error) {
tries := 0
for {
result, err := s.PostPersistentNotificationStore.GetSingle(postID)
if err == nil {
return result, nil
}
if !isRepeatableError(err) {
return result, err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return result, err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPersistentNotificationStore) UpdateLastActivity(postIds []string) error {
tries := 0
for {
err := s.PostPersistentNotificationStore.UpdateLastActivity(postIds)
if err == nil {
return nil
}
if !isRepeatableError(err) {
return err
}
tries++
if tries >= 3 {
err = errors.Wrap(err, "giving up after 3 consecutive repeatable transaction failures")
return err
}
timepkg.Sleep(100 * timepkg.Millisecond)
}
}
func (s *RetryLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
tries := 0
@@ -14750,6 +14907,7 @@ func New(childStore store.Store) *RetryLayer {
newStore.PluginStore = &RetryLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &RetryLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostAcknowledgementStore = &RetryLayerPostAcknowledgementStore{PostAcknowledgementStore: childStore.PostAcknowledgement(), Root: &newStore}
newStore.PostPersistentNotificationStore = &RetryLayerPostPersistentNotificationStore{PostPersistentNotificationStore: childStore.PostPersistentNotification(), Root: &newStore}
newStore.PostPriorityStore = &RetryLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &RetryLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}

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

@@ -57,6 +57,7 @@ func genStore() *mocks.Store {
mock.On("Draft").Return(&mocks.DraftStore{})
mock.On("PostPriority").Return(&mocks.PostPriorityStore{})
mock.On("PostAcknowledgement").Return(&mocks.PostAcknowledgementStore{})
mock.On("PostPersistentNotification").Return(&mocks.PostPersistentNotificationStore{})
mock.On("TrueUpReview").Return(&mocks.TrueUpReviewStore{})
return mock
}

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

@@ -0,0 +1,193 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"database/sql"
"github.com/mattermost/mattermost-server/server/public/model"
"github.com/mattermost/mattermost-server/server/v8/channels/store"
sq "github.com/mattermost/squirrel"
"github.com/pkg/errors"
)
type SqlPostPersistentNotificationStore struct {
*SqlStore
}
func newSqlPostPersistentNotificationStore(sqlStore *SqlStore) store.PostPersistentNotificationStore {
return &SqlPostPersistentNotificationStore{
SqlStore: sqlStore,
}
}
func (s *SqlPostPersistentNotificationStore) GetSingle(postID string) (*model.PostPersistentNotifications, error) {
builder := s.getQueryBuilder().
Select("PostId, CreateAt, LastSentAt, DeleteAt, SentCount").
From("PersistentNotifications").
Where(sq.And{
sq.Eq{"DeleteAt": 0},
sq.Eq{"PostId": postID},
})
post := &model.PostPersistentNotifications{}
err := s.GetReplicaX().GetBuilder(post, builder)
if err != nil {
if err == sql.ErrNoRows {
return nil, store.NewErrNotFound("Persistent Notification Post", postID)
}
return nil, errors.Wrapf(err, "failed to get the persistent notification post=%s", postID)
}
return post, nil
}
// Get returns only valid posts.
func (s *SqlPostPersistentNotificationStore) Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error) {
if params.PerPage == 0 {
params.PerPage = 1000
}
builder := s.getQueryBuilder().
Select("PostId, CreateAt, LastSentAt, DeleteAt, SentCount").
From("PersistentNotifications").
Where(sq.And{
sq.Eq{"DeleteAt": 0},
sq.LtOrEq{"CreateAt": params.MaxTime},
sq.LtOrEq{"LastSentAt": params.MaxTime},
sq.Lt{"SentCount": params.MaxSentCount},
}).
Limit(uint64(params.PerPage))
var posts []*model.PostPersistentNotifications
// Replica may not have the latest changes(done by UpdateLastActivity func)
// by the time this Get func is called again in the loop.
err := s.GetMasterX().SelectBuilder(&posts, builder)
if err != nil {
return nil, errors.Wrap(err, "failed to get notifications")
}
return posts, nil
}
func (s *SqlPostPersistentNotificationStore) UpdateLastActivity(postIds []string) error {
builder := s.getQueryBuilder().
Update("PersistentNotifications").
Set("LastSentAt", model.GetMillis()).
Set("SentCount", sq.Expr("SentCount+1")).
Where(sq.Eq{"PostId": postIds})
_, err := s.GetMasterX().ExecBuilder(builder)
if err != nil {
return errors.Wrapf(err, "failed to update last activity for posts %s", postIds)
}
return nil
}
func (s *SqlPostPersistentNotificationStore) Delete(postIds []string) error {
count := len(postIds)
if count == 0 {
return nil
}
builder := s.getQueryBuilder().
Update("PersistentNotifications").
Set("DeleteAt", model.GetMillis()).
Where(sq.Eq{"PostId": postIds})
_, err := s.GetMasterX().ExecBuilder(builder)
if err != nil {
return errors.Wrapf(err, "failed to delete notifications for posts %s", postIds)
}
return nil
}
func (s *SqlPostPersistentNotificationStore) DeleteExpired(maxSentCount int16) error {
builder := s.getQueryBuilder().
Update("PersistentNotifications").
Set("DeleteAt", model.GetMillis()).
Where(sq.And{
sq.Eq{"DeleteAt": 0},
sq.GtOrEq{"SentCount": maxSentCount},
})
_, err := s.GetMasterX().ExecBuilder(builder)
if err != nil {
return errors.Wrap(err, "failed to delete notifications")
}
return nil
}
func (s *SqlPostPersistentNotificationStore) DeleteByChannel(channelIds []string) error {
count := len(channelIds)
if count == 0 {
return nil
}
deleteAt := model.GetMillis()
var builder sq.UpdateBuilder
builderType := s.getQueryBuilder()
if s.DriverName() == model.DatabaseDriverMysql {
builder = builderType.
Update("PersistentNotifications, Posts").
Set("PersistentNotifications.DeleteAt", deleteAt)
}
if s.DriverName() == model.DatabaseDriverPostgres {
builder = builderType.
Update("PersistentNotifications").
Set("DeleteAt", deleteAt).
From("Posts")
}
builder = builder.Where(sq.And{
sq.Expr("Posts.Id = PersistentNotifications.PostId"),
sq.Eq{"Posts.ChannelId": channelIds},
})
_, err := s.GetMasterX().ExecBuilder(builder)
if err != nil {
return errors.Wrapf(err, "failed to delete notifications for channels %s", channelIds)
}
return nil
}
func (s *SqlPostPersistentNotificationStore) DeleteByTeam(teamIds []string) error {
count := len(teamIds)
if count == 0 {
return nil
}
deleteAt := model.GetMillis()
var builder sq.UpdateBuilder
builderType := s.getQueryBuilder()
if s.DriverName() == model.DatabaseDriverMysql {
builder = builderType.
Update("PersistentNotifications, Posts, Channels").
Set("PersistentNotifications.DeleteAt", deleteAt)
}
if s.DriverName() == model.DatabaseDriverPostgres {
builder = builderType.
Update("PersistentNotifications").
Set("DeleteAt", deleteAt).
From("Posts, Channels")
}
builder = builder.Where(sq.And{
sq.Expr("Posts.Id = PersistentNotifications.PostId"),
sq.Expr("Posts.ChannelId = Channels.Id"),
sq.Eq{"Channels.TeamId": teamIds},
})
_, err := s.GetMasterX().ExecBuilder(builder)
if err != nil {
return errors.Wrapf(err, "failed to delete notifications for teams %s", teamIds)
}
return nil
}

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

@@ -0,0 +1,14 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package sqlstore
import (
"testing"
"github.com/mattermost/mattermost-server/server/v8/channels/store/storetest"
)
func TestPostPersistentNotificationStore(t *testing.T) {
StoreTestWithSqlStore(t, storetest.TestPostPersistentNotificationStore)
}

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

@@ -223,6 +223,10 @@ func (s *SqlPostStore) SaveMultiple(posts []*model.Post) ([]*model.Post, int, er
return nil, -1, errors.Wrap(err, "failed to save PostPriority")
}
if err = s.savePostsPersistentNotifications(transaction, posts); err != nil {
return nil, -1, errors.Wrap(err, "failed to save posts persistent notifications")
}
if err = transaction.Commit(); err != nil {
// don't need to rollback here since the transaction is already closed
return posts, -1, errors.Wrap(err, "commit_transaction")
@@ -3004,6 +3008,20 @@ func (s *SqlPostStore) savePostsPriority(transaction *sqlxTxWrapper, posts []*mo
return nil
}
func (s *SqlPostStore) savePostsPersistentNotifications(transaction *sqlxTxWrapper, posts []*model.Post) error {
for _, post := range posts {
if priority := post.GetPriority(); priority != nil && priority.PersistentNotifications != nil && *priority.PersistentNotifications {
if _, err := transaction.NamedExec(`INSERT INTO PersistentNotifications (PostId, CreateAt, LastSentAt, DeleteAt, SentCount) VALUES (:PostId, :CreateAt, :LastSentAt, :DeleteAt, :SentCount)`, &model.PostPersistentNotifications{
PostId: post.Id,
CreateAt: post.CreateAt,
}); err != nil {
return err
}
}
}
return nil
}
func (s *SqlPostStore) updateThreadsFromPosts(transaction *sqlxTxWrapper, posts []*model.Post) error {
postsByRoot := map[string][]*model.Post{}
var rootIds []string

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

@@ -71,48 +71,49 @@ const (
var tablesToCheckForCollation = []string{"incomingwebhooks", "preferences", "users", "uploadsessions", "channels", "publicchannels"}
type SqlStoreStores struct {
team store.TeamStore
channel store.ChannelStore
post store.PostStore
retentionPolicy store.RetentionPolicyStore
thread store.ThreadStore
user store.UserStore
bot store.BotStore
audit store.AuditStore
cluster store.ClusterDiscoveryStore
remoteCluster store.RemoteClusterStore
compliance store.ComplianceStore
session store.SessionStore
oauth store.OAuthStore
system store.SystemStore
webhook store.WebhookStore
command store.CommandStore
commandWebhook store.CommandWebhookStore
preference store.PreferenceStore
license store.LicenseStore
token store.TokenStore
emoji store.EmojiStore
status store.StatusStore
fileInfo store.FileInfoStore
uploadSession store.UploadSessionStore
reaction store.ReactionStore
job store.JobStore
userAccessToken store.UserAccessTokenStore
plugin store.PluginStore
channelMemberHistory store.ChannelMemberHistoryStore
role store.RoleStore
scheme store.SchemeStore
TermsOfService store.TermsOfServiceStore
productNotices store.ProductNoticesStore
group store.GroupStore
UserTermsOfService store.UserTermsOfServiceStore
linkMetadata store.LinkMetadataStore
sharedchannel store.SharedChannelStore
draft store.DraftStore
notifyAdmin store.NotifyAdminStore
postPriority store.PostPriorityStore
postAcknowledgement store.PostAcknowledgementStore
trueUpReview store.TrueUpReviewStore
team store.TeamStore
channel store.ChannelStore
post store.PostStore
retentionPolicy store.RetentionPolicyStore
thread store.ThreadStore
user store.UserStore
bot store.BotStore
audit store.AuditStore
cluster store.ClusterDiscoveryStore
remoteCluster store.RemoteClusterStore
compliance store.ComplianceStore
session store.SessionStore
oauth store.OAuthStore
system store.SystemStore
webhook store.WebhookStore
command store.CommandStore
commandWebhook store.CommandWebhookStore
preference store.PreferenceStore
license store.LicenseStore
token store.TokenStore
emoji store.EmojiStore
status store.StatusStore
fileInfo store.FileInfoStore
uploadSession store.UploadSessionStore
reaction store.ReactionStore
job store.JobStore
userAccessToken store.UserAccessTokenStore
plugin store.PluginStore
channelMemberHistory store.ChannelMemberHistoryStore
role store.RoleStore
scheme store.SchemeStore
TermsOfService store.TermsOfServiceStore
productNotices store.ProductNoticesStore
group store.GroupStore
UserTermsOfService store.UserTermsOfServiceStore
linkMetadata store.LinkMetadataStore
sharedchannel store.SharedChannelStore
draft store.DraftStore
notifyAdmin store.NotifyAdminStore
postPriority store.PostPriorityStore
postAcknowledgement store.PostAcknowledgementStore
postPersistentNotification store.PostPersistentNotificationStore
trueUpReview store.TrueUpReviewStore
}
type SqlStore struct {
@@ -232,6 +233,7 @@ func New(settings model.SqlSettings, metrics einterfaces.MetricsInterface) *SqlS
store.stores.notifyAdmin = newSqlNotifyAdminStore(store)
store.stores.postPriority = newSqlPostPriorityStore(store)
store.stores.postAcknowledgement = newSqlPostAcknowledgementStore(store)
store.stores.postPersistentNotification = newSqlPostPersistentNotificationStore(store)
store.stores.trueUpReview = newSqlTrueUpReviewStore(store)
store.stores.preference.(*SqlPreferenceStore).deleteUnusedFeatures()
@@ -1076,6 +1078,10 @@ func (ss *SqlStore) PostAcknowledgement() store.PostAcknowledgementStore {
return ss.stores.postAcknowledgement
}
func (ss *SqlStore) PostPersistentNotification() store.PostPersistentNotificationStore {
return ss.stores.postPersistentNotification
}
func (ss *SqlStore) TrueUpReview() store.TrueUpReviewStore {
return ss.stores.trueUpReview
}

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

@@ -84,6 +84,7 @@ type Store interface {
NotifyAdmin() NotifyAdminStore
PostPriority() PostPriorityStore
PostAcknowledgement() PostAcknowledgementStore
PostPersistentNotification() PostPersistentNotificationStore
TrueUpReview() TrueUpReviewStore
}
@@ -999,6 +1000,16 @@ type PostAcknowledgementStore interface {
Delete(acknowledgement *model.PostAcknowledgement) error
}
type PostPersistentNotificationStore interface {
Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error)
GetSingle(postID string) (*model.PostPersistentNotifications, error)
UpdateLastActivity(postIds []string) error
Delete(postIds []string) error
DeleteExpired(maxSentCount int16) error
DeleteByChannel(channelIds []string) error
DeleteByTeam(teamIds []string) error
}
type TrueUpReviewStore interface {
GetTrueUpReviewStatus(dueDate int64) (*model.TrueUpReviewStatus, error)
CreateTrueUpReviewStatusRecord(reviewStatus *model.TrueUpReviewStatus) (*model.TrueUpReviewStatus, error)

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

@@ -0,0 +1,152 @@
// Code generated by mockery v2.23.2. DO NOT EDIT.
// Regenerate this file using `make store-mocks`.
package mocks
import (
model "github.com/mattermost/mattermost-server/server/public/model"
mock "github.com/stretchr/testify/mock"
)
// PostPersistentNotificationStore is an autogenerated mock type for the PostPersistentNotificationStore type
type PostPersistentNotificationStore struct {
mock.Mock
}
// Delete provides a mock function with given fields: postIds
func (_m *PostPersistentNotificationStore) Delete(postIds []string) error {
ret := _m.Called(postIds)
var r0 error
if rf, ok := ret.Get(0).(func([]string) error); ok {
r0 = rf(postIds)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteByChannel provides a mock function with given fields: channelIds
func (_m *PostPersistentNotificationStore) DeleteByChannel(channelIds []string) error {
ret := _m.Called(channelIds)
var r0 error
if rf, ok := ret.Get(0).(func([]string) error); ok {
r0 = rf(channelIds)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteByTeam provides a mock function with given fields: teamIds
func (_m *PostPersistentNotificationStore) DeleteByTeam(teamIds []string) error {
ret := _m.Called(teamIds)
var r0 error
if rf, ok := ret.Get(0).(func([]string) error); ok {
r0 = rf(teamIds)
} else {
r0 = ret.Error(0)
}
return r0
}
// DeleteExpired provides a mock function with given fields: maxSentCount
func (_m *PostPersistentNotificationStore) DeleteExpired(maxSentCount int16) error {
ret := _m.Called(maxSentCount)
var r0 error
if rf, ok := ret.Get(0).(func(int16) error); ok {
r0 = rf(maxSentCount)
} else {
r0 = ret.Error(0)
}
return r0
}
// Get provides a mock function with given fields: params
func (_m *PostPersistentNotificationStore) Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error) {
ret := _m.Called(params)
var r0 []*model.PostPersistentNotifications
var r1 error
if rf, ok := ret.Get(0).(func(model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error)); ok {
return rf(params)
}
if rf, ok := ret.Get(0).(func(model.GetPersistentNotificationsPostsParams) []*model.PostPersistentNotifications); ok {
r0 = rf(params)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).([]*model.PostPersistentNotifications)
}
}
if rf, ok := ret.Get(1).(func(model.GetPersistentNotificationsPostsParams) error); ok {
r1 = rf(params)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// GetSingle provides a mock function with given fields: postID
func (_m *PostPersistentNotificationStore) GetSingle(postID string) (*model.PostPersistentNotifications, error) {
ret := _m.Called(postID)
var r0 *model.PostPersistentNotifications
var r1 error
if rf, ok := ret.Get(0).(func(string) (*model.PostPersistentNotifications, error)); ok {
return rf(postID)
}
if rf, ok := ret.Get(0).(func(string) *model.PostPersistentNotifications); ok {
r0 = rf(postID)
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(*model.PostPersistentNotifications)
}
}
if rf, ok := ret.Get(1).(func(string) error); ok {
r1 = rf(postID)
} else {
r1 = ret.Error(1)
}
return r0, r1
}
// UpdateLastActivity provides a mock function with given fields: postIds
func (_m *PostPersistentNotificationStore) UpdateLastActivity(postIds []string) error {
ret := _m.Called(postIds)
var r0 error
if rf, ok := ret.Get(0).(func([]string) error); ok {
r0 = rf(postIds)
} else {
r0 = ret.Error(0)
}
return r0
}
type mockConstructorTestingTNewPostPersistentNotificationStore interface {
mock.TestingT
Cleanup(func())
}
// NewPostPersistentNotificationStore creates a new instance of PostPersistentNotificationStore. It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewPostPersistentNotificationStore(t mockConstructorTestingTNewPostPersistentNotificationStore) *PostPersistentNotificationStore {
mock := &PostPersistentNotificationStore{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}

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

@@ -500,6 +500,22 @@ func (_m *Store) PostAcknowledgement() store.PostAcknowledgementStore {
return r0
}
// PostPersistentNotification provides a mock function with given fields:
func (_m *Store) PostPersistentNotification() store.PostPersistentNotificationStore {
ret := _m.Called()
var r0 store.PostPersistentNotificationStore
if rf, ok := ret.Get(0).(func() store.PostPersistentNotificationStore); ok {
r0 = rf()
} else {
if ret.Get(0) != nil {
r0 = ret.Get(0).(store.PostPersistentNotificationStore)
}
}
return r0
}
// PostPriority provides a mock function with given fields:
func (_m *Store) PostPriority() store.PostPriorityStore {
ret := _m.Called()

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

@@ -0,0 +1,452 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
package storetest
import (
"testing"
"time"
"github.com/mattermost/mattermost-server/server/public/model"
"github.com/mattermost/mattermost-server/server/v8/channels/store"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestPostPersistentNotificationStore(t *testing.T, ss store.Store, s SqlStore) {
t.Run("Get", func(t *testing.T) { testPostPersistentNotificationStoreGet(t, ss) })
t.Run("Delete", func(t *testing.T) { testPostPersistentNotificationStoreDelete(t, ss) })
t.Run("UpdateLastSentAt", func(t *testing.T) { testPostPersistentNotificationStoreUpdateLastSentAt(t, ss) })
}
func testPostPersistentNotificationStoreGet(t *testing.T, ss store.Store) {
p1 := model.Post{}
p1.ChannelId = model.NewId()
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.CreateAt = 10
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p2 := model.Post{}
p2.ChannelId = p1.ChannelId
p2.UserId = model.NewId()
p2.Message = NewTestId()
p2.CreateAt = 20
p2.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(true),
},
}
// Invalid - Has no Priority
p3 := model.Post{}
p3.ChannelId = p1.ChannelId
p3.UserId = model.NewId()
p3.Message = NewTestId()
p3.CreateAt = 30
// Invalid - Notification is false
p4 := model.Post{}
p4.ChannelId = p1.ChannelId
p4.UserId = model.NewId()
p4.Message = NewTestId()
p4.CreateAt = 40
p4.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(false),
},
}
p5 := model.Post{}
p5.ChannelId = p1.ChannelId
p5.UserId = model.NewId()
p5.Message = NewTestId()
p5.CreateAt = 50
p5.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
defer ss.Post().PermanentDeleteByChannel(p1.ChannelId)
defer ss.PostPersistentNotification().Delete([]string{p1.Id, p2.Id, p3.Id, p4.Id, p5.Id})
t.Run("Get Single", func(t *testing.T) {
pn, err := ss.PostPersistentNotification().GetSingle(p1.Id)
require.NoError(t, err)
assert.Equal(t, p1.Id, pn.PostId)
pn, err = ss.PostPersistentNotification().GetSingle(p2.Id)
require.NoError(t, err)
assert.Equal(t, p2.Id, pn.PostId)
pn, err = ss.PostPersistentNotification().GetSingle(p5.Id)
require.NoError(t, err)
assert.Equal(t, p5.Id, pn.PostId)
pn, err = ss.PostPersistentNotification().GetSingle(p3.Id)
require.Error(t, err)
require.Zero(t, pn)
pn, err = ss.PostPersistentNotification().GetSingle(p4.Id)
require.Error(t, err)
require.Zero(t, pn)
})
t.Run("Get all before MaxTime", func(t *testing.T) {
validIDs := []string{p1.Id, p2.Id, p5.Id}
getIDs := func(posts []*model.PostPersistentNotifications) (ids []string) {
for _, p := range posts {
ids = append(ids, p.PostId)
}
return
}
// p5 is filtered by maxTime
pn, err := ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: 45,
MaxSentCount: 60,
PerPage: 20,
})
require.NoError(t, err)
require.Len(t, pn, 2)
assert.Contains(t, getIDs(pn), p1.Id)
assert.Contains(t, getIDs(pn), p2.Id)
// nothing is filtered out
pn, err = ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: 100,
MaxSentCount: 60,
PerPage: 20,
})
require.NoError(t, err)
require.Len(t, pn, 3)
assert.ElementsMatch(t, validIDs, getIDs(pn))
})
}
func testPostPersistentNotificationStoreUpdateLastSentAt(t *testing.T, ss store.Store) {
p1 := model.Post{}
p1.ChannelId = model.NewId()
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.CreateAt = 10
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
defer ss.Post().PermanentDeleteByChannel(p1.ChannelId)
defer ss.PostPersistentNotification().Delete([]string{p1.Id})
// Update from 0 value
now := model.GetTimeForMillis(model.GetMillis())
delta := 2 * time.Second
err = ss.PostPersistentNotification().UpdateLastActivity([]string{p1.Id})
require.NoError(t, err)
pn, err := ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: model.GetMillisForTime(now.Add(delta)),
MaxSentCount: 60,
})
require.NoError(t, err)
require.Len(t, pn, 1)
assert.WithinDuration(t, now, model.GetTimeForMillis(pn[0].LastSentAt), delta)
time.Sleep(time.Second)
// Update from non-zero value
now = model.GetTimeForMillis(model.GetMillis())
delta = 2 * time.Second
err = ss.PostPersistentNotification().UpdateLastActivity([]string{p1.Id})
require.NoError(t, err)
pn, err = ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: model.GetMillisForTime(now.Add(delta)),
MaxSentCount: 60,
})
require.NoError(t, err)
require.Len(t, pn, 1)
assert.WithinDuration(t, now, model.GetTimeForMillis(pn[0].LastSentAt), delta)
}
func testPostPersistentNotificationStoreDelete(t *testing.T, ss store.Store) {
t.Run("Delete", func(t *testing.T) {
p1 := model.Post{}
p1.ChannelId = model.NewId()
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.CreateAt = 10
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p2 := model.Post{}
p2.ChannelId = p1.ChannelId
p2.UserId = model.NewId()
p2.Message = NewTestId()
p2.CreateAt = 20
p2.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(true),
},
}
p3 := model.Post{}
p3.ChannelId = p1.ChannelId
p3.UserId = model.NewId()
p3.Message = NewTestId()
p3.CreateAt = 30
p3.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
defer ss.Post().PermanentDeleteByChannel(p1.ChannelId)
defer ss.PostPersistentNotification().Delete([]string{p1.Id, p2.Id, p3.Id})
err = ss.PostPersistentNotification().Delete([]string{p1.Id, p3.Id})
require.NoError(t, err)
pn, err := ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: 100,
MaxSentCount: 6,
PerPage: 20,
})
require.NoError(t, err)
require.Len(t, pn, 1)
assert.Equal(t, p2.Id, pn[0].PostId)
})
t.Run("Delete By Channel", func(t *testing.T) {
p1 := model.Post{}
p1.ChannelId = model.NewId()
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.CreateAt = 10
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p2 := model.Post{}
p2.ChannelId = p1.ChannelId
p2.UserId = model.NewId()
p2.Message = NewTestId()
p2.CreateAt = 20
p2.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(true),
},
}
p3 := model.Post{}
p3.ChannelId = p1.ChannelId
p3.UserId = model.NewId()
p3.Message = NewTestId()
p3.CreateAt = 30
p3.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p4 := model.Post{}
p4.ChannelId = model.NewId()
p4.UserId = model.NewId()
p4.Message = NewTestId()
p4.CreateAt = 40
p4.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p5 := model.Post{}
p5.ChannelId = p4.ChannelId
p5.UserId = model.NewId()
p5.Message = NewTestId()
p5.CreateAt = 50
p5.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
defer ss.Post().PermanentDeleteByChannel(p1.ChannelId)
defer ss.Post().PermanentDeleteByChannel(p4.ChannelId)
defer ss.PostPersistentNotification().Delete([]string{p1.Id, p2.Id, p3.Id, p4.Id, p5.Id})
err = ss.PostPersistentNotification().DeleteByChannel([]string{p1.ChannelId})
require.NoError(t, err)
pn, err := ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: 100,
MaxSentCount: 6,
PerPage: 20,
})
require.NoError(t, err)
require.Len(t, pn, 2)
assert.ElementsMatch(t, []string{p4.Id, p5.Id}, []string{pn[0].PostId, pn[1].PostId})
})
t.Run("Delete By Team", func(t *testing.T) {
t1 := &model.Team{DisplayName: "t1", Name: NewTestId(), Email: MakeEmail(), Type: model.TeamOpen}
_, err := ss.Team().Save(t1)
require.NoError(t, err)
t2 := &model.Team{DisplayName: "t2", Name: NewTestId(), Email: MakeEmail(), Type: model.TeamOpen}
_, err = ss.Team().Save(t2)
require.NoError(t, err)
c1 := &model.Channel{TeamId: t1.Id, Name: model.NewId(), DisplayName: "c1", Type: model.ChannelTypeOpen}
_, err = ss.Channel().Save(c1, -1)
require.NoError(t, err)
c2 := &model.Channel{TeamId: t1.Id, Name: model.NewId(), DisplayName: "c2", Type: model.ChannelTypeOpen}
_, err = ss.Channel().Save(c2, -1)
require.NoError(t, err)
c3 := &model.Channel{TeamId: t2.Id, Name: model.NewId(), DisplayName: "c1", Type: model.ChannelTypeOpen}
_, err = ss.Channel().Save(c3, -1)
require.NoError(t, err)
p1 := model.Post{}
p1.ChannelId = c1.Id
p1.UserId = model.NewId()
p1.Message = NewTestId()
p1.CreateAt = 10
p1.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p2 := model.Post{}
p2.ChannelId = p1.ChannelId
p2.UserId = model.NewId()
p2.Message = NewTestId()
p2.CreateAt = 20
p2.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(true),
PersistentNotifications: model.NewBool(true),
},
}
p3 := model.Post{}
p3.ChannelId = c2.Id
p3.UserId = model.NewId()
p3.Message = NewTestId()
p3.CreateAt = 30
p3.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString(model.PostPriorityUrgent),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p4 := model.Post{}
p4.ChannelId = c3.Id
p4.UserId = model.NewId()
p4.Message = NewTestId()
p4.CreateAt = 40
p4.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
p5 := model.Post{}
p5.ChannelId = p4.ChannelId
p5.UserId = model.NewId()
p5.Message = NewTestId()
p5.CreateAt = 50
p5.Metadata = &model.PostMetadata{
Priority: &model.PostPriority{
Priority: model.NewString("important"),
RequestedAck: model.NewBool(false),
PersistentNotifications: model.NewBool(true),
},
}
_, errIdx, err := ss.Post().SaveMultiple([]*model.Post{&p1, &p2, &p3, &p4, &p5})
require.NoError(t, err)
require.Equal(t, -1, errIdx)
defer ss.Post().PermanentDeleteByChannel(c1.Id)
defer ss.Post().PermanentDeleteByChannel(c2.Id)
defer ss.Post().PermanentDeleteByChannel(c3.Id)
defer ss.Channel().PermanentDeleteByTeam(t1.Id)
defer ss.Channel().PermanentDeleteByTeam(t2.Id)
defer ss.Team().PermanentDelete(t1.Id)
defer ss.Team().PermanentDelete(t2.Id)
defer ss.PostPersistentNotification().Delete([]string{p1.Id, p2.Id, p3.Id, p4.Id, p5.Id})
err = ss.PostPersistentNotification().DeleteByTeam([]string{t1.Id})
require.NoError(t, err)
pn, err := ss.PostPersistentNotification().Get(model.GetPersistentNotificationsPostsParams{
MaxTime: 100,
MaxSentCount: 6,
PerPage: 20,
})
require.NoError(t, err)
require.Len(t, pn, 2)
assert.ElementsMatch(t, []string{p4.Id, p5.Id}, []string{pn[0].PostId, pn[1].PostId})
})
}

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

@@ -17,49 +17,50 @@ import (
// Store can be used to provide mock stores for testing.
type Store struct {
TeamStore mocks.TeamStore
ChannelStore mocks.ChannelStore
PostStore mocks.PostStore
UserStore mocks.UserStore
RetentionPolicyStore mocks.RetentionPolicyStore
BotStore mocks.BotStore
AuditStore mocks.AuditStore
ClusterDiscoveryStore mocks.ClusterDiscoveryStore
RemoteClusterStore mocks.RemoteClusterStore
ComplianceStore mocks.ComplianceStore
SessionStore mocks.SessionStore
OAuthStore mocks.OAuthStore
SystemStore mocks.SystemStore
WebhookStore mocks.WebhookStore
CommandStore mocks.CommandStore
CommandWebhookStore mocks.CommandWebhookStore
PreferenceStore mocks.PreferenceStore
LicenseStore mocks.LicenseStore
TokenStore mocks.TokenStore
EmojiStore mocks.EmojiStore
ThreadStore mocks.ThreadStore
StatusStore mocks.StatusStore
FileInfoStore mocks.FileInfoStore
UploadSessionStore mocks.UploadSessionStore
ReactionStore mocks.ReactionStore
JobStore mocks.JobStore
UserAccessTokenStore mocks.UserAccessTokenStore
PluginStore mocks.PluginStore
ChannelMemberHistoryStore mocks.ChannelMemberHistoryStore
RoleStore mocks.RoleStore
SchemeStore mocks.SchemeStore
TermsOfServiceStore mocks.TermsOfServiceStore
GroupStore mocks.GroupStore
UserTermsOfServiceStore mocks.UserTermsOfServiceStore
LinkMetadataStore mocks.LinkMetadataStore
SharedChannelStore mocks.SharedChannelStore
ProductNoticesStore mocks.ProductNoticesStore
DraftStore mocks.DraftStore
context context.Context
NotifyAdminStore mocks.NotifyAdminStore
PostPriorityStore mocks.PostPriorityStore
PostAcknowledgementStore mocks.PostAcknowledgementStore
TrueUpReviewStore mocks.TrueUpReviewStore
TeamStore mocks.TeamStore
ChannelStore mocks.ChannelStore
PostStore mocks.PostStore
UserStore mocks.UserStore
RetentionPolicyStore mocks.RetentionPolicyStore
BotStore mocks.BotStore
AuditStore mocks.AuditStore
ClusterDiscoveryStore mocks.ClusterDiscoveryStore
RemoteClusterStore mocks.RemoteClusterStore
ComplianceStore mocks.ComplianceStore
SessionStore mocks.SessionStore
OAuthStore mocks.OAuthStore
SystemStore mocks.SystemStore
WebhookStore mocks.WebhookStore
CommandStore mocks.CommandStore
CommandWebhookStore mocks.CommandWebhookStore
PreferenceStore mocks.PreferenceStore
LicenseStore mocks.LicenseStore
TokenStore mocks.TokenStore
EmojiStore mocks.EmojiStore
ThreadStore mocks.ThreadStore
StatusStore mocks.StatusStore
FileInfoStore mocks.FileInfoStore
UploadSessionStore mocks.UploadSessionStore
ReactionStore mocks.ReactionStore
JobStore mocks.JobStore
UserAccessTokenStore mocks.UserAccessTokenStore
PluginStore mocks.PluginStore
ChannelMemberHistoryStore mocks.ChannelMemberHistoryStore
RoleStore mocks.RoleStore
SchemeStore mocks.SchemeStore
TermsOfServiceStore mocks.TermsOfServiceStore
GroupStore mocks.GroupStore
UserTermsOfServiceStore mocks.UserTermsOfServiceStore
LinkMetadataStore mocks.LinkMetadataStore
SharedChannelStore mocks.SharedChannelStore
ProductNoticesStore mocks.ProductNoticesStore
DraftStore mocks.DraftStore
context context.Context
NotifyAdminStore mocks.NotifyAdminStore
PostPriorityStore mocks.PostPriorityStore
PostAcknowledgementStore mocks.PostAcknowledgementStore
PostPersistentNotificationStore mocks.PostPersistentNotificationStore
TrueUpReviewStore mocks.TrueUpReviewStore
}
func (s *Store) SetContext(context context.Context) { s.context = context }
@@ -110,6 +111,9 @@ func (s *Store) PostPriority() store.PostPriorityStore { return &s.PostPriorit
func (s *Store) PostAcknowledgement() store.PostAcknowledgementStore {
return &s.PostAcknowledgementStore
}
func (s *Store) PostPersistentNotification() store.PostPersistentNotificationStore {
return &s.PostPersistentNotificationStore
}
func (s *Store) MarkSystemRanUnitTests() { /* do nothing */ }
func (s *Store) Close() { /* do nothing */ }
func (s *Store) LockToMaster() { /* do nothing */ }
@@ -171,5 +175,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
&s.NotifyAdminStore,
&s.PostPriorityStore,
&s.PostAcknowledgementStore,
&s.PostPersistentNotificationStore,
)
}

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

@@ -17,49 +17,50 @@ import (
type TimerLayer struct {
store.Store
Metrics einterfaces.MetricsInterface
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
Metrics einterfaces.MetricsInterface
AuditStore store.AuditStore
BotStore store.BotStore
ChannelStore store.ChannelStore
ChannelMemberHistoryStore store.ChannelMemberHistoryStore
ClusterDiscoveryStore store.ClusterDiscoveryStore
CommandStore store.CommandStore
CommandWebhookStore store.CommandWebhookStore
ComplianceStore store.ComplianceStore
DraftStore store.DraftStore
EmojiStore store.EmojiStore
FileInfoStore store.FileInfoStore
GroupStore store.GroupStore
JobStore store.JobStore
LicenseStore store.LicenseStore
LinkMetadataStore store.LinkMetadataStore
NotifyAdminStore store.NotifyAdminStore
OAuthStore store.OAuthStore
PluginStore store.PluginStore
PostStore store.PostStore
PostAcknowledgementStore store.PostAcknowledgementStore
PostPersistentNotificationStore store.PostPersistentNotificationStore
PostPriorityStore store.PostPriorityStore
PreferenceStore store.PreferenceStore
ProductNoticesStore store.ProductNoticesStore
ReactionStore store.ReactionStore
RemoteClusterStore store.RemoteClusterStore
RetentionPolicyStore store.RetentionPolicyStore
RoleStore store.RoleStore
SchemeStore store.SchemeStore
SessionStore store.SessionStore
SharedChannelStore store.SharedChannelStore
StatusStore store.StatusStore
SystemStore store.SystemStore
TeamStore store.TeamStore
TermsOfServiceStore store.TermsOfServiceStore
ThreadStore store.ThreadStore
TokenStore store.TokenStore
TrueUpReviewStore store.TrueUpReviewStore
UploadSessionStore store.UploadSessionStore
UserStore store.UserStore
UserAccessTokenStore store.UserAccessTokenStore
UserTermsOfServiceStore store.UserTermsOfServiceStore
WebhookStore store.WebhookStore
}
func (s *TimerLayer) Audit() store.AuditStore {
@@ -142,6 +143,10 @@ func (s *TimerLayer) PostAcknowledgement() store.PostAcknowledgementStore {
return s.PostAcknowledgementStore
}
func (s *TimerLayer) PostPersistentNotification() store.PostPersistentNotificationStore {
return s.PostPersistentNotificationStore
}
func (s *TimerLayer) PostPriority() store.PostPriorityStore {
return s.PostPriorityStore
}
@@ -330,6 +335,11 @@ type TimerLayerPostAcknowledgementStore struct {
Root *TimerLayer
}
type TimerLayerPostPersistentNotificationStore struct {
store.PostPersistentNotificationStore
Root *TimerLayer
}
type TimerLayerPostPriorityStore struct {
store.PostPriorityStore
Root *TimerLayer
@@ -6186,6 +6196,118 @@ func (s *TimerLayerPostAcknowledgementStore) Save(postID string, userID string,
return result, err
}
func (s *TimerLayerPostPersistentNotificationStore) Delete(postIds []string) error {
start := time.Now()
err := s.PostPersistentNotificationStore.Delete(postIds)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.Delete", success, elapsed)
}
return err
}
func (s *TimerLayerPostPersistentNotificationStore) DeleteByChannel(channelIds []string) error {
start := time.Now()
err := s.PostPersistentNotificationStore.DeleteByChannel(channelIds)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.DeleteByChannel", success, elapsed)
}
return err
}
func (s *TimerLayerPostPersistentNotificationStore) DeleteByTeam(teamIds []string) error {
start := time.Now()
err := s.PostPersistentNotificationStore.DeleteByTeam(teamIds)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.DeleteByTeam", success, elapsed)
}
return err
}
func (s *TimerLayerPostPersistentNotificationStore) DeleteExpired(maxSentCount int16) error {
start := time.Now()
err := s.PostPersistentNotificationStore.DeleteExpired(maxSentCount)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.DeleteExpired", success, elapsed)
}
return err
}
func (s *TimerLayerPostPersistentNotificationStore) Get(params model.GetPersistentNotificationsPostsParams) ([]*model.PostPersistentNotifications, error) {
start := time.Now()
result, err := s.PostPersistentNotificationStore.Get(params)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.Get", success, elapsed)
}
return result, err
}
func (s *TimerLayerPostPersistentNotificationStore) GetSingle(postID string) (*model.PostPersistentNotifications, error) {
start := time.Now()
result, err := s.PostPersistentNotificationStore.GetSingle(postID)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.GetSingle", success, elapsed)
}
return result, err
}
func (s *TimerLayerPostPersistentNotificationStore) UpdateLastActivity(postIds []string) error {
start := time.Now()
err := s.PostPersistentNotificationStore.UpdateLastActivity(postIds)
elapsed := float64(time.Since(start)) / float64(time.Second)
if s.Root.Metrics != nil {
success := "false"
if err == nil {
success = "true"
}
s.Root.Metrics.ObserveStoreMethodDuration("PostPersistentNotificationStore.UpdateLastActivity", success, elapsed)
}
return err
}
func (s *TimerLayerPostPriorityStore) GetForPost(postId string) (*model.PostPriority, error) {
start := time.Now()
@@ -11663,6 +11785,7 @@ func New(childStore store.Store, metrics einterfaces.MetricsInterface) *TimerLay
newStore.PluginStore = &TimerLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
newStore.PostStore = &TimerLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
newStore.PostAcknowledgementStore = &TimerLayerPostAcknowledgementStore{PostAcknowledgementStore: childStore.PostAcknowledgement(), Root: &newStore}
newStore.PostPersistentNotificationStore = &TimerLayerPostPersistentNotificationStore{PostPersistentNotificationStore: childStore.PostPersistentNotification(), Root: &newStore}
newStore.PostPriorityStore = &TimerLayerPostPriorityStore{PostPriorityStore: childStore.PostPriority(), Root: &newStore}
newStore.PreferenceStore = &TimerLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}