mirror of
https://github.com/opencloud-eu/opencloud.git
synced 2026-01-07 21:00:30 -06:00
Add filtering of in-app notifications based on user settings
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
Enhancement: Part II: Filtering of in-app notifications
|
||||
|
||||
Part II: In-app notifications are now filtered based on the notification preferences in the user settings
|
||||
|
||||
https://github.com/owncloud/ocis/pull/10779
|
||||
https://github.com/owncloud/ocis/issues/10769
|
||||
102
services/userlog/pkg/service/filter.go
Normal file
102
services/userlog/pkg/service/filter.go
Normal file
@@ -0,0 +1,102 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/middleware"
|
||||
settingssvc "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/owncloud/ocis/v2/services/settings/pkg/store/defaults"
|
||||
micrometadata "go-micro.dev/v4/metadata"
|
||||
)
|
||||
|
||||
type userlogFilter struct {
|
||||
log log.Logger
|
||||
valueClient settingssvc.ValueService
|
||||
}
|
||||
|
||||
func newUserlogFilter(l log.Logger, vc settingssvc.ValueService) *userlogFilter {
|
||||
return &userlogFilter{log: l, valueClient: vc}
|
||||
}
|
||||
|
||||
// execute removes users who should not receive an in-app notifications for the event
|
||||
func (ulf userlogFilter) execute(ctx context.Context, event events.Event, executant *user.UserId, users []string) []string {
|
||||
filteredUsers := ulf.filterExecutant(users, executant)
|
||||
return ulf.filterUsersBySettings(ctx, filteredUsers, event)
|
||||
}
|
||||
|
||||
// filterExecutant removes the executant
|
||||
func (ulf userlogFilter) filterExecutant(users []string, executant *user.UserId) []string {
|
||||
var filteredUsers []string
|
||||
for _, u := range users {
|
||||
if u != executant.GetOpaqueId() {
|
||||
filteredUsers = append(filteredUsers, u)
|
||||
}
|
||||
}
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
// filterUsersBySettings removes users who have disabled in-app notifications for the event
|
||||
func (ulf userlogFilter) filterUsersBySettings(ctx context.Context, users []string, event events.Event) []string {
|
||||
var filteredUsers []string
|
||||
var settingId string
|
||||
// map type to settings key
|
||||
switch event.Event.(type) {
|
||||
case events.ShareCreated:
|
||||
settingId = defaults.SettingUUIDProfileEventShareCreated
|
||||
case events.ShareRemoved:
|
||||
settingId = defaults.SettingUUIDProfileEventShareRemoved
|
||||
case events.ShareExpired:
|
||||
settingId = defaults.SettingUUIDProfileEventShareExpired
|
||||
case events.SpaceShared:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceShared
|
||||
case events.SpaceUnshared:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceUnshared
|
||||
case events.SpaceMembershipExpired:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceMembershipExpired
|
||||
case events.SpaceDisabled:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceDisabled
|
||||
case events.SpaceDeleted:
|
||||
settingId = defaults.SettingUUIDProfileEventSpaceDeleted
|
||||
default:
|
||||
// event that cannot be disabled
|
||||
return users
|
||||
}
|
||||
|
||||
for _, u := range users {
|
||||
enabled, err := getSetting(ctx, ulf.valueClient, u, settingId)
|
||||
if err != nil {
|
||||
ulf.log.Error().Err(err).Str("userId", u).Str("settingId", settingId).Msg("cannot get user event setting")
|
||||
continue
|
||||
}
|
||||
if enabled {
|
||||
filteredUsers = append(filteredUsers, u)
|
||||
}
|
||||
}
|
||||
|
||||
return filteredUsers
|
||||
}
|
||||
|
||||
func getSetting(ctx context.Context, vc settingssvc.ValueService, userId string, settingId string) (bool, error) {
|
||||
resp, err := vc.GetValueByUniqueIdentifiers(
|
||||
micrometadata.Set(ctx, middleware.AccountID, userId),
|
||||
&settingssvc.GetValueByUniqueIdentifiersRequest{
|
||||
AccountUuid: userId,
|
||||
SettingId: settingId,
|
||||
},
|
||||
)
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
val := resp.GetValue().GetValue().GetCollectionValue().GetValues()
|
||||
for _, option := range val {
|
||||
if option.GetKey() == "in-app" {
|
||||
return option.GetBoolValue(), nil
|
||||
}
|
||||
}
|
||||
return false, errors.New("no setting found")
|
||||
}
|
||||
97
services/userlog/pkg/service/filter_test.go
Normal file
97
services/userlog/pkg/service/filter_test.go
Normal file
@@ -0,0 +1,97 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
user "github.com/cs3org/go-cs3apis/cs3/identity/user/v1beta1"
|
||||
"github.com/cs3org/reva/v2/pkg/events"
|
||||
"github.com/owncloud/ocis/v2/ocis-pkg/log"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
settings "github.com/owncloud/ocis/v2/protogen/gen/ocis/services/settings/v0"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"go-micro.dev/v4/client"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testLogger = log.NewLogger()
|
||||
|
||||
func TestUserlogFilter_execute(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
event events.Event
|
||||
executant *user.UserId
|
||||
users []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
vc settings.ValueService
|
||||
args args
|
||||
want []string
|
||||
}{
|
||||
{"executant", settings.MockValueService{}, args{executant: &user.UserId{OpaqueId: "executant"}, users: []string{"foo", "executant"}}, []string{"foo"}},
|
||||
{"no connection to ValueService", settings.MockValueService{
|
||||
GetValueByUniqueIdentifiersFunc: func(ctx context.Context, req *settings.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settings.GetValueResponse, error) {
|
||||
return nil, errors.New("no connection to ValueService")
|
||||
},
|
||||
}, args{users: []string{"foo"}, event: events.Event{Event: events.ShareCreated{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"no setting in ValueService response", settings.MockValueService{
|
||||
GetValueByUniqueIdentifiersFunc: func(ctx context.Context, req *settings.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settings.GetValueResponse, error) {
|
||||
return &settings.GetValueResponse{}, nil
|
||||
},
|
||||
}, args{users: []string{"foo"}, event: events.Event{Event: events.ShareCreated{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"ValueService nil response", settings.MockValueService{
|
||||
GetValueByUniqueIdentifiersFunc: func(ctx context.Context, req *settings.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settings.GetValueResponse, error) {
|
||||
return nil, nil
|
||||
},
|
||||
}, args{users: []string{"foo"}, event: events.Event{Event: events.ShareCreated{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"event that cannot be disabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.BytesReceived{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"ShareCreated enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.ShareCreated{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"ShareRemoved enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.ShareRemoved{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"ShareExpired enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.ShareExpired{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"SpaceShared enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceShared{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"SpaceUnshared enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceUnshared{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"SpaceMembershipExpired enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceMembershipExpired{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"SpaceDisabled enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceDisabled{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"SpaceDeleted enabled", setupMockValueService(true), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceDeleted{}}, ctx: context.TODO()}, []string{"foo"}},
|
||||
{"ShareCreated disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.ShareCreated{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"ShareRemoved disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.ShareRemoved{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"ShareExpired disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.ShareExpired{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"SpaceShared disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceShared{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"SpaceUnshared disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceUnshared{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"SpaceMembershipExpired disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceMembershipExpired{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"SpaceDisabled disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceDisabled{}}, ctx: context.TODO()}, []string(nil)},
|
||||
{"SpaceDeleted disabled", setupMockValueService(false), args{users: []string{"foo"}, event: events.Event{Event: events.SpaceDeleted{}}, ctx: context.TODO()}, []string(nil)},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
ulf := userlogFilter{
|
||||
log: testLogger,
|
||||
valueClient: tt.vc,
|
||||
}
|
||||
assert.Equal(t, tt.want, ulf.execute(tt.args.ctx, tt.args.event, tt.args.executant, tt.args.users))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func setupMockValueService(inApp bool) settings.ValueService {
|
||||
return settings.MockValueService{
|
||||
GetValueByUniqueIdentifiersFunc: func(ctx context.Context, req *settings.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settings.GetValueResponse, error) {
|
||||
return &settings.GetValueResponse{
|
||||
Value: &settingsmsg.ValueWithIdentifier{
|
||||
Value: &settingsmsg.Value{
|
||||
Value: &settingsmsg.Value_CollectionValue{
|
||||
CollectionValue: &settingsmsg.CollectionValue{
|
||||
Values: []*settingsmsg.CollectionOption{
|
||||
{
|
||||
Key: "in-app",
|
||||
Option: &settingsmsg.CollectionOption_BoolValue{BoolValue: inApp},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -39,6 +39,7 @@ type UserlogService struct {
|
||||
tp trace.TracerProvider
|
||||
tracer trace.Tracer
|
||||
publisher events.Publisher
|
||||
filter *userlogFilter
|
||||
}
|
||||
|
||||
// NewUserlogService returns an EventHistory service
|
||||
@@ -69,6 +70,7 @@ func NewUserlogService(opts ...Option) (*UserlogService, error) {
|
||||
tp: o.TraceProvider,
|
||||
tracer: o.TraceProvider.Tracer("github.com/owncloud/ocis/services/userlog/pkg/service"),
|
||||
publisher: o.Stream,
|
||||
filter: newUserlogFilter(o.Logger, o.ValueClient),
|
||||
}
|
||||
|
||||
for _, e := range o.RegisteredEvents {
|
||||
@@ -190,10 +192,7 @@ func (ul *UserlogService) processEvent(event events.Event) {
|
||||
}
|
||||
|
||||
// II) filter users who want to receive the event
|
||||
// This step is postponed for later.
|
||||
// For now each user should get all events she is eligible to receive
|
||||
// ...except notifications for their own actions
|
||||
users = removeExecutant(users, executant)
|
||||
users = ul.filter.execute(ctx, event, executant, users)
|
||||
|
||||
// III) store the eventID for each user
|
||||
for _, id := range users {
|
||||
@@ -461,13 +460,3 @@ func (ul *UserlogService) alterGlobalEvents(ctx context.Context, alter func(map[
|
||||
Value: val,
|
||||
})
|
||||
}
|
||||
|
||||
func removeExecutant(users []string, executant *user.UserId) []string {
|
||||
var usrs []string
|
||||
for _, u := range users {
|
||||
if u != executant.GetOpaqueId() {
|
||||
usrs = append(usrs, u)
|
||||
}
|
||||
}
|
||||
return usrs
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package service_test
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
settingsmsg "github.com/owncloud/ocis/v2/protogen/gen/ocis/messages/settings/v0"
|
||||
"reflect"
|
||||
"time"
|
||||
|
||||
@@ -77,7 +78,22 @@ var _ = Describe("UserlogService", func() {
|
||||
gatewayClient.On("GetUser", mock.Anything, mock.Anything).Return(&user.GetUserResponse{User: &user.User{Id: &user.UserId{OpaqueId: "userid"}}, Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil)
|
||||
gatewayClient.On("Authenticate", mock.Anything, mock.Anything).Return(&gateway.AuthenticateResponse{Status: &rpc.Status{Code: rpc.Code_CODE_OK}}, nil)
|
||||
vc.GetValueByUniqueIdentifiersFunc = func(ctx context.Context, req *settingssvc.GetValueByUniqueIdentifiersRequest, opts ...client.CallOption) (*settingssvc.GetValueResponse, error) {
|
||||
return nil, nil
|
||||
return &settingssvc.GetValueResponse{
|
||||
Value: &settingsmsg.ValueWithIdentifier{
|
||||
Value: &settingsmsg.Value{
|
||||
Value: &settingsmsg.Value_CollectionValue{
|
||||
CollectionValue: &settingsmsg.CollectionValue{
|
||||
Values: []*settingsmsg.CollectionOption{
|
||||
{
|
||||
Key: "in-app",
|
||||
Option: &settingsmsg.CollectionOption_BoolValue{BoolValue: true},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
ul, err = service.NewUserlogService(
|
||||
|
||||
Reference in New Issue
Block a user