MM-27918 In-Product notices support (#15316)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
43ed6ad690
Коммит
4e9ddd4686
@@ -36,6 +36,7 @@ type OpenTracingLayer struct {
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
RoleStore store.RoleStore
|
||||
SchemeStore store.SchemeStore
|
||||
@@ -124,6 +125,10 @@ func (s *OpenTracingLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) ProductNotices() store.ProductNoticesStore {
|
||||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
@@ -270,6 +275,11 @@ type OpenTracingLayerPreferenceStore struct {
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerProductNoticesStore struct {
|
||||
store.ProductNoticesStore
|
||||
Root *OpenTracingLayer
|
||||
}
|
||||
|
||||
type OpenTracingLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *OpenTracingLayer
|
||||
@@ -5537,6 +5547,78 @@ func (s *OpenTracingLayerPreferenceStore) Save(preferences *model.Preferences) e
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerProductNoticesStore) Clear(notices []string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ProductNoticesStore.Clear")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ProductNoticesStore.Clear(notices)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerProductNoticesStore) ClearOldNotices(currentNotices *model.ProductNotices) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ProductNoticesStore.ClearOldNotices")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ProductNoticesStore.ClearOldNotices(currentNotices)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerProductNoticesStore) GetViews(userId string) ([]model.ProductNoticeViewState, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ProductNoticesStore.GetViews")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
result, err := s.ProductNoticesStore.GetViews(userId)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerProductNoticesStore) View(userId string, notices []string) error {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ProductNoticesStore.View")
|
||||
s.Root.Store.SetContext(newCtx)
|
||||
defer func() {
|
||||
s.Root.Store.SetContext(origCtx)
|
||||
}()
|
||||
|
||||
defer span.Finish()
|
||||
err := s.ProductNoticesStore.View(userId, notices)
|
||||
if err != nil {
|
||||
span.LogFields(spanlog.Error(err))
|
||||
ext.Error.Set(span, true)
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *OpenTracingLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
origCtx := s.Root.Store.Context()
|
||||
span, newCtx := tracing.StartSpanWithParentByContext(s.Root.Store.Context(), "ReactionStore.BulkGetForPosts")
|
||||
@@ -9585,6 +9667,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.PreferenceStore = &OpenTracingLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &OpenTracingLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &OpenTracingLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RoleStore = &OpenTracingLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore}
|
||||
newStore.SchemeStore = &OpenTracingLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore}
|
||||
|
||||
@@ -38,6 +38,7 @@ type RetryLayer struct {
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
RoleStore store.RoleStore
|
||||
SchemeStore store.SchemeStore
|
||||
@@ -126,6 +127,10 @@ func (s *RetryLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) ProductNotices() store.ProductNoticesStore {
|
||||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *RetryLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
@@ -272,6 +277,11 @@ type RetryLayerPreferenceStore struct {
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerProductNoticesStore struct {
|
||||
store.ProductNoticesStore
|
||||
Root *RetryLayer
|
||||
}
|
||||
|
||||
type RetryLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *RetryLayer
|
||||
@@ -4706,6 +4716,86 @@ func (s *RetryLayerPreferenceStore) Save(preferences *model.Preferences) error {
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerProductNoticesStore) Clear(notices []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ProductNoticesStore.Clear(notices)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerProductNoticesStore) ClearOldNotices(currentNotices *model.ProductNotices) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ProductNoticesStore.ClearOldNotices(currentNotices)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerProductNoticesStore) GetViews(userId string) ([]model.ProductNoticeViewState, error) {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
result, err := s.ProductNoticesStore.GetViews(userId)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerProductNoticesStore) View(userId string, notices []string) error {
|
||||
|
||||
tries := 0
|
||||
for {
|
||||
err := s.ProductNoticesStore.View(userId, notices)
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (s *RetryLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
|
||||
tries := 0
|
||||
@@ -8261,6 +8351,7 @@ func New(childStore store.Store) *RetryLayer {
|
||||
newStore.PluginStore = &RetryLayerPluginStore{PluginStore: childStore.Plugin(), Root: &newStore}
|
||||
newStore.PostStore = &RetryLayerPostStore{PostStore: childStore.Post(), Root: &newStore}
|
||||
newStore.PreferenceStore = &RetryLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &RetryLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &RetryLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RoleStore = &RetryLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore}
|
||||
newStore.SchemeStore = &RetryLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore}
|
||||
|
||||
@@ -34,6 +34,7 @@ func genStore() *mocks.Store {
|
||||
mock.On("Plugin").Return(&mocks.PluginStore{})
|
||||
mock.On("Post").Return(&mocks.PostStore{})
|
||||
mock.On("Preference").Return(&mocks.PreferenceStore{})
|
||||
mock.On("ProductNotices").Return(&mocks.ProductNoticesStore{})
|
||||
mock.On("Reaction").Return(&mocks.ReactionStore{})
|
||||
mock.On("Role").Return(&mocks.RoleStore{})
|
||||
mock.On("Scheme").Return(&mocks.SchemeStore{})
|
||||
|
||||
125
store/sqlstore/product_notices_store.go
Обычный файл
125
store/sqlstore/product_notices_store.go
Обычный файл
@@ -0,0 +1,125 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
sq "github.com/Masterminds/squirrel"
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"time"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type SqlProductNoticesStore struct {
|
||||
SqlStore
|
||||
}
|
||||
|
||||
func newSqlProductNoticesStore(sqlStore SqlStore) store.ProductNoticesStore {
|
||||
s := SqlProductNoticesStore{sqlStore}
|
||||
|
||||
for _, db := range sqlStore.GetAllConns() {
|
||||
table := db.AddTableWithName(model.ProductNoticeViewState{}, "ProductNoticeViewState").SetKeys(false, "UserId", "NoticeId")
|
||||
table.ColMap("UserId").SetMaxSize(26)
|
||||
table.ColMap("NoticeId").SetMaxSize(26)
|
||||
}
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func (s SqlProductNoticesStore) createIndexesIfNotExists() {
|
||||
s.CreateIndexIfNotExists("idx_notice_views_timestamp", "ProductNoticeViewState", "Timestamp")
|
||||
s.CreateIndexIfNotExists("idx_notice_views_user_id", "ProductNoticeViewState", "UserId")
|
||||
s.CreateIndexIfNotExists("idx_notice_views_notice_id", "ProductNoticeViewState", "NoticeId")
|
||||
|
||||
s.CreateCompositeIndexIfNotExists("idx_notice_views_user_notice", "ProductNoticeViewState", []string{"UserId", "NoticeId"})
|
||||
|
||||
}
|
||||
|
||||
func (s SqlProductNoticesStore) Clear(notices []string) error {
|
||||
sql, args, _ := s.getQueryBuilder().Delete("ProductNoticeViewState").Where(sq.Eq{"NoticeId": notices}).ToSql()
|
||||
if _, err := s.GetMaster().Exec(sql, args...); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete records from ProductNoticeViewState")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SqlProductNoticesStore) ClearOldNotices(currentNotices *model.ProductNotices) error {
|
||||
var notices []string
|
||||
for _, currentNotice := range *currentNotices {
|
||||
notices = append(notices, currentNotice.ID)
|
||||
}
|
||||
sql, args, _ := s.getQueryBuilder().Delete("ProductNoticeViewState").Where(sq.NotEq{"NoticeId": notices}).ToSql()
|
||||
if _, err := s.GetMaster().Exec(sql, args...); err != nil {
|
||||
return errors.Wrapf(err, "failed to delete records from ProductNoticeViewState")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SqlProductNoticesStore) View(userId string, notices []string) error {
|
||||
transaction, err := s.GetMaster().Begin()
|
||||
if err != nil {
|
||||
return errors.Wrap(err, "begin_transaction")
|
||||
}
|
||||
defer finalizeTransaction(transaction)
|
||||
|
||||
var noticeStates []model.ProductNoticeViewState
|
||||
sql, args, _ := s.getQueryBuilder().
|
||||
Select("*").
|
||||
From("ProductNoticeViewState").
|
||||
Where(sq.And{sq.Eq{"UserId": userId}, sq.Eq{"NoticeId": notices}}).
|
||||
ToSql()
|
||||
if _, err := transaction.Select(¬iceStates, sql, args...); err != nil {
|
||||
return errors.Wrapf(err, "failed to get ProductNoticeViewState with userId=%s", userId)
|
||||
}
|
||||
|
||||
now := time.Now().UTC().Unix()
|
||||
|
||||
// update existing records
|
||||
for i := range noticeStates {
|
||||
noticeStates[i].Viewed += 1
|
||||
noticeStates[i].Timestamp = now
|
||||
if _, err := transaction.Update(¬iceStates[i]); err != nil {
|
||||
return errors.Wrapf(err, "failed to update ProductNoticeViewState")
|
||||
}
|
||||
}
|
||||
|
||||
// add new ones
|
||||
haveNoticeState := func(n string) bool {
|
||||
for _, ns := range noticeStates {
|
||||
if ns.NoticeId == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
for _, noticeId := range notices {
|
||||
if !haveNoticeState(noticeId) {
|
||||
if err := transaction.Insert(&model.ProductNoticeViewState{
|
||||
UserId: userId,
|
||||
NoticeId: noticeId,
|
||||
Viewed: 1,
|
||||
Timestamp: now,
|
||||
}); err != nil {
|
||||
return errors.Wrapf(err, "failed to insert ProductNoticeViewState")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := transaction.Commit(); err != nil {
|
||||
return errors.Wrap(err, "commit_transaction")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s SqlProductNoticesStore) GetViews(userId string) ([]model.ProductNoticeViewState, error) {
|
||||
var noticeStates []model.ProductNoticeViewState
|
||||
sql, args, _ := s.getQueryBuilder().Select("*").From("ProductNoticeViewState").Where(sq.Eq{"UserId": userId}).ToSql()
|
||||
if _, err := s.GetReplica().Select(¬iceStates, sql, args...); err != nil {
|
||||
return nil, errors.Wrapf(err, "failed to get ProductNoticeViewState with userId=%s", userId)
|
||||
}
|
||||
return noticeStates, nil
|
||||
}
|
||||
14
store/sqlstore/product_notices_store_test.go
Обычный файл
14
store/sqlstore/product_notices_store_test.go
Обычный файл
@@ -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/v5/store/storetest"
|
||||
)
|
||||
|
||||
func TestProductNoticesStore(t *testing.T) {
|
||||
StoreTest(t, storetest.TestProductNoticesStore)
|
||||
}
|
||||
@@ -98,6 +98,7 @@ type SqlSupplierStores struct {
|
||||
role store.RoleStore
|
||||
scheme store.SchemeStore
|
||||
TermsOfService store.TermsOfServiceStore
|
||||
productNotices store.ProductNoticesStore
|
||||
group store.GroupStore
|
||||
UserTermsOfService store.UserTermsOfServiceStore
|
||||
linkMetadata store.LinkMetadataStore
|
||||
@@ -170,7 +171,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
|
||||
supplier.stores.role = newSqlRoleStore(supplier)
|
||||
supplier.stores.scheme = newSqlSchemeStore(supplier)
|
||||
supplier.stores.group = newSqlGroupStore(supplier)
|
||||
|
||||
supplier.stores.productNotices = newSqlProductNoticesStore(supplier)
|
||||
err := supplier.GetMaster().CreateTablesIfNotExists()
|
||||
if err != nil {
|
||||
mlog.Critical("Error creating database tables.", mlog.Err(err))
|
||||
@@ -209,6 +210,7 @@ func NewSqlSupplier(settings model.SqlSettings, metrics einterfaces.MetricsInter
|
||||
supplier.stores.userAccessToken.(*SqlUserAccessTokenStore).createIndexesIfNotExists()
|
||||
supplier.stores.plugin.(*SqlPluginStore).createIndexesIfNotExists()
|
||||
supplier.stores.TermsOfService.(SqlTermsOfServiceStore).createIndexesIfNotExists()
|
||||
supplier.stores.productNotices.(SqlProductNoticesStore).createIndexesIfNotExists()
|
||||
supplier.stores.UserTermsOfService.(SqlUserTermsOfServiceStore).createIndexesIfNotExists()
|
||||
supplier.stores.linkMetadata.(*SqlLinkMetadataStore).createIndexesIfNotExists()
|
||||
supplier.stores.group.(*SqlGroupStore).createIndexesIfNotExists()
|
||||
@@ -1169,6 +1171,10 @@ func (ss *SqlSupplier) TermsOfService() store.TermsOfServiceStore {
|
||||
return ss.stores.TermsOfService
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) ProductNotices() store.ProductNoticesStore {
|
||||
return ss.stores.productNotices
|
||||
}
|
||||
|
||||
func (ss *SqlSupplier) UserTermsOfService() store.UserTermsOfServiceStore {
|
||||
return ss.stores.UserTermsOfService
|
||||
}
|
||||
|
||||
@@ -50,6 +50,7 @@ type Store interface {
|
||||
ChannelMemberHistory() ChannelMemberHistoryStore
|
||||
Plugin() PluginStore
|
||||
TermsOfService() TermsOfServiceStore
|
||||
ProductNotices() ProductNoticesStore
|
||||
Group() GroupStore
|
||||
UserTermsOfService() UserTermsOfServiceStore
|
||||
LinkMetadata() LinkMetadataStore
|
||||
@@ -646,6 +647,13 @@ type TermsOfServiceStore interface {
|
||||
Get(id string, allowFromCache bool) (*model.TermsOfService, error)
|
||||
}
|
||||
|
||||
type ProductNoticesStore interface {
|
||||
View(userId string, notices []string) error
|
||||
Clear(notices []string) error
|
||||
ClearOldNotices(currentNotices *model.ProductNotices) error
|
||||
GetViews(userId string) ([]model.ProductNoticeViewState, error)
|
||||
}
|
||||
|
||||
type UserTermsOfServiceStore interface {
|
||||
GetByUser(userId string) (*model.UserTermsOfService, error)
|
||||
Save(userTermsOfService *model.UserTermsOfService) (*model.UserTermsOfService, error)
|
||||
|
||||
80
store/storetest/mocks/ProductNoticesStore.go
Обычный файл
80
store/storetest/mocks/ProductNoticesStore.go
Обычный файл
@@ -0,0 +1,80 @@
|
||||
// Code generated by mockery v1.0.0. DO NOT EDIT.
|
||||
|
||||
// Regenerate this file using `make store-mocks`.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
model "github.com/mattermost/mattermost-server/v5/model"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// ProductNoticesStore is an autogenerated mock type for the ProductNoticesStore type
|
||||
type ProductNoticesStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// Clear provides a mock function with given fields: notices
|
||||
func (_m *ProductNoticesStore) Clear(notices []string) error {
|
||||
ret := _m.Called(notices)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func([]string) error); ok {
|
||||
r0 = rf(notices)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// ClearOldNotices provides a mock function with given fields: currentNotices
|
||||
func (_m *ProductNoticesStore) ClearOldNotices(currentNotices *model.ProductNotices) error {
|
||||
ret := _m.Called(currentNotices)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(*model.ProductNotices) error); ok {
|
||||
r0 = rf(currentNotices)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetViews provides a mock function with given fields: userId
|
||||
func (_m *ProductNoticesStore) GetViews(userId string) ([]model.ProductNoticeViewState, error) {
|
||||
ret := _m.Called(userId)
|
||||
|
||||
var r0 []model.ProductNoticeViewState
|
||||
if rf, ok := ret.Get(0).(func(string) []model.ProductNoticeViewState); ok {
|
||||
r0 = rf(userId)
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).([]model.ProductNoticeViewState)
|
||||
}
|
||||
}
|
||||
|
||||
var r1 error
|
||||
if rf, ok := ret.Get(1).(func(string) error); ok {
|
||||
r1 = rf(userId)
|
||||
} else {
|
||||
r1 = ret.Error(1)
|
||||
}
|
||||
|
||||
return r0, r1
|
||||
}
|
||||
|
||||
// View provides a mock function with given fields: userId, notices
|
||||
func (_m *ProductNoticesStore) View(userId string, notices []string) error {
|
||||
ret := _m.Called(userId, notices)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(string, []string) error); ok {
|
||||
r0 = rf(userId, notices)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
@@ -395,6 +395,22 @@ func (_m *Store) Preference() store.PreferenceStore {
|
||||
return r0
|
||||
}
|
||||
|
||||
// ProductNotices provides a mock function with given fields:
|
||||
func (_m *Store) ProductNotices() store.ProductNoticesStore {
|
||||
ret := _m.Called()
|
||||
|
||||
var r0 store.ProductNoticesStore
|
||||
if rf, ok := ret.Get(0).(func() store.ProductNoticesStore); ok {
|
||||
r0 = rf()
|
||||
} else {
|
||||
if ret.Get(0) != nil {
|
||||
r0 = ret.Get(0).(store.ProductNoticesStore)
|
||||
}
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// Reaction provides a mock function with given fields:
|
||||
func (_m *Store) Reaction() store.ReactionStore {
|
||||
ret := _m.Called()
|
||||
|
||||
79
store/storetest/product_notices_store.go
Обычный файл
79
store/storetest/product_notices_store.go
Обычный файл
@@ -0,0 +1,79 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package storetest
|
||||
|
||||
import (
|
||||
"github.com/mattermost/mattermost-server/v5/model"
|
||||
"testing"
|
||||
|
||||
"github.com/mattermost/mattermost-server/v5/store"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestProductNoticesStore(t *testing.T, ss store.Store) {
|
||||
t.Run("TestAddViewed", func(t *testing.T) { testAddViewed(t, ss) })
|
||||
t.Run("TestUpdateViewed", func(t *testing.T) { testUpdateViewed(t, ss) })
|
||||
t.Run("TestClearOld", func(t *testing.T) { testClearOld(t, ss) })
|
||||
}
|
||||
|
||||
func testAddViewed(t *testing.T, ss store.Store) {
|
||||
notices := []string{"noticeA", "noticeB"}
|
||||
defer ss.ProductNotices().Clear(notices)
|
||||
|
||||
err := ss.ProductNotices().View("testuser", notices)
|
||||
require.NoError(t, err)
|
||||
err = ss.ProductNotices().View("testuser2", notices)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := ss.ProductNotices().GetViews("testuser")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 2)
|
||||
}
|
||||
|
||||
func testUpdateViewed(t *testing.T, ss store.Store) {
|
||||
noticesA := []string{"noticeA", "noticeB"}
|
||||
noticesB := []string{"noticeB", "noticeC"}
|
||||
defer ss.ProductNotices().Clear(noticesA)
|
||||
defer ss.ProductNotices().Clear(noticesB)
|
||||
// mark two notices
|
||||
err := ss.ProductNotices().View("testuser", noticesA)
|
||||
require.NoError(t, err)
|
||||
// mark one old and one new
|
||||
err = ss.ProductNotices().View("testuser", noticesB)
|
||||
require.NoError(t, err)
|
||||
|
||||
res, err := ss.ProductNotices().GetViews("testuser")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 3)
|
||||
|
||||
// make sure that one B has two views
|
||||
require.Equal(t, res[0].Viewed, int32(1))
|
||||
require.Equal(t, res[1].Viewed, int32(2))
|
||||
require.Equal(t, res[2].Viewed, int32(1))
|
||||
|
||||
// make sure that B's timestamp was updated
|
||||
require.GreaterOrEqual(t, res[1].Timestamp, res[0].Timestamp)
|
||||
}
|
||||
|
||||
func testClearOld(t *testing.T, ss store.Store) {
|
||||
noticesA := []string{"noticeA", "noticeB"}
|
||||
defer ss.ProductNotices().Clear(noticesA)
|
||||
// mark two notices
|
||||
err := ss.ProductNotices().View("testuser", noticesA)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = ss.ProductNotices().ClearOldNotices(&model.ProductNotices{
|
||||
{
|
||||
ID: "noticeA",
|
||||
},
|
||||
{
|
||||
ID: "noticeC",
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
res, err := ss.ProductNotices().GetViews("testuser")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, res, 1)
|
||||
|
||||
}
|
||||
@@ -47,6 +47,7 @@ type Store struct {
|
||||
GroupStore mocks.GroupStore
|
||||
UserTermsOfServiceStore mocks.UserTermsOfServiceStore
|
||||
LinkMetadataStore mocks.LinkMetadataStore
|
||||
ProductNoticesStore mocks.ProductNoticesStore
|
||||
context context.Context
|
||||
}
|
||||
|
||||
@@ -57,6 +58,7 @@ func (s *Store) Channel() store.ChannelStore { return &s.C
|
||||
func (s *Store) Post() store.PostStore { return &s.PostStore }
|
||||
func (s *Store) User() store.UserStore { return &s.UserStore }
|
||||
func (s *Store) Bot() store.BotStore { return &s.BotStore }
|
||||
func (s *Store) ProductNotices() store.ProductNoticesStore { return &s.ProductNoticesStore }
|
||||
func (s *Store) Audit() store.AuditStore { return &s.AuditStore }
|
||||
func (s *Store) ClusterDiscovery() store.ClusterDiscoveryStore { return &s.ClusterDiscoveryStore }
|
||||
func (s *Store) Compliance() store.ComplianceStore { return &s.ComplianceStore }
|
||||
@@ -131,5 +133,6 @@ func (s *Store) AssertExpectations(t mock.TestingT) bool {
|
||||
&s.PluginStore,
|
||||
&s.RoleStore,
|
||||
&s.SchemeStore,
|
||||
&s.ProductNoticesStore,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -36,6 +36,7 @@ type TimerLayer struct {
|
||||
PluginStore store.PluginStore
|
||||
PostStore store.PostStore
|
||||
PreferenceStore store.PreferenceStore
|
||||
ProductNoticesStore store.ProductNoticesStore
|
||||
ReactionStore store.ReactionStore
|
||||
RoleStore store.RoleStore
|
||||
SchemeStore store.SchemeStore
|
||||
@@ -124,6 +125,10 @@ func (s *TimerLayer) Preference() store.PreferenceStore {
|
||||
return s.PreferenceStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) ProductNotices() store.ProductNoticesStore {
|
||||
return s.ProductNoticesStore
|
||||
}
|
||||
|
||||
func (s *TimerLayer) Reaction() store.ReactionStore {
|
||||
return s.ReactionStore
|
||||
}
|
||||
@@ -270,6 +275,11 @@ type TimerLayerPreferenceStore struct {
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerProductNoticesStore struct {
|
||||
store.ProductNoticesStore
|
||||
Root *TimerLayer
|
||||
}
|
||||
|
||||
type TimerLayerReactionStore struct {
|
||||
store.ReactionStore
|
||||
Root *TimerLayer
|
||||
@@ -5015,6 +5025,70 @@ func (s *TimerLayerPreferenceStore) Save(preferences *model.Preferences) error {
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerProductNoticesStore) Clear(notices []string) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ProductNoticesStore.Clear(notices)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ProductNoticesStore.Clear", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerProductNoticesStore) ClearOldNotices(currentNotices *model.ProductNotices) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ProductNoticesStore.ClearOldNotices(currentNotices)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ProductNoticesStore.ClearOldNotices", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerProductNoticesStore) GetViews(userId string) ([]model.ProductNoticeViewState, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
result, err := s.ProductNoticesStore.GetViews(userId)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ProductNoticesStore.GetViews", success, elapsed)
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (s *TimerLayerProductNoticesStore) View(userId string, notices []string) error {
|
||||
start := timemodule.Now()
|
||||
|
||||
err := s.ProductNoticesStore.View(userId, notices)
|
||||
|
||||
elapsed := float64(timemodule.Since(start)) / float64(timemodule.Second)
|
||||
if s.Root.Metrics != nil {
|
||||
success := "false"
|
||||
if err == nil {
|
||||
success = "true"
|
||||
}
|
||||
s.Root.Metrics.ObserveStoreMethodDuration("ProductNoticesStore.View", success, elapsed)
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *TimerLayerReactionStore) BulkGetForPosts(postIds []string) ([]*model.Reaction, error) {
|
||||
start := timemodule.Now()
|
||||
|
||||
@@ -8669,6 +8743,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.PreferenceStore = &TimerLayerPreferenceStore{PreferenceStore: childStore.Preference(), Root: &newStore}
|
||||
newStore.ProductNoticesStore = &TimerLayerProductNoticesStore{ProductNoticesStore: childStore.ProductNotices(), Root: &newStore}
|
||||
newStore.ReactionStore = &TimerLayerReactionStore{ReactionStore: childStore.Reaction(), Root: &newStore}
|
||||
newStore.RoleStore = &TimerLayerRoleStore{RoleStore: childStore.Role(), Root: &newStore}
|
||||
newStore.SchemeStore = &TimerLayerSchemeStore{SchemeStore: childStore.Scheme(), Root: &newStore}
|
||||
|
||||
Ссылка в новой задаче
Block a user