diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs index fb676fe288..dbf3692b29 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.Domain/Volo/Abp/AuditLogging/IAuditLogRepository.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Net; using System.Threading; using System.Threading.Tasks; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories; namespace Volo.Abp.AuditLogging @@ -44,5 +45,27 @@ namespace Volo.Abp.AuditLogging Task> GetAverageExecutionDurationPerDayAsync( DateTime startDate, DateTime endDate); + + Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default); + + Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default); } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs index 27445045e7..6dc4bfadb8 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/AbpAuditLoggingEfCoreQueryableExtensions.cs @@ -18,5 +18,17 @@ namespace Volo.Abp.AuditLogging .Include(x => x.Actions) .Include(x => x.EntityChanges).ThenInclude(ec=>ec.PropertyChanges); } + + public static IQueryable IncludeDetails( + this IQueryable queryable, + bool include = true) + { + if (!include) + { + return queryable; + } + + return queryable.Include(x => x.PropertyChanges); + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs index 1fabbcbe7c..2a80767cd6 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.EntityFrameworkCore/Volo/Abp/AuditLogging/EntityFrameworkCore/EfCoreAuditLogRepository.cs @@ -6,6 +6,7 @@ using System.Net; using System.Threading; using System.Threading.Tasks; using Microsoft.EntityFrameworkCore; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore; @@ -139,5 +140,60 @@ namespace Volo.Abp.AuditLogging.EntityFrameworkCore { return GetQueryable().IncludeDetails(); } + + public virtual async Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName, includeDetails); + + return await query.OrderBy(sorting ?? "changeTime desc") + .PageBy(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + } + + public virtual async Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + + var totalCount = await query.LongCountAsync(GetCancellationToken(cancellationToken)); + + return totalCount; + } + + protected virtual IQueryable GetEntityChangeListQuery( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false) + { + return DbContext.Set().AsNoTracking().IncludeDetails(includeDetails) + .WhereIf(auditLogId.HasValue, e => e.AuditLogId == auditLogId) + .WhereIf(startTime.HasValue, e => e.ChangeTime >= startTime) + .WhereIf(endTime.HasValue, e => e.ChangeTime <= endTime) + .WhereIf(changeType.HasValue, e => e.ChangeType == changeType) + .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityId == entityId) + .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), + e => e.EntityTypeFullName == entityTypeFullName); + } } } diff --git a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs index fdb9bca387..8705e398dd 100644 --- a/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs +++ b/modules/audit-logging/src/Volo.Abp.AuditLogging.MongoDB/Volo/Abp/AuditLogging/MongoDB/MongoAuditLogRepository.cs @@ -7,6 +7,7 @@ using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; using MongoDB.Driver.Linq; +using Volo.Abp.Auditing; using Volo.Abp.Domain.Repositories.MongoDB; using Volo.Abp.MongoDB; @@ -138,5 +139,109 @@ namespace Volo.Abp.AuditLogging.MongoDB return result.ToDictionary(element => element.Day.ClearTime(), element => element.avgExecutionTime); } + + public virtual async Task> GetEntityChangeListAsync( + string sorting = null, + int maxResultCount = 50, + int skipCount = 0, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + bool includeDetails = false, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + + var auditLogs = await query.As>() + .PageBy>(skipCount, maxResultCount) + .ToListAsync(GetCancellationToken(cancellationToken)); + + // TODO: Improve this specification + + return auditLogs + .SelectMany(x => x.EntityChanges.Where(y => + IsSatisfiedEntityChange(y, auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName))) + .AsQueryable().OrderBy(sorting ?? "changeTime desc").ToList(); + } + + public virtual async Task GetEntityChangeCountAsync( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null, + CancellationToken cancellationToken = default) + { + var query = GetEntityChangeListQuery(auditLogId, startTime, endTime, changeType, entityId, entityTypeFullName); + var count = await query.As>() + .LongCountAsync(GetCancellationToken(cancellationToken)); + + return count; + } + + protected virtual IQueryable GetEntityChangeListQuery( + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null) + { + return GetMongoQueryable() + .Where(x => x.EntityChanges != null) + .WhereIf(auditLogId.HasValue, e => e.Id == auditLogId) + .WhereIf(startTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= startTime)) + .WhereIf(endTime.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeTime >= endTime)) + .WhereIf(changeType.HasValue, e => e.EntityChanges.Any(ec => ec.ChangeType == changeType)) + .WhereIf(!string.IsNullOrWhiteSpace(entityId), e => e.EntityChanges.Any(ec => ec.EntityId == entityId)) + .WhereIf(!string.IsNullOrWhiteSpace(entityTypeFullName), + e => e.EntityChanges.Any(ec => ec.EntityTypeFullName == entityTypeFullName)); + } + + protected virtual bool IsSatisfiedEntityChange( + EntityChange entityChange, + Guid? auditLogId = null, + DateTime? startTime = null, + DateTime? endTime = null, + EntityChangeType? changeType = null, + string entityId = null, + string entityTypeFullName = null) + { + if (auditLogId != null && auditLogId != entityChange.AuditLogId) + { + return false; + } + + if (startTime != null && startTime.Value >= entityChange.ChangeTime) + { + return false; + } + + if (endTime != null && endTime.Value <= entityChange.ChangeTime) + { + return false; + } + + if (changeType != null && changeType != entityChange.ChangeType) + { + return false; + } + + if (entityId != null && entityId != entityChange.EntityId) + { + return false; + } + + if (entityTypeFullName != null && entityTypeFullName != entityChange.EntityTypeFullName) + { + return false; + } + + return true; + } } } diff --git a/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs index f88c10c079..7acf357e3b 100644 --- a/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs +++ b/modules/audit-logging/test/Volo.Abp.AuditLogging.TestBase/Volo/Abp/AuditLogging/AuditLogRepository_Tests.cs @@ -333,5 +333,330 @@ namespace Volo.Abp.AuditLogging results.Count.ShouldBe(1); results.Values.First().ShouldBe(50); // (45 + 55) / 2 } + + [Fact] + public async Task GetEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = DateTime.Now, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(); + entityChanges.ShouldNotBeNull(); + entityChanges.Count.ShouldBe(3); + + entityChanges.Single(x => x.ChangeType == EntityChangeType.Created).ShouldNotBeNull(); + entityChanges.Single(x => x.ChangeType == EntityChangeType.Deleted).ShouldNotBeNull(); + entityChanges.Single(x => x.ChangeType == EntityChangeType.Updated).ShouldNotBeNull(); + } + + [Fact] + public async Task GetOrderedEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var deletedEntityChangeTime = new DateTime(2000, 05, 05, 05, 05, 05); + var createdEntityChangeTime = new DateTime(2005, 05, 05, 05, 05, 05); + var updatedEntityChangeTime = new DateTime(2010, 05, 05, 05, 05, 05); + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = deletedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = createdEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = updatedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChangesDesc = await AuditLogRepository.GetEntityChangeListAsync(); + entityChangesDesc.ShouldNotBeNull(); + entityChangesDesc.Count.ShouldBe(3); + + entityChangesDesc.First().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Updated"); + entityChangesDesc.Last().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Deleted"); + + var entityChangesAsc = await AuditLogRepository.GetEntityChangeListAsync("changeTime asc"); + + entityChangesAsc.First().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Deleted"); + entityChangesAsc.Last().EntityTypeFullName.ShouldBe("Volo.Abp.AuditLogging.TestEntity_Updated"); + } + + [Fact] + public async Task GetSpecifiedEntityChangeListAsync() + { + // Arrange + var userId = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var userId2 = new Guid("4456fb0d-74cc-4807-9eee-23e551e6cb06"); + var ipAddress = "153.1.7.61"; + var firstComment = "first Comment"; + + var deletedEntityChangeTime = new DateTime(2000, 05, 05, 05, 05, 05); + var createdEntityChangeTime = new DateTime(2005, 05, 05, 05, 05, 05); + var updatedEntityChangeTime = new DateTime(2010, 05, 05, 05, 05, 05); + + var log1 = new AuditLogInfo + { + UserId = userId, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + UserName = "Douglas", + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Deleted", + ChangeType = EntityChangeType.Deleted, + ChangeTime = deletedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + }, + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Created", + ChangeType = EntityChangeType.Created, + ChangeTime = createdEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + var log2 = new AuditLogInfo + { + UserId = userId2, + ImpersonatorUserId = Guid.NewGuid(), + ImpersonatorTenantId = Guid.NewGuid(), + ExecutionTime = DateTime.Today, + ExecutionDuration = 42, + ClientIpAddress = ipAddress, + ClientName = "MyDesktop", + BrowserInfo = "Chrome", + Comments = new List { firstComment, "Second Comment" }, + HttpStatusCode = (int?)HttpStatusCode.BadGateway, + EntityChanges = { + new EntityChangeInfo + { + EntityId = Guid.NewGuid().ToString(), + EntityTypeFullName = "Volo.Abp.AuditLogging.TestEntity_Updated", + ChangeType = EntityChangeType.Updated, + ChangeTime = updatedEntityChangeTime, + PropertyChanges = new List + { + new EntityPropertyChangeInfo + { + PropertyTypeFullName = typeof(string).FullName, + PropertyName = "Name", + NewValue = "New value", + OriginalValue = null + } + } + } + } + }; + + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log1)); + await AuditLogRepository.InsertAsync(new AuditLog(GuidGenerator, log2)); + + //Assert + var entityChanges = await AuditLogRepository.GetEntityChangeListAsync(changeType: EntityChangeType.Created); + entityChanges.ShouldNotBeNull(); + entityChanges.Count.ShouldBe(1); + } } }