From 6f57d44f02bee389f0c103083b64bf40ff332df1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Mon, 8 May 2023 13:20:29 +0300 Subject: [PATCH 1/4] minor code refactor --- .../DefaultNotificationDistributer.cs | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/Abp/Notifications/DefaultNotificationDistributer.cs b/src/Abp/Notifications/DefaultNotificationDistributer.cs index fa2b5d746a..23bf1213ee 100644 --- a/src/Abp/Notifications/DefaultNotificationDistributer.cs +++ b/src/Abp/Notifications/DefaultNotificationDistributer.cs @@ -73,11 +73,11 @@ protected virtual async Task GetUsersAsync(NotificationInfo no if (!notificationInfo.UserIds.IsNullOrEmpty()) { - //Directly get from UserIds + // Directly get from UserIds userIds = notificationInfo .UserIds .Split(",") - .Select(uidAsStr => UserIdentifier.Parse(uidAsStr)) + .Select(UserIdentifier.Parse) .Where(uid => SettingManager.GetSettingValueForUser(NotificationSettingNames.ReceiveNotifications, uid.TenantId, uid.UserId)) @@ -85,8 +85,9 @@ protected virtual async Task GetUsersAsync(NotificationInfo no } else { - //Get subscribed users - + // Get subscribed users + // TODO@6618 -> allow filtering by NotificationSubscription.TargetNotifiers here... + var tenantIds = GetTenantIds(notificationInfo); List subscriptions; @@ -94,7 +95,7 @@ protected virtual async Task GetUsersAsync(NotificationInfo no if (tenantIds.IsNullOrEmpty() || (tenantIds.Length == 1 && tenantIds[0] == NotificationInfo.AllTenantIds.To())) { - //Get all subscribed users of all tenants + // Get all subscribed users of all tenants subscriptions = await _notificationStore.GetSubscriptionsAsync( notificationInfo.NotificationName, notificationInfo.EntityTypeName, @@ -103,7 +104,7 @@ protected virtual async Task GetUsersAsync(NotificationInfo no } else { - //Get all subscribed users of specified tenant(s) + // Get all subscribed users of specified tenant(s) subscriptions = await _notificationStore.GetSubscriptionsAsync( tenantIds, notificationInfo.NotificationName, @@ -112,10 +113,10 @@ protected virtual async Task GetUsersAsync(NotificationInfo no ); } - //Remove invalid subscriptions + // Remove invalid subscriptions var invalidSubscriptions = new Dictionary(); - //TODO: Group subscriptions per tenant for potential performance improvement + // TODO: Group subscriptions per tenant for potential performance improvement foreach (var subscription in subscriptions) { using (CurrentUnitOfWork.SetTenantId(subscription.TenantId)) @@ -350,7 +351,7 @@ protected virtual async Task NotifyAsync(UserNotification[] userNotifications) using (var notifier = _iocResolver.ResolveAsDisposable(notifierType)) { UserNotification[] notificationsToSendWithThatNotifier; - + // if UseOnlyIfRequestedAsTarget is true, then we should send notifications which requests this notifier if (notifier.Object.UseOnlyIfRequestedAsTarget) { @@ -363,15 +364,18 @@ protected virtual async Task NotifyAsync(UserNotification[] userNotifications) // notifier allows to send any notifications // we can send all notifications which does not have TargetNotifiersList(since there is no target, we can send it with any notifier) // or current notifier is in TargetNotifiersList - + notificationsToSendWithThatNotifier = userNotifications .Where(n => - n.TargetNotifiersList == null || n.TargetNotifiersList.Count == 0 ||// if there is no target notifiers, send it to all of them - n.TargetNotifiersList.Contains(notifierType.FullName)// if there is target notifiers, check if current notifier is in it + n.TargetNotifiersList == null || + n.TargetNotifiersList.Count == + 0 || // if there is no target notifiers, send it to all of them + n.TargetNotifiersList.Contains(notifierType + .FullName) // if there is target notifiers, check if current notifier is in it ) .ToArray(); } - + if (notificationsToSendWithThatNotifier.Length == 0) { continue; From 36c74f1fb04238d04dc597d11da1f713dddeb51d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Wed, 14 Jun 2023 09:53:19 +0300 Subject: [PATCH 2/4] Add unit tests for targetNotifiers distribution --- .../Notifications/NotificationStore.cs | 194 ++++++++++++------ .../DefaultNotificationDistributer.cs | 58 +++++- src/Abp/Notifications/INotificationStore.cs | 12 +- .../INotificationSubscriptionManager.cs | 24 ++- .../NotificationSubscriptionInfo.cs | 18 +- .../NotificationSubscriptionManager.cs | 117 +++++++---- .../Notifications/NullNotificationStore.cs | 12 +- .../NotificationDistributer_Tests.cs | 13 ++ .../Notifications/NotificationStore_Tests.cs | 2 +- .../NotificationSubscription_Tests.cs | 124 +++++++++++ 10 files changed, 440 insertions(+), 134 deletions(-) create mode 100644 test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationSubscription_Tests.cs diff --git a/src/Abp.Zero.Common/Notifications/NotificationStore.cs b/src/Abp.Zero.Common/Notifications/NotificationStore.cs index 2d1443ae36..53dbbc17c7 100644 --- a/src/Abp.Zero.Common/Notifications/NotificationStore.cs +++ b/src/Abp.Zero.Common/Notifications/NotificationStore.cs @@ -6,6 +6,7 @@ using Abp.Dependency; using Abp.Domain.Repositories; using Abp.Domain.Uow; +using Abp.Extensions; using Abp.Linq; using Abp.Linq.Expressions; using Abp.Linq.Extensions; @@ -18,7 +19,7 @@ namespace Abp.Notifications public class NotificationStore : INotificationStore, ITransientDependency { public IAsyncQueryableExecuter AsyncQueryableExecuter { get; set; } - + private readonly IRepository _notificationRepository; private readonly IRepository _tenantNotificationRepository; private readonly IRepository _userNotificationRepository; @@ -40,7 +41,7 @@ public class NotificationStore : INotificationStore, ITransientDependency _userNotificationRepository = userNotificationRepository; _notificationSubscriptionRepository = notificationSubscriptionRepository; _unitOfWorkManager = unitOfWorkManager; - + AsyncQueryableExecuter = NullAsyncQueryableExecuter.Instance; } @@ -185,17 +186,21 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification public virtual async Task> GetSubscriptionsAsync( string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => { using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant)) { - return await _notificationSubscriptionRepository.GetAllListAsync(s => - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers ); + + return await _notificationSubscriptionRepository.GetAllListAsync(predicate); } }); } @@ -203,17 +208,21 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification public virtual List GetSubscriptions( string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return _unitOfWorkManager.WithUnitOfWork(() => { using (_unitOfWorkManager.Current.DisableFilter(AbpDataFilters.MayHaveTenant)) { - return _notificationSubscriptionRepository.GetAllList(s => - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers ); + + return _notificationSubscriptionRepository.GetAllList(predicate); } }); } @@ -222,7 +231,8 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification int?[] tenantIds, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => { @@ -230,8 +240,15 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification foreach (var tenantId in tenantIds) { - subscriptions.AddRange(await GetSubscriptionsAsync(tenantId, notificationName, entityTypeName, - entityId)); + subscriptions.AddRange( + await GetSubscriptionsAsync( + tenantId, + notificationName, + entityTypeName, + entityId, + targetNotifiers + ) + ); } return subscriptions; @@ -242,7 +259,8 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification int?[] tenantIds, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return _unitOfWorkManager.WithUnitOfWork(() => { @@ -250,7 +268,15 @@ public virtual void InsertUserNotification(UserNotificationInfo userNotification foreach (var tenantId in tenantIds) { - subscriptions.AddRange(GetSubscriptions(tenantId, notificationName, entityTypeName, entityId)); + subscriptions.AddRange( + GetSubscriptions( + tenantId, + notificationName, + entityTypeName, + entityId, + targetNotifiers + ) + ); } return subscriptions; @@ -283,36 +309,68 @@ public virtual List GetSubscriptions(UserIdentifie int? tenantId, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => { using (_unitOfWorkManager.Current.SetTenantId(tenantId)) { - return await _notificationSubscriptionRepository.GetAllListAsync(s => - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers ); + + return await _notificationSubscriptionRepository.GetAllListAsync(predicate); } }); } + protected virtual ExpressionStarter GetNotificationSubscriptionPredicate( + string notificationName, string entityTypeName, + string entityId, string targetNotifiers) + { + var predicate = PredicateBuilder.New(); + predicate = predicate.And(e => e.NotificationName == notificationName); + predicate = predicate.And(e => e.EntityTypeName == entityTypeName); + predicate = predicate.And(e => e.EntityId == entityId); + + if (!targetNotifiers.IsNullOrEmpty()) + { + var targetNotifierPredicate = PredicateBuilder.New(); + var targetNotifierList = targetNotifiers.Split(NotificationInfo.NotificationTargetSeparator); + foreach (var targetNotifier in targetNotifierList) + { + targetNotifierPredicate = targetNotifierPredicate.Or(e => e.TargetNotifiers.Contains(targetNotifier)); + } + + predicate = predicate.And(targetNotifierPredicate); + } + + return predicate; + } + protected virtual List GetSubscriptions( int? tenantId, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return _unitOfWorkManager.WithUnitOfWork(() => { using (_unitOfWorkManager.Current.SetTenantId(tenantId)) { - return _notificationSubscriptionRepository.GetAllList(s => - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers ); + + return _notificationSubscriptionRepository.GetAllList(predicate); } }); } @@ -321,18 +379,23 @@ public virtual List GetSubscriptions(UserIdentifie UserIdentifier user, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => { using (_unitOfWorkManager.Current.SetTenantId(user.TenantId)) { - return await _notificationSubscriptionRepository.CountAsync(s => - s.UserId == user.UserId && - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId - ) > 0; + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers + ); + + predicate = predicate.And(e => e.UserId == user.UserId); + + return await _notificationSubscriptionRepository.CountAsync(predicate) > 0; } }); } @@ -341,18 +404,23 @@ public virtual List GetSubscriptions(UserIdentifie UserIdentifier user, string notificationName, string entityTypeName, - string entityId) + string entityId, + string targetNotifiers) { return _unitOfWorkManager.WithUnitOfWork(() => { using (_unitOfWorkManager.Current.SetTenantId(user.TenantId)) { - return _notificationSubscriptionRepository.Count(s => - s.UserId == user.UserId && - s.NotificationName == notificationName && - s.EntityTypeName == entityTypeName && - s.EntityId == entityId - ) > 0; + var predicate = GetNotificationSubscriptionPredicate( + notificationName, + entityTypeName, + entityId, + targetNotifiers + ); + + predicate = predicate.And(e => e.UserId == user.UserId); + + return _notificationSubscriptionRepository.Count(predicate) > 0; } }); } @@ -756,18 +824,17 @@ public virtual void InsertTenantNotification(TenantNotificationInfo tenantNotifi public virtual async Task DeleteNotificationAsync(NotificationInfo notification) { - await _unitOfWorkManager.WithUnitOfWorkAsync(async () => await _notificationRepository.DeleteAsync(notification)); + await _unitOfWorkManager.WithUnitOfWorkAsync(async () => + await _notificationRepository.DeleteAsync(notification)); } public virtual void DeleteNotification(NotificationInfo notification) { - _unitOfWorkManager.WithUnitOfWork(() => - { - _notificationRepository.Delete(notification); - }); + _unitOfWorkManager.WithUnitOfWork(() => { _notificationRepository.Delete(notification); }); } - public async Task> GetNotificationsPublishedByUserAsync(UserIdentifier user, string notificationName, DateTime? startDate, DateTime? endDate) + public async Task> GetNotificationsPublishedByUserAsync( + UserIdentifier user, string notificationName, DateTime? startDate, DateTime? endDate) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => { @@ -789,25 +856,26 @@ public async Task> GetNotificationsPub } var result = new List(); - - var unPublishedNotifications = await AsyncQueryableExecuter.ToListAsync(queryForNotPublishedNotifications - .Select(x => - new GetNotificationsCreatedByUserOutput() - { - Data = x.Data, - Severity = x.Severity, - NotificationName = x.NotificationName, - DataTypeName = x.DataTypeName, - IsPublished = false, - CreationTime = x.CreationTime - }) + + var unPublishedNotifications = await AsyncQueryableExecuter.ToListAsync( + queryForNotPublishedNotifications + .Select(x => + new GetNotificationsCreatedByUserOutput() + { + Data = x.Data, + Severity = x.Severity, + NotificationName = x.NotificationName, + DataTypeName = x.DataTypeName, + IsPublished = false, + CreationTime = x.CreationTime + }) ); - + result.AddRange(unPublishedNotifications); var queryForPublishedNotifications = _tenantNotificationRepository.GetAll() .Where(n => n.CreatorUserId == user.UserId && n.NotificationName == notificationName); - + if (startDate.HasValue) { queryForPublishedNotifications = queryForPublishedNotifications @@ -822,7 +890,7 @@ public async Task> GetNotificationsPub queryForPublishedNotifications = queryForPublishedNotifications .OrderByDescending(n => n.CreationTime); - + var publishedNotifications = await AsyncQueryableExecuter.ToListAsync(queryForPublishedNotifications .Select(x => new GetNotificationsCreatedByUserOutput() @@ -842,4 +910,4 @@ public async Task> GetNotificationsPub }); } } -} +} \ No newline at end of file diff --git a/src/Abp/Notifications/DefaultNotificationDistributer.cs b/src/Abp/Notifications/DefaultNotificationDistributer.cs index 23bf1213ee..538bb7905b 100644 --- a/src/Abp/Notifications/DefaultNotificationDistributer.cs +++ b/src/Abp/Notifications/DefaultNotificationDistributer.cs @@ -87,7 +87,7 @@ protected virtual async Task GetUsersAsync(NotificationInfo no { // Get subscribed users // TODO@6618 -> allow filtering by NotificationSubscription.TargetNotifiers here... - + var tenantIds = GetTenantIds(notificationInfo); List subscriptions; @@ -99,7 +99,8 @@ protected virtual async Task GetUsersAsync(NotificationInfo no subscriptions = await _notificationStore.GetSubscriptionsAsync( notificationInfo.NotificationName, notificationInfo.EntityTypeName, - notificationInfo.EntityId + notificationInfo.EntityId, + notificationInfo.TargetNotifiers ); } else @@ -109,7 +110,8 @@ protected virtual async Task GetUsersAsync(NotificationInfo no tenantIds, notificationInfo.NotificationName, notificationInfo.EntityTypeName, - notificationInfo.EntityId + notificationInfo.EntityId, + notificationInfo.TargetNotifiers ); } @@ -187,7 +189,8 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) subscriptions = _notificationStore.GetSubscriptions( notificationInfo.NotificationName, notificationInfo.EntityTypeName, - notificationInfo.EntityId + notificationInfo.EntityId, + notificationInfo.TargetNotifiers ); } else @@ -197,7 +200,8 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) tenantIds, notificationInfo.NotificationName, notificationInfo.EntityTypeName, - notificationInfo.EntityId + notificationInfo.EntityId, + notificationInfo.TargetNotifiers ); } @@ -258,7 +262,8 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) .ToArray(); } - protected virtual async Task> SaveUserNotificationsAsync(UserIdentifier[] users, + protected virtual async Task> SaveUserNotificationsAsync( + UserIdentifier[] users, NotificationInfo notificationInfo) { return await _unitOfWorkManager.WithUnitOfWorkAsync(async () => @@ -270,13 +275,24 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) { using (_unitOfWorkManager.Current.SetTenantId(tenantGroup.Key)) { - var tenantNotificationInfo = new TenantNotificationInfo(_guidGenerator.Create(), - tenantGroup.Key, notificationInfo); + var tenantNotificationInfo = new TenantNotificationInfo( + _guidGenerator.Create(), + tenantGroup.Key, + notificationInfo + ); + await _notificationStore.InsertTenantNotificationAsync(tenantNotificationInfo); await _unitOfWorkManager.Current.SaveChangesAsync(); //To get tenantNotification.Id. var tenantNotification = tenantNotificationInfo.ToTenantNotification(); + var userNotificationSubscriptions = await _notificationStore.GetSubscriptionsAsync( + notificationInfo.NotificationName, + notificationInfo.EntityTypeName, + notificationInfo.EntityId, + null + ); + foreach (var user in tenantGroup) { var userNotification = new UserNotificationInfo(_guidGenerator.Create()) @@ -284,7 +300,11 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) TenantId = tenantGroup.Key, UserId = user.UserId, TenantNotificationId = tenantNotificationInfo.Id, - TargetNotifiers = notificationInfo.TargetNotifiers + TargetNotifiers = GetTargetNotifiersForUser( + user, + notificationInfo, + userNotificationSubscriptions + ) }; await _notificationStore.InsertUserNotificationAsync(userNotification); @@ -299,6 +319,26 @@ protected virtual UserIdentifier[] GetUsers(NotificationInfo notificationInfo) }); } + protected virtual string GetTargetNotifiersForUser( + UserIdentifier user, + NotificationInfo notificationInfo, + List userNotificationSubscriptions + ) + { + if (userNotificationSubscriptions.IsNullOrEmpty()) + { + return notificationInfo.TargetNotifiers; + } + + var userSubscription = userNotificationSubscriptions.FirstOrDefault(un => un.UserId == user.UserId); + if (userSubscription == null) + { + return notificationInfo.TargetNotifiers; + } + + return userSubscription.TargetNotifiers; + } + protected virtual List SaveUserNotifications( UserIdentifier[] users, NotificationInfo notificationInfo) diff --git a/src/Abp/Notifications/INotificationStore.cs b/src/Abp/Notifications/INotificationStore.cs index ae1b2eda44..10145e91cb 100644 --- a/src/Abp/Notifications/INotificationStore.cs +++ b/src/Abp/Notifications/INotificationStore.cs @@ -62,22 +62,22 @@ public interface INotificationStore /// /// Gets subscriptions for a notification. /// - Task> GetSubscriptionsAsync(string notificationName, string entityTypeName, string entityId); + Task> GetSubscriptionsAsync(string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Gets subscriptions for a notification. /// - List GetSubscriptions(string notificationName, string entityTypeName, string entityId); + List GetSubscriptions(string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Gets subscriptions for a notification for specified tenant(s). /// - Task> GetSubscriptionsAsync(int?[] tenantIds, string notificationName, string entityTypeName, string entityId); + Task> GetSubscriptionsAsync(int?[] tenantIds, string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Gets subscriptions for a notification for specified tenant(s). /// - List GetSubscriptions(int?[] tenantIds, string notificationName, string entityTypeName, string entityId); + List GetSubscriptions(int?[] tenantIds, string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Gets subscriptions for a user. @@ -92,12 +92,12 @@ public interface INotificationStore /// /// Checks if a user subscribed for a notification /// - Task IsSubscribedAsync(UserIdentifier user, string notificationName, string entityTypeName, string entityId); + Task IsSubscribedAsync(UserIdentifier user, string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Checks if a user subscribed for a notification /// - bool IsSubscribed(UserIdentifier user, string notificationName, string entityTypeName, string entityId); + bool IsSubscribed(UserIdentifier user, string notificationName, string entityTypeName, string entityId, string targetNotifiers); /// /// Updates a user notification state. diff --git a/src/Abp/Notifications/INotificationSubscriptionManager.cs b/src/Abp/Notifications/INotificationSubscriptionManager.cs index 710f36519f..fc8549c66a 100644 --- a/src/Abp/Notifications/INotificationSubscriptionManager.cs +++ b/src/Abp/Notifications/INotificationSubscriptionManager.cs @@ -15,7 +15,8 @@ public interface INotificationSubscriptionManager /// User /// Name of the notification. /// entity identifier - Task SubscribeAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + Task SubscribeAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Subscribes to a notification for given user and notification informations. @@ -23,7 +24,8 @@ public interface INotificationSubscriptionManager /// User /// Name of the notification. /// entity identifier - void Subscribe(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + void Subscribe(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Subscribes to all available notifications for given user. @@ -61,7 +63,8 @@ public interface INotificationSubscriptionManager /// /// Name of the notification. /// entity identifier - Task> GetSubscriptionsAsync(string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + Task> GetSubscriptionsAsync(string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Gets all subscribtions for given notification (including all tenants). @@ -69,7 +72,8 @@ public interface INotificationSubscriptionManager /// /// Name of the notification. /// entity identifier - List GetSubscriptions(string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + List GetSubscriptions(string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Gets all subscribtions for given notification. @@ -77,7 +81,8 @@ public interface INotificationSubscriptionManager /// Tenant id. Null for the host. /// Name of the notification. /// entity identifier - Task> GetSubscriptionsAsync(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + Task> GetSubscriptionsAsync(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Gets all subscribtions for given notification. @@ -85,7 +90,8 @@ public interface INotificationSubscriptionManager /// Tenant id. Null for the host. /// Name of the notification. /// entity identifier - List GetSubscriptions(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + List GetSubscriptions(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Gets subscribed notifications for a user. @@ -105,7 +111,8 @@ public interface INotificationSubscriptionManager /// User. /// Name of the notification. /// entity identifier - Task IsSubscribedAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + Task IsSubscribedAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); /// /// Checks if a user subscribed for a notification. @@ -113,6 +120,7 @@ public interface INotificationSubscriptionManager /// User. /// Name of the notification. /// entity identifier - bool IsSubscribed(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null); + /// target notifier + bool IsSubscribed(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null, string targetNotifiers = null); } } diff --git a/src/Abp/Notifications/NotificationSubscriptionInfo.cs b/src/Abp/Notifications/NotificationSubscriptionInfo.cs index 7f13567982..58b4c51275 100644 --- a/src/Abp/Notifications/NotificationSubscriptionInfo.cs +++ b/src/Abp/Notifications/NotificationSubscriptionInfo.cs @@ -1,8 +1,11 @@ using System; +using System.Collections.Generic; using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations.Schema; +using System.Linq; using Abp.Domain.Entities; using Abp.Domain.Entities.Auditing; +using Abp.Extensions; using Abp.Json; namespace Abp.Notifications @@ -48,6 +51,12 @@ public class NotificationSubscriptionInfo : CreationAuditedEntity, IMayHav [StringLength(NotificationInfo.MaxEntityIdLength)] public virtual string EntityId { get; set; } + /// + /// which realtime notifiers should handle this notification + /// + [StringLength(NotificationInfo.MaxTargetNotifiersLength)] + public virtual string TargetNotifiers { get; set; } + /// /// Initializes a new instance of the class. /// @@ -58,7 +67,13 @@ public NotificationSubscriptionInfo() /// /// Initializes a new instance of the class. /// - public NotificationSubscriptionInfo(Guid id, int? tenantId, long userId, string notificationName, EntityIdentifier entityIdentifier = null) + public NotificationSubscriptionInfo( + Guid id, + int? tenantId, + long userId, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { Id = id; TenantId = tenantId; @@ -67,6 +82,7 @@ public NotificationSubscriptionInfo(Guid id, int? tenantId, long userId, string EntityTypeName = entityIdentifier == null ? null : entityIdentifier.Type.FullName; EntityTypeAssemblyQualifiedName = entityIdentifier == null ? null : entityIdentifier.Type.AssemblyQualifiedName; EntityId = entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(); + TargetNotifiers = targetNotifiers; } } } \ No newline at end of file diff --git a/src/Abp/Notifications/NotificationSubscriptionManager.cs b/src/Abp/Notifications/NotificationSubscriptionManager.cs index b99f6a12eb..5ad49dacc7 100644 --- a/src/Abp/Notifications/NotificationSubscriptionManager.cs +++ b/src/Abp/Notifications/NotificationSubscriptionManager.cs @@ -20,7 +20,7 @@ public class NotificationSubscriptionManager : INotificationSubscriptionManager, /// Initializes a new instance of the class. /// public NotificationSubscriptionManager( - INotificationStore store, + INotificationStore store, INotificationDefinitionManager notificationDefinitionManager, IGuidGenerator guidGenerator) { @@ -29,9 +29,13 @@ public class NotificationSubscriptionManager : INotificationSubscriptionManager, _guidGenerator = guidGenerator; } - public async Task SubscribeAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) + public async Task SubscribeAsync( + UserIdentifier user, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { - if (await IsSubscribedAsync(user, notificationName, entityIdentifier)) + if (await IsSubscribedAsync(user, notificationName, entityIdentifier, targetNotifiers)) { return; } @@ -42,14 +46,19 @@ public async Task SubscribeAsync(UserIdentifier user, string notificationName, E user.TenantId, user.UserId, notificationName, - entityIdentifier - ) - ); + entityIdentifier, + targetNotifiers + ) + ); } - public void Subscribe(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) + public void Subscribe( + UserIdentifier user, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { - if (IsSubscribed(user, notificationName, entityIdentifier)) + if (IsSubscribed(user, notificationName, entityIdentifier, targetNotifiers)) { return; } @@ -60,15 +69,16 @@ public void Subscribe(UserIdentifier user, string notificationName, EntityIdenti user.TenantId, user.UserId, notificationName, - entityIdentifier - ) - ); + entityIdentifier, + targetNotifiers + ) + ); } public async Task SubscribeToAllAvailableNotificationsAsync(UserIdentifier user) { var notificationDefinitions = (await _notificationDefinitionManager - .GetAllAvailableAsync(user)) + .GetAllAvailableAsync(user)) .Where(nd => nd.EntityType == null) .ToList(); @@ -81,7 +91,7 @@ public async Task SubscribeToAllAvailableNotificationsAsync(UserIdentifier user) public void SubscribeToAllAvailableNotifications(UserIdentifier user) { var notificationDefinitions = (_notificationDefinitionManager - .GetAllAvailable(user)) + .GetAllAvailable(user)) .Where(nd => nd.EntityType == null) .ToList(); @@ -91,34 +101,37 @@ public void SubscribeToAllAvailableNotifications(UserIdentifier user) } } - public async Task UnsubscribeAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) + public async Task UnsubscribeAsync(UserIdentifier user, string notificationName, + EntityIdentifier entityIdentifier = null) { await _store.DeleteSubscriptionAsync( user, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + ); } public void Unsubscribe(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) { - _store.DeleteSubscription( + _store.DeleteSubscription( user, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + ); } // TODO: Can work only for single database approach! - public async Task> GetSubscriptionsAsync(string notificationName, EntityIdentifier entityIdentifier = null) + public async Task> GetSubscriptionsAsync(string notificationName, + EntityIdentifier entityIdentifier = null, string targetNotifiers = null) { var notificationSubscriptionInfos = await _store.GetSubscriptionsAsync( notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); return notificationSubscriptionInfos .Select(nsi => nsi.ToNotificationSubscription()) @@ -126,41 +139,55 @@ public async Task> GetSubscriptionsAsync(string n } // TODO: Can work only for single database approach! - public List GetSubscriptions(string notificationName, EntityIdentifier entityIdentifier = null) + public List GetSubscriptions( + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { - var notificationSubscriptionInfos = _store.GetSubscriptions( + var notificationSubscriptionInfos = _store.GetSubscriptions( notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); return notificationSubscriptionInfos .Select(nsi => nsi.ToNotificationSubscription()) .ToList(); } - public async Task> GetSubscriptionsAsync(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null) + public async Task> GetSubscriptionsAsync( + int? tenantId, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { var notificationSubscriptionInfos = await _store.GetSubscriptionsAsync( - new[] { tenantId }, + new[] {tenantId}, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); return notificationSubscriptionInfos .Select(nsi => nsi.ToNotificationSubscription()) .ToList(); } - public List GetSubscriptions(int? tenantId, string notificationName, EntityIdentifier entityIdentifier = null) + public List GetSubscriptions( + int? tenantId, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { - var notificationSubscriptionInfos = _store.GetSubscriptions( - new[] { tenantId }, + var notificationSubscriptionInfos = _store.GetSubscriptions( + new[] {tenantId}, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); return notificationSubscriptionInfos .Select(nsi => nsi.ToNotificationSubscription()) @@ -178,31 +205,41 @@ public async Task> GetSubscribedNotificationsAsyn public List GetSubscribedNotifications(UserIdentifier user) { - var notificationSubscriptionInfos = _store.GetSubscriptions(user); + var notificationSubscriptionInfos = _store.GetSubscriptions(user); return notificationSubscriptionInfos .Select(nsi => nsi.ToNotificationSubscription()) .ToList(); } - public Task IsSubscribedAsync(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) + public Task IsSubscribedAsync( + UserIdentifier user, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { return _store.IsSubscribedAsync( user, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); } - public bool IsSubscribed(UserIdentifier user, string notificationName, EntityIdentifier entityIdentifier = null) + public bool IsSubscribed( + UserIdentifier user, + string notificationName, + EntityIdentifier entityIdentifier = null, + string targetNotifiers = null) { return _store.IsSubscribed( user, notificationName, entityIdentifier == null ? null : entityIdentifier.Type.FullName, - entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString() - ); + entityIdentifier == null ? null : entityIdentifier.Id.ToJsonString(), + targetNotifiers + ); } } } \ No newline at end of file diff --git a/src/Abp/Notifications/NullNotificationStore.cs b/src/Abp/Notifications/NullNotificationStore.cs index a217c7063b..cf92a3a1e4 100644 --- a/src/Abp/Notifications/NullNotificationStore.cs +++ b/src/Abp/Notifications/NullNotificationStore.cs @@ -34,12 +34,12 @@ public Task InsertUserNotificationAsync(UserNotificationInfo userNotification) return Task.FromResult(0); } - public Task> GetSubscriptionsAsync(string notificationName, string entityTypeName = null, string entityId = null) + public Task> GetSubscriptionsAsync(string notificationName, string entityTypeName = null, string entityId = null, string targetNotifiers = null) { return Task.FromResult(new List()); } - public Task> GetSubscriptionsAsync(int?[] tenantIds, string notificationName, string entityTypeName, string entityId) + public Task> GetSubscriptionsAsync(int?[] tenantIds, string notificationName, string entityTypeName, string entityId, string targetNotifiers = null) { return Task.FromResult(new List()); } @@ -49,7 +49,7 @@ public Task> GetSubscriptionsAsync(UserIdenti return Task.FromResult(new List()); } - public Task IsSubscribedAsync(UserIdentifier user, string notificationName, string entityTypeName, string entityId) + public Task IsSubscribedAsync(UserIdentifier user, string notificationName, string entityTypeName, string entityId, string targetNotifiers = null) { return Task.FromResult(false); } @@ -125,12 +125,12 @@ public void InsertUserNotification(UserNotificationInfo userNotification) { } - public List GetSubscriptions(string notificationName, string entityTypeName, string entityId) + public List GetSubscriptions(string notificationName, string entityTypeName, string entityId, string targetNotifiers = null) { return new List(); } - public List GetSubscriptions(int?[] tenantIds, string notificationName, string entityTypeName, string entityId) + public List GetSubscriptions(int?[] tenantIds, string notificationName, string entityTypeName, string entityId, string targetNotifiers = null) { return new List(); } @@ -140,7 +140,7 @@ public List GetSubscriptions(UserIdentifier user) return new List(); } - public bool IsSubscribed(UserIdentifier user, string notificationName, string entityTypeName, string entityId) + public bool IsSubscribed(UserIdentifier user, string notificationName, string entityTypeName, string entityId, string targetNotifiers = null) { return false; } diff --git a/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationDistributer_Tests.cs b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationDistributer_Tests.cs index 8fe84316ec..51b5fc7d69 100644 --- a/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationDistributer_Tests.cs +++ b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationDistributer_Tests.cs @@ -29,5 +29,18 @@ public async Task Should_Distribute_Notification_Using_Custom_Distributer() //Assert _fakeNotificationDistributer.IsDistributeCalled.ShouldBeTrue(); } + + [Fact] + public async Task Should_Distribute_Notification_Using_Custom_Distributer2() + { + //Arrange + var notificationData = new NotificationData(); + + //Act + await _publisher.PublishAsync("TestNotification", notificationData, severity: NotificationSeverity.Success, userIds: new[] { AbpSession.ToUserIdentifier() }); + + //Assert + _fakeNotificationDistributer.IsDistributeCalled.ShouldBeTrue(); + } } } diff --git a/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationStore_Tests.cs b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationStore_Tests.cs index c2b40270e6..f1957f7b5e 100644 --- a/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationStore_Tests.cs +++ b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationStore_Tests.cs @@ -31,7 +31,6 @@ public NotificationStore_Tests() [Fact] public async Task Should_Get_All_Notifications() { - var userIdentifier = AbpSession.ToUserIdentifier(); await _notificationPublisher.PublishAsync("Test", userIds: new[] { userIdentifier }); @@ -41,6 +40,7 @@ public async Task Should_Get_All_Notifications() allNotifications.Count.ShouldBe(1); } + [Fact] public async Task Should_Get_All_Notifications_Between_StartDate_EndDate() { diff --git a/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationSubscription_Tests.cs b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationSubscription_Tests.cs new file mode 100644 index 0000000000..36e7c3419b --- /dev/null +++ b/test/Abp.ZeroCore.Tests/Zero/Notifications/NotificationSubscription_Tests.cs @@ -0,0 +1,124 @@ +using System.Collections.Generic; +using System.Threading.Tasks; +using Abp.Notifications; +using Abp.Runtime.Session; +using Castle.MicroKernel.Registration; +using Shouldly; +using Xunit; + +namespace Abp.Zero.Notifications +{ + public class NotificationSubscription_Tests : AbpZeroTestBase + { + private readonly INotificationSubscriptionManager _notificationSubscriptionManager; + private readonly Notifier1 _realTimeNotifier1; + private readonly Notifier2 _realTimeNotifier2; + private readonly INotificationPublisher _publisher; + + public NotificationSubscription_Tests() + { + _realTimeNotifier1 = new Notifier1(); + _realTimeNotifier2 = new Notifier2(); + + RegisterRealTimeNotifiers(new List + { + _realTimeNotifier1, + _realTimeNotifier2 + }); + + AddRealTimeNotifiers(new List + { + _realTimeNotifier1, + _realTimeNotifier2 + }); + + _notificationSubscriptionManager = Resolve(); + _publisher = LocalIocManager.Resolve(); + } + + private void AddRealTimeNotifiers(List realTimeNotifiers) + { + // Use DefaultNotificationDistributer + var defaultNotificationDistributor = LocalIocManager.Resolve(); + LocalIocManager.IocContainer.Register( + Component.For().Instance(defaultNotificationDistributor) + .LifestyleSingleton() + .IsDefault() + .Named("DefaultNotificationDistributer") + ); + + // Add notifiers + var notificationConfiguration = LocalIocManager.Resolve(); + foreach (var realTimeNotifier in realTimeNotifiers) + { + notificationConfiguration.Notifiers.Add(realTimeNotifier.GetType()); + } + } + + private void RegisterRealTimeNotifiers(List realTimeNotifiers) + { + foreach (var realTimeNotifier in realTimeNotifiers) + { + var realTimeNotifierType = realTimeNotifier.GetType(); + LocalIocManager.IocContainer.Register( + Component.For(realTimeNotifierType) + .Instance(realTimeNotifier) + .LifestyleSingleton() + ); + } + } + + [Fact] + public async Task Should_Not_Get_Not_Subscribed_Notification_Targets() + { + var notificationName = "CustomNotification"; + + // Arrange -> subscribe to CustomNotification with _realTimeNotifier + await _notificationSubscriptionManager.SubscribeAsync( + AbpSession.ToUserIdentifier(), + notificationName, + null, + _realTimeNotifier1.GetType().FullName + ); + + // Act + var subscriptions = await _notificationSubscriptionManager.GetSubscriptionsAsync( + AbpSession.TenantId, + notificationName, + null, + _realTimeNotifier2.GetType().FullName + ); + + // Assert + subscriptions.Count.ShouldBe(0); + } + + [Fact] + public async Task Should_Not_Publish_Notification_For_Not_Subscribed_Notification_Targets() + { + var notificationName = "TestNotification"; + + // Arrange -> subscribe to CustomNotification with _realTimeNotifier + await _notificationSubscriptionManager.SubscribeAsync( + AbpSession.ToUserIdentifier(), + notificationName, + null, + _realTimeNotifier1.GetType().FullName + ); + + var notificationData = new NotificationData(); + + //Act + await _publisher.PublishAsync( + "TestNotification", + notificationData, + severity: NotificationSeverity.Success, + userIds: new[] {AbpSession.ToUserIdentifier()} + ); + + //Assert + _realTimeNotifier1.IsSendNotificationCalled.ShouldBeTrue(); + _realTimeNotifier2.IsSendNotificationCalled.ShouldBeFalse(); + } + } +} \ No newline at end of file From a952dd4e69f31665919624a8cd0411a0cd06795c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Wed, 14 Jun 2023 10:11:17 +0300 Subject: [PATCH 3/4] Updated documentation for target notifier subscription --- doc/WebSite/Notification-System.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/doc/WebSite/Notification-System.md b/doc/WebSite/Notification-System.md index b63d0cfe68..b84e5efe79 100644 --- a/doc/WebSite/Notification-System.md +++ b/doc/WebSite/Notification-System.md @@ -56,7 +56,10 @@ persistence. ### Subscribe to Notifications The **INotificationSubscriptionManager** provides an API to **subscribe** to -notifications. Examples: +notifications. A User can subscribe to a specific notification, to a notification related to a specific entity. +A user can also select specific notifiers when subscribing to a notification. In this way, user will not be notified by other notifiers. Full type name of the notifier must be provided when selecting a target Notifier. It can be set using `new EmailRealTimeNotifier().GetType().FullName` and accepts comma separated multiple values. + +Examples: public class MyService : ITransientDependency { From f58c1b90ff57d80b7fe36460c6516396e2669217 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C4=B0smail=20=C3=87A=C4=9EDA=C5=9E?= Date: Wed, 14 Jun 2023 10:16:39 +0300 Subject: [PATCH 4/4] global.json update --- global.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/global.json b/global.json index 08585a2b3b..d97aed02df 100644 --- a/global.json +++ b/global.json @@ -1,5 +1,6 @@ { "sdk": { - "version": "7.0.100" + "version": "7.0.100", + "rollForward": "latestFeature" } } \ No newline at end of file