From 1d4295431368fb47087188ad199091b67e280579 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 13:08:03 +0800 Subject: [PATCH 001/134] Batch publish events from outbox to the event bus --- .../PostgreSqlDbContextEventOutbox.cs | 2 +- .../DistributedEvents/DbContextEventOutbox.cs | 11 + .../Azure/AzureDistributedEventBus.cs | 9 +- .../Kafka/KafkaDistributedEventBus.cs | 9 +- .../RabbitMq/RabbitMqDistributedEventBus.cs | 509 ++++++++++-------- .../Rebus/RebusDistributedEventBus.cs | 9 +- .../Distributed/DistributedEventBusBase.cs | 236 ++++---- .../Abp/EventBus/Distributed/IEventOutbox.cs | 2 + .../Distributed/ISupportsEventBoxes.cs | 6 + .../EventBus/Distributed/InboxProcessor.cs | 4 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 21 +- .../MultipleOutgoingEventPublishResult.cs | 14 + .../MongoDbContextEventOutbox.cs | 14 + 13 files changed, 488 insertions(+), 358 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs index e5f2e0c4b6..7c5ef12fa7 100644 --- a/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.EntityFrameworkCore.PostgreSql/Volo/Abp/EntityFrameworkCore/DistributedEvents/PostgreSqlDbContextEventOutbox.cs @@ -13,7 +13,7 @@ public class PostgreSqlDbContextEventOutbox : DbContextEventOutbox : IDbContextEventOutbox ids) + { + var dbContext = (IHasEventOutbox)await DbContextProvider.GetDbContextAsync(); + var outgoingEvents = await dbContext.OutgoingEvents.Where(x => ids.Contains(x.Id)).ToListAsync(); + if (outgoingEvents.Any()) + { + dbContext.RemoveRange(outgoingEvents); + } + } } diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index 49add5df84..b253164161 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -85,12 +85,17 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await TriggerHandlersAsync(eventType, eventData); } - public override async Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) + public async override Task PublishFromOutboxAsync(OutgoingEventInfo outgoingEvent, OutboxConfig outboxConfig) { await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData); } - public override async Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { var eventType = _eventTypes.GetOrDefault(incomingEvent.EventName); if (eventType == null) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index ec1901161a..23ae188170 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -162,7 +162,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync( eventType, @@ -196,7 +196,12 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public override async Task ProcessFromInboxAsync( + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 1564cfd213..c65e9a60d1 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -16,225 +16,290 @@ using Volo.Abp.Threading; using Volo.Abp.Timing; using Volo.Abp.Uow; -namespace Volo.Abp.EventBus.RabbitMq; - -/* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) - */ -[Dependency(ReplaceServices = true)] -[ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] -public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency +namespace Volo.Abp.EventBus.RabbitMq { - protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } - protected IConnectionPool ConnectionPool { get; } - protected IRabbitMqSerializer Serializer { get; } - - //TODO: Accessing to the List may not be thread-safe! - protected ConcurrentDictionary> HandlerFactories { get; } - protected ConcurrentDictionary EventTypes { get; } - protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } - protected IRabbitMqMessageConsumer Consumer { get; private set; } - - public RabbitMqDistributedEventBus( - IOptions options, - IConnectionPool connectionPool, - IRabbitMqSerializer serializer, - IServiceScopeFactory serviceScopeFactory, - IOptions distributedEventBusOptions, - IRabbitMqMessageConsumerFactory messageConsumerFactory, - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - IGuidGenerator guidGenerator, - IClock clock) - : base( - serviceScopeFactory, - currentTenant, - unitOfWorkManager, - distributedEventBusOptions, - guidGenerator, - clock) + /* TODO: How to handle unsubscribe to unbind on RabbitMq (may not be possible for) + */ + [Dependency(ReplaceServices = true)] + [ExposeServices(typeof(IDistributedEventBus), typeof(RabbitMqDistributedEventBus))] + public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDependency { - ConnectionPool = connectionPool; - Serializer = serializer; - MessageConsumerFactory = messageConsumerFactory; - AbpRabbitMqEventBusOptions = options.Value; - - HandlerFactories = new ConcurrentDictionary>(); - EventTypes = new ConcurrentDictionary(); - } + protected AbpRabbitMqEventBusOptions AbpRabbitMqEventBusOptions { get; } + protected IConnectionPool ConnectionPool { get; } + protected IRabbitMqSerializer Serializer { get; } + + //TODO: Accessing to the List may not be thread-safe! + protected ConcurrentDictionary> HandlerFactories { get; } + protected ConcurrentDictionary EventTypes { get; } + protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } + protected IRabbitMqMessageConsumer Consumer { get; private set; } + + public RabbitMqDistributedEventBus( + IOptions options, + IConnectionPool connectionPool, + IRabbitMqSerializer serializer, + IServiceScopeFactory serviceScopeFactory, + IOptions distributedEventBusOptions, + IRabbitMqMessageConsumerFactory messageConsumerFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IGuidGenerator guidGenerator, + IClock clock) + : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager, + distributedEventBusOptions, + guidGenerator, + clock) + { + ConnectionPool = connectionPool; + Serializer = serializer; + MessageConsumerFactory = messageConsumerFactory; + AbpRabbitMqEventBusOptions = options.Value; - public void Initialize() - { - Consumer = MessageConsumerFactory.Create( - new ExchangeDeclareConfiguration( - AbpRabbitMqEventBusOptions.ExchangeName, - type: "direct", - durable: true - ), - new QueueDeclareConfiguration( - AbpRabbitMqEventBusOptions.ClientName, - durable: true, - exclusive: false, - autoDelete: false - ), - AbpRabbitMqEventBusOptions.ConnectionName - ); - - Consumer.OnMessageReceived(ProcessEventAsync); - - SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); - } + HandlerFactories = new ConcurrentDictionary>(); + EventTypes = new ConcurrentDictionary(); + } - private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) - { - var eventName = ea.RoutingKey; - var eventType = EventTypes.GetOrDefault(eventName); - if (eventType == null) + public void Initialize() { - return; + Consumer = MessageConsumerFactory.Create( + new ExchangeDeclareConfiguration( + AbpRabbitMqEventBusOptions.ExchangeName, + type: "direct", + durable: true + ), + new QueueDeclareConfiguration( + AbpRabbitMqEventBusOptions.ClientName, + durable: true, + exclusive: false, + autoDelete: false + ), + AbpRabbitMqEventBusOptions.ConnectionName + ); + + Consumer.OnMessageReceived(ProcessEventAsync); + + SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); } - var eventBytes = ea.Body.ToArray(); + private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) + { + var eventName = ea.RoutingKey; + var eventType = EventTypes.GetOrDefault(eventName); + if (eventType == null) + { + return; + } + + var eventBytes = ea.Body.ToArray(); + + if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventBytes)) + { + return; + } + + var eventData = Serializer.Deserialize(eventBytes, eventType); + + await TriggerHandlersAsync(eventType, eventData); + } - if (await AddToInboxAsync(ea.BasicProperties.MessageId, eventName, eventType, eventBytes)) + public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) { - return; + var handlerFactories = GetOrCreateHandlerFactories(eventType); + + if (factory.IsInFactories(handlerFactories)) + { + return NullDisposable.Instance; + } + + handlerFactories.Add(factory); + + if (handlerFactories.Count == 1) //TODO: Multi-threading! + { + Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType)); + } + + return new EventHandlerFactoryUnregistrar(this, eventType, factory); } - var eventData = Serializer.Deserialize(eventBytes, eventType); + /// + public override void Unsubscribe(Func action) + { + Check.NotNull(action, nameof(action)); - await TriggerHandlersAsync(eventType, eventData); - } + GetOrCreateHandlerFactories(typeof(TEvent)) + .Locking(factories => + { + factories.RemoveAll( + factory => + { + var singleInstanceFactory = factory as SingleInstanceHandlerFactory; + if (singleInstanceFactory == null) + { + return false; + } + + var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; + if (actionHandler == null) + { + return false; + } + + return actionHandler.Action == action; + }); + }); + } - public override IDisposable Subscribe(Type eventType, IEventHandlerFactory factory) - { - var handlerFactories = GetOrCreateHandlerFactories(eventType); + /// + public override void Unsubscribe(Type eventType, IEventHandler handler) + { + GetOrCreateHandlerFactories(eventType) + .Locking(factories => + { + factories.RemoveAll( + factory => + factory is SingleInstanceHandlerFactory && + (factory as SingleInstanceHandlerFactory).HandlerInstance == handler + ); + }); + } - if (factory.IsInFactories(handlerFactories)) + /// + public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) { - return NullDisposable.Instance; + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); } - handlerFactories.Add(factory); + /// + public override void UnsubscribeAll(Type eventType) + { + GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); + } - if (handlerFactories.Count == 1) //TODO: Multi-threading! + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - Consumer.BindAsync(EventNameAttribute.GetNameOrDefault(eventType)); + await PublishAsync(eventType, eventData, null); } - return new EventHandlerFactoryUnregistrar(this, eventType, factory); - } + protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) + { + unitOfWork.AddOrReplaceDistributedEvent(eventRecord); + } - /// - public override void Unsubscribe(Func action) - { - Check.NotNull(action, nameof(action)); + public override Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig) + { + return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + } - GetOrCreateHandlerFactories(typeof(TEvent)) - .Locking(factories => + public async override Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig) + { + using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { - factories.RemoveAll( - factory => + var outgoingEventArray = outgoingEvents.ToArray(); + channel.ConfirmSelect(); + + var pendingConfirms = new ConcurrentDictionary(); + var failures = new ConcurrentBag(); + + void CleanPendingConfirms(ulong sequenceNumber, bool multiple, bool ack) + { + if (multiple) { - var singleInstanceFactory = factory as SingleInstanceHandlerFactory; - if (singleInstanceFactory == null) + var confirmed = pendingConfirms.Where(x => x.Key <= sequenceNumber); + foreach (var entry in confirmed) { - return false; + pendingConfirms.TryRemove(entry.Key, out var eventId); + + if (!ack) + { + failures.Add(eventId); + } } + } + else + { + pendingConfirms.TryRemove(sequenceNumber, out var eventId); - var actionHandler = singleInstanceFactory.HandlerInstance as ActionEventHandler; - if (actionHandler == null) + if (!ack) { - return false; + failures.Add(eventId); } + } + } - return actionHandler.Action == action; - }); - }); - } + foreach (var outgoingEvent in outgoingEventArray) + { + pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); + await PublishInternalAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); + } - /// - public override void Unsubscribe(Type eventType, IEventHandler handler) - { - GetOrCreateHandlerFactories(eventType) - .Locking(factories => - { - factories.RemoveAll( - factory => - factory is SingleInstanceHandlerFactory && - (factory as SingleInstanceHandlerFactory).HandlerInstance == handler - ); - }); - } + channel.BasicAcks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, true); + channel.BasicNacks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, false); - /// - public override void Unsubscribe(Type eventType, IEventHandlerFactory factory) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Remove(factory)); - } - - /// - public override void UnsubscribeAll(Type eventType) - { - GetOrCreateHandlerFactories(eventType).Locking(factories => factories.Clear()); - } + channel.WaitForConfirms(); - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) - { - await PublishAsync(eventType, eventData, null); - } - - protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) - { - unitOfWork.AddOrReplaceDistributedEvent(eventRecord); - } - - public override Task PublishFromOutboxAsync( - OutgoingEventInfo outgoingEvent, - OutboxConfig outboxConfig) - { - return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); - } + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); + } + } - public override async Task ProcessFromInboxAsync( - IncomingEventInfo incomingEvent, - InboxConfig inboxConfig) - { - var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); - if (eventType == null) + public async override Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig) { - return; + var eventType = EventTypes.GetOrDefault(incomingEvent.EventName); + if (eventType == null) + { + return; + } + + var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); + var exceptions = new List(); + await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); + if (exceptions.Any()) + { + ThrowOriginalExceptions(eventType, exceptions); + } } - var eventData = Serializer.Deserialize(incomingEvent.EventData, eventType); - var exceptions = new List(); - await TriggerHandlersAsync(eventType, eventData, exceptions, inboxConfig); - if (exceptions.Any()) + protected override byte[] Serialize(object eventData) { - ThrowOriginalExceptions(eventType, exceptions); + return Serializer.Serialize(eventData); } - } - protected override byte[] Serialize(object eventData) - { - return Serializer.Serialize(eventData); - } + public virtual Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) + { + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + var body = Serializer.Serialize(eventData); - public Task PublishAsync(Type eventType, object eventData, IBasicProperties properties, Dictionary headersArguments = null) - { - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - var body = Serializer.Serialize(eventData); + return PublishAsync(eventName, body, properties, headersArguments); + } - return PublishAsync(eventName, body, properties, headersArguments); - } + protected virtual Task PublishAsync( + string eventName, + byte[] body, + IBasicProperties properties, + Dictionary headersArguments = null, + Guid? eventId = null) + { + using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) + { + return PublishInternalAsync(channel, eventName, body, properties, headersArguments, eventId); + } + } - protected Task PublishAsync( - string eventName, - byte[] body, - IBasicProperties properties, - Dictionary headersArguments = null, - Guid? eventId = null) - { - using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) + protected virtual Task PublishInternalAsync( + IModel channel, + string eventName, + byte[] body, + IBasicProperties properties, + Dictionary headersArguments = null, + Guid? eventId = null) { channel.ExchangeDeclare( AbpRabbitMqEventBusOptions.ExchangeName, @@ -262,68 +327,68 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe basicProperties: properties, body: body ); - } - return Task.CompletedTask; - } - - private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) - { - if (headersArguments == null) - { - return; + return Task.CompletedTask; } - properties.Headers ??= new Dictionary(); - - foreach (var header in headersArguments) + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) { - properties.Headers[header.Key] = header.Value; - } - } - - private List GetOrCreateHandlerFactories(Type eventType) - { - return HandlerFactories.GetOrAdd( - eventType, - type => + if (headersArguments == null) { - var eventName = EventNameAttribute.GetNameOrDefault(type); - EventTypes[eventName] = type; - return new List(); + return; } - ); - } - protected override IEnumerable GetHandlerFactories(Type eventType) - { - var handlerFactoryList = new List(); + properties.Headers ??= new Dictionary(); - foreach (var handlerFactory in - HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) - { - handlerFactoryList.Add( - new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + foreach (var header in headersArguments) + { + properties.Headers[header.Key] = header.Value; + } } - return handlerFactoryList.ToArray(); - } - - private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) - { - //Should trigger same type - if (handlerEventType == targetEventType) + private List GetOrCreateHandlerFactories(Type eventType) { - return true; + return HandlerFactories.GetOrAdd( + eventType, + type => + { + var eventName = EventNameAttribute.GetNameOrDefault(type); + EventTypes[eventName] = type; + return new List(); + } + ); } - //TODO: Support inheritance? But it does not support on subscription to RabbitMq! - //Should trigger for inherited types - if (handlerEventType.IsAssignableFrom(targetEventType)) + protected override IEnumerable GetHandlerFactories(Type eventType) { - return true; + var handlerFactoryList = new List(); + + foreach (var handlerFactory in + HandlerFactories.Where(hf => ShouldTriggerEventForHandler(eventType, hf.Key))) + { + handlerFactoryList.Add( + new EventTypeWithEventHandlerFactories(handlerFactory.Key, handlerFactory.Value)); + } + + return handlerFactoryList.ToArray(); } - return false; + private static bool ShouldTriggerEventForHandler(Type targetEventType, Type handlerEventType) + { + //Should trigger same type + if (handlerEventType == targetEventType) + { + return true; + } + + //TODO: Support inheritance? But it does not support on subscription to RabbitMq! + //Should trigger for inherited types + if (handlerEventType.IsAssignableFrom(targetEventType)) + { + return true; + } + + return false; + } } } diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 0542a705eb..aad844a4d7 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -146,7 +146,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen await TriggerHandlersAsync(eventType, eventData); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); } @@ -210,7 +210,12 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public override async Task ProcessFromInboxAsync( + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + { + throw new NotImplementedException(); + } + + public async override Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig) { diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index dee9d1f7d3..178dea1340 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -8,156 +8,162 @@ using Volo.Abp.MultiTenancy; using Volo.Abp.Timing; using Volo.Abp.Uow; -namespace Volo.Abp.EventBus.Distributed; - -public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventBus, ISupportsEventBoxes +namespace Volo.Abp.EventBus.Distributed { - protected IGuidGenerator GuidGenerator { get; } - protected IClock Clock { get; } - protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } - - protected DistributedEventBusBase( - IServiceScopeFactory serviceScopeFactory, - ICurrentTenant currentTenant, - IUnitOfWorkManager unitOfWorkManager, - IOptions abpDistributedEventBusOptions, - IGuidGenerator guidGenerator, - IClock clock - ) : base( - serviceScopeFactory, - currentTenant, - unitOfWorkManager) - { - GuidGenerator = guidGenerator; - Clock = clock; - AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; - } - - public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventBus, ISupportsEventBoxes { - return Subscribe(typeof(TEvent), handler); - } + protected IGuidGenerator GuidGenerator { get; } + protected IClock Clock { get; } + protected AbpDistributedEventBusOptions AbpDistributedEventBusOptions { get; } + + protected DistributedEventBusBase( + IServiceScopeFactory serviceScopeFactory, + ICurrentTenant currentTenant, + IUnitOfWorkManager unitOfWorkManager, + IOptions abpDistributedEventBusOptions, + IGuidGenerator guidGenerator, + IClock clock + ) : base( + serviceScopeFactory, + currentTenant, + unitOfWorkManager) + { + GuidGenerator = guidGenerator; + Clock = clock; + AbpDistributedEventBusOptions = abpDistributedEventBusOptions.Value; + } - public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) - { - return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true); - } + public IDisposable Subscribe(IDistributedEventHandler handler) where TEvent : class + { + return Subscribe(typeof(TEvent), handler); + } - public Task PublishAsync( - TEvent eventData, - bool onUnitOfWorkComplete = true, - bool useOutbox = true) - where TEvent : class - { - return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); - } + public override Task PublishAsync(Type eventType, object eventData, bool onUnitOfWorkComplete = true) + { + return PublishAsync(eventType, eventData, onUnitOfWorkComplete, useOutbox: true); + } - public async Task PublishAsync( - Type eventType, - object eventData, - bool onUnitOfWorkComplete = true, - bool useOutbox = true) - { - if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) + public Task PublishAsync( + TEvent eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) + where TEvent : class { - AddToUnitOfWork( - UnitOfWorkManager.Current, - new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) - ); - return; + return PublishAsync(typeof(TEvent), eventData, onUnitOfWorkComplete, useOutbox); } - if (useOutbox) + public async Task PublishAsync( + Type eventType, + object eventData, + bool onUnitOfWorkComplete = true, + bool useOutbox = true) { - if (await AddToOutboxAsync(eventType, eventData)) + if (onUnitOfWorkComplete && UnitOfWorkManager.Current != null) { + AddToUnitOfWork( + UnitOfWorkManager.Current, + new UnitOfWorkEventRecord(eventType, eventData, EventOrderGenerator.GetNext(), useOutbox) + ); return; } - } - await PublishToEventBusAsync(eventType, eventData); - } + if (useOutbox) + { + if (await AddToOutboxAsync(eventType, eventData)) + { + return; + } + } - public abstract Task PublishFromOutboxAsync( - OutgoingEventInfo outgoingEvent, - OutboxConfig outboxConfig - ); + await PublishToEventBusAsync(eventType, eventData); + } - public abstract Task ProcessFromInboxAsync( - IncomingEventInfo incomingEvent, - InboxConfig inboxConfig); + public abstract Task PublishFromOutboxAsync( + OutgoingEventInfo outgoingEvent, + OutboxConfig outboxConfig + ); - private async Task AddToOutboxAsync(Type eventType, object eventData) - { - var unitOfWork = UnitOfWorkManager.Current; - if (unitOfWork == null) - { - return false; - } + public abstract Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig + ); + + public abstract Task ProcessFromInboxAsync( + IncomingEventInfo incomingEvent, + InboxConfig inboxConfig); - foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values) + private async Task AddToOutboxAsync(Type eventType, object eventData) { - if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) + var unitOfWork = UnitOfWorkManager.Current; + if (unitOfWork == null) { - var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); - var eventName = EventNameAttribute.GetNameOrDefault(eventType); - await eventOutbox.EnqueueAsync( - new OutgoingEventInfo( - GuidGenerator.Create(), - eventName, - Serialize(eventData), - Clock.Now - ) - ); - return true; + return false; } - } - return false; - } + foreach (var outboxConfig in AbpDistributedEventBusOptions.Outboxes.Values) + { + if (outboxConfig.Selector == null || outboxConfig.Selector(eventType)) + { + var eventOutbox = (IEventOutbox)unitOfWork.ServiceProvider.GetRequiredService(outboxConfig.ImplementationType); + var eventName = EventNameAttribute.GetNameOrDefault(eventType); + await eventOutbox.EnqueueAsync( + new OutgoingEventInfo( + GuidGenerator.Create(), + eventName, + Serialize(eventData), + Clock.Now + ) + ); + return true; + } + } - protected async Task AddToInboxAsync( - string messageId, - string eventName, - Type eventType, - byte[] eventBytes) - { - if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) - { return false; } - using (var scope = ServiceScopeFactory.CreateScope()) + protected async Task AddToInboxAsync( + string messageId, + string eventName, + Type eventType, + byte[] eventBytes) { - foreach (var inboxConfig in AbpDistributedEventBusOptions.Inboxes.Values) + if (AbpDistributedEventBusOptions.Inboxes.Count <= 0) { - if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) - { - var eventInbox = (IEventInbox)scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + return false; + } - if (!messageId.IsNullOrEmpty()) + using (var scope = ServiceScopeFactory.CreateScope()) + { + foreach (var inboxConfig in AbpDistributedEventBusOptions.Inboxes.Values) + { + if (inboxConfig.EventSelector == null || inboxConfig.EventSelector(eventType)) { - if (await eventInbox.ExistsByMessageIdAsync(messageId)) + var eventInbox = (IEventInbox) scope.ServiceProvider.GetRequiredService(inboxConfig.ImplementationType); + + if (!messageId.IsNullOrEmpty()) { - continue; + if (await eventInbox.ExistsByMessageIdAsync(messageId)) + { + continue; + } } - } - await eventInbox.EnqueueAsync( - new IncomingEventInfo( - GuidGenerator.Create(), - messageId, - eventName, - eventBytes, - Clock.Now - ) - ); + await eventInbox.EnqueueAsync( + new IncomingEventInfo( + GuidGenerator.Create(), + messageId, + eventName, + eventBytes, + Clock.Now + ) + ); + } } } + + return true; } - return true; + protected abstract byte[] Serialize(object eventData); } - - protected abstract byte[] Serialize(object eventData); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs index 6ecefcf002..018747945c 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/IEventOutbox.cs @@ -12,4 +12,6 @@ public interface IEventOutbox Task> GetWaitingEventsAsync(int maxCount, CancellationToken cancellationToken = default); Task DeleteAsync(Guid id); + + Task DeleteManyAsync(IEnumerable ids); } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs index 37405fb58a..7c23be5ecd 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs @@ -1,3 +1,4 @@ +using System.Collections.Generic; using System.Threading.Tasks; namespace Volo.Abp.EventBus.Distributed; @@ -9,6 +10,11 @@ public interface ISupportsEventBoxes OutboxConfig outboxConfig ); + Task PublishManyFromOutboxAsync( + IEnumerable outgoingEvents, + OutboxConfig outboxConfig + ); + Task ProcessFromInboxAsync( IncomingEventInfo incomingEvent, InboxConfig inboxConfig diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index 50c1a4e009..0d22245d13 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -98,7 +98,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency break; } - Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); + //Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); foreach (var waitingEvent in waitingEvents) { @@ -113,7 +113,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency await uow.CompleteAsync(); } - Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); + //Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 6ed60ba34c..d42110e4ea 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -1,4 +1,6 @@ using System; +using System.Diagnostics; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; @@ -76,25 +78,20 @@ public class OutboxSender : IOutboxSender, ITransientDependency while (true) { var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); - if (waitingEvents.Count <= 0) + if (waitingEvents.Count < 1000) { break; } Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - foreach (var waitingEvent in waitingEvents) - { - await DistributedEventBus - .AsSupportsEventBoxes() - .PublishFromOutboxAsync( - waitingEvent, - OutboxConfig - ); + var result = await DistributedEventBus + .AsSupportsEventBoxes() + .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); - await Outbox.DeleteAsync(waitingEvent.Id); - Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); - } + await Outbox.DeleteManyAsync(result.PublishedOutgoingEvents.Select(x => x.Id).ToArray()); + + Logger.LogInformation($"Sent {result.PublishedOutgoingEvents.Count} events to message broker"); } } else diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs new file mode 100644 index 0000000000..515dec1ae6 --- /dev/null +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.EventBus; + +public class MultipleOutgoingEventPublishResult +{ + public IReadOnlyList PublishedOutgoingEvents { get; } + + public MultipleOutgoingEventPublishResult(IReadOnlyList outgoingEvents) + { + PublishedOutgoingEvents = outgoingEvents; + } +} diff --git a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs index 1f3ae999b0..fae0709953 100644 --- a/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs +++ b/framework/src/Volo.Abp.MongoDB/Volo/Abp/MongoDB/DistributedEvents/MongoDbContextEventOutbox.cs @@ -68,4 +68,18 @@ public class MongoDbContextEventOutbox : IMongoDbContextEventOu await dbContext.OutgoingEvents.DeleteOneAsync(x => x.Id.Equals(id)); } } + + [UnitOfWork] + public async Task DeleteManyAsync(IEnumerable ids) + { + var dbContext = (IHasEventOutbox)await MongoDbContextProvider.GetDbContextAsync(); + if (dbContext.SessionHandle != null) + { + await dbContext.OutgoingEvents.DeleteManyAsync(dbContext.SessionHandle, x => ids.Contains(x.Id)); + } + else + { + await dbContext.OutgoingEvents.DeleteManyAsync(x => ids.Contains(x.Id)); + } + } } From 5ee59194637c187ecfee34aab7163c801e182044 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 20:24:39 +0800 Subject: [PATCH 002/134] Batch publish events from outbox to the kafka event bus --- .../Kafka/KafkaDistributedEventBus.cs | 44 ++++++++++++++----- .../Abp/EventBus/Kafka/MessageExtensions.cs | 18 ++++++++ .../EventBus/Distributed/InboxProcessor.cs | 4 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 2 +- 4 files changed, 55 insertions(+), 13 deletions(-) create mode 100644 framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index 23ae188170..aef7662095 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -77,12 +77,7 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return; } - string messageId = null; - - if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) - { - messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); - } + var messageId = message.GetMessageId(); if (await AddToInboxAsync(messageId, eventName, eventType, message.Value)) { @@ -196,9 +191,38 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var pendingConfirms = new ConcurrentDictionary(); + var outgoingEventArray = outgoingEvents.ToArray(); + + var tasks = new List(); + foreach (var outgoingEvent in outgoingEventArray) + { + var messageId = outgoingEvent.Id.ToString("N"); + pendingConfirms.TryAdd(messageId, outgoingEvent.Id); + + var task = PublishAsync( + AbpKafkaEventBusOptions.TopicName, + outgoingEvent.EventName, + outgoingEvent.EventData, + new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} }, + null + ); + + tasks.Add(task.ContinueWith(t => + { + if (!t.IsFaulted) + { + var message = t.Result.Message; + pendingConfirms.TryRemove(message.GetMessageId(), out _); + } + })); + } + + await Task.WhenAll(tasks); + + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !pendingConfirms.Select(p => p.Value).Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync( @@ -244,13 +268,13 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishAsync(topicName, eventName, body, headers, headersArguments); } - private async Task PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) + private Task> PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); SetEventMessageHeaders(headers, headersArguments); - await producer.ProduceAsync( + return producer.ProduceAsync( topicName, new Message { diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs new file mode 100644 index 0000000000..17a80ec87c --- /dev/null +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/MessageExtensions.cs @@ -0,0 +1,18 @@ +using Confluent.Kafka; + +namespace Volo.Abp.EventBus.Kafka; + +public static class MessageExtensions +{ + public static string GetMessageId(this Message message) + { + string messageId = null; + + if (message.Headers.TryGetLastBytes("messageId", out var messageIdBytes)) + { + messageId = System.Text.Encoding.UTF8.GetString(messageIdBytes); + } + + return messageId; + } +} diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs index 0d22245d13..50c1a4e009 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/InboxProcessor.cs @@ -98,7 +98,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency break; } - //Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); + Logger.LogInformation($"Found {waitingEvents.Count} events in the inbox."); foreach (var waitingEvent in waitingEvents) { @@ -113,7 +113,7 @@ public class InboxProcessor : IInboxProcessor, ITransientDependency await uow.CompleteAsync(); } - //Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); + Logger.LogInformation($"Processed the incoming event with id = {waitingEvent.Id:N}"); } } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index d42110e4ea..17dec0a810 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -78,7 +78,7 @@ public class OutboxSender : IOutboxSender, ITransientDependency while (true) { var waitingEvents = await Outbox.GetWaitingEventsAsync(EventBusBoxesOptions.OutboxWaitingEventMaxCount, StoppingToken); - if (waitingEvents.Count < 1000) + if (waitingEvents.Count <= 0) { break; } From f36c44fc78203c4f520cfce62d99fce82ecb24c4 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 20:50:17 +0800 Subject: [PATCH 003/134] Batch publish events from outbox to the azure event bus --- .../Azure/AzureDistributedEventBus.cs | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index b253164161..a253a8ba96 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -90,9 +90,38 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var outgoingEventArray = outgoingEvents.ToArray(); + var failures = new List(); + + var publisher = await _publisherPool.GetAsync( + _options.TopicName, + _options.ConnectionName); + + using var messageBatch = await publisher.CreateMessageBatchAsync(); + + var failed = false; + foreach (var outgoingEvent in outgoingEventArray) + { + if (failed) + { + failures.Add(outgoingEvent.Id); + continue; + } + + var body = _serializer.Serialize(outgoingEvent.EventData); + + if (!messageBatch.TryAddMessage(new ServiceBusMessage(body) { Subject = outgoingEvent.EventName })) + { + failed = true; + failures.Add(outgoingEvent.Id); + } + } + + await publisher.SendMessagesAsync(messageBatch); + + return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) @@ -183,7 +212,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen .Locking(factories => factories.Clear()); } - protected override async Task PublishToEventBusAsync(Type eventType, object eventData) + protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { await PublishAsync(EventNameAttribute.GetNameOrDefault(eventType), eventData); } From 9ec54780d475ca56c2c6086039a9bb4d42a4371c Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Sun, 9 Jan 2022 21:18:48 +0800 Subject: [PATCH 004/134] Batch publish events from outbox to the rebus event bus --- .../EventBus/Rebus/AbpRebusEventBusOptions.cs | 13 +--------- .../Rebus/RebusDistributedEventBus.cs | 24 ++++++++++++++++--- 2 files changed, 22 insertions(+), 15 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs index 5238cdd8bb..4c39b9bf1d 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/AbpRebusEventBusOptions.cs @@ -20,24 +20,13 @@ public class AbpRebusEventBusOptions } private Action _configurer; - [NotNull] - public Func Publish { - get => _publish; - set => _publish = Check.NotNull(value, nameof(value)); - } - private Func _publish; + public Func Publish { get; set; } public AbpRebusEventBusOptions() { - _publish = DefaultPublish; _configurer = DefaultConfigure; } - private async Task DefaultPublish(IBus bus, Type eventType, object eventData) - { - await bus.Advanced.Routing.Send(InputQueueName, eventData); - } - private void DefaultConfigure(RebusConfigurer configure) { configure.Subscriptions(s => s.StoreInMemory()); diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index aad844a4d7..fc6a0667af 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -148,7 +148,18 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen protected async override Task PublishToEventBusAsync(Type eventType, object eventData) { - await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); + await PublishAsync(eventType, eventData); + } + + protected virtual async Task PublishAsync(Type eventType, object eventData) + { + if (AbpRebusEventBusOptions.Publish != null) + { + await AbpRebusEventBusOptions.Publish(Rebus, eventType, eventData); + return; + } + + await Rebus.Advanced.Routing.Send(AbpRebusEventBusOptions.InputQueueName, eventData); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -210,9 +221,16 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - throw new NotImplementedException(); + var outgoingEventArray = outgoingEvents.ToArray(); + + foreach (var outgoingEvent in outgoingEventArray) + { + await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + } + + return new MultipleOutgoingEventPublishResult(outgoingEventArray); } public async override Task ProcessFromInboxAsync( From 325f82a3d7a7448f7e7b3b73438f965810b93500 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 10 Jan 2022 17:11:06 +0800 Subject: [PATCH 005/134] Update AzureDistributedEventBus --- .../Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index ffcf6df8c7..5e91a224c5 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -110,9 +110,7 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen continue; } - var body = _serializer.Serialize(outgoingEvent.EventData); - - if (!messageBatch.TryAddMessage(new ServiceBusMessage(body) { Subject = outgoingEvent.EventName })) + if (!messageBatch.TryAddMessage(new ServiceBusMessage(outgoingEvent.EventData) { Subject = outgoingEvent.EventName })) { failed = true; failures.Add(outgoingEvent.Id); From 3364e7e2273b395b87a5395513a3b5585ac5ddf9 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 14 Jan 2022 14:28:30 +0800 Subject: [PATCH 006/134] Update RabbitMqDistributedEventBus.cs --- .../Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index a9cfb4dc23..a9d6a42207 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -238,7 +238,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe foreach (var outgoingEvent in outgoingEventArray) { pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); - await PublishInternalAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, + await PublishAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); } @@ -293,11 +293,11 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe { using (var channel = ConnectionPool.Get(AbpRabbitMqEventBusOptions.ConnectionName).CreateModel()) { - return PublishInternalAsync(channel, eventName, body, properties, headersArguments, eventId); + return PublishAsync(channel, eventName, body, properties, headersArguments, eventId); } } - protected virtual Task PublishInternalAsync( + protected virtual Task PublishAsync( IModel channel, string eventName, byte[] body, From 0c45dccd54f9c70b6fb2008df81b22655a5f2d20 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 25 Jan 2022 14:17:09 +0800 Subject: [PATCH 007/134] Update Rebus --- .../Abp/EventBus/Rebus/RebusDistributedEventBus.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index 81611e180a..a5cac31235 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -7,6 +7,7 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Rebus.Bus; using Rebus.Pipeline; +using Rebus.Transport; using Volo.Abp.DependencyInjection; using Volo.Abp.EventBus.Distributed; using Volo.Abp.Guids; @@ -227,9 +228,14 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { var outgoingEventArray = outgoingEvents.ToArray(); - foreach (var outgoingEvent in outgoingEventArray) + using (var scope = new RebusTransactionScope()) { - await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + foreach (var outgoingEvent in outgoingEventArray) + { + await PublishFromOutboxAsync(outgoingEvent, outboxConfig); + } + + await scope.CompleteAsync(); } return new MultipleOutgoingEventPublishResult(outgoingEventArray); From 282d9ada01463503afbfce2bcbe34f68b9cb48db Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Tue, 25 Jan 2022 14:20:56 +0800 Subject: [PATCH 008/134] Update RabbitMqMessageConsumer.cs --- .../Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs index 049e8da712..8ce184295a 100644 --- a/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs +++ b/framework/src/Volo.Abp.RabbitMQ/Volo/Abp/RabbitMQ/RabbitMqMessageConsumer.cs @@ -176,14 +176,6 @@ public class RabbitMqMessageConsumer : IRabbitMqMessageConsumer, ITransientDepen } catch (Exception ex) { - if (ex is OperationInterruptedException operationInterruptedException && - operationInterruptedException.ShutdownReason.ReplyCode == 406 && - operationInterruptedException.Message.Contains("arg 'x-dead-letter-exchange'")) - { - Logger.LogException(ex, LogLevel.Warning); - await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); - } - Logger.LogException(ex, LogLevel.Warning); await ExceptionNotifier.NotifyAsync(ex, logLevel: LogLevel.Warning); } From b73781874aaa245505264da1dabe7366314176a2 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Fri, 11 Mar 2022 15:43:05 +0300 Subject: [PATCH 009/134] feat: Convert short date,time and datetime functions to pipe #11909 --- .../packages/core/src/lib/core.module.ts | 10 ++++++ .../packages/core/src/lib/pipes/index.ts | 3 ++ .../src/lib/pipes/short-date-time.pipe.ts | 32 +++++++++++++++++++ .../core/src/lib/pipes/short-date.pipe.ts | 32 +++++++++++++++++++ .../core/src/lib/pipes/short-time.pipe.ts | 32 +++++++++++++++++++ 5 files changed, 109 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts create mode 100644 npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts create mode 100644 npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts diff --git a/npm/ng-packs/packages/core/src/lib/core.module.ts b/npm/ng-packs/packages/core/src/lib/core.module.ts index 1de96ef0ba..238ed9bb02 100644 --- a/npm/ng-packs/packages/core/src/lib/core.module.ts +++ b/npm/ng-packs/packages/core/src/lib/core.module.ts @@ -34,6 +34,9 @@ import { TENANT_KEY } from './tokens/tenant-key.token'; import { noop } from './utils/common-utils'; import './utils/date-extensions'; import { getInitialData, localeInitializer } from './utils/initial-utils'; +import { ShortDateTimePipe } from './pipes/short-date-time.pipe'; +import { ShortTimePipe } from './pipes/short-time.pipe'; +import { ShortDatePipe } from './pipes/short-date.pipe'; export function storageFactory(): OAuthStorage { return oAuthStorage; @@ -67,6 +70,9 @@ export function storageFactory(): OAuthStorage { SortPipe, StopPropagationDirective, ToInjectorPipe, + ShortDateTimePipe, + ShortTimePipe, + ShortDatePipe ], imports: [ OAuthModule, @@ -92,6 +98,10 @@ export function storageFactory(): OAuthStorage { SortPipe, StopPropagationDirective, ToInjectorPipe, + ShortDateTimePipe, + ShortTimePipe, + ShortDatePipe + ], providers: [LocalizationPipe] }) diff --git a/npm/ng-packs/packages/core/src/lib/pipes/index.ts b/npm/ng-packs/packages/core/src/lib/pipes/index.ts index 31e0a76593..71ce383cd3 100644 --- a/npm/ng-packs/packages/core/src/lib/pipes/index.ts +++ b/npm/ng-packs/packages/core/src/lib/pipes/index.ts @@ -1,3 +1,6 @@ export * from './localization.pipe'; export * from './sort.pipe'; export * from './to-injector.pipe'; +export * from './short-date.pipe'; +export * from './short-time.pipe'; +export * from './short-date-time.pipe'; diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts new file mode 100644 index 0000000000..ef12be47d8 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-date-time.pipe.ts @@ -0,0 +1,32 @@ +import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; +import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { ConfigStateService } from '../services'; +import { getShortDateShortTimeFormat } from '../utils/date-utils'; + +@Pipe({ + name: 'shortDateTime', + pure: true, +}) +export class ShortDateTimePipe extends DatePipe implements PipeTransform { + + constructor(private configStateService: ConfigStateService, + @Inject(LOCALE_ID) locale: string, + @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string|null + ) { + super(locale, defaultTimezone) + } + + transform(value: Date | string | number, format?: string, timezone?: string, locale?: string): string | null; + transform(value: null | undefined, format?: string, timezone?: string, locale?: string): null; + transform( + value: string|number|Date|null|undefined, timezone?: string, + locale?: string): string|null { + + const format = getShortDateShortTimeFormat(this.configStateService); + return super.transform(value,format,timezone,locale) + } + + +} + + diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts new file mode 100644 index 0000000000..30c9bd9323 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-date.pipe.ts @@ -0,0 +1,32 @@ +import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; +import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { ConfigStateService } from '../services'; +import { getShortDateFormat } from '../utils/date-utils'; + +@Pipe({ + name: 'shortDate', + pure: true, +}) +export class ShortDatePipe extends DatePipe implements PipeTransform { + + constructor(private configStateService: ConfigStateService, + @Inject(LOCALE_ID) locale: string, + @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string|null + ) { + super(locale, defaultTimezone) + } + + transform(value: Date | string | number, format?: string, timezone?: string, locale?: string): string | null; + transform(value: null | undefined, format?: string, timezone?: string, locale?: string): null; + transform( + value: string|number|Date|null|undefined, timezone?: string, + locale?: string): string|null { + + const format = getShortDateFormat(this.configStateService); + return super.transform(value,format,timezone,locale) + } + + +} + + diff --git a/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts b/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts new file mode 100644 index 0000000000..cf0450673d --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/pipes/short-time.pipe.ts @@ -0,0 +1,32 @@ +import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE } from '@angular/common'; +import { Inject, LOCALE_ID, Optional, Pipe, PipeTransform } from '@angular/core'; +import { ConfigStateService } from '../services'; +import { getShortTimeFormat } from '../utils/date-utils'; + +@Pipe({ + name: 'shortTime', + pure: true, +}) +export class ShortTimePipe extends DatePipe implements PipeTransform { + + constructor(private configStateService: ConfigStateService, + @Inject(LOCALE_ID) locale: string, + @Inject(DATE_PIPE_DEFAULT_TIMEZONE) @Optional() defaultTimezone?: string|null + ) { + super(locale, defaultTimezone) + } + + transform(value: Date | string | number, format?: string, timezone?: string, locale?: string): string | null; + transform(value: null | undefined, format?: string, timezone?: string, locale?: string): null; + transform( + value: string|number|Date|null|undefined, timezone?: string, + locale?: string): string|null { + + const format = getShortTimeFormat(this.configStateService); + return super.transform(value,format,timezone,locale) + } + + +} + + From 7221600e75719a494447953b789e79539fb7f10e Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Thu, 17 Mar 2022 21:55:33 +0800 Subject: [PATCH 010/134] Refactor --- .../Azure/AzureDistributedEventBus.cs | 15 +--- .../Kafka/KafkaDistributedEventBus.cs | 76 ++++++++++++------- .../RabbitMq/RabbitMqDistributedEventBus.cs | 69 +++++++---------- .../Rebus/RebusDistributedEventBus.cs | 8 +- .../Distributed/AbpEventBusBoxesOptions.cs | 6 ++ .../Distributed/DistributedEventBusBase.cs | 2 +- .../Distributed/ISupportsEventBoxes.cs | 2 +- .../Abp/EventBus/Distributed/OutboxSender.cs | 46 +++++++++-- .../MultipleOutgoingEventPublishResult.cs | 14 ---- .../Volo/Abp/Kafka/ConsumerPool.cs | 1 + .../Volo/Abp/Kafka/KafkaMessageConsumer.cs | 21 +++-- .../Volo/Abp/Kafka/ProducerPool.cs | 31 +++++--- 12 files changed, 161 insertions(+), 130 deletions(-) delete mode 100644 framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs diff --git a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs index add6670bd4..fa6e155081 100644 --- a/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Azure/Volo/Abp/EventBus/Azure/AzureDistributedEventBus.cs @@ -92,10 +92,9 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen await PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, outgoingEvent.Id); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var outgoingEventArray = outgoingEvents.ToArray(); - var failures = new List(); var publisher = await _publisherPool.GetAsync( _options.TopicName, @@ -103,25 +102,15 @@ public class AzureDistributedEventBus : DistributedEventBusBase, ISingletonDepen using var messageBatch = await publisher.CreateMessageBatchAsync(); - var failed = false; foreach (var outgoingEvent in outgoingEventArray) { - if (failed) - { - failures.Add(outgoingEvent.Id); - continue; - } - if (!messageBatch.TryAddMessage(new ServiceBusMessage(outgoingEvent.EventData) { Subject = outgoingEvent.EventName })) { - failed = true; - failures.Add(outgoingEvent.Id); + throw new AbpException("The message is too large to fit in the batch. Set AbpEventBusBoxesOptions.OutboxWaitingEventMaxCount to reduce the number"); } } await publisher.SendMessagesAsync(messageBatch); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); } public async override Task ProcessFromInboxAsync(IncomingEventInfo incomingEvent, InboxConfig inboxConfig) diff --git a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs index e271ad502c..4dba728c51 100644 --- a/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Kafka/Volo/Abp/EventBus/Kafka/KafkaDistributedEventBus.cs @@ -193,38 +193,40 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen ); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { - var pendingConfirms = new ConcurrentDictionary(); + var producer = ProducerPool.Get(); var outgoingEventArray = outgoingEvents.ToArray(); - - var tasks = new List(); - foreach (var outgoingEvent in outgoingEventArray) + producer.BeginTransaction(); + try { - var messageId = outgoingEvent.Id.ToString("N"); - pendingConfirms.TryAdd(messageId, outgoingEvent.Id); - - var task = PublishAsync( - AbpKafkaEventBusOptions.TopicName, - outgoingEvent.EventName, - outgoingEvent.EventData, - new Headers { { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} }, - null - ); - - tasks.Add(task.ContinueWith(t => - { - if (!t.IsFaulted) - { - var message = t.Result.Message; - pendingConfirms.TryRemove(message.GetMessageId(), out _); - } - })); + foreach (var outgoingEvent in outgoingEventArray) + { + var messageId = outgoingEvent.Id.ToString("N"); + var headers = new Headers + { + { "messageId", System.Text.Encoding.UTF8.GetBytes(messageId)} + }; + + producer.Produce( + AbpKafkaEventBusOptions.TopicName, + new Message + { + Key = outgoingEvent.EventName, + Value = outgoingEvent.EventData, + Headers = headers + }); + } + + producer.CommitTransaction(); } - - await Task.WhenAll(tasks); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !pendingConfirms.Select(p => p.Value).Contains(x.Id)).ToList()); + catch (Exception e) + { + producer.AbortTransaction(); + throw; + } + + return Task.CompletedTask; } public async override Task ProcessFromInboxAsync( @@ -270,10 +272,26 @@ public class KafkaDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishAsync(topicName, eventName, body, headers, headersArguments); } - private Task> PublishAsync(string topicName, string eventName, byte[] body, Headers headers, Dictionary headersArguments) + private Task> PublishAsync( + string topicName, + string eventName, + byte[] body, + Headers headers, + Dictionary headersArguments) { var producer = ProducerPool.Get(AbpKafkaEventBusOptions.ConnectionName); + return PublishAsync(producer, topicName, eventName, body, headers, headersArguments); + } + + private Task> PublishAsync( + IProducer producer, + string topicName, + string eventName, + byte[] body, + Headers headers, + Dictionary headersArguments) + { SetEventMessageHeaders(headers, headersArguments); return producer.ProduceAsync( diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index 9b3bc5b76e..b9210a6eb8 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -34,6 +34,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe protected IRabbitMqMessageConsumerFactory MessageConsumerFactory { get; } protected IRabbitMqMessageConsumer Consumer { get; private set; } + private bool _exchangeCreated; + public RabbitMqDistributedEventBus( IOptions options, IConnectionPool connectionPool, @@ -84,6 +86,8 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); + + } private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) @@ -197,7 +201,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe return PublishAsync(outgoingEvent.EventName, outgoingEvent.EventData, null, eventId: outgoingEvent.Id); } - public async override Task PublishManyFromOutboxAsync( + public async override Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig) { @@ -206,48 +210,17 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe var outgoingEventArray = outgoingEvents.ToArray(); channel.ConfirmSelect(); - var pendingConfirms = new ConcurrentDictionary(); - var failures = new ConcurrentBag(); - - void CleanPendingConfirms(ulong sequenceNumber, bool multiple, bool ack) - { - if (multiple) - { - var confirmed = pendingConfirms.Where(x => x.Key <= sequenceNumber); - foreach (var entry in confirmed) - { - pendingConfirms.TryRemove(entry.Key, out var eventId); - - if (!ack) - { - failures.Add(eventId); - } - } - } - else - { - pendingConfirms.TryRemove(sequenceNumber, out var eventId); - - if (!ack) - { - failures.Add(eventId); - } - } - } - foreach (var outgoingEvent in outgoingEventArray) { - pendingConfirms.TryAdd(channel.NextPublishSeqNo, outgoingEvent.Id); - await PublishAsync(channel, outgoingEvent.EventName, outgoingEvent.EventData, null, + await PublishAsync( + channel, + outgoingEvent.EventName, + outgoingEvent.EventData, + properties: null, eventId: outgoingEvent.Id); } - channel.BasicAcks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, true); - channel.BasicNacks += (_, ea) => CleanPendingConfirms(ea.DeliveryTag, ea.Multiple, false); - - channel.WaitForConfirms(); - - return new MultipleOutgoingEventPublishResult(outgoingEventArray.Where(x => !failures.Contains(x.Id)).ToList()); + channel.WaitForConfirmsOrDie(); } } @@ -308,11 +281,7 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Dictionary headersArguments = null, Guid? eventId = null) { - channel.ExchangeDeclare( - AbpRabbitMqEventBusOptions.ExchangeName, - "direct", - durable: true - ); + EnsureExchangeExists(channel); if (properties == null) { @@ -338,6 +307,20 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe return Task.CompletedTask; } + private void EnsureExchangeExists(IModel channel) + { + if (_exchangeCreated) + { + return; + } + + channel.ExchangeDeclare( + AbpRabbitMqEventBusOptions.ExchangeName, + "direct", + durable: true + ); + } + private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) { if (headersArguments == null) diff --git a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs index a5cac31235..4ccd72ba15 100644 --- a/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.Rebus/Volo/Abp/EventBus/Rebus/RebusDistributedEventBus.cs @@ -162,7 +162,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return; } - await Rebus.Advanced.Routing.Send(AbpRebusEventBusOptions.InputQueueName, eventData); + await Rebus.Publish(eventData); } protected override void AddToUnitOfWork(IUnitOfWork unitOfWork, UnitOfWorkEventRecord eventRecord) @@ -224,7 +224,7 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen return PublishToEventBusAsync(eventType, eventData); } - public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) + public async override Task PublishManyFromOutboxAsync(IEnumerable outgoingEvents, OutboxConfig outboxConfig) { var outgoingEventArray = outgoingEvents.ToArray(); @@ -234,11 +234,9 @@ public class RebusDistributedEventBus : DistributedEventBusBase, ISingletonDepen { await PublishFromOutboxAsync(outgoingEvent, outboxConfig); } - + await scope.CompleteAsync(); } - - return new MultipleOutgoingEventPublishResult(outgoingEventArray); } public async override Task ProcessFromInboxAsync( diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs index a46fc76238..749aa3d6d2 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/AbpEventBusBoxesOptions.cs @@ -34,6 +34,11 @@ public class AbpEventBusBoxesOptions /// Default: 2 hours /// public TimeSpan WaitTimeToDeleteProcessedInboxEvents { get; set; } + + /// + /// Default: false + /// + public bool OutboxPublishInBatch { get; set; } public AbpEventBusBoxesOptions() { @@ -43,5 +48,6 @@ public class AbpEventBusBoxesOptions PeriodTimeSpan = TimeSpan.FromSeconds(2); DistributedLockWaitDuration = TimeSpan.FromSeconds(15); WaitTimeToDeleteProcessedInboxEvents = TimeSpan.FromHours(2); + OutboxPublishInBatch = false; } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs index 0add75f9c4..69ae91505d 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/DistributedEventBusBase.cs @@ -85,7 +85,7 @@ public abstract class DistributedEventBusBase : EventBusBase, IDistributedEventB OutboxConfig outboxConfig ); - public abstract Task PublishManyFromOutboxAsync( + public abstract Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig ); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs index 7c23be5ecd..73f74a68ad 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/ISupportsEventBoxes.cs @@ -10,7 +10,7 @@ public interface ISupportsEventBoxes OutboxConfig outboxConfig ); - Task PublishManyFromOutboxAsync( + Task PublishManyFromOutboxAsync( IEnumerable outgoingEvents, OutboxConfig outboxConfig ); diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs index 17dec0a810..ac92740e86 100644 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs +++ b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/Distributed/OutboxSender.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Threading; @@ -84,14 +85,15 @@ public class OutboxSender : IOutboxSender, ITransientDependency } Logger.LogInformation($"Found {waitingEvents.Count} events in the outbox."); - - var result = await DistributedEventBus - .AsSupportsEventBoxes() - .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); - - await Outbox.DeleteManyAsync(result.PublishedOutgoingEvents.Select(x => x.Id).ToArray()); - - Logger.LogInformation($"Sent {result.PublishedOutgoingEvents.Count} events to message broker"); + + if (EventBusBoxesOptions.OutboxPublishInBatch) + { + await PublishOutgoingMessagesInBatchAsync(waitingEvents); + } + else + { + await PublishOutgoingMessagesAsync(waitingEvents); + } } } else @@ -105,4 +107,32 @@ public class OutboxSender : IOutboxSender, ITransientDependency } } } + + protected virtual async Task PublishOutgoingMessagesAsync(List waitingEvents) + { + foreach (var waitingEvent in waitingEvents) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .PublishFromOutboxAsync( + waitingEvent, + OutboxConfig + ); + + await Outbox.DeleteAsync(waitingEvent.Id); + + Logger.LogInformation($"Sent the event to the message broker with id = {waitingEvent.Id:N}"); + } + } + + protected virtual async Task PublishOutgoingMessagesInBatchAsync(List waitingEvents) + { + await DistributedEventBus + .AsSupportsEventBoxes() + .PublishManyFromOutboxAsync(waitingEvents, OutboxConfig); + + await Outbox.DeleteManyAsync(waitingEvents.Select(x => x.Id).ToArray()); + + Logger.LogInformation($"Sent {waitingEvents.Count} events to message broker"); + } } diff --git a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs b/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs deleted file mode 100644 index 515dec1ae6..0000000000 --- a/framework/src/Volo.Abp.EventBus/Volo/Abp/EventBus/MultipleOutgoingEventPublishResult.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System.Collections.Generic; -using Volo.Abp.EventBus.Distributed; - -namespace Volo.Abp.EventBus; - -public class MultipleOutgoingEventPublishResult -{ - public IReadOnlyList PublishedOutgoingEvents { get; } - - public MultipleOutgoingEventPublishResult(IReadOnlyList outgoingEvents) - { - PublishedOutgoingEvents = outgoingEvents; - } -} diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs index 81a5c195c1..7147c81271 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ConsumerPool.cs @@ -76,6 +76,7 @@ public class ConsumerPool : IConsumerPool, ISingletonDependency try { + consumer.Value.Unsubscribe(); consumer.Value.Close(); consumer.Value.Dispose(); } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs index da21f48883..704c15845f 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/KafkaMessageConsumer.cs @@ -80,9 +80,11 @@ public class KafkaMessageConsumer : IKafkaMessageConsumer, ITransientDependency, protected virtual async Task Timer_Elapsed(AbpAsyncTimer timer) { - await CreateTopicAsync(); - Consume(); - Timer.Stop(); + if (Consumer == null) + { + await CreateTopicAsync(); + Consume(); + } } protected virtual async Task CreateTopicAsync() @@ -164,12 +166,21 @@ public class KafkaMessageConsumer : IKafkaMessageConsumer, ITransientDependency, public virtual void Dispose() { + Timer.Stop(); if (Consumer == null) { return; } - Consumer.Close(); - Consumer.Dispose(); + try + { + Consumer.Unsubscribe(); + Consumer.Close(); + Consumer.Dispose(); + Consumer = null; + } + catch (ObjectDisposedException) + { + } } } diff --git a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs index 49287a20e9..0a31a3483a 100644 --- a/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs +++ b/framework/src/Volo.Abp.Kafka/Volo/Abp/Kafka/ProducerPool.cs @@ -17,6 +17,8 @@ public class ProducerPool : IProducerPool, ISingletonDependency protected ConcurrentDictionary>> Producers { get; } protected TimeSpan TotalDisposeWaitDuration { get; set; } = TimeSpan.FromSeconds(10); + + protected TimeSpan DefaultTransactionsWaitDuration { get; set; } = TimeSpan.FromSeconds(30); public ILogger Logger { get; set; } @@ -37,11 +39,18 @@ public class ProducerPool : IProducerPool, ISingletonDependency return Producers.GetOrAdd( connectionName, connection => new Lazy>(() => { - var config = Options.Connections.GetOrDefault(connection); - - Options.ConfigureProducer?.Invoke(new ProducerConfig(config)); - - return new ProducerBuilder(config).Build(); + var producerConfig = new ProducerConfig(Options.Connections.GetOrDefault(connection)); + Options.ConfigureProducer?.Invoke(producerConfig); + + if (producerConfig.TransactionalId.IsNullOrWhiteSpace()) + { + producerConfig.TransactionalId = Guid.NewGuid().ToString(); + } + + var producer = new ProducerBuilder(producerConfig).Build(); + producer.InitTransactions(DefaultTransactionsWaitDuration); + + return producer; })).Value; } @@ -69,7 +78,7 @@ public class ProducerPool : IProducerPool, ISingletonDependency foreach (var producer in Producers.Values) { var poolItemDisposeStopwatch = Stopwatch.StartNew(); - + try { producer.Value.Dispose(); @@ -77,19 +86,19 @@ public class ProducerPool : IProducerPool, ISingletonDependency catch { } - + poolItemDisposeStopwatch.Stop(); - + remainingWaitDuration = remainingWaitDuration > poolItemDisposeStopwatch.Elapsed ? remainingWaitDuration.Subtract(poolItemDisposeStopwatch.Elapsed) : TimeSpan.Zero; } - + poolDisposeStopwatch.Stop(); - + Logger.LogInformation( $"Disposed Kafka Producer Pool ({Producers.Count} producers in {poolDisposeStopwatch.Elapsed.TotalMilliseconds:0.00} ms)."); - + if (poolDisposeStopwatch.Elapsed.TotalSeconds > 5.0) { Logger.LogWarning( From f1cbcb16c7e7fa5f4b21603d981fdbf9619dc223 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Fri, 18 Mar 2022 11:55:14 +0800 Subject: [PATCH 011/134] Update RabbitMqDistributedEventBus --- .../RabbitMq/RabbitMqDistributedEventBus.cs | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs index b9210a6eb8..73ca0cbb61 100644 --- a/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs +++ b/framework/src/Volo.Abp.EventBus.RabbitMQ/Volo/Abp/EventBus/RabbitMq/RabbitMqDistributedEventBus.cs @@ -86,8 +86,6 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe Consumer.OnMessageReceived(ProcessEventAsync); SubscribeHandlers(AbpDistributedEventBusOptions.Handlers); - - } private async Task ProcessEventAsync(IModel channel, BasicDeliverEventArgs ea) @@ -313,12 +311,20 @@ public class RabbitMqDistributedEventBus : DistributedEventBusBase, ISingletonDe { return; } - - channel.ExchangeDeclare( - AbpRabbitMqEventBusOptions.ExchangeName, - "direct", - durable: true - ); + + try + { + channel.ExchangeDeclarePassive(AbpRabbitMqEventBusOptions.ExchangeName); + } + catch (Exception) + { + channel.ExchangeDeclare( + AbpRabbitMqEventBusOptions.ExchangeName, + "direct", + durable: true + ); + } + _exchangeCreated = true; } private void SetEventMessageHeaders(IBasicProperties properties, Dictionary headersArguments) From d484affc5b46dd7d7f88bc2a2a6af0bf01e08bd7 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 30 Mar 2022 20:24:42 +0300 Subject: [PATCH 012/134] Add document of date pipes --- docs/en/UI/Angular/DateTime-Format-Pipe.md | 29 ++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 docs/en/UI/Angular/DateTime-Format-Pipe.md diff --git a/docs/en/UI/Angular/DateTime-Format-Pipe.md b/docs/en/UI/Angular/DateTime-Format-Pipe.md new file mode 100644 index 0000000000..4841ffe665 --- /dev/null +++ b/docs/en/UI/Angular/DateTime-Format-Pipe.md @@ -0,0 +1,29 @@ +# DateTime Format Pipes + +You can format date by Date pipe of angular. + +Example + +```html + {{today | date 'dd/mm/yy'}} +``` + +ShortDate, ShortTime and ShortDateTime format data like angular's data pipe but easier. Also the pipes get format from config service by culture. + +# ShortDate Pipe + +```html + {{today | shortDatePipe }} +``` + +# ShortTime Pipe + +```html + {{today | shortTimePipe }} +``` + +# ShortDateTime Pipe + +```html + {{today | shortDateTimePipe }} +``` From e66e5a824702779521ff4ebb97825a23cc48476a Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Tue, 12 Apr 2022 18:08:45 +0300 Subject: [PATCH 013/134] Update Page-Toolbar-Extensions.md --- docs/en/UI/Angular/Page-Toolbar-Extensions.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/en/UI/Angular/Page-Toolbar-Extensions.md b/docs/en/UI/Angular/Page-Toolbar-Extensions.md index 7e63950224..1edc9594fb 100644 --- a/docs/en/UI/Angular/Page-Toolbar-Extensions.md +++ b/docs/en/UI/Angular/Page-Toolbar-Extensions.md @@ -21,9 +21,9 @@ The following code prepares a constant named `identityToolbarActionContributors` import { eIdentityComponents, - IdentityToolbarActionContributors, - IdentityUserDto, + IdentityToolbarActionContributors } from '@abp/ng.identity'; +import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { ToolbarAction, ToolbarActionList } from '@abp/ng.theme.shared/extensions'; const logUserNames = new ToolbarAction({ @@ -93,7 +93,7 @@ We need to have a component before we can pass it to the toolbar action contribu ```js // src/app/click-me-button.component.ts -import { IdentityUserDto } from '@abp/ng.identity'; +import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { ActionData, EXTENSIONS_ACTION_DATA } from '@abp/ng.theme.shared/extensions'; import { Component, Inject } from '@angular/core'; @@ -127,9 +127,9 @@ The following code prepares a constant named `identityToolbarActionContributors` import { eIdentityComponents, - IdentityToolbarActionContributors, - IdentityUserDto, + IdentityToolbarActionContributors } from '@abp/ng.identity'; +import { IdentityUserDto } from '@abp/ng.identity/proxy'; import { ToolbarActionList, ToolbarComponent } from '@abp/ng.theme.shared/extensions'; import { ClickMeButtonComponent } from './click-me-button.component'; @@ -362,7 +362,7 @@ export function reorderUserContributors( ) { // drop "New User" button const newUserActionNode = actionList.dropByValue( - 'AbpIdentity::NewUser', + 'AbpIdentity::NewUser', (action, text) => action['text'] === text, ); From 58227186f8bda6f663ce1acd2ed201c31bf5cefa Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 18 Apr 2022 17:27:36 +0800 Subject: [PATCH 014/134] Add `Bootstrap-Modules.md` --- docs/en/Bootstrap-Modules.md | 128 ++++++++++++++++++ .../Getting-Started-AspNetCore-Application.md | 22 ++- docs/en/docs-nav.json | 4 + 3 files changed, 140 insertions(+), 14 deletions(-) create mode 100644 docs/en/Bootstrap-Modules.md diff --git a/docs/en/Bootstrap-Modules.md b/docs/en/Bootstrap-Modules.md new file mode 100644 index 0000000000..904197e4e3 --- /dev/null +++ b/docs/en/Bootstrap-Modules.md @@ -0,0 +1,128 @@ +# Bootstrap Modules + +ABP framework can be bootstrapped in various applications. like ASP NET Core or Console, WPF, etc. + +Modules can be started sync or async, Corresponds to various event methods in the module. We recommend using **async**, Especially if you want to make async calls in the module's methods. + +The `PreConfigureServices`, `ConfigureServices`, `PostConfigureServices`, `OnPreApplicationInitialization`, `OnApplicationInitialization`, `OnPostApplicationInitialization` methods of module have the Async version. + +> Async methods automatically call sync methods by default. + +If you use async methods in your module, please keep the same sync methods for compatibility. + +````csharp +public async override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) +{ + await AsyncMethod(); +} + +public override void OnApplicationInitialization(ApplicationInitializationContext context) +{ + AsyncHelper.RunSync(() => OnApplicationInitializationAsync(context)); +} +```` + +## ASP NET Core + +Bootstrap ABP by using [WebApplication](https://docs.microsoft.com/en-us/dotnet/api/microsoft.aspnetcore.builder.webapplication?view=aspnetcore-6.0) + +````csharp +var builder = WebApplication.CreateBuilder(args); +builder.Host.AddAppSettingsSecretsJson() + .UseAutofac(); + +await builder.Services.AddApplicationAsync(); +var app = builder.Build(); + +await app.InitializeApplicationAsync(); +await app.RunAsync(); +```` + +## Console + +Bootstrap ABP in Console App. + +````csharp +var abpApplication = await AbpApplicationFactory.CreateAsync(options => +{ + options.UseAutofac(); +}); + +await _abpApplication.InitializeAsync(); + +var helloWorldService = _abpApplication.ServiceProvider.GetRequiredService(); + +await helloWorldService.SayHelloAsync(); +```` + +Bootstrap ABP by using [HostedService](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/host/hosted-services?view=aspnetcore-6.0&tabs=visual-studio#ihostedservice-interface) + +````csharp +public class MyHostedService : IHostedService +{ + private IAbpApplicationWithInternalServiceProvider _abpApplication; + + private readonly IConfiguration _configuration; + private readonly IHostEnvironment _hostEnvironment; + + public MyHostedService(IConfiguration configuration, IHostEnvironment hostEnvironment) + { + _configuration = configuration; + _hostEnvironment = hostEnvironment; + } + + public async Task StartAsync(CancellationToken cancellationToken) + { + _abpApplication = await AbpApplicationFactory.CreateAsync(options => + { + options.Services.ReplaceConfiguration(_configuration); + options.Services.AddSingleton(_hostEnvironment); + + options.UseAutofac(); + options.Services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog()); + }); + + await _abpApplication.InitializeAsync(); + + var helloWorldService = _abpApplication.ServiceProvider.GetRequiredService(); + + await helloWorldService.SayHelloAsync(); + } + + public async Task StopAsync(CancellationToken cancellationToken) + { + await _abpApplication.ShutdownAsync(); + } +} +```` + +## WPF + +Bootstrap ABP on [OnStartup](https://docs.microsoft.com/en-us/dotnet/api/system.windows.application.onstartup?view=windowsdesktop-6.0) method. + +````csharp +public partial class App : Application +{ + private IAbpApplicationWithInternalServiceProvider _abpApplication; + + protected async override void OnStartup(StartupEventArgs e) + { + _abpApplication = await AbpApplicationFactory.CreateAsync(options => + { + options.UseAutofac(); + options.Services.AddLogging(loggingBuilder => loggingBuilder.AddSerilog(dispose: true)); + }); + + await _abpApplication.InitializeAsync(); + + _abpApplication.Services.GetRequiredService()?.Show(); + } + + protected async override void OnExit(ExitEventArgs e) + { + await _abpApplication.ShutdownAsync(); + } +} + +```` + diff --git a/docs/en/Getting-Started-AspNetCore-Application.md b/docs/en/Getting-Started-AspNetCore-Application.md index c5b72a6c1d..ef14fd0c54 100644 --- a/docs/en/Getting-Started-AspNetCore-Application.md +++ b/docs/en/Getting-Started-AspNetCore-Application.md @@ -77,20 +77,17 @@ using BasicAspNetCoreApplication; var builder = WebApplication.CreateBuilder(args); -builder.Services.ReplaceConfiguration(builder.Configuration); - -builder.Services.AddApplication(); +await builder.Services.AddApplicationAsync(); var app = builder.Build(); -app.InitializeApplication(); - -app.Run(); +await app.InitializeApplicationAsync(); +await app.RunAsync(); ```` -``builder.Services.AddApplication();`` adds all services defined in all modules starting from the ``AppModule``. +``builder.Services.AddApplicationAsync();`` adds all services defined in all modules starting from the ``AppModule``. -``app.InitializeApplication()`` initializes and starts the application. +``app.InitializeApplicationAsync()`` initializes and starts the application. ## Run the Application! @@ -128,15 +125,12 @@ var builder = WebApplication.CreateBuilder(args); builder.Host.UseAutofac(); //Add this line -builder.Services.ReplaceConfiguration(builder.Configuration); - -builder.Services.AddApplication(); +await builder.Services.AddApplicationAsync(); var app = builder.Build(); -app.InitializeApplication(); - -app.Run(); +await app.InitializeApplicationAsync(); +await app.RunAsync(); ```` diff --git a/docs/en/docs-nav.json b/docs/en/docs-nav.json index 8c08724b62..f553cad14a 100644 --- a/docs/en/docs-nav.json +++ b/docs/en/docs-nav.json @@ -426,6 +426,10 @@ "text": "Basics", "path": "Module-Development-Basics.md" }, + { + "text": "Bootstrap", + "path": "Bootstrap-Modules.md" + }, { "text": "Plug-In Modules", "path": "PlugIn-Modules.md" From d9cbdc4e48845eef362692debf1f4c231d3d8200 Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Apr 2022 16:43:11 +0300 Subject: [PATCH 015/134] Update FeatureManagementModal.razor.cs --- .../Components/FeatureManagementModal.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs index a35d529725..e2cb9c160c 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Blazor/Components/FeatureManagementModal.razor.cs @@ -25,7 +25,7 @@ public partial class FeatureManagementModal [Inject] protected IOptions LocalizationOptions { get; set; } - [Inject] private ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } + [Inject] protected ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } protected Modal Modal; From 649719738de8185e1f12351b86e258cf2b5371af Mon Sep 17 00:00:00 2001 From: enisn Date: Mon, 18 Apr 2022 16:43:38 +0300 Subject: [PATCH 016/134] Make members are protected and overridable in PermissionManagementModal.razor.cs --- .../PermissionManagementModal.razor.cs | 42 +++++++++---------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs index 2a908140b5..e74b3d8212 100644 --- a/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs +++ b/modules/permission-management/src/Volo.Abp.PermissionManagement.Blazor/Components/PermissionManagementModal.razor.cs @@ -11,25 +11,25 @@ namespace Volo.Abp.PermissionManagement.Blazor.Components; public partial class PermissionManagementModal { - [Inject] private IPermissionAppService PermissionAppService { get; set; } - [Inject] private ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } + [Inject] protected IPermissionAppService PermissionAppService { get; set; } + [Inject] protected ICurrentApplicationConfigurationCacheResetService CurrentApplicationConfigurationCacheResetService { get; set; } - private Modal _modal; + protected Modal _modal; - private string _providerName; - private string _providerKey; + protected string _providerName; + protected string _providerKey; - private string _entityDisplayName; - private List _groups; + protected string _entityDisplayName; + protected List _groups; - private List _disabledPermissions = new List(); + protected List _disabledPermissions = new List(); - private string _selectedTabName; + protected string _selectedTabName; - private int _grantedPermissionCount = 0; - private int _notGrantedPermissionCount = 0; + protected int _grantedPermissionCount = 0; + protected int _notGrantedPermissionCount = 0; - private bool GrantAll { + protected bool GrantAll { get { if (_notGrantedPermissionCount == 0) { @@ -113,12 +113,12 @@ public partial class PermissionManagementModal } } - private Task CloseModal() + protected Task CloseModal() { return InvokeAsync(_modal.Hide); } - private async Task SaveAsync() + protected virtual async Task SaveAsync() { try { @@ -142,12 +142,12 @@ public partial class PermissionManagementModal } } - private string GetNormalizedGroupName(string name) + protected virtual string GetNormalizedGroupName(string name) { return "PermissionGroup_" + name.Replace(".", "_"); } - private void GroupGrantAllChanged(bool value, PermissionGroupDto permissionGroup) + protected virtual void GroupGrantAllChanged(bool value, PermissionGroupDto permissionGroup) { foreach (var permission in permissionGroup.Permissions) { @@ -158,7 +158,7 @@ public partial class PermissionManagementModal } } - private void PermissionChanged(bool value, PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + protected virtual void PermissionChanged(bool value, PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) { SetPermissionGrant(permission, value); @@ -200,22 +200,22 @@ public partial class PermissionManagementModal permission.IsGranted = value; } - private PermissionGrantInfoDto GetParentPermission(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + protected PermissionGrantInfoDto GetParentPermission(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) { return permissionGroup.Permissions.First(x => x.Name == permission.ParentName); } - private List GetChildPermissions(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) + protected List GetChildPermissions(PermissionGroupDto permissionGroup, PermissionGrantInfoDto permission) { return permissionGroup.Permissions.Where(x => x.Name.StartsWith(permission.Name)).ToList(); } - private bool IsDisabledPermission(PermissionGrantInfoDto permissionGrantInfo) + protected bool IsDisabledPermission(PermissionGrantInfoDto permissionGrantInfo) { return _disabledPermissions.Any(x => x == permissionGrantInfo); } - private string GetShownName(PermissionGrantInfoDto permissionGrantInfo) + protected virtual string GetShownName(PermissionGrantInfoDto permissionGrantInfo) { if (!IsDisabledPermission(permissionGrantInfo)) { From c2fbd7e2a4070d6a2417c913cf4501a1cb18716c Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Mon, 18 Apr 2022 19:24:18 +0300 Subject: [PATCH 017/134] Add proper title to Abp-permission-manager.component #9581 --- .../identity/src/lib/components/roles/roles.component.html | 1 + .../identity/src/lib/components/users/users.component.html | 1 + .../identity/src/lib/components/users/users.component.ts | 4 +++- .../identity/src/lib/defaults/default-users-entity-actions.ts | 2 +- .../src/lib/components/permission-management.component.html | 4 ++-- .../src/lib/components/permission-management.component.ts | 3 +++ 6 files changed, 11 insertions(+), 4 deletions(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html index 09dcdb1be3..20d5509d15 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html @@ -55,6 +55,7 @@ }; let init = initTemplate " + [title]="providerKey" (abpInit)="init(abpPermissionManagement)" > diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index 6f42485364..a481b9d697 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -95,6 +95,7 @@ }; let init = initTemplate " + [title]="userName" (abpInit)="init(abpPermissionManagement)" > diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 6f458a781b..1c9f6f0f17 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -64,6 +64,7 @@ export class UsersComponent implements OnInit { onVisiblePermissionChange = event => { this.visiblePermissions = event; }; + userName: string; get roleGroups(): FormGroup[] { return ((this.form.get('roleNames') as FormArray)?.controls as FormGroup[]) || []; @@ -176,8 +177,9 @@ export class UsersComponent implements OnInit { this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res)); } - openPermissionsModal(providerKey: string) { + openPermissionsModal(providerKey: string, userName:string) { this.providerKey = providerKey; + this.userName = userName; setTimeout(() => { this.visiblePermissions = true; }, 0); diff --git a/npm/ng-packs/packages/identity/src/lib/defaults/default-users-entity-actions.ts b/npm/ng-packs/packages/identity/src/lib/defaults/default-users-entity-actions.ts index 13f058dd37..0bd21c571b 100644 --- a/npm/ng-packs/packages/identity/src/lib/defaults/default-users-entity-actions.ts +++ b/npm/ng-packs/packages/identity/src/lib/defaults/default-users-entity-actions.ts @@ -15,7 +15,7 @@ export const DEFAULT_USERS_ENTITY_ACTIONS = EntityAction.createMany { const component = data.getInjected(UsersComponent); - component.openPermissionsModal(data.record.id); + component.openPermissionsModal(data.record.id, data.record.userName); }, permission: 'AbpIdentity.Users.ManagePermissions', }, diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html index a28607bb59..d2d14035ca 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html @@ -2,8 +2,8 @@

- {{ 'AbpPermissionManagement::Permissions' | abpLocalization }} - - {{ data.entityDisplayName }} + {{ 'AbpPermissionManagement::Permissions' | abpLocalization }} + - {{ title }}

diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index e43acdd605..bc07bb6583 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -64,6 +64,9 @@ export class PermissionManagementComponent return this._visible; } + @Input() + title = ''; + set visible(value: boolean) { if (value === this._visible) return; From fd46f43417488556509f5286da138f5cb0c8a6fa Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Apr 2022 02:38:55 +0000 Subject: [PATCH 018/134] Bump prismjs from 1.19.0 to 1.28.0 in /modules/docs/app/VoloDocs.Web Bumps [prismjs](https://github.com/PrismJS/prism) from 1.19.0 to 1.28.0. - [Release notes](https://github.com/PrismJS/prism/releases) - [Changelog](https://github.com/PrismJS/prism/blob/master/CHANGELOG.md) - [Commits](https://github.com/PrismJS/prism/compare/v1.19.0...v1.28.0) --- updated-dependencies: - dependency-name: prismjs dependency-type: indirect ... Signed-off-by: dependabot[bot] --- .../docs/app/VoloDocs.Web/package-lock.json | 1433 +++++++---------- modules/docs/app/VoloDocs.Web/yarn.lock | 6 +- 2 files changed, 557 insertions(+), 882 deletions(-) diff --git a/modules/docs/app/VoloDocs.Web/package-lock.json b/modules/docs/app/VoloDocs.Web/package-lock.json index e764349469..9ae1d55d3d 100644 --- a/modules/docs/app/VoloDocs.Web/package-lock.json +++ b/modules/docs/app/VoloDocs.Web/package-lock.json @@ -5,251 +5,269 @@ "requires": true, "dependencies": { "@abp/anchor-js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/anchor-js/-/anchor-js-1.1.1.tgz", - "integrity": "sha512-hHyYYJ09hhT5xeQJUsBN43yT+y49FKcigq4Wrx8448TrW7r2NJD5i3Xy3BGEstNSlfcQPTsmciQnbkcU2rX1HQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/anchor-js/-/anchor-js-5.2.1.tgz", + "integrity": "sha512-61+rrfSQyZacqUJ5qQxkoWYffWcd7AArkj8DmEHmFY4e28hH3P9eXMcuGBoJ85pXleAPEmVYswc/xZiTMNHkvg==", "requires": { - "@abp/core": "^1.1.1", - "anchor-js": "^4.2.2" + "@abp/core": "~5.2.1", + "anchor-js": "^4.3.1" } }, "@abp/aspnetcore.mvc.ui": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-1.1.1.tgz", - "integrity": "sha512-wZbptVCSxZzEjkJx+/sWrH9Pikp9nOy7V8Htz+L+S7/qAzfXu5PRVV8ahddfAcDHRk30buRhdbJlCVdt6hkZ6g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui/-/aspnetcore.mvc.ui-5.2.1.tgz", + "integrity": "sha512-VUSPOKjBSF+NxfwdsEVQte8u7mGP1t7jd1+ej2ND8JEKYJ1Vh7z2mfsT+lQaEJg0JWggU1AxkIMOOfHDNTU3Kg==", "requires": { "ansi-colors": "^4.1.1", "extend-object": "^1.0.0", + "glob": "^7.1.6", "gulp": "^4.0.2", "merge-stream": "^2.0.0", - "path": "^0.12.7", - "rimraf": "^3.0.0" + "micromatch": "^4.0.2" } }, "@abp/aspnetcore.mvc.ui.theme.basic": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-1.1.1.tgz", - "integrity": "sha512-ooXtCM3TWN69RU7xs6avnzOQBXzsiHY5BEIogzSBialZC4uG5H56qrIr4MbsFNae+PQM23Mw2tnJ/Z7dutURCQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.basic/-/aspnetcore.mvc.ui.theme.basic-5.2.1.tgz", + "integrity": "sha512-DYr9ROcTPfCRHxD1QSWqLZ9+ARbO5p9I6SRo893NtJ39aHacAa9RIAwZmP0JLG0C4hLXfJLKXJ2DpNcwY+ubXA==", "requires": { - "@abp/aspnetcore.mvc.ui.theme.shared": "^1.1.1" + "@abp/aspnetcore.mvc.ui.theme.shared": "~5.2.1" } }, "@abp/aspnetcore.mvc.ui.theme.shared": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-1.1.1.tgz", - "integrity": "sha512-LilSyefzT1+rcTU7vbWxcO8TwBgGZIx6QbMUrDicSTH6LLJ9S5+yNaGNJbbZKDG6qx0BEoC1u8dE8KCUshwxoQ==", - "requires": { - "@abp/aspnetcore.mvc.ui": "^1.1.1", - "@abp/bootstrap": "^1.1.1", - "@abp/bootstrap-datepicker": "^1.1.1", - "@abp/datatables.net-bs4": "^1.1.1", - "@abp/font-awesome": "^1.1.1", - "@abp/jquery-form": "^1.1.1", - "@abp/jquery-validation-unobtrusive": "^1.1.1", - "@abp/lodash": "^1.1.1", - "@abp/luxon": "^1.1.1", - "@abp/malihu-custom-scrollbar-plugin": "^1.1.1", - "@abp/select2": "^1.1.1", - "@abp/sweetalert": "^1.1.1", - "@abp/timeago": "^1.1.1", - "@abp/toastr": "^1.1.1" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/aspnetcore.mvc.ui.theme.shared/-/aspnetcore.mvc.ui.theme.shared-5.2.1.tgz", + "integrity": "sha512-/1C5RyPIRPZT5ir8Len2EnSt1KfWcRdPyn/avAG+9JKBZ8FoUL8mO2/ffESOvikh/wItZZgxJ5VEJVGwHNjgdQ==", + "requires": { + "@abp/aspnetcore.mvc.ui": "~5.2.1", + "@abp/bootstrap": "~5.2.1", + "@abp/bootstrap-datepicker": "~5.2.1", + "@abp/datatables.net-bs5": "~5.2.1", + "@abp/font-awesome": "~5.2.1", + "@abp/jquery-form": "~5.2.1", + "@abp/jquery-validation-unobtrusive": "~5.2.1", + "@abp/lodash": "~5.2.1", + "@abp/luxon": "~5.2.1", + "@abp/malihu-custom-scrollbar-plugin": "~5.2.1", + "@abp/select2": "~5.2.1", + "@abp/sweetalert2": "~5.2.1", + "@abp/timeago": "~5.2.1", + "@abp/toastr": "~5.2.1" } }, "@abp/bootstrap": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/bootstrap/-/bootstrap-1.1.1.tgz", - "integrity": "sha512-OIaGJaizhI8UNfy4bnw2xT2Z0QG7BJJrjxOPGepfd4jn/AUi/vFdOpJFWvu2P9PwSzRmn/LuSlr2WONDOdPVWQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/bootstrap/-/bootstrap-5.2.1.tgz", + "integrity": "sha512-vFW8OxfRhiDkIrDVIn3TyGkGyiCLLFmPMjSOmMg3o2XPdRk5uhwSBzWYpk/m+kmPpP6cEsJMxaHpCsirSlPE+A==", "requires": { - "@abp/core": "^1.1.1", - "bootstrap": "^4.3.1" + "@abp/core": "~5.2.1", + "bootstrap": "^5.1.3" } }, "@abp/bootstrap-datepicker": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/bootstrap-datepicker/-/bootstrap-datepicker-1.1.1.tgz", - "integrity": "sha512-RIQLSrKBu/cTAU2lFenSAoKcMp7wgF4e3nP4/iOu5ZtCgti5vUjMEcqEvBxtlwKTHMXsTG4GtNKqjTwjXMjONQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/bootstrap-datepicker/-/bootstrap-datepicker-5.2.1.tgz", + "integrity": "sha512-UPdVu9t7XybINSfonQN0DB9Lpz1r5vCz7F8CMpbjQprvPmsFmkAZyY0p6MS3kGO5eu5rlpGAGPBGOTeSfEp9ww==", "requires": { "bootstrap-datepicker": "^1.9.0" } }, "@abp/clipboard": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/clipboard/-/clipboard-1.1.1.tgz", - "integrity": "sha512-O6b7VCAh2mxjkPhgUZYQBhXf0u+dWRECBOYN4KxYiKfk+2xL8X+34Ls7hVLbyuD+4xCcNirMZIG5CnS8auHC/A==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/clipboard/-/clipboard-5.2.1.tgz", + "integrity": "sha512-aouNTDz8t+8M4O2a+UsEdtABRsyhvzGpXqCG2+LYE1vA3I+CKhglkvEFp+GyIgWsipEHY1U1w6V3qZtcRINn+A==", "requires": { - "@abp/core": "^1.1.1", - "clipboard": "^2.0.4" + "@abp/core": "~5.2.1", + "clipboard": "^2.0.8" } }, "@abp/core": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/core/-/core-1.1.1.tgz", - "integrity": "sha512-OvUG7xRvk8nSqwC1s45YPnTuhC2OWe1AVa1nnC6FVHMH/g1Je7UJwnbu47K7uNS+lDRJUIktNbufYKiwutEjRg==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/core/-/core-5.2.1.tgz", + "integrity": "sha512-FDOhIPjig3oGxkbadJZzFSC1ZHzgQV4R75fsDNH56lQ9mTyRUPQdg0Y54eCtY7yOSjiJOctOUUWHaxoFG7frGQ==", + "requires": { + "@abp/utils": "~5.2.1" + } }, "@abp/datatables.net": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/datatables.net/-/datatables.net-1.1.1.tgz", - "integrity": "sha512-y76IDBlgc0n1YgQqJ+9cfzXpLwr2arhdIfSmqX+qRXz6GfVNY7e3JijkFSgDKUzKGYo1HZMzgJmDmeNIRwMZsQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/datatables.net/-/datatables.net-5.2.1.tgz", + "integrity": "sha512-6Q3+W+d8e4TMAkZr/IdPDQuL1v+tjbS50ChLvrJX/BLb4fBhu1LGJWWKzKJFj721DwIsuQQiM4uq9xX/TjiS0w==", "requires": { - "@abp/core": "^1.1.1", - "datatables.net": "^1.10.20" + "@abp/jquery": "~5.2.1", + "datatables.net": "^1.11.4" } }, - "@abp/datatables.net-bs4": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/datatables.net-bs4/-/datatables.net-bs4-1.1.1.tgz", - "integrity": "sha512-t40xQIGBMLPZiSbcZHW3AwE8uk+xcl7OitBT1jym0XPKVtgJsHez3ynDE5v/PjHe+ColCG8lTRjRnNoXo5dzDw==", + "@abp/datatables.net-bs5": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/datatables.net-bs5/-/datatables.net-bs5-5.2.1.tgz", + "integrity": "sha512-B8lSAeMM9qOwYbDK/Dhp7BX5lFaCpao4RCPcSqgFrye8vlH8bcobmp4tMD23r24y/gRIEuQBcKzp0Lf0OUpLhA==", "requires": { - "@abp/datatables.net": "^1.1.1", - "datatables.net-bs4": "^1.10.20" + "@abp/datatables.net": "~5.2.1", + "datatables.net-bs5": "^1.11.4" } }, "@abp/docs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/docs/-/docs-1.1.1.tgz", - "integrity": "sha512-z2Dk7EOhBdFo6BYbgccD91PIg37L2ehkgRT4RWf61dRyjPAQgPwwwyVSqApFmKveWPCzreHoTPKbnrU7EzAssw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/docs/-/docs-5.2.1.tgz", + "integrity": "sha512-WZCCY73vyIpRu7hypPiP9CRr4Bvzkv3up0WeGQ4rK9LiZWNSxG9PXv4lYeD4cuHg0zgxH9d/6toYToaIJNqDCQ==", "requires": { - "@abp/anchor-js": "^1.1.1", - "@abp/clipboard": "^1.1.1", - "@abp/malihu-custom-scrollbar-plugin": "^1.1.1", - "@abp/popper.js": "^1.1.1", - "@abp/prismjs": "^1.1.1" + "@abp/anchor-js": "~5.2.1", + "@abp/clipboard": "~5.2.1", + "@abp/malihu-custom-scrollbar-plugin": "~5.2.1", + "@abp/popper.js": "~5.2.1", + "@abp/prismjs": "~5.2.1" } }, "@abp/font-awesome": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/font-awesome/-/font-awesome-1.1.1.tgz", - "integrity": "sha512-6RHbixi7IVWAb3JCHrUmEYD3HmAH4R75Nuo54LvFzATrh4G6gdBONIeDuTo79OTxe4Zhp+WLxeA49Y21kt77mg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/font-awesome/-/font-awesome-5.2.1.tgz", + "integrity": "sha512-9fAUdA9QeNRMjp6v8i6EOR480bjB4OzqzriFCKUu4k6VwbA6PxUsJIRFyKIt5UpC12Zqdhpkyj0iG6tE0nRekQ==", "requires": { - "@abp/core": "^1.1.1", - "@fortawesome/fontawesome-free": "^5.11.2" + "@abp/core": "~5.2.1", + "@fortawesome/fontawesome-free": "^5.15.4" } }, "@abp/jquery": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/jquery/-/jquery-1.1.1.tgz", - "integrity": "sha512-kj4BTtXF0VbCzCqRXnRVEbGndR3F8NlbBhVQN6BQktOuZta3fvx7f2+pSok8vQv0ddmqUFY7FTT2Ei3l4363LQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/jquery/-/jquery-5.2.1.tgz", + "integrity": "sha512-FiIRnDx/gm6JR8QljiulwCc5d8+YC123X0qxMIBI8IY9vznEX+Jk48jYG8fLABnRqKEIYfV8UsYSK8IJx3mcSg==", "requires": { - "@abp/core": "^1.1.1", - "jquery": "^3.4.1" + "@abp/core": "~5.2.1", + "jquery": "~3.6.0" } }, "@abp/jquery-form": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/jquery-form/-/jquery-form-1.1.1.tgz", - "integrity": "sha512-AIIdN36f8xwr4LgiNnBHohJ5tlxh/r+DuDtXcScpZN6GWBE+XgUotN0pZIIva82IxCyUNdDudzgluX9IjI+00w==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-form/-/jquery-form-5.2.1.tgz", + "integrity": "sha512-L7uKs7vReOQEETG9xIDq5aXjshbaPa+ZZQcCbn2uwY813e0ErS7Rb1mnowEt/LNEB02AtLet1B4TDVwZUl1uXQ==", "requires": { - "@abp/jquery": "^1.1.1", - "jquery-form": "^4.2.2" + "@abp/jquery": "~5.2.1", + "jquery-form": "^4.3.0" } }, "@abp/jquery-validation": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/jquery-validation/-/jquery-validation-1.1.1.tgz", - "integrity": "sha512-YvAjIW8epp+ddu01BTUkZWPfEADAvNPJeUrrZ6OpcPWM15Tf+ddr4ATgJ1LCg0Bh5F09iQC855osow3lt8sc7g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-validation/-/jquery-validation-5.2.1.tgz", + "integrity": "sha512-Rr/+SWGlXJ53jfysMB/HVNZqsJKCF3rg23ip2Kg6Q+kQTvWVRE3tpkpoBJczOii5tPUk/A/lsJKgRlcsnP0ASw==", "requires": { - "@abp/jquery": "^1.1.1", - "jquery-validation": "^1.19.1" + "@abp/jquery": "~5.2.1", + "jquery-validation": "^1.19.3" } }, "@abp/jquery-validation-unobtrusive": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-1.1.1.tgz", - "integrity": "sha512-q1b0KG8l3DXUiW8JXdq9l1jR/CwgzrZdxwdKGLB2J/oxHlywQIb7yrjR6WGCshjPpcx2SkOL0j/ZXMIMh533hQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-5.2.1.tgz", + "integrity": "sha512-uZ36D1FfoLdBb6h44fQ3kZuTk4gJ5yzhyOprkgMsGAJDVakX7w/W4V3ThpiEO+iUpNKTboVIhW2QQ0AXK9rrsg==", "requires": { - "@abp/jquery-validation": "^1.1.1", - "jquery-validation-unobtrusive": "^3.2.11" + "@abp/jquery-validation": "~5.2.1", + "jquery-validation-unobtrusive": "^3.2.12" } }, "@abp/lodash": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/lodash/-/lodash-1.1.1.tgz", - "integrity": "sha512-nH7bRS28Tf4hEXcpKHd1IM+MzYTqX8t3htGmsLX4UESQd52eODYOIldtX6gm3OW1O6ECwW6si/o0M2pTEpQqvg==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/lodash/-/lodash-5.2.1.tgz", + "integrity": "sha512-ILg3X5tTH2HhJMRmg7BP/r+Kstm/nf+0aNQ2exsJoMMnKE7CC0eYQjpSgrze6GwG3a13eamyTlrz+RrlIm5IBA==", "requires": { - "@abp/core": "^1.1.1", - "lodash": "^4.17.15" + "@abp/core": "~5.2.1", + "lodash": "^4.17.21" } }, "@abp/luxon": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/luxon/-/luxon-1.1.1.tgz", - "integrity": "sha512-WNu8JRSb5FDXfcDwjMYyeYeUN48uuDc/I2cdo3xd1rcY+lbmbzxoG9IYOlE8cRHdgX3z82qsZXFs2lcAy0Le2g==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/luxon/-/luxon-5.2.1.tgz", + "integrity": "sha512-D3KVsba969UBYktdbCxq1JQp4kYZ1S7rIMymDJMBoHByXxwwdeXMkvuphAifBmSYTt3K6bNoZdR0VxtnNlPn2A==", "requires": { - "luxon": "^1.21.3" + "@abp/core": "~5.2.1", + "luxon": "^2.3.0" } }, "@abp/malihu-custom-scrollbar-plugin": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-1.1.1.tgz", - "integrity": "sha512-n4b4QK/L1Czdx0oOpUR/bWjK9VENexfUSV/aMjwzHhDmEFABAmEfhIpudCYDwewGswrd7C9agmBvakv2rwPQeA==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/malihu-custom-scrollbar-plugin/-/malihu-custom-scrollbar-plugin-5.2.1.tgz", + "integrity": "sha512-5mvABMCT7tiwPl1vUK8kriN/SRi2gC4VqkEuxghT7uBQG9Cqh5jhJrl80M9ZK/oQFind3r6+SF8OlfwF8yvxHQ==", "requires": { - "@abp/core": "^1.1.1", + "@abp/core": "~5.2.1", "malihu-custom-scrollbar-plugin": "^3.1.5" } }, "@abp/popper.js": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/popper.js/-/popper.js-1.1.1.tgz", - "integrity": "sha512-heR73cqmMsVPNgsPxBYbkvc842R3hEEuDAj4oaXZwVTeWXayU6TdDcGdIrfwMZwW2eWivYNnO0bMOVmuhZKTTQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/popper.js/-/popper.js-5.2.1.tgz", + "integrity": "sha512-poQhd5EYjU2/udJWlDEd5mIPWmw6AzNOzAd5V4OUHMai+BHeuhIXQ6mopvxf9lpzytGoFe2ZIiQ547Wfq4Fl/Q==", "requires": { - "@abp/core": "^1.1.1", - "popper.js": "^1.16.0" + "@abp/core": "~5.2.1", + "@popperjs/core": "^2.11.2" } }, "@abp/prismjs": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/prismjs/-/prismjs-1.1.1.tgz", - "integrity": "sha512-kZh2imqVTMDWmE2v+S4wMsigu/hSyVaz3VvZzGctFNctzC17LeD6z6ymfrtQ5BPJbCxGSpHwOt4/Y8bycPTEuQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/prismjs/-/prismjs-5.2.1.tgz", + "integrity": "sha512-YNgcM7Kvmu3hGXJh4B8gl7rLzC28VuZYYP7AVptVSbTz/n6usCo21evG/st8L3vXixuQkvnNpBFgacJnHdSJZQ==", "requires": { - "@abp/core": "^1.1.1", - "prismjs": "^1.17.1" + "@abp/clipboard": "~5.2.1", + "@abp/core": "~5.2.1", + "prismjs": "^1.26.0" } }, "@abp/select2": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/select2/-/select2-1.1.1.tgz", - "integrity": "sha512-t0qcJhD+uo2+XWr4nmMQLAx7MRGQUBdZ81YmGty045ReoSaEKQf4haLkzBcMzpBRusiyMQO/PbxjtwMw/xJQTQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/select2/-/select2-5.2.1.tgz", + "integrity": "sha512-JH/PqOxhTY05sUyN7of6TNai0W4M3N3OF3Hlwmr8i7hNdYfFwJvQnQzKeKrk/vt8Hv44/JTQDlNKU02BmSBfOQ==", "requires": { - "@abp/core": "^1.1.1", - "select2": "^4.0.12" + "@abp/core": "~5.2.1", + "select2": "^4.0.13" } }, - "@abp/sweetalert": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/sweetalert/-/sweetalert-1.1.1.tgz", - "integrity": "sha512-V6K/qg7J/bdFmom2kaXYeiLvcmLHFl+MacPX4yYAK2biZdb2pWOkUdmcAzZdOT+UruKfLRhvraVC2uXDySi9NA==", + "@abp/sweetalert2": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/sweetalert2/-/sweetalert2-5.2.1.tgz", + "integrity": "sha512-laaF/5WhYw+hNJRTfMzO93fVhaYqnnOcQTUlkGgsZMe2gwebyX73VI8O8Xw7zXmN1Tu/JwqRI46qiafDrPFTLg==", "requires": { - "@abp/core": "^1.1.1", - "sweetalert": "^2.1.2" + "@abp/core": "~5.2.1", + "sweetalert2": "^11.3.6" } }, "@abp/timeago": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/timeago/-/timeago-1.1.1.tgz", - "integrity": "sha512-QYYih/4n6XhCqkRw7fBfyg58T5CHqJHyz7SAfq86RiKAJ4jVtjdSVxj3XKxz8eCb56wZGsO1xXXStI3vdLwwNw==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/timeago/-/timeago-5.2.1.tgz", + "integrity": "sha512-xmgqKEKusB6pcqFhMaz8RTi886ad8RrRMYgMWSw4Zjk1Lr9EqQwKtcE43Ve5XWJamh2Wpk8H7IKLQKHfrV12oA==", "requires": { - "@abp/jquery": "^1.1.1", + "@abp/jquery": "~5.2.1", "timeago": "^1.6.7" } }, "@abp/toastr": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@abp/toastr/-/toastr-1.1.1.tgz", - "integrity": "sha512-GDewBppm+0FO6kTTy0huczoH9P5q6lFicHFAoEawAMkuWJFW/Ihv/YnEvKGDQwGftuVSWexfqBMN/RZ5YSOiGQ==", + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/toastr/-/toastr-5.2.1.tgz", + "integrity": "sha512-HrnIzvM9LgQdzlmLmvHUVSG4PmWfx9YuozxkFTv+AGa2FAPby5W9hbQ025ry3bPkU9lGWSu/w7JSDqoiL16bPA==", "requires": { - "@abp/jquery": "^1.1.1", + "@abp/jquery": "~5.2.1", "toastr": "^2.1.4" } }, + "@abp/utils": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/@abp/utils/-/utils-5.2.1.tgz", + "integrity": "sha512-9hxI24aRZCnxCP+WsOoCltSg4YqG9WtW06t9/f6hFO9B0udXIKyV+95Ndipca/R1G94Snx81ifSwAa+DHbFfvQ==", + "requires": { + "just-compare": "^1.3.0" + } + }, "@fortawesome/fontawesome-free": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.13.0.tgz", - "integrity": "sha512-xKOeQEl5O47GPZYIMToj6uuA2syyFlq9EMSl2ui0uytjY9xbe8XS0pexNWmxrdcCyNGyDmLyYw5FtKsalBUeOg==" + "version": "5.15.4", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.4.tgz", + "integrity": "sha512-eYm8vijH/hpzr/6/1CJ/V/Eb1xQFW2nnUKArb3z+yUWv7HTwj6M7SP957oMjfZjAHU6qpoNc2wQvIxBLWYa/Jg==" + }, + "@popperjs/core": { + "version": "2.11.5", + "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.5.tgz", + "integrity": "sha512-9X2obfABZuDVLCgPK9aX0a/x4jaOEweTTWE2+9sr0Qqqevj2Uv5XorvusThmc9XGYpS9yI+fhh8RTafBtGposw==" }, "anchor-js": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/anchor-js/-/anchor-js-4.2.2.tgz", - "integrity": "sha512-Rg1tGaG4K3avYqDh7rOYCE/odWxpUiHStnlKL/bGOt9cl6NjR06zhPGVQcCAjE5PT48oQeHVgqNmLzxh0Kuk4A==" + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/anchor-js/-/anchor-js-4.3.1.tgz", + "integrity": "sha512-TziERoibspey7KSm95oIdzTxiogXonJl7inQI07Y3cI25DKQaLkUftB7RhCuSb1GcwunHL6/PcIKM4dDUb9xYQ==" }, "ansi-colors": { "version": "4.1.1", @@ -281,6 +299,36 @@ "requires": { "micromatch": "^3.1.4", "normalize-path": "^2.1.1" + }, + "dependencies": { + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + }, + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "append-buffer": { @@ -441,9 +489,9 @@ } }, "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" }, "base": { "version": "0.11.2", @@ -510,9 +558,9 @@ } }, "bootstrap": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", - "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==" + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.1.3.tgz", + "integrity": "sha512-fcQztozJ8jToQWXxVuEyXWW+dSo8AiXWKwiSSrKWsRB/Qt+Ewwza+JWoLKiTuQLaEPhdNAJ7+Dosc9DOIqNy7Q==" }, "bootstrap-datepicker": { "version": "1.9.0", @@ -564,9 +612,9 @@ "integrity": "sha1-WWFrSYME1Var1GaWayLu2j7KX74=" }, "buffer-from": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", - "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==" + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==" }, "cache-base": { "version": "1.0.1", @@ -584,6 +632,15 @@ "unset-value": "^1.0.0" } }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, "camelcase": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-3.0.0.tgz", @@ -606,13 +663,6 @@ "path-is-absolute": "^1.0.0", "readdirp": "^2.2.1", "upath": "^1.1.1" - }, - "dependencies": { - "normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" - } } }, "class-utils": { @@ -637,9 +687,9 @@ } }, "clipboard": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.6.tgz", - "integrity": "sha512-g5zbiixBRk/wyKakSwCKd7vQXDjFnAMGHoEyBogG/bw9kTD9GvdAvaoRR1ALcEzt3pVKxZR0pViekPMIS0QyGg==", + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/clipboard/-/clipboard-2.0.10.tgz", + "integrity": "sha512-cz3m2YVwFz95qSEbCDi2fzLN/epEN9zXBvfgAoGkvGOJZATMl9gtTDVOtBYkx2ODUJl2kvmud7n32sV2BpYR4g==", "requires": { "good-listener": "^1.2.2", "select": "^1.1.2", @@ -732,9 +782,9 @@ } }, "convert-source-map": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", - "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.8.0.tgz", + "integrity": "sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==", "requires": { "safe-buffer": "~5.1.1" } @@ -745,18 +795,25 @@ "integrity": "sha1-Z29us8OZl8LuGsOpJP1hJHSPV40=" }, "copy-props": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.4.tgz", - "integrity": "sha512-7cjuUME+p+S3HZlbllgsn2CDwS+5eCCX16qBgNC4jgSTf49qR1VKy/Zhl400m0IQXl/bPGEVqncgUUMjrr4s8A==", + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/copy-props/-/copy-props-2.0.5.tgz", + "integrity": "sha512-XBlx8HSqrT0ObQwmSzM7WE5k8FxTV75h1DX1Z3n6NhQ/UYYAvInWYmG06vFt7hQZArE2fuO62aihiWIVQwh1sw==", "requires": { - "each-props": "^1.3.0", - "is-plain-object": "^2.0.1" + "each-props": "^1.3.2", + "is-plain-object": "^5.0.0" + }, + "dependencies": { + "is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==" + } } }, "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "d": { "version": "1.0.1", @@ -768,19 +825,19 @@ } }, "datatables.net": { - "version": "1.10.20", - "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.20.tgz", - "integrity": "sha512-4E4S7tTU607N3h0fZPkGmAtr9mwy462u+VJ6gxYZ8MxcRIjZqHy3Dv1GNry7i3zQCktTdWbULVKBbkAJkuHEnQ==", + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.11.5.tgz", + "integrity": "sha512-nlFst2xfwSWaQgaOg5sXVG3cxYC0tH8E8d65289w9ROgF2TmLULOOpcdMpyxxUim/qEwVSEem42RjkTWEpr3eA==", "requires": { "jquery": ">=1.7" } }, - "datatables.net-bs4": { - "version": "1.10.20", - "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.20.tgz", - "integrity": "sha512-kQmMUMsHMOlAW96ztdoFqjSbLnlGZQ63iIM82kHbmldsfYdzuyhbb4hTx6YNBi481WCO3iPSvI6YodNec46ZAw==", + "datatables.net-bs5": { + "version": "1.11.5", + "resolved": "https://registry.npmjs.org/datatables.net-bs5/-/datatables.net-bs5-1.11.5.tgz", + "integrity": "sha512-1zyh972GtuK1uAb9h8nP3jJ7f/3UgCDq69LAaZS2bVd4mEHECJ6vrZLacxrkOHOs/q/H3v5sEMeZ46vXz8ox4w==", "requires": { - "datatables.net": "1.10.20", + "datatables.net": ">=1.11.3", "jquery": ">=1.7" } }, @@ -823,11 +880,12 @@ "integrity": "sha1-vLgrqnKtebQmp2cy8aga1t8m1oQ=" }, "define-properties": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", - "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", "requires": { - "object-keys": "^1.0.12" + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" } }, "define-property": { @@ -914,13 +972,13 @@ } }, "es5-ext": { - "version": "0.10.53", - "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", - "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "version": "0.10.60", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.60.tgz", + "integrity": "sha512-jpKNXIt60htYG59/9FGf2PYT3pwMpnEbNKysU+k/4FGwyGtMotOvcZOuW+EmXXYASRqYSXQfGL5cVIthOTgbkg==", "requires": { - "es6-iterator": "~2.0.3", - "es6-symbol": "~3.1.3", - "next-tick": "~1.0.0" + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.3", + "next-tick": "^1.1.0" } }, "es6-iterator": { @@ -933,11 +991,6 @@ "es6-symbol": "^3.1.1" } }, - "es6-object-assign": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/es6-object-assign/-/es6-object-assign-1.1.0.tgz", - "integrity": "sha1-wsNYJlYkfDnqEHyx5mUrb58kUjw=" - }, "es6-symbol": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", @@ -999,17 +1052,17 @@ } }, "ext": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", - "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.6.0.tgz", + "integrity": "sha512-sdBImtzkq2HpkdRLtlLWDa6w4DX22ijZLKx8BMPUuKe1c5lbN6xwQDQCxSfxBQnHZ13ls/FH0MQZx/q/gr6FQg==", "requires": { - "type": "^2.0.0" + "type": "^2.5.0" }, "dependencies": { "type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/type/-/type-2.0.0.tgz", - "integrity": "sha512-KBt58xCHry4Cejnc2ISQAF7QY+ORngsWfxezO68+12hKV6lQY8P/psIkcbjeHWn7MqcgciWJyCCevFMJdIXpow==" + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.6.0.tgz", + "integrity": "sha512-eiDBDOmkih5pMbo9OqsqPRGMljLodLcwd5XD5JbtNB0o89xZAwynY9EdCDsJU7LtcVCClu9DvM7/0Ep1hYX3EQ==" } } }, @@ -1112,6 +1165,11 @@ "time-stamp": "^1.0.0" } }, + "fast-levenshtein": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-1.1.4.tgz", + "integrity": "sha1-5qdUzI8V5YmHqpy9J69m/W9OWvk=" + }, "file-uri-to-path": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", @@ -1157,6 +1215,28 @@ "is-glob": "^4.0.0", "micromatch": "^3.0.4", "resolve-dir": "^1.0.1" + }, + "dependencies": { + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } } }, "fined": { @@ -1221,487 +1301,13 @@ "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" }, "fsevents": { - "version": "1.2.12", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.12.tgz", - "integrity": "sha512-Ggd/Ktt7E7I8pxZRbGIs7vwqAPscSESMrCSkx2FtWeqmheJgCo2R74fTsZFCifr0VTPwqRpPv17+6b8Zp7th0Q==", + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-1.2.13.tgz", + "integrity": "sha512-oWb1Z6mkHIskLzEJ/XWX0srkpkTQ7vaopMQkyaEIoq0fmtFVxOthb8cCxeT+p3ynTdkk/RZwbgG4brR5BeWECw==", "optional": true, "requires": { "bindings": "^1.5.0", - "nan": "^2.12.1", - "node-pre-gyp": "*" - }, - "dependencies": { - "abbrev": { - "version": "1.1.1", - "bundled": true, - "optional": true - }, - "ansi-regex": { - "version": "2.1.1", - "bundled": true, - "optional": true - }, - "aproba": { - "version": "1.2.0", - "bundled": true, - "optional": true - }, - "are-we-there-yet": { - "version": "1.1.5", - "bundled": true, - "optional": true, - "requires": { - "delegates": "^1.0.0", - "readable-stream": "^2.0.6" - } - }, - "balanced-match": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "brace-expansion": { - "version": "1.1.11", - "bundled": true, - "optional": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "chownr": { - "version": "1.1.4", - "bundled": true, - "optional": true - }, - "code-point-at": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "concat-map": { - "version": "0.0.1", - "bundled": true, - "optional": true - }, - "console-control-strings": { - "version": "1.1.0", - "bundled": true, - "optional": true - }, - "core-util-is": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "debug": { - "version": "3.2.6", - "bundled": true, - "optional": true, - "requires": { - "ms": "^2.1.1" - } - }, - "deep-extend": { - "version": "0.6.0", - "bundled": true, - "optional": true - }, - "delegates": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "detect-libc": { - "version": "1.0.3", - "bundled": true, - "optional": true - }, - "fs-minipass": { - "version": "1.2.7", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.6.0" - } - }, - "fs.realpath": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "gauge": { - "version": "2.7.4", - "bundled": true, - "optional": true, - "requires": { - "aproba": "^1.0.3", - "console-control-strings": "^1.0.0", - "has-unicode": "^2.0.0", - "object-assign": "^4.1.0", - "signal-exit": "^3.0.0", - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1", - "wide-align": "^1.1.0" - } - }, - "glob": { - "version": "7.1.6", - "bundled": true, - "optional": true, - "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - } - }, - "has-unicode": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "iconv-lite": { - "version": "0.4.24", - "bundled": true, - "optional": true, - "requires": { - "safer-buffer": ">= 2.1.2 < 3" - } - }, - "ignore-walk": { - "version": "3.0.3", - "bundled": true, - "optional": true, - "requires": { - "minimatch": "^3.0.4" - } - }, - "inflight": { - "version": "1.0.6", - "bundled": true, - "optional": true, - "requires": { - "once": "^1.3.0", - "wrappy": "1" - } - }, - "inherits": { - "version": "2.0.4", - "bundled": true, - "optional": true - }, - "ini": { - "version": "1.3.5", - "bundled": true, - "optional": true - }, - "is-fullwidth-code-point": { - "version": "1.0.0", - "bundled": true, - "optional": true, - "requires": { - "number-is-nan": "^1.0.0" - } - }, - "isarray": { - "version": "1.0.0", - "bundled": true, - "optional": true - }, - "minimatch": { - "version": "3.0.4", - "bundled": true, - "optional": true, - "requires": { - "brace-expansion": "^1.1.7" - } - }, - "minimist": { - "version": "1.2.5", - "bundled": true, - "optional": true - }, - "minipass": { - "version": "2.9.0", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "^5.1.2", - "yallist": "^3.0.0" - } - }, - "minizlib": { - "version": "1.3.3", - "bundled": true, - "optional": true, - "requires": { - "minipass": "^2.9.0" - } - }, - "mkdirp": { - "version": "0.5.3", - "bundled": true, - "optional": true, - "requires": { - "minimist": "^1.2.5" - } - }, - "ms": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "needle": { - "version": "2.3.3", - "bundled": true, - "optional": true, - "requires": { - "debug": "^3.2.6", - "iconv-lite": "^0.4.4", - "sax": "^1.2.4" - } - }, - "node-pre-gyp": { - "version": "0.14.0", - "bundled": true, - "optional": true, - "requires": { - "detect-libc": "^1.0.2", - "mkdirp": "^0.5.1", - "needle": "^2.2.1", - "nopt": "^4.0.1", - "npm-packlist": "^1.1.6", - "npmlog": "^4.0.2", - "rc": "^1.2.7", - "rimraf": "^2.6.1", - "semver": "^5.3.0", - "tar": "^4.4.2" - } - }, - "nopt": { - "version": "4.0.3", - "bundled": true, - "optional": true, - "requires": { - "abbrev": "1", - "osenv": "^0.1.4" - } - }, - "npm-bundled": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npm-normalize-package-bin": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "npm-packlist": { - "version": "1.4.8", - "bundled": true, - "optional": true, - "requires": { - "ignore-walk": "^3.0.1", - "npm-bundled": "^1.0.1", - "npm-normalize-package-bin": "^1.0.1" - } - }, - "npmlog": { - "version": "4.1.2", - "bundled": true, - "optional": true, - "requires": { - "are-we-there-yet": "~1.1.2", - "console-control-strings": "~1.1.0", - "gauge": "~2.7.3", - "set-blocking": "~2.0.0" - } - }, - "number-is-nan": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "object-assign": { - "version": "4.1.1", - "bundled": true, - "optional": true - }, - "once": { - "version": "1.4.0", - "bundled": true, - "optional": true, - "requires": { - "wrappy": "1" - } - }, - "os-homedir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "os-tmpdir": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "osenv": { - "version": "0.1.5", - "bundled": true, - "optional": true, - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "path-is-absolute": { - "version": "1.0.1", - "bundled": true, - "optional": true - }, - "process-nextick-args": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "rc": { - "version": "1.2.8", - "bundled": true, - "optional": true, - "requires": { - "deep-extend": "^0.6.0", - "ini": "~1.3.0", - "minimist": "^1.2.0", - "strip-json-comments": "~2.0.1" - } - }, - "readable-stream": { - "version": "2.3.7", - "bundled": true, - "optional": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "rimraf": { - "version": "2.7.1", - "bundled": true, - "optional": true, - "requires": { - "glob": "^7.1.3" - } - }, - "safe-buffer": { - "version": "5.1.2", - "bundled": true, - "optional": true - }, - "safer-buffer": { - "version": "2.1.2", - "bundled": true, - "optional": true - }, - "sax": { - "version": "1.2.4", - "bundled": true, - "optional": true - }, - "semver": { - "version": "5.7.1", - "bundled": true, - "optional": true - }, - "set-blocking": { - "version": "2.0.0", - "bundled": true, - "optional": true - }, - "signal-exit": { - "version": "3.0.2", - "bundled": true, - "optional": true - }, - "string-width": { - "version": "1.0.2", - "bundled": true, - "optional": true, - "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" - } - }, - "string_decoder": { - "version": "1.1.1", - "bundled": true, - "optional": true, - "requires": { - "safe-buffer": "~5.1.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "bundled": true, - "optional": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-json-comments": { - "version": "2.0.1", - "bundled": true, - "optional": true - }, - "tar": { - "version": "4.4.13", - "bundled": true, - "optional": true, - "requires": { - "chownr": "^1.1.1", - "fs-minipass": "^1.2.5", - "minipass": "^2.8.6", - "minizlib": "^1.2.1", - "mkdirp": "^0.5.0", - "safe-buffer": "^5.1.2", - "yallist": "^3.0.3" - } - }, - "util-deprecate": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "wide-align": { - "version": "1.1.3", - "bundled": true, - "optional": true, - "requires": { - "string-width": "^1.0.2 || 2" - } - }, - "wrappy": { - "version": "1.0.2", - "bundled": true, - "optional": true - }, - "yallist": { - "version": "3.1.1", - "bundled": true, - "optional": true - } + "nan": "^2.12.1" } }, "function-bind": { @@ -1714,15 +1320,25 @@ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, "get-value": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/get-value/-/get-value-2.0.6.tgz", "integrity": "sha1-3BXKHGcjh8p2vTesCjlbogQqLCg=" }, "glob": { - "version": "7.1.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", - "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", + "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", "requires": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -1769,15 +1385,16 @@ } }, "glob-watcher": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.3.tgz", - "integrity": "sha512-8tWsULNEPHKQ2MR4zXuzSmqbdyV5PtwwCaWSGQ1WwHsJ07ilNeN1JB8ntxhckbnpSHaf9dXFUHzIWvm1I13dsg==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/glob-watcher/-/glob-watcher-5.0.5.tgz", + "integrity": "sha512-zOZgGGEHPklZNjZQaZ9f41i7F2YwE+tS5ZHrDhbBCk3stwahn5vQxnFmBJZHoYdusR6R1bLSXeGUy/BhctwKzw==", "requires": { "anymatch": "^2.0.0", "async-done": "^1.2.0", "chokidar": "^2.0.0", "is-negated-glob": "^1.0.0", "just-debounce": "^1.0.0", + "normalize-path": "^3.0.0", "object.defaults": "^1.1.0" } }, @@ -1820,9 +1437,9 @@ } }, "graceful-fs": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz", - "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ==" + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==" }, "gulp": { "version": "4.0.2", @@ -1844,9 +1461,9 @@ } }, "gulp-cli": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.2.0.tgz", - "integrity": "sha512-rGs3bVYHdyJpLqR0TUBnlcZ1O5O++Zs4bA0ajm+zr3WFCfiSLjGwoCBqFs18wzN+ZxahT9DkOK5nDf26iDsWjA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/gulp-cli/-/gulp-cli-2.3.0.tgz", + "integrity": "sha512-zzGBl5fHo0EKSXsHzjspp3y5CONegCm8ErO5Qh0UzFzk2y4tMvzLWhoDokADbarfZRL2pGpRp7yt6gfJX4ph7A==", "requires": { "ansi-colors": "^1.0.1", "archy": "^1.0.0", @@ -1856,7 +1473,7 @@ "copy-props": "^2.0.1", "fancy-log": "^1.3.2", "gulplog": "^1.0.0", - "interpret": "^1.1.0", + "interpret": "^1.4.0", "isobject": "^3.0.1", "liftoff": "^3.1.0", "matchdep": "^2.0.0", @@ -1864,7 +1481,7 @@ "pretty-hrtime": "^1.0.0", "replace-homedir": "^1.0.0", "semver-greatest-satisfied-range": "^1.1.0", - "v8flags": "^3.0.1", + "v8flags": "^3.2.0", "yargs": "^7.1.0" } } @@ -1878,10 +1495,26 @@ "glogg": "^1.0.0" } }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, "has-symbols": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", - "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, "has-value": { "version": "1.0.0", @@ -1921,9 +1554,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==" + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==" }, "inflight": { "version": "1.0.6", @@ -1940,14 +1573,14 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==" }, "interpret": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.2.0.tgz", - "integrity": "sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==" + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==" }, "invert-kv": { "version": "1.0.0", @@ -1999,6 +1632,14 @@ "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==" }, + "is-core-module": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.8.1.tgz", + "integrity": "sha512-SdNCUs284hr40hFTFP6l0IfZ/RSrMXF3qgoRHd3/79unUTvrFO/JoXwkGm+5J/Oe3E/b5GsnG330uUNgRpu1PA==", + "requires": { + "has": "^1.0.3" + } + }, "is-data-descriptor": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", @@ -2053,9 +1694,9 @@ } }, "is-glob": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", - "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "requires": { "is-extglob": "^2.1.1" } @@ -2138,14 +1779,14 @@ "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=" }, "jquery": { - "version": "3.4.1", - "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", - "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.6.0.tgz", + "integrity": "sha512-JVzAR/AjBvVt2BmYhxRCSYysDsPcssdmTFnzyLEts9qNwmjmu4JTAMYubEfwVOSwpQ1I1sKKFcxhZCI2buerfw==" }, "jquery-form": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/jquery-form/-/jquery-form-4.2.2.tgz", - "integrity": "sha512-HJTef7DRBSg8ge/RNUw8rUTTtB3l8ozO0OhD16AzDl+eIXp4skgCqRTd9fYPsOzL+pN6+1B9wvbTLGjgikz8Tg==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/jquery-form/-/jquery-form-4.3.0.tgz", + "integrity": "sha512-q3uaVCEWdLOYUCI6dpNdwf/7cJFOsUgdpq6r0taxtGQ5NJSkOzofyWm4jpOuJ5YxdmL1FI5QR+q+HB63HHLGnQ==", "requires": { "jquery": ">=1.7.2" } @@ -2156,16 +1797,16 @@ "integrity": "sha1-BvAzXxbjU6aV5yBr9QUDy1I6buU=" }, "jquery-validation": { - "version": "1.19.1", - "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.1.tgz", - "integrity": "sha512-QNnrZBqSltWUEJx+shOY5WtfrIb0gWmDjFfQP8rZKqMMSfpRSwEkSqhfHPvDfkObD8Hnv5KHSYI8yg73sVFdqA==" + "version": "1.19.3", + "resolved": "https://registry.npmjs.org/jquery-validation/-/jquery-validation-1.19.3.tgz", + "integrity": "sha512-iXxCS5W7STthSTMFX/NDZfWHBLbJ1behVK3eAgHXAV8/0vRa9M4tiqHvJMr39VGWHMGdlkhrtrkBuaL2UlE8yw==" }, "jquery-validation-unobtrusive": { - "version": "3.2.11", - "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.11.tgz", - "integrity": "sha512-3FQPllaWdD+Aq55zJLGSW39+eXPDz1HhwAvrSwYi8zHQ8DVcu5IJ1HVeTiCl0BnCnrIBvfFU3zEB/DrGdcoRIQ==", + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/jquery-validation-unobtrusive/-/jquery-validation-unobtrusive-3.2.12.tgz", + "integrity": "sha512-kPixGhVcuat7vZXngGFfSIksy4VlzZcHyRgnBIZdsfVneCU+D5sITC8T8dD/9c9K/Q+qkMlgp7ufJHz93nKSuQ==", "requires": { - "jquery": ">=1.8", + "jquery": "^3.5.1", "jquery-validation": ">=1.16" } }, @@ -2174,10 +1815,15 @@ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=" }, + "just-compare": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/just-compare/-/just-compare-1.5.1.tgz", + "integrity": "sha512-xDEEFHNIyJNmN4uo/2RVeUcay9THtN/5ka/iw98Y/gsa8w9KXZQuyaf5eFUY6VlntA2+G+bdPmdhqqTs7T+BRw==" + }, "just-debounce": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.0.0.tgz", - "integrity": "sha1-h/zPrv/AtozRnVX2cilD+SnqNeo=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/just-debounce/-/just-debounce-1.1.0.tgz", + "integrity": "sha512-qpcRocdkUmf+UTNBYx5w6dexX5J31AKK1OmPwH630a83DdVVUIngk55RSAiIGpQyoH0dlr872VHfPjnQnK1qDQ==" }, "kind-of": { "version": "6.0.3", @@ -2194,9 +1840,9 @@ } }, "lazystream": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.0.tgz", - "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", + "integrity": "sha512-b94GiNHQNy6JNTrt5w6zNyffMrNkXZb3KTkCZJb2V1xaEGCk093vkZ2jk3tpaeP33/OiXC+WvK9AxUebnf5nbw==", "requires": { "readable-stream": "^2.0.5" } @@ -2245,14 +1891,14 @@ } }, "lodash": { - "version": "4.17.15", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", - "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" }, "luxon": { - "version": "1.22.2", - "resolved": "https://registry.npmjs.org/luxon/-/luxon-1.22.2.tgz", - "integrity": "sha512-vq6eSaOOw1fKob+JXwfu0e3/UFUT4G4HTFRJab7dch8J1OdOGW/vXqCiJsY7rm2In+5gKNYx0EtnYT0Tc5V4Qw==" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/luxon/-/luxon-2.3.2.tgz", + "integrity": "sha512-MlAQQVMFhGk4WUA6gpfsy0QycnKP0+NlCBJRVRNPxxSIbjrCbQ65nrpJD3FVyJNZLuJ0uoqL57ye6BmDYgHaSw==" }, "make-iterator": { "version": "1.0.1", @@ -2312,6 +1958,26 @@ "requires": { "is-extglob": "^2.1.0" } + }, + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } } } }, @@ -2321,29 +1987,49 @@ "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==" }, "micromatch": { - "version": "3.1.10", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", - "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "dependencies": { + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "requires": { + "fill-range": "^7.0.1" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "requires": { + "is-number": "^7.0.0" + } + } } }, "minimatch": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", - "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -2378,9 +2064,9 @@ "integrity": "sha512-kDcwXR4PS7caBpuRYYBUz9iVixUk3anO3f5OYFiIPwK/20vCzKCHyKoulbiDY1S53zD2bxUpxN/IJ+TnXjfvxg==" }, "nan": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.0.tgz", - "integrity": "sha512-INOFj37C7k3AfaNTtX8RhsTw7qRy7eLET14cROi9+5HAVbbHuIWUHEauBv5qT4Av2tWasiTY1Jw6puUNqRJXQg==", + "version": "2.15.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.15.0.tgz", + "integrity": "sha512-8ZtvEnA2c5aYCZYd1cvgdnU6cqwixRoYg70xPLWUws5ORTa/lnw+u4amixRS/Ac5U5mQVgp9pnlSUnbNWFaWZQ==", "optional": true }, "nanomatch": { @@ -2402,9 +2088,9 @@ } }, "next-tick": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", - "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz", + "integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==" }, "normalize-package-data": { "version": "2.5.0", @@ -2418,12 +2104,9 @@ } }, "normalize-path": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", - "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", - "requires": { - "remove-trailing-separator": "^1.0.1" - } + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, "now-and-later": { "version": "2.0.1", @@ -2480,14 +2163,14 @@ } }, "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" } }, "object.defaults": { @@ -2584,15 +2267,6 @@ "resolved": "https://registry.npmjs.org/pascalcase/-/pascalcase-0.1.1.tgz", "integrity": "sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ=" }, - "path": { - "version": "0.12.7", - "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", - "integrity": "sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=", - "requires": { - "process": "^0.11.1", - "util": "^0.10.3" - } - }, "path-dirname": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/path-dirname/-/path-dirname-1.0.2.tgz", @@ -2612,9 +2286,9 @@ "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, "path-parse": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", - "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==" + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" }, "path-root": { "version": "0.1.1", @@ -2639,6 +2313,11 @@ "pinkie-promise": "^2.0.0" } }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==" + }, "pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", @@ -2657,11 +2336,6 @@ "pinkie": "^2.0.0" } }, - "popper.js": { - "version": "1.16.1", - "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.1.tgz", - "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" - }, "posix-character-classes": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz", @@ -2673,28 +2347,15 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=" }, "prismjs": { - "version": "1.19.0", - "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.19.0.tgz", - "integrity": "sha512-IVFtbW9mCWm9eOIaEkNyo2Vl4NnEifis2GQ7/MLRG5TQe6t+4Sj9J5QWI9i3v+SS43uZBlCAOn+zYTVYQcPXJw==", - "requires": { - "clipboard": "^2.0.0" - } - }, - "process": { - "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI=" + "version": "1.28.0", + "resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.28.0.tgz", + "integrity": "sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw==" }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, - "promise-polyfill": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-6.1.0.tgz", - "integrity": "sha1-36lpQ+qcEh/KTem1hoyznTRy4Fc=" - }, "pump": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/pump/-/pump-2.0.1.tgz", @@ -2755,6 +2416,28 @@ "graceful-fs": "^4.1.11", "micromatch": "^3.1.10", "readable-stream": "^2.0.2" + }, + "dependencies": { + "micromatch": { + "version": "3.1.10", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz", + "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } } }, "rechoir": { @@ -2799,9 +2482,9 @@ "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=" }, "repeat-element": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", - "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==" + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.4.tgz", + "integrity": "sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ==" }, "repeat-string": { "version": "1.6.1", @@ -2809,9 +2492,9 @@ "integrity": "sha1-jcrkcOHIirwtYA//Sndihtp15jc=" }, "replace-ext": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.0.tgz", - "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/replace-ext/-/replace-ext-1.0.1.tgz", + "integrity": "sha512-yD5BHCe7quCgBph4rMQ+0KkIRKwWCrHDOX1p1Gp6HwjPM5kVoCdKGNhN7ydqqsX6lJEnQDKZ/tFMiEdQ1dvPEw==" }, "replace-homedir": { "version": "1.0.0", @@ -2834,11 +2517,13 @@ "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" }, "resolve": { - "version": "1.15.1", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.15.1.tgz", - "integrity": "sha512-84oo6ZTtoTUpjgNEr5SJyzQhzL72gaRodsSfyxC/AXRvwu0Yse9H8eF9IpGo7b8YetZhlI6v7ZQ6bKBFV/6S7w==", + "version": "1.22.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.0.tgz", + "integrity": "sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==", "requires": { - "path-parse": "^1.0.6" + "is-core-module": "^2.8.1", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" } }, "resolve-dir": { @@ -2868,14 +2553,6 @@ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz", "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==" }, - "rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "requires": { - "glob": "^7.1.3" - } - }, "safe-buffer": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", @@ -3053,9 +2730,9 @@ } }, "source-map-url": { - "version": "0.4.0", - "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.0.tgz", - "integrity": "sha1-PpNdfd1zYxuXZZlW1VEo6HtQhKM=" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/source-map-url/-/source-map-url-0.4.1.tgz", + "integrity": "sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw==" }, "sparkles": { "version": "1.0.1", @@ -3063,32 +2740,32 @@ "integrity": "sha512-dSO0DDYUahUt/0/pD/Is3VIm5TGJjludZ0HVymmhYF6eNA53PVLhnUk0znSYbH8IYBuJdCE+1luR22jNLMaQdw==" }, "spdx-correct": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", - "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.1.tgz", + "integrity": "sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==", "requires": { "spdx-expression-parse": "^3.0.0", "spdx-license-ids": "^3.0.0" } }, "spdx-exceptions": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz", - "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==" + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz", + "integrity": "sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==" }, "spdx-expression-parse": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz", - "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", "requires": { "spdx-exceptions": "^2.1.0", "spdx-license-ids": "^3.0.0" } }, "spdx-license-ids": { - "version": "3.0.5", - "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz", - "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==" + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.11.tgz", + "integrity": "sha512-Ctl2BrFiM0X3MANYgj3CkygxhRmr9mi6xhejbdO960nF6EDJApTYpn0BQnDKlnNBULKiCN1n3w9EBkHK8ZWg+g==" }, "split-string": { "version": "3.1.0", @@ -3166,6 +2843,11 @@ "is-utf8": "^0.2.0" } }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==" + }, "sver-compat": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/sver-compat/-/sver-compat-1.5.0.tgz", @@ -3175,14 +2857,10 @@ "es6-symbol": "^3.1.1" } }, - "sweetalert": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/sweetalert/-/sweetalert-2.1.2.tgz", - "integrity": "sha512-iWx7X4anRBNDa/a+AdTmvAzQtkN1+s4j/JJRWlHpYE8Qimkohs8/XnFcWeYHH2lMA8LRCa5tj2d244If3S/hzA==", - "requires": { - "es6-object-assign": "^1.1.0", - "promise-polyfill": "^6.0.2" - } + "sweetalert2": { + "version": "11.4.8", + "resolved": "https://registry.npmjs.org/sweetalert2/-/sweetalert2-11.4.8.tgz", + "integrity": "sha512-BDS/+E8RwaekGSxCPUbPnsRAyQ439gtXkTF/s98vY2l9DaVEOMjGj1FaQSorfGREKsbbxGSP7UXboibL5vgTMA==" }, "through2": { "version": "2.0.5", @@ -3299,15 +2977,16 @@ "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=" }, "undertaker": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.2.1.tgz", - "integrity": "sha512-71WxIzDkgYk9ZS+spIB8iZXchFhAdEo2YU8xYqBYJ39DIUIqziK78ftm26eecoIY49X0J2MLhG4hr18Yp6/CMA==", + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/undertaker/-/undertaker-1.3.0.tgz", + "integrity": "sha512-/RXwi5m/Mu3H6IHQGww3GNt1PNXlbeCuclF2QYR14L/2CHPz3DFZkvB5hZ0N/QUkiXWCACML2jXViIQEQc2MLg==", "requires": { "arr-flatten": "^1.0.1", "arr-map": "^2.0.0", "bach": "^1.0.0", "collection-map": "^1.0.0", "es6-weak-map": "^2.0.1", + "fast-levenshtein": "^1.0.0", "last-run": "^1.1.0", "object.defaults": "^1.0.0", "object.reduce": "^1.0.0", @@ -3390,30 +3069,15 @@ "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" }, - "util": { - "version": "0.10.4", - "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", - "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", - "requires": { - "inherits": "2.0.3" - }, - "dependencies": { - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" - } - } - }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, "v8flags": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.1.3.tgz", - "integrity": "sha512-amh9CCg3ZxkzQ48Mhcb8iX7xpAfYJgePHxWMQCBWECpOSqJUXgY26ncA61UTV0BkPqfhcy6mzwCIoP4ygxpW8w==", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/v8flags/-/v8flags-3.2.0.tgz", + "integrity": "sha512-mH8etigqMfiGWdeXpaaqGfs6BndypxusHHcv2qSHyZkGEznCd/qAXCWWRzeowtL54147cktFOC4P5y+kl8d8Jg==", "requires": { "homedir-polyfill": "^1.0.1" } @@ -3433,9 +3097,9 @@ "integrity": "sha1-HCQ6ULWVwb5Up1S/7OhWO5/42BM=" }, "vinyl": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", - "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.1.tgz", + "integrity": "sha512-LII3bXRFBZLlezoG5FfZVcXflZgWP/4dCwKtxd5ky9+LOtM4CS3bIRQsmR1KMnMW07jpE8fqR2lcxPZ+8sJIcw==", "requires": { "clone": "^2.1.1", "clone-buffer": "^1.0.0", @@ -3481,6 +3145,16 @@ "now-and-later": "^2.0.0", "remove-bom-buffer": "^3.0.0", "vinyl": "^2.0.0" + }, + "dependencies": { + "normalize-path": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-2.1.1.tgz", + "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", + "requires": { + "remove-trailing-separator": "^1.0.1" + } + } } }, "which": { @@ -3516,14 +3190,14 @@ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" }, "y18n": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.1.tgz", - "integrity": "sha1-bRX7qITAhnnA136I53WegR4H+kE=" + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-3.2.2.tgz", + "integrity": "sha512-uGZHXkHnhF0XeeAPgnKfPv1bgKAYyVvmNL1xlKsPYZPaIHxGti2hHqvOCQv71XMsLxu1QjergkqogUnms5D3YQ==" }, "yargs": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.0.tgz", - "integrity": "sha1-a6MY6xaWFyf10oT46gA+jWFU0Mg=", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-7.1.2.tgz", + "integrity": "sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==", "requires": { "camelcase": "^3.0.0", "cliui": "^3.2.0", @@ -3537,15 +3211,16 @@ "string-width": "^1.0.2", "which-module": "^1.0.0", "y18n": "^3.2.1", - "yargs-parser": "^5.0.0" + "yargs-parser": "^5.0.1" } }, "yargs-parser": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.0.tgz", - "integrity": "sha1-J17PDX/+Bcd+ZOfIbkzZS/DhIoo=", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-5.0.1.tgz", + "integrity": "sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==", "requires": { - "camelcase": "^3.0.0" + "camelcase": "^3.0.0", + "object.assign": "^4.1.0" } } } diff --git a/modules/docs/app/VoloDocs.Web/yarn.lock b/modules/docs/app/VoloDocs.Web/yarn.lock index 802094ec7c..8e5f9ddd6d 100644 --- a/modules/docs/app/VoloDocs.Web/yarn.lock +++ b/modules/docs/app/VoloDocs.Web/yarn.lock @@ -2009,9 +2009,9 @@ pretty-hrtime@^1.0.0: resolved "https://registry.yarnpkg.com/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1" prismjs@^1.26.0: - version "1.27.0" - resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.27.0.tgz#bb6ee3138a0b438a3653dd4d6ce0cc6510a45057" - integrity sha512-t13BGPUlFDR7wRB5kQDG4jjl7XeuH6jbJGt11JHPL96qwsEHNX2+68tFXqc1/k+/jALsbSWJKUOT/hcYAZ5LkA== + version "1.28.0" + resolved "https://registry.yarnpkg.com/prismjs/-/prismjs-1.28.0.tgz#0d8f561fa0f7cf6ebca901747828b149147044b6" + integrity sha512-8aaXdYvl1F7iC7Xm1spqSaY/OJBpYW3v+KJ+F17iYxvdc8sfjW194COK5wVhMZX45tGteiBQgdvD/nhxcRwylw== process-nextick-args@^2.0.0, process-nextick-args@~2.0.0: version "2.0.0" From 14373bb559ce23b6be5dd39fea57c932a30d2a53 Mon Sep 17 00:00:00 2001 From: maliming Date: Tue, 19 Apr 2022 11:33:03 +0800 Subject: [PATCH 019/134] Add notes to the `Module-Development-Basics` --- docs/en/Module-Development-Basics.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/en/Module-Development-Basics.md b/docs/en/Module-Development-Basics.md index 63cbc31893..17c0e91808 100644 --- a/docs/en/Module-Development-Basics.md +++ b/docs/en/Module-Development-Basics.md @@ -29,6 +29,8 @@ public class BlogModule : AbpModule ``ConfigureServices`` is the main method to add your services to the dependency injection system and configure other modules. Example: +> These methods have Async versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. + ````C# public class BlogModule : AbpModule { @@ -63,6 +65,8 @@ See the [Configuration](Configuration.md) document for more about the configurat ``AbpModule`` class also defines ``PreConfigureServices`` and ``PostConfigureServices`` methods to override and write your code just before and just after ``ConfigureServices``. Notice that the code you have written into these methods will be executed before/after the ``ConfigureServices`` methods of all other modules. +> These methods have Async versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. + ### Application Initialization Once all the services of all modules are configured, the application starts by initializing all modules. In this phase, you can resolve services from ``IServiceProvider`` since it's ready and available. @@ -71,10 +75,18 @@ Once all the services of all modules are configured, the application starts by i You can override ``OnApplicationInitialization`` method to execute code while application is being started. Example: +> These methods have Async versions too, and if you want to make asynchronous calls inside these methods, override the asynchronous versions instead of the synchronous ones. + ````C# public class BlogModule : AbpModule { - //... + // Asynchronous methods call synchronous methods by default, You only need to implement one of them. + + public override Task OnApplicationInitializationAsync(ApplicationInitializationContext context) + { + var myService = context.ServiceProvider.GetService(); + await myService.DoSomethingAsync(); + } public override void OnApplicationInitialization(ApplicationInitializationContext context) { From 061d152bcf855e32f0940e22c993689b6ca9f27f Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 19 Apr 2022 11:05:01 +0300 Subject: [PATCH 020/134] Update en.json --- .../AbpIoLocalization/Www/Localization/Resources/en.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json index 0b9d6d96b2..35b755e8c3 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/en.json @@ -214,7 +214,11 @@ "SeeDocs": "See Docs", "None": "None", "Application": "Application", + "ApplicationExplanation": "Creates a fully layered solution based on Domain Driven Design practices. Recommended for long-term projects that need a maintainable and extensible codebase.", + "ApplicationNoLayer": "Application (single layer)", + "ApplicationNoLayerExplanation": "Creates a single-layer web application. Recommended for building an application with a simpler and easy to understand architecture.", "Module": "Module", + "ModuleExplanation": "Creates a reusable, fully layered application module solution. You can use this option to create modules for your modular application.", "PackageName": "Package Name", "LicenseURL": "License URL", "License": "License", From 9f4dacc984657c4ff77cc49a55f2dbfdb7bf9412 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Tue, 19 Apr 2022 11:13:36 +0300 Subject: [PATCH 021/134] Update tr.json --- .../AbpIoLocalization/Www/Localization/Resources/tr.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json index f9c6d54204..ad8393fab3 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Www/Localization/Resources/tr.json @@ -214,7 +214,11 @@ "SeeDocs": "Dökümanı Görüntüle", "None": "Hiç", "Application": "Uygulama", + "ApplicationExplanation": "Domain Driven Design pratikleri üzerine oluşturulmuş çok katmanlı bir çözüm oluşturur. Bakıma ve geliştirmeye açık kod tabanına ihtiyaç duyan uzun süreli projeler için önerilir.", + "ApplicationNoLayer": "Uygulama (tek katmanlı)", + "ApplicationNoLayerExplanation": "Tek katmanlı Web uygulaması oluşturur. Daha basit ve anlaması kolay mimari ile uygulama geliştirmek için önerilir.", "Module": "Modül", + "ModuleExplanation": "Tamamen katmanlanmış, tekrar kullanılabilir bir uygulama modülü oluşturur. Uygulamanıza modül yaratmak için bu seçeneği kullanabilirsiniz.", "PackageName": "Paket adı", "LicenseURL": "Lisans Linki", "License": "Lisans", From d871f35b3440d214a856afc0ccca11fac467a5c1 Mon Sep 17 00:00:00 2001 From: albert <9526587+ebicoglu@users.noreply.github.com> Date: Wed, 20 Apr 2022 04:21:17 +0300 Subject: [PATCH 022/134] Add comparision table --- .../_results/with-without-abp-comparison.png | Bin 0 -> 62756 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test/AbpPerfTest/_results/with-without-abp-comparison.png diff --git a/test/AbpPerfTest/_results/with-without-abp-comparison.png b/test/AbpPerfTest/_results/with-without-abp-comparison.png new file mode 100644 index 0000000000000000000000000000000000000000..997e1bda13919de5a4a4c13b52c25a3218335b4e GIT binary patch literal 62756 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

E3EJ+Cx)iXLumd$sN7rR> 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
cY%&+B><=(Jnh7MAgltRXWJUPk zg#_5)4EF}1FxL=NrKBN_L0e%md-W`WLr^(Zgje5LpV{>lQ7(%5#+N&vqxP&psi2{k zEPM9ME+{qNA59^=$F+_P^S1CkF_cgCVEDlp=6<_NwEgh151wymY}h6EJ_t0F|I|R; zFsrUcX|AFWT~lt|uyc$bQgK=&G+7=Z@mS{G@5eeeR6b1$^YMpN5^2#FqF{4xo%;GV zMxZ-ONLCEiN{Z}b(o9~fum>DPDi^0u?8pPV@I{|u*@L~FAx-Nr1GpkNc|uz@?Y&1` zz7%C_H~G`3v5)CmSduE7=foMdyC?y`I>NHFUpEsS>brl)GRO4{TIR3jLl(vPv}iCd zXNdjRH4sXUZYcNPwQ^tHRHzu38I$;8FI@GB1qvR_J|2yjy}|-1L-{SwDV`+%xz*F~ zAp&e$Sh!0^6oc~S+4f&>(=hM4>Qdgd1UX%#5m;YuP0my$MgojrMszoL>wC* zv1=~)+$b23AXuev^h}w;F09M*Xbn-w&xD9aD6(T_P`zL)xvD)RA4fiK=M}^f4qsatT~sjNpUvEWe{3FYeb7J zs<4bpl%n8FHPTOrUw!m@w?BB?J2YaYXN>uD{{$CTRlBqN?$@2&>Z-iOB+3$+ViCF* zYEPJL#9t@YR;gd@`9QG>`qW*jvc0$h_!v+_m&VE40*bRDQ096SU0SZ0g(|7%y${XKW9lfypYM7>3zd-A1=hPBg-L68To<82rdUNfKaRAP% zd#O{rExT8$sP&wF?p4=b`9cbkLxdK^K0XQfpklXyDw$)63?5-0$#A{{=2lp*_?B0c z?6r)55}%+ggD_LGBGa8nu(^4vFyO8gr*17&)51o#(;RcZYq;S&%WRzIjK^u2SR?6* zz(O={O4s5nhL?~|c%hm7tE;u_pQ9m`>W!Ld31A?E{=<2_Jk>2K*ULb(o+Mb0t%gS$ zq*)MJ8h@}ftJ@MPcLn-ZIv~yWYfNYG|hVS*n!Z&=zMJu{9sm1;+m2xA#`8g|$qZ?hqauwU%ne-SLNbyci z1y(qo#P)WT7T$aKO`gw<(|kT%E=TU9wpG+G@)w+jfZL93{@HTEO&OALT&7nqp_!`B zE|j(H%1J_KIc>~^-$ki=hCqKl%$bhS?${icV+x3S19^T-=1yXZKhH!~dgKiI*skom zXG})#^LghKPGVnDrp)P68CE@ira``9|5*m#5Om8^kJ0KLq%QcCav*Htef2j>QoZt2 zk9o)iho1(U)x|xupATf5Fl+I46Q@7B_-_Z6{vFl*_XBeOk7 Date: Wed, 20 Apr 2022 13:25:47 +0300 Subject: [PATCH 023/134] add scroll-index feature to blogging document --- docs/en/Modules/Cms-Kit/Blogging.md | 7 ++++++- .../images/cmskit-module-features-dialog-2.png | Bin 0 -> 10405 bytes .../cmskit-module-features-scroll-index.png | Bin 0 -> 108721 bytes 3 files changed, 6 insertions(+), 1 deletion(-) create mode 100644 docs/en/images/cmskit-module-features-dialog-2.png create mode 100644 docs/en/images/cmskit-module-features-scroll-index.png diff --git a/docs/en/Modules/Cms-Kit/Blogging.md b/docs/en/Modules/Cms-Kit/Blogging.md index 43ab85e129..12039b1669 100644 --- a/docs/en/Modules/Cms-Kit/Blogging.md +++ b/docs/en/Modules/Cms-Kit/Blogging.md @@ -33,7 +33,12 @@ Blog feature uses some of the other CMS Kit features. You can enable or disable You can select/deselect the desired features for blog posts. -![features-dialog](../../images/cmskit-module-features-dialog.png) +![features-dialog](../../images/cmskit-module-features-dialog-2.png) + +##### Quick Navigation Bar In Blog Post +If you enable "Quick navigation bar in blog posts", it will enabled scroll index as seen below. + +![scroll-index](../../images/cmskit-module-features-scroll-index.png) ### Blog Post Management diff --git a/docs/en/images/cmskit-module-features-dialog-2.png b/docs/en/images/cmskit-module-features-dialog-2.png new file mode 100644 index 0000000000000000000000000000000000000000..b6dd409e0a59c728f50fb4d257f5ed06a129498b GIT binary patch literal 10405 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 literal 0 HcmV?d00001 diff --git a/docs/en/images/cmskit-module-features-scroll-index.png b/docs/en/images/cmskit-module-features-scroll-index.png new file mode 100644 index 0000000000000000000000000000000000000000..0bc3dedcc5a6474e5187ca176368bd41a5663b27 GIT binary patch literal 108721 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 aUs%qZQg}p9bXZ_Q=!%)`Z Date: Wed, 20 Apr 2022 14:11:03 +0300 Subject: [PATCH 024/134] jasmine-core bump to 4.0 --- templates/app-nolayers/angular/package.json | 2 +- templates/app/angular/package.json | 2 +- templates/module/angular/package.json | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/templates/app-nolayers/angular/package.json b/templates/app-nolayers/angular/package.json index 5e66eb239a..6fdafdd93a 100644 --- a/templates/app-nolayers/angular/package.json +++ b/templates/app-nolayers/angular/package.json @@ -49,7 +49,7 @@ "@typescript-eslint/eslint-plugin": "5.3.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", - "jasmine-core": "~3.7.0", + "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.1.0", diff --git a/templates/app/angular/package.json b/templates/app/angular/package.json index 5e66eb239a..6fdafdd93a 100644 --- a/templates/app/angular/package.json +++ b/templates/app/angular/package.json @@ -49,7 +49,7 @@ "@typescript-eslint/eslint-plugin": "5.3.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", - "jasmine-core": "~3.7.0", + "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.1.0", diff --git a/templates/module/angular/package.json b/templates/module/angular/package.json index 3f2ae010fe..c7a0344227 100644 --- a/templates/module/angular/package.json +++ b/templates/module/angular/package.json @@ -52,7 +52,7 @@ "@typescript-eslint/eslint-plugin": "5.3.0", "@typescript-eslint/parser": "5.3.0", "eslint": "^8.2.0", - "jasmine-core": "~3.7.0", + "jasmine-core": "~4.0.0", "karma": "~6.3.0", "karma-chrome-launcher": "~3.1.0", "karma-coverage": "~2.1.0", From a67509ce05c776d9dd28a6e4d5fe46bf8547a699 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Wed, 20 Apr 2022 15:38:12 +0300 Subject: [PATCH 025/134] Cli: Check in gitignore files --- .../ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs index 69dada9a8e..437721f30c 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/TemplateCodeDeleteStep.cs @@ -8,7 +8,7 @@ public class TemplateCodeDeleteStep : ProjectBuildPipelineStep { foreach (var file in context.Files) { - if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".cshtml") || file.Name.EndsWith(".json")) + if (file.Name.EndsWith(".cs") || file.Name.EndsWith(".csproj") || file.Name.EndsWith(".cshtml") || file.Name.EndsWith(".json") || file.Name.EndsWith(".gitignore")) { file.RemoveTemplateCode(context.Symbols); file.RemoveTemplateCodeMarkers(); From 88d86f87172ea7243df4f0151238198ce9d17382 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 20 Apr 2022 14:23:28 +0300 Subject: [PATCH 026/134] Update @angular/core @angular/cli @angular-eslint/schematics to 13.3 --- templates/app/angular/package.json | 38 +++++++++++++-------------- templates/module/angular/package.json | 38 +++++++++++++-------------- 2 files changed, 38 insertions(+), 38 deletions(-) diff --git a/templates/app/angular/package.json b/templates/app/angular/package.json index 6fdafdd93a..cdddc9c658 100644 --- a/templates/app/angular/package.json +++ b/templates/app/angular/package.json @@ -20,30 +20,30 @@ "@abp/ng.tenant-management": "~5.2.1", "@abp/ng.theme.basic": "~5.2.1", "@abp/ng.theme.shared": "~5.2.1", - "@angular/animations": "~13.1.1", - "@angular/common": "~13.1.1", - "@angular/compiler": "~13.1.1", - "@angular/core": "~13.1.1", - "@angular/forms": "~13.1.1", - "@angular/localize": "~13.1.1", - "@angular/platform-browser": "~13.1.1", - "@angular/platform-browser-dynamic": "~13.1.1", - "@angular/router": "~13.1.1", + "@angular/animations": "~13.3.3", + "@angular/common": "~13.3.3", + "@angular/compiler": "~13.3.3", + "@angular/core": "~13.3.3", + "@angular/forms": "~13.3.3", + "@angular/localize": "~13.3.3", + "@angular/platform-browser": "~13.3.3", + "@angular/platform-browser-dynamic": "~13.3.3", + "@angular/router": "~13.3.3", "rxjs": "~6.6.0", "tslib": "^2.1.0", "zone.js": "~0.11.4" }, "devDependencies": { "@abp/ng.schematics": "~5.2.1", - "@angular-devkit/build-angular": "~13.1.2", - "@angular-eslint/builder": "~13.0.1", - "@angular-eslint/eslint-plugin": "~13.0.1", - "@angular-eslint/eslint-plugin-template": "~13.0.1", - "@angular-eslint/schematics": "~13.0.1", - "@angular-eslint/template-parser": "~13.0.1", - "@angular/cli": "~13.1.2", - "@angular/compiler-cli": "~13.1.1", - "@angular/language-service": "~13.1.1", + "@angular-devkit/build-angular": "~13.3.3", + "@angular-eslint/builder": "~13.2.1", + "@angular-eslint/eslint-plugin": "~13.2.1", + "@angular-eslint/eslint-plugin-template": "~13.2.1", + "@angular-eslint/schematics": "~13.2.1", + "@angular-eslint/template-parser": "~13.2.1", + "@angular/cli": "~13.3.3", + "@angular/compiler-cli": "~13.3.3", + "@angular/language-service": "~13.3.3", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.3.0", @@ -58,4 +58,4 @@ "ng-packagr": "^13.1.2", "typescript": "~4.5.4" } -} +} \ No newline at end of file diff --git a/templates/module/angular/package.json b/templates/module/angular/package.json index c7a0344227..05f715c7c2 100644 --- a/templates/module/angular/package.json +++ b/templates/module/angular/package.json @@ -23,30 +23,30 @@ "@abp/ng.tenant-management": "~5.2.1", "@abp/ng.theme.basic": "~5.2.1", "@abp/ng.theme.shared": "~5.2.1", - "@angular/animations": "~13.1.1", - "@angular/common": "~13.1.1", - "@angular/compiler": "~13.1.1", - "@angular/core": "~13.1.1", - "@angular/forms": "~13.1.1", - "@angular/localize": "~13.1.1", - "@angular/platform-browser": "~13.1.1", - "@angular/platform-browser-dynamic": "~13.1.1", - "@angular/router": "~13.1.1", + "@angular/animations": "~13.3.3", + "@angular/common": "~13.3.3", + "@angular/compiler": "~13.3.3", + "@angular/core": "~13.3.3", + "@angular/forms": "~13.3.3", + "@angular/localize": "~13.3.3", + "@angular/platform-browser": "~13.3.3", + "@angular/platform-browser-dynamic": "~13.3.3", + "@angular/router": "~13.3.3", "rxjs": "~6.6.0", "tslib": "^2.1.0", "zone.js": "~0.11.4" }, "devDependencies": { "@abp/ng.schematics": "~5.2.1", - "@angular-devkit/build-angular": "~13.1.2", - "@angular-eslint/builder": "~13.0.1", - "@angular-eslint/eslint-plugin": "~13.0.1", - "@angular-eslint/eslint-plugin-template": "~13.0.1", - "@angular-eslint/schematics": "~13.0.1", - "@angular-eslint/template-parser": "~13.0.1", - "@angular/cli": "~13.1.2", - "@angular/compiler-cli": "~13.1.1", - "@angular/language-service": "~13.1.1", + "@angular-devkit/build-angular": "~13.3.3", + "@angular-eslint/builder": "~13.2.1", + "@angular-eslint/eslint-plugin": "~13.2.1", + "@angular-eslint/eslint-plugin-template": "~13.2.1", + "@angular-eslint/schematics": "~13.2.1", + "@angular-eslint/template-parser": "~13.2.1", + "@angular/cli": "~13.3.3", + "@angular/compiler-cli": "~13.3.3", + "@angular/language-service": "~13.3.3", "@types/jasmine": "~3.6.0", "@types/node": "^12.11.1", "@typescript-eslint/eslint-plugin": "5.3.0", @@ -62,4 +62,4 @@ "symlink-manager": "^1.5.0", "typescript": "~4.5.4" } -} +} \ No newline at end of file From 8744d1953bca2eb0ab9ae2b673e5d63f77a95547 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 20 Apr 2022 16:39:21 +0300 Subject: [PATCH 027/134] Change permisson title input name to entityDisplayName --- .../identity/src/lib/components/roles/roles.component.html | 1 - .../identity/src/lib/components/users/users.component.html | 2 +- .../identity/src/lib/components/users/users.component.ts | 6 +++--- .../src/lib/components/permission-management.component.html | 6 +++--- .../src/lib/components/permission-management.component.ts | 2 +- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html index 20d5509d15..09dcdb1be3 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/roles/roles.component.html @@ -55,7 +55,6 @@ }; let init = initTemplate " - [title]="providerKey" (abpInit)="init(abpPermissionManagement)" > diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html index a481b9d697..7873adf6ea 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.html @@ -95,7 +95,7 @@ }; let init = initTemplate " - [title]="userName" + [entityDisplayName]="entityDisplayName" (abpInit)="init(abpPermissionManagement)" > diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 1c9f6f0f17..90a453e5c9 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -64,7 +64,7 @@ export class UsersComponent implements OnInit { onVisiblePermissionChange = event => { this.visiblePermissions = event; }; - userName: string; + entityDisplayName: string; get roleGroups(): FormGroup[] { return ((this.form.get('roleNames') as FormArray)?.controls as FormGroup[]) || []; @@ -177,9 +177,9 @@ export class UsersComponent implements OnInit { this.list.hookToQuery(query => this.service.getList(query)).subscribe(res => (this.data = res)); } - openPermissionsModal(providerKey: string, userName:string) { + openPermissionsModal(providerKey: string, entityDisplayName?: string) { this.providerKey = providerKey; - this.userName = userName; + this.entityDisplayName = entityDisplayName; setTimeout(() => { this.visiblePermissions = true; }, 0); diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html index d2d14035ca..34396b97b4 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html @@ -1,9 +1,9 @@ - +

- {{ 'AbpPermissionManagement::Permissions' | abpLocalization }} - - {{ title }} + {{ 'AbpPermissionManagement::Permissions' | abpLocalization }} - + {{ entityDisplayName || data.data.entityDisplayName }}

diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index bc07bb6583..4b53350208 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -65,7 +65,7 @@ export class PermissionManagementComponent } @Input() - title = ''; + entityDisplayName: string | undefined; set visible(value: boolean) { if (value === this._visible) return; From 909b62566c8c91a9aaf2c3d467165d02a5c44602 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 20 Apr 2022 16:40:29 +0300 Subject: [PATCH 028/134] Update permission-management.component.html --- .../src/lib/components/permission-management.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html index 34396b97b4..5a9fa0dd5a 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.html @@ -3,7 +3,7 @@

{{ 'AbpPermissionManagement::Permissions' | abpLocalization }} - - {{ entityDisplayName || data.data.entityDisplayName }} + {{ entityDisplayName || data.entityDisplayName }}

From 0f0f4e0d504cfb349e008c88ac22a768e6232a33 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 20 Apr 2022 16:59:36 +0300 Subject: [PATCH 029/134] Fix lint error on PermissonManagement --- .../src/lib/components/permission-management.component.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts index 4b53350208..496b3bb989 100644 --- a/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts +++ b/npm/ng-packs/packages/permission-management/src/lib/components/permission-management.component.ts @@ -59,14 +59,14 @@ export class PermissionManagementComponent protected _visible = false; + @Input() + entityDisplayName: string | undefined; + @Input() get visible(): boolean { return this._visible; } - @Input() - entityDisplayName: string | undefined; - set visible(value: boolean) { if (value === this._visible) return; From 44ad5168a204b3e965fbf71b5662effb35443ff6 Mon Sep 17 00:00:00 2001 From: Mahmut Gundogdu Date: Wed, 20 Apr 2022 17:07:42 +0300 Subject: [PATCH 030/134] fix lint error on user component --- .../identity/src/lib/components/users/users.component.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts index 90a453e5c9..b8fadc81a1 100644 --- a/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts +++ b/npm/ng-packs/packages/identity/src/lib/components/users/users.component.ts @@ -59,12 +59,13 @@ export class UsersComponent implements OnInit { permissionManagementKey = ePermissionManagementComponents.PermissionManagement; + entityDisplayName: string; + trackByFn: TrackByFunction = (index, item) => Object.keys(item)[0] || index; onVisiblePermissionChange = event => { this.visiblePermissions = event; }; - entityDisplayName: string; get roleGroups(): FormGroup[] { return ((this.form.get('roleNames') as FormArray)?.controls as FormGroup[]) || []; From c2c1cc52205474e6bc9a882fc475561ddeff2be8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=C3=BCseyin=20Tosun?= Date: Thu, 21 Apr 2022 10:58:41 +0300 Subject: [PATCH 031/134] Grammatical Correction Given tenant does not exist --- .../Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json index 25516ba765..f3259dbb54 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.MultiTenancy/Volo/Abp/AspNetCore/Mvc/UI/MultiTenancy/Localization/en.json @@ -1,7 +1,7 @@ { "culture": "en", "texts": { - "GivenTenantIsNotExist": "Given tenant is not exist: {0}", + "GivenTenantIsNotExist": "Given tenant does not exist: {0}", "GivenTenantIsNotAvailable": "Given tenant is not available: {0}", "Tenant": "Tenant", "Switch": "switch", From c5db087f58e7d6aad62f2a6dab233ce1df852b5a Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Thu, 21 Apr 2022 15:40:03 +0300 Subject: [PATCH 032/134] Cli: Add license code to module source code downloads --- .../ModuleProjectBuildPipelineBuilder.cs | 2 ++ .../Steps/CreateAppSettingsSecretsStep.cs | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs index 35bcb71d53..2d28143921 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs @@ -14,6 +14,8 @@ public static class ModuleProjectBuildPipelineBuilder pipeline.Steps.Add(new ReplaceCommonPropsStep()); pipeline.Steps.Add(new MakeProxyJsonFileEmbeddedStep()); pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep()); + pipeline.Steps.Add(new CreateAppSettingsSecretsStep()); + pipeline.Steps.Add(new LicenseCodeReplaceStep()); pipeline.Steps.Add(new UpdateNuGetConfigStep("/NuGet.Config")); pipeline.Steps.Add(new CreateProjectResultZipStep()); diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs index 5a84bda994..7a5f792b15 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/CreateAppSettingsSecretsStep.cs @@ -40,7 +40,21 @@ public class CreateAppSettingsSecretsStep : ProjectBuildPipelineStep private static byte[] GetAppSettingsSecretJsonContent(ProjectBuildContext context) { - return context.Template.IsPro() + bool condition; + if (context.Template != null) + { + condition = context.Template.IsPro(); + } + else if (context.Module != null) + { + condition = context.Module.IsPro; + } + else + { + condition = false; + } + + return condition ? $"{{{Environment.NewLine} \"AbpLicenseCode\": \"{CliConsts.LicenseCodePlaceHolder}\" {Environment.NewLine}}}".GetBytes() : $"{{{Environment.NewLine}}}".GetBytes(); } From a1e284fa951c603118d0c754b4fc21d553292a04 Mon Sep 17 00:00:00 2001 From: Yunus Emre Kalkan Date: Thu, 21 Apr 2022 15:42:26 +0300 Subject: [PATCH 033/134] Update ModuleProjectBuildPipelineBuilder.cs --- .../Building/ModuleProjectBuildPipelineBuilder.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs index 2d28143921..9a5eecb4f6 100644 --- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs +++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/ModuleProjectBuildPipelineBuilder.cs @@ -14,8 +14,13 @@ public static class ModuleProjectBuildPipelineBuilder pipeline.Steps.Add(new ReplaceCommonPropsStep()); pipeline.Steps.Add(new MakeProxyJsonFileEmbeddedStep()); pipeline.Steps.Add(new ReplaceConfigureAwaitPropsStep()); - pipeline.Steps.Add(new CreateAppSettingsSecretsStep()); - pipeline.Steps.Add(new LicenseCodeReplaceStep()); + + if (context.Module.IsPro) + { + pipeline.Steps.Add(new CreateAppSettingsSecretsStep()); + pipeline.Steps.Add(new LicenseCodeReplaceStep()); + } + pipeline.Steps.Add(new UpdateNuGetConfigStep("/NuGet.Config")); pipeline.Steps.Add(new CreateProjectResultZipStep()); From a1b924ddfc6d045b2be4e68aa4440720de7e2405 Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 22 Apr 2022 11:50:27 +0300 Subject: [PATCH 034/134] Make datagrids columns orderable --- .../Pages/Identity/RoleManagement.razor.cs | 4 +++- .../Pages/Identity/UserManagement.razor.cs | 5 ++++- .../Pages/TenantManagement/TenantManagement.razor.cs | 2 ++ 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index f14e5cef90..cf4d1c8ecd 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -81,11 +81,13 @@ public partial class RoleManagement new TableColumn { Title = L["Actions"], - Actions = EntityActions.Get() + Actions = EntityActions.Get(), + Sortable = true, }, new TableColumn { Title = L["RoleName"], + Sortable = true, Data = nameof(IdentityRoleDto.Name), Component = typeof(RoleNameComponent) }, diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs index 982f77882d..5919ed9ed3 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/UserManagement.razor.cs @@ -171,22 +171,25 @@ public partial class UserManagement new TableColumn { Title = L["Actions"], - Actions = EntityActions.Get() + Actions = EntityActions.Get(), }, new TableColumn { Title = L["UserName"], Data = nameof(IdentityUserDto.UserName), + Sortable = true, }, new TableColumn { Title = L["Email"], Data = nameof(IdentityUserDto.Email), + Sortable = true, }, new TableColumn { Title = L["PhoneNumber"], Data = nameof(IdentityUserDto.PhoneNumber), + Sortable = true, } }); diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs index 134bbcd439..57987cf127 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs @@ -110,10 +110,12 @@ public partial class TenantManagement { Title = L["Actions"], Actions = EntityActions.Get() + Sortable = true, }, new TableColumn { Title = L["TenantName"], + Sortable = true, Data = nameof(TenantDto.Name), }, }); From 5b9fed9f80ddbf357f872f6a51b726053213c870 Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 22 Apr 2022 11:52:45 +0300 Subject: [PATCH 035/134] Add missing comma --- .../Pages/TenantManagement/TenantManagement.razor.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs index 57987cf127..8e4b0efa97 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs @@ -109,7 +109,7 @@ public partial class TenantManagement new TableColumn { Title = L["Actions"], - Actions = EntityActions.Get() + Actions = EntityActions.Get(), Sortable = true, }, new TableColumn From 2877be99065c0818b6bd00e59422624ed80d4f63 Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 22 Apr 2022 16:42:44 +0300 Subject: [PATCH 036/134] Remove Sortable property from "Actions" column --- .../Pages/Identity/RoleManagement.razor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs index cf4d1c8ecd..cfb86b5007 100644 --- a/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs +++ b/modules/identity/src/Volo.Abp.Identity.Blazor/Pages/Identity/RoleManagement.razor.cs @@ -82,7 +82,6 @@ public partial class RoleManagement { Title = L["Actions"], Actions = EntityActions.Get(), - Sortable = true, }, new TableColumn { From d02722790ddca5188a1099d57b7f6c6d5e497cdc Mon Sep 17 00:00:00 2001 From: enisn Date: Fri, 22 Apr 2022 16:46:59 +0300 Subject: [PATCH 037/134] Remove Sortable property from "Actions" column --- .../Pages/TenantManagement/TenantManagement.razor.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs index 8e4b0efa97..0f0e689f2f 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Blazor/Pages/TenantManagement/TenantManagement.razor.cs @@ -110,7 +110,6 @@ public partial class TenantManagement { Title = L["Actions"], Actions = EntityActions.Get(), - Sortable = true, }, new TableColumn { From b998015760649a3d36343d9a5b820787700fd345 Mon Sep 17 00:00:00 2001 From: muhammedaltug Date: Fri, 22 Apr 2022 16:53:28 +0300 Subject: [PATCH 038/134] proxy generation api versioning imp --- .../packages/schematics/src/models/method.ts | 7 +-- .../packages/schematics/src/utils/index.ts | 1 + .../packages/schematics/src/utils/methods.ts | 4 ++ .../packages/schematics/src/utils/service.ts | 46 +++++++++++++++++-- .../packages/schematics/src/utils/text.ts | 3 ++ 5 files changed, 55 insertions(+), 6 deletions(-) create mode 100644 npm/ng-packs/packages/schematics/src/utils/methods.ts diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index b6ee494089..7e6147b2d4 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -1,5 +1,6 @@ import { eBindingSourceId, eMethodModifier } from '../enums'; -import { camel } from '../utils/text'; +import { camel, camelizeHyphen } from '../utils/text'; +import { getParamName } from '../utils/methods'; import { ParameterInBody } from './api-definition'; import { Property } from './model'; import { Omissible } from './util'; @@ -50,12 +51,12 @@ export class Body { ? shouldQuote(paramName) ? `${descriptorName}['${paramName}']` : `${descriptorName}.${paramName}` - : nameOnMethod; + : camelizeHyphen(nameOnMethod); switch (bindingSourceId) { case eBindingSourceId.Model: case eBindingSourceId.Query: - this.params.push(paramName === value ? value : `${paramName}: ${value}`); + this.params.push(paramName === value ? value : `${getParamName(paramName)}: ${value}`); break; case eBindingSourceId.Body: this.body = value; diff --git a/npm/ng-packs/packages/schematics/src/utils/index.ts b/npm/ng-packs/packages/schematics/src/utils/index.ts index e6df05b2e2..eeb12e6d9e 100644 --- a/npm/ng-packs/packages/schematics/src/utils/index.ts +++ b/npm/ng-packs/packages/schematics/src/utils/index.ts @@ -7,6 +7,7 @@ export * from './enum'; export * from './file'; export * from './generics'; export * from './import'; +export * from './methods'; export * from './model'; export * from './namespace'; export * from './path'; diff --git a/npm/ng-packs/packages/schematics/src/utils/methods.ts b/npm/ng-packs/packages/schematics/src/utils/methods.ts new file mode 100644 index 0000000000..7b7547b9e1 --- /dev/null +++ b/npm/ng-packs/packages/schematics/src/utils/methods.ts @@ -0,0 +1,4 @@ +// eslint-disable-next-line @typescript-eslint/no-var-requires +const shouldQuote = require('should-quote'); +export const getParamName = (paramName: string) => + shouldQuote(paramName) ? `["${paramName}"]` : paramName; diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index 70655c821d..f37384d27d 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -20,6 +20,8 @@ import { createTypesToImportsReducer, removeTypeModifiers, } from './type'; +import { eBindingSourceId } from '../enums'; +import { camelizeHyphen } from './text'; export function serializeParameters(parameters: Property[]) { return parameters.map(p => p.name + p.optional + ': ' + p.type + p.default, '').join(', '); @@ -79,8 +81,12 @@ export function createActionToSignatureMapper() { return (action: Action) => { const signature = new Signature({ name: getMethodNameFromAction(action) }); - - signature.parameters = action.parametersOnMethod.map(p => { + const versionParameter = getVersionParameter(action); + const parameters = [ + ...action.parametersOnMethod, + ...(versionParameter ? [versionParameter] : []), + ]; + signature.parameters = parameters.map(p => { const type = adaptType(p.typeSimple); const parameter = new Property({ name: p.name, type }); parameter.setDefault(p.defaultValue); @@ -93,7 +99,41 @@ export function createActionToSignatureMapper() { } function getMethodNameFromAction(action: Action): string { - return action.uniqueName.split('Async')[0]; + return action.uniqueName.replace('Async', ''); +} + +function getVersionParameter(action: Action) { + const versionParameter = action.parameters.find( + p => + (p.name == 'apiVersion' && p.bindingSourceId == eBindingSourceId.Path) || + (p.name == 'api-version' && p.bindingSourceId == eBindingSourceId.Query), + ); + const bestVersion = findBestApiVersion(action); + return versionParameter && bestVersion + ? { + ...versionParameter, + name: camelizeHyphen(versionParameter.name), + defaultValue: `"${bestVersion}"`, + } + : null; +} + +// Implementation of https://github.com/abpframework/abp/commit/c3f77c1229508279015054a9b4f5586404a88a14#diff-a4dbf6be9a1aa21d8294f11047774949363ee6b601980bf3225e8046c0748c9eR101 +function findBestApiVersion(action: Action) { + /* + TODO: Implement configuredVersion when js proxies implemented + let configuredVersion = null; + if (action.supportedVersions.includes(configuredVersion)) { + return configuredVersion; + } + */ + + if (!action.supportedVersions?.length) { + // TODO: return configuredVersion if exists or '1.0' + return '1.0'; + } + //TODO: Ensure to get the latest version! + return action.supportedVersions[action.supportedVersions.length - 1]; } function createActionToImportsReducer( diff --git a/npm/ng-packs/packages/schematics/src/utils/text.ts b/npm/ng-packs/packages/schematics/src/utils/text.ts index b7a8def0f4..6215ffaacd 100644 --- a/npm/ng-packs/packages/schematics/src/utils/text.ts +++ b/npm/ng-packs/packages/schematics/src/utils/text.ts @@ -55,3 +55,6 @@ function isUpperCase(str = '') { function toLowerCase(str = '') { return str.toLowerCase(); } +export function camelizeHyphen(str: string) { + return str.replace(/-([a-z])/g, g => g[1].toUpperCase()); +} From 7dd744137ce40f727d3fb02a25cecd4831acf6df Mon Sep 17 00:00:00 2001 From: muhammedaltug Date: Fri, 22 Apr 2022 17:08:34 +0300 Subject: [PATCH 039/134] rollback string replace - move condition from ternary op to if --- npm/ng-packs/packages/schematics/src/models/method.ts | 9 +++++---- npm/ng-packs/packages/schematics/src/utils/service.ts | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/npm/ng-packs/packages/schematics/src/models/method.ts b/npm/ng-packs/packages/schematics/src/models/method.ts index 7e6147b2d4..d8f56e383e 100644 --- a/npm/ng-packs/packages/schematics/src/models/method.ts +++ b/npm/ng-packs/packages/schematics/src/models/method.ts @@ -47,11 +47,12 @@ export class Body { const { bindingSourceId, descriptorName, jsonName, name, nameOnMethod } = param; const camelName = camel(name); const paramName = jsonName || camelName; - const value = descriptorName - ? shouldQuote(paramName) + let value = camelizeHyphen(nameOnMethod); + if (descriptorName) { + value = shouldQuote(paramName) ? `${descriptorName}['${paramName}']` - : `${descriptorName}.${paramName}` - : camelizeHyphen(nameOnMethod); + : `${descriptorName}.${paramName}`; + } switch (bindingSourceId) { case eBindingSourceId.Model: diff --git a/npm/ng-packs/packages/schematics/src/utils/service.ts b/npm/ng-packs/packages/schematics/src/utils/service.ts index f37384d27d..4e2fdc7c02 100644 --- a/npm/ng-packs/packages/schematics/src/utils/service.ts +++ b/npm/ng-packs/packages/schematics/src/utils/service.ts @@ -99,7 +99,7 @@ export function createActionToSignatureMapper() { } function getMethodNameFromAction(action: Action): string { - return action.uniqueName.replace('Async', ''); + return action.uniqueName.split('Async')[0]; } function getVersionParameter(action: Action) { From dd6a97f994f43902f3c86a0d128061aaf6ea4e97 Mon Sep 17 00:00:00 2001 From: Galip Tolga Erdem Date: Fri, 22 Apr 2022 22:28:25 +0300 Subject: [PATCH 040/134] Update Abp-5_0.md --- docs/en/Migration-Guides/Abp-5_0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/en/Migration-Guides/Abp-5_0.md b/docs/en/Migration-Guides/Abp-5_0.md index 35780786af..758f2b91ff 100644 --- a/docs/en/Migration-Guides/Abp-5_0.md +++ b/docs/en/Migration-Guides/Abp-5_0.md @@ -117,5 +117,5 @@ db.AbpUsers.updateMany({},{$set:{ IsActive : true }}) ## See Also * [Angular UI 4.x to 5.0 Migration Guide](Abp-5_0-Angular.md) -* [ASP.NET Core MVC / Razor Pages UI 4.x to 5.0 Migration Guide]() +* [ASP.NET Core MVC / Razor Pages UI 4.x to 5.0 Migration Guide](Abp-5-0-MVC.md) * [Blazor UI 4.x to 5.0 Migration Guide](Abp-5-0-Blazor.md) From ac7b6d3dc975674f82eceb90cdf071240f526982 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Sun, 24 Apr 2022 17:22:35 +0800 Subject: [PATCH 041/134] Create docs/zh-Hans/Testing.md --- docs/zh-Hans/Testing.md | 768 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 768 insertions(+) create mode 100644 docs/zh-Hans/Testing.md diff --git a/docs/zh-Hans/Testing.md b/docs/zh-Hans/Testing.md new file mode 100644 index 0000000000..5bb72803c8 --- /dev/null +++ b/docs/zh-Hans/Testing.md @@ -0,0 +1,768 @@ +# 自动化测试 + +## 介绍 + +ABP框架的设计考虑了可测试性. 有一些不同级别的自动化测试: + +* **单元测试**: 通常只测试一个类(或者一起测试几个类). 这些测试会很快. 然而, 你通常需要处理对服务依赖项的模拟. +* **集成测试**: 你通常会测试一个服务, 但这一次你不会模拟基本的基础设施和服务, 以查看它们是否正确地协同工作. +* **用户界面测试**: 测试应用程序的UI, 就像用户与应用程序交互一样. + +### 单元测试 vs 集成测试 + +与单元测试相比, 集成测试有一些显著的**优势**: + +* **编写更加简单** 因为你不需要模拟和处理依赖关系. +* 你的测试代码运行于所有真正的服务和基础设施(包括数据库映射和查询), 因此它更接近于**真正的应用程序测试**. + +同时它们有一些缺点: + +* 与单元测试相比, 它们**更慢**, 因为所有的基础设施都准备好了测试用例. +* 服务中的一个bug可能会导致多个测试用例失败, 因此在某些情况下, 可能会**更难找到真正的问题**. + +我们建议混合使用: 在必要的地方编写单元测试或集成测试, 并且有效的编写和维护它. + +## 应用程序启动模板 + +测试基础设施提供[应用程序启动模板](Startup-Templates/Application.md) , 并已经正确安装和配置. + +### 测试项目 + +请参见Visual Studio中的以下解决方案: + +![solution-test-projects](images/solution-test-projects.png) + +按层级系统分为多个测试项目: + +* `Domain.Tests` 用于测试领域层对象 (例如[领域服务](Domain-Services.md) 和 [实体](Entities.md)). +* `Application.Tests` 用于测试应用层对象 (例如[应用服务](Application-Services.md)). +* `EntityFrameworkCore.Tests` 用于测试你的自定义仓储实现或EF Core映射(如果你使用其他[数据访问](Data-Access.md))的话, 该项目将有所不同). +* `Web.Tests` 用于测试UI层(如页面、控制器和视图组件). 该项目仅适用于MVC / Razor页面应用程序. +* `TestBase` 包含一些由其他项目共享/使用的类. + +> `HttpApi.Client.ConsoleTestApp` 不是自动化测试的应用程序. 它是一个示例的控制台应用程序, 展示了如何从.NET控制台应用程序中调用HTTP API. + +以下的部分将介绍这些项目中包含的基类和其他基础设施. + +### 测试基础设施 + +解决方案中已经安装了以下库: + +* [xUnit](https://xunit.net/) 作为测试框架. +* [NSubstitute](https://nsubstitute.github.io/) 用于模拟. +* [Shouldly](https://github.com/shouldly/shouldly) 用于断言. + +虽然你可以用自己喜欢的工具替换它们, 但本文档和示例将基于这些工具. + +## 测试资源管理器 + +你可以在Visual Studio中使用测试资源管理器查看和运行测试. 其他IDE, 请参阅它们自己的文档. + +### 打开测试资源管理器 + +打开*测试*菜单下的*测试资源管理器*(如果尚未打开): + +![vs-test-explorer](images/vs-test-explorer.png) + +### 运行测试 + +然后, 你可以单击在视图中运行所有测试或运行按钮来运行测试. 初始启动模板为你提供了一些测试用例: + +![vs-startup-template-tests](images/vs-startup-template-tests.png) + +### 并行运行测试 + +支持并行运行测试. **强烈建议**并行运行所有测试, 这比逐个运行测试要快得多. + +要启用它, 请单击设置(齿轮)按钮附近的插入符号图标, 然后选择*并行运行测试*. + +![vs-run-tests-in-parallel](images/vs-run-tests-in-parallel.png) + +## 单元测试 + +对于单元测试, 不需要太多的配置. 通常会实例化你的类, 并对要测试的对象提供一些预先配置的模拟对象. + +### 没有依赖项的类 + +要测试的类没有依赖项是最简单的情况, 你可以直接实例化类, 调用其方法并做出断言. + +#### 示例: 测试实体 + +假设你有一个 `Issue` [实体](Entities.md), 如下所示: + +````csharp +using System; +using Volo.Abp.Domain.Entities; + +namespace MyProject.Issues +{ + public class Issue : AggregateRoot + { + public string Title { get; set; } + public string Description { get; set; } + public bool IsLocked { get; set; } + public bool IsClosed { get; private set; } + public DateTime? CloseDate { get; private set; } + + public void Close() + { + IsClosed = true; + CloseDate = DateTime.UtcNow; + } + + public void Open() + { + if (!IsClosed) + { + return; + } + + if (IsLocked) + { + throw new IssueStateException("You can not open a locked issue!"); + } + + IsClosed = true; + CloseDate = null; + } + } +} + +```` + +请注意, `IsClosed`和`CloseDate`属性具有私有setter, 可以使用`Open()`和`Close()`方法强制执行某些业务逻辑: + +* 无论何时关闭issue, `CloseDate`都应设置为[当前时间](Timing.md). +* 如果issue被锁定, 则无法重新打开. 如果它被重新打开, `CloseDate`应该设置为`null`. + +由于`Issue`实体是领域层的一部分, 所以我们应该在`Domain.Tests`项目中测试它. 在`Domain.Tests`项目中创建一个`Issue_Tests`类: + +````csharp +using Shouldly; +using Xunit; + +namespace MyProject.Issues +{ + public class Issue_Tests + { + [Fact] + public void Should_Set_The_CloseDate_Whenever_Close_An_Issue() + { + // Arrange + + var issue = new Issue(); + issue.CloseDate.ShouldBeNull(); // null at the beginning + + // Act + + issue.Close(); + + // Assert + + issue.IsClosed.ShouldBeTrue(); + issue.CloseDate.ShouldNotBeNull(); + } + } +} +```` + +这个测试遵循AAA(Arrange-Act-Assert)模式: + +* **Arrange** 部分创建一个`Issue`实体, 并确保`CloseDate`在初始值为`null`. +* **Act** 部分执行我们想要测试的方法. +* **Assert** 部分检查`Issue`属性是否与我们预期的相同. + +`[Fact]`属性由[xUnit](https://xunit.net/)并将方法标记为测试方法. `Should...`扩展方法由[Shouldly](https://github.com/shouldly/shouldly)提供. 你可以直接使用xUnit中的`Assert`类, 使用Shouldly让它更舒适、更直观. + +当你执行测试时, 你将看到它成功通过: + +![issue-first-test](images/issue-first-test.png) + +让我们再添加两种测试方法: + +````csharp +[Fact] +public void Should_Allow_To_ReOpen_An_Issue() +{ + // Arrange + + var issue = new Issue(); + issue.Close(); + + // Act + + issue.Open(); + + // Assert + + issue.IsClosed.ShouldBeFalse(); + issue.CloseDate.ShouldBeNull(); +} + +[Fact] +public void Should_Not_Allow_To_ReOpen_A_Locked_Issue() +{ + // Arrange + + var issue = new Issue(); + issue.Close(); + issue.IsLocked = true; + + // Act & Assert + + Assert.Throws(() => + { + issue.Open(); + }); +} +```` + +`Assert.Throws` 检查执行的代码是否匹配引发的异常. + +> 有关这些库的更多信息, 请参阅xUnit & Shoudly的文档. + +### 具有依赖项的类 + +如果你的服务中有依赖项, 并且你想对该服务进行单元测试, 那么你需要模拟这些依赖项. + +#### 示例: 测试领域服务 + +假设你有一个`IssueManager` [领域服务](Domain-Services.md), 定义如下: + +````csharp +using System; +using System.Threading.Tasks; +using Volo.Abp; +using Volo.Abp.Domain.Services; + +namespace MyProject.Issues +{ + public class IssueManager : DomainService + { + public const int MaxAllowedOpenIssueCountForAUser = 3; + + private readonly IIssueRepository _issueRepository; + + public IssueManager(IIssueRepository issueRepository) + { + _issueRepository = issueRepository; + } + + public async Task AssignToUserAsync(Issue issue, Guid userId) + { + var issueCount = await _issueRepository.GetIssueCountOfUserAsync(userId); + + if (issueCount >= MaxAllowedOpenIssueCountForAUser) + { + throw new BusinessException( + code: "IM:00392", + message: $"You can not assign more" + + $"than {MaxAllowedOpenIssueCountForAUser} issues to a user!" + ); + } + + issue.AssignedUserId = userId; + } + } +} +```` + +`IssueManager`依赖于`IssueRepository`服务, 在本例中将模拟该服务. + +**业务逻辑**: 示例`AssignToUserAsync`不允许向用户分配超过3个issue (`MaxAllowedOpenIssueCountForAUser`常量). 在这种情况下, 如果要分配issue, 首先需要取消现有issue的分配. + +下面的测试用例给出一个有效的赋值: + +````csharp +using System; +using System.Threading.Tasks; +using NSubstitute; +using Shouldly; +using Volo.Abp; +using Xunit; + +namespace MyProject.Issues +{ + public class IssueManager_Tests + { + [Fact] + public async Task Should_Assign_An_Issue_To_A_User() + { + // Arrange + + var userId = Guid.NewGuid(); + + var fakeRepo = Substitute.For(); + fakeRepo.GetIssueCountOfUserAsync(userId).Returns(1); + + var issueManager = new IssueManager(fakeRepo); + + var issue = new Issue(); + + // Act + + await issueManager.AssignToUserAsync(issue, userId); + + //Assert + + issue.AssignedUserId.ShouldBe(userId); + await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId); + } + } +} +```` + +* `Substitute.For` 创建一个模拟(假)对象, 该对象被传递到`IssueManager`构造函数中. +* `fakeRepo.GetIssueCountOfUserAsync(userId).Returns(1)` 确保仓储中的`GetIssueContofuseRasync`方法返回`1`. +* `issueManager.AssignToUserAsync` 不会引发任何异常, 因为仓储统计当前分配的issue数量并且返回`1`. +* `issue.AssignedUserId.ShouldBe(userId);` 行检查`AssignedUserId`的值是否正确. +* `await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId);` 检查 `IssueManager` 实际只调用了 `GetIssueCountOfUserAsync` 方法一次. + +让我们添加第二个测试, 看看它是否能阻止将issue分配给超过分配数量的用户: + +````csharp +[Fact] +public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() +{ + // Arrange + + var userId = Guid.NewGuid(); + + var fakeRepo = Substitute.For(); + fakeRepo + .GetIssueCountOfUserAsync(userId) + .Returns(IssueManager.MaxAllowedOpenIssueCountForAUser); + + var issueManager = new IssueManager(fakeRepo); + + // Act & Assert + + var issue = new Issue(); + + await Assert.ThrowsAsync(async () => + { + await issueManager.AssignToUserAsync(issue, userId); + }); + + issue.AssignedUserId.ShouldBeNull(); + await fakeRepo.Received(1).GetIssueCountOfUserAsync(userId); +} +```` + +> 有关模拟的更多信息, 请参阅[NSubstitute](https://nsubstitute.github.io/)文档. + +模拟单个依赖项相对容易. 但是, 当依赖关系增长时, 设置测试对象和模拟所有依赖关系变得越来越困难. 请参阅不需要模拟依赖项的*Integration Tests*部分. + +### 提示: 共享测试类构造函数 + +[xUnit](https://xunit.net/) 为每个测试方法创建一个**新测试类实例**(本例中为`IssueManager_Tests`). 因此, 你可以将一些*Arrange*代码移动到构造函数中, 以减少代码重复. 构造函数将针对每个测试用例执行, 并且不会相互影响, 即使它们是并行工作. + +**示例: 重构`IssueManager_Tests`以减少代码重复** + +````csharp +using System; +using System.Threading.Tasks; +using NSubstitute; +using Shouldly; +using Volo.Abp; +using Xunit; + +namespace MyProject.Issues +{ + public class IssueManager_Tests + { + private readonly Guid _userId; + private readonly IIssueRepository _fakeRepo; + private readonly IssueManager _issueManager; + private readonly Issue _issue; + + public IssueManager_Tests() + { + _userId = Guid.NewGuid(); + _fakeRepo = Substitute.For(); + _issueManager = new IssueManager(_fakeRepo); + _issue = new Issue(); + } + + [Fact] + public async Task Should_Assign_An_Issue_To_A_User() + { + // Arrange + _fakeRepo.GetIssueCountOfUserAsync(_userId).Returns(1); + + // Act + await _issueManager.AssignToUserAsync(_issue, _userId); + + //Assert + _issue.AssignedUserId.ShouldBe(_userId); + await _fakeRepo.Received(1).GetIssueCountOfUserAsync(_userId); + } + + [Fact] + public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() + { + // Arrange + _fakeRepo + .GetIssueCountOfUserAsync(_userId) + .Returns(IssueManager.MaxAllowedOpenIssueCountForAUser); + + // Act & Assert + await Assert.ThrowsAsync(async () => + { + await _issueManager.AssignToUserAsync(_issue, _userId); + }); + + _issue.AssignedUserId.ShouldBeNull(); + await _fakeRepo.Received(1).GetIssueCountOfUserAsync(_userId); + } + } +} +```` + +> 保持测试代码整洁, 以创建可维护的测试组件. + +## 集成测试 + +> 你还可以按照[Web应用程序开发教程](Tutorials/Part-1.md)学习开发全栈应用程序, 包括集成测试. + +### 集成测试基础 + +ABP为编写集成测试提供了完整的基础设施. 所有ABP基础设施和服务都将在你的测试中执行. 应用程序启动模板附带了为你预先配置的必要基础设施; + +#### 数据库 + +启动模板使用EF Core配置**内存中的SQLite**数据库(对于MongoDB, 它使用[Mongo2Go](https://github.com/Mongo2Go/Mongo2Go)). 因此, 所有配置和查询都是针对真实数据库执行的, 你甚至可以测试数据库事务. + +使用内存中的SQLite数据库有两个主要优点: + +* 它比外部DBMS更快. +* 它会为每个测试用例创建一个**新的数据库**, 这样测试就不会相互影响. + +> **提示**: 不要将EF Core的内存数据库用于高级集成测试. 它不是一个真正的DBMS, 在细节上有很多不同. 例如, 它不支持事务和回滚场景, 因此无法真正测试失败的场景. 另一方面, 内存中的SQLite是一个真正的DBMS, 支持SQL数据库的基本功能. + +### 种子数据 + +针对空数据库编写测试是不现实的. 在大多数情况下, 需要在数据库中保存一些初始数据. 例如, 如果你编写了一个查询、更新和删除产品的测试类, 那么在执行测试用例之前, 在数据库中有一些产品数据会很有帮助. + +ABP的[种子数据](Data-Seeding.md)系统是一种强大的初始化数据的方法. 应用程序启动模板在`.TestBase`项目中有一个*YourProject*TestDataSeedContributor类. 你可以在其中添加, 以获得可用于每个测试方法的初始数据. + +**示例: 创建一些Issue作为种子数据** + +````csharp +using System.Threading.Tasks; +using MyProject.Issues; +using Volo.Abp.Data; +using Volo.Abp.DependencyInjection; + +namespace MyProject +{ + public class MyProjectTestDataSeedContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IIssueRepository _issueRepository; + + public MyProjectTestDataSeedContributor(IIssueRepository issueRepository) + { + _issueRepository = issueRepository; + } + + public async Task SeedAsync(DataSeedContext context) + { + await _issueRepository.InsertAsync( + new Issue + { + Title = "Test issue one", + Description = "Test issue one description", + AssignedUserId = TestData.User1Id + }); + + await _issueRepository.InsertAsync( + new Issue + { + Title = "Test issue two", + Description = "Test issue two description", + AssignedUserId = TestData.User1Id + }); + + await _issueRepository.InsertAsync( + new Issue + { + Title = "Test issue three", + Description = "Test issue three description", + AssignedUserId = TestData.User1Id + }); + + await _issueRepository.InsertAsync( + new Issue + { + Title = "Test issue four", + Description = "Test issue four description", + AssignedUserId = TestData.User2Id + }); + } + } +} +```` + +还创建了一个静态类来存储用户的 `Id`: + +````csharp +using System; + +namespace MyProject +{ + public static class TestData + { + public static Guid User1Id = Guid.Parse("41951813-5CF9-4204-8B18-CD765DBCBC9B"); + public static Guid User2Id = Guid.Parse("2DAB4460-C21B-4925-BF41-A52750A9B999"); + } +} +```` + +通过这种方式, 我们可以使用这些已知Issue和用户的`Id`来运行测试. + +### 示例: 测试领域服务 + +`AbpIntegratedTest`类 (定义在[Volo.Abp.TestBase](https://www.nuget.org/packages/Volo.Abp.TestBase)) 用于编写集成到ABP框架的测试. `T`是用于设置和初始化应用程序的根模块的类型. + +应用程序启动模板在每个测试项目中都有基类, 因此你可以从这些基类派生, 以使其更简单. + +`IssueManager`测试将被重写成集成测试 + +````csharp +using System.Threading.Tasks; +using Shouldly; +using Volo.Abp; +using Xunit; + +namespace MyProject.Issues +{ + public class IssueManager_Integration_Tests : MyProjectDomainTestBase + { + private readonly IssueManager _issueManager; + private readonly Issue _issue; + + public IssueManager_Integration_Tests() + { + _issueManager = GetRequiredService(); + _issue = new Issue + { + Title = "Test title", + Description = "Test description" + }; + } + + [Fact] + public async Task Should_Not_Allow_To_Assign_Issues_Over_The_Limit() + { + // Act & Assert + await Assert.ThrowsAsync(async () => + { + await _issueManager.AssignToUserAsync(_issue, TestData.User1Id); + }); + + _issue.AssignedUserId.ShouldBeNull(); + } + + [Fact] + public async Task Should_Assign_An_Issue_To_A_User() + { + // Act + await _issueManager.AssignToUserAsync(_issue, TestData.User2Id); + + //Assert + _issue.AssignedUserId.ShouldBe(TestData.User2Id); + } + } +} +```` + +* 第一个测试方法将issue分配给User1, 其中User1已经分配了种子数据代码中的3个issue. 因此, 它抛出了一个`BusinessException`. +* 第二种测试方法将issue分配给User2, User2只分配了一个issue. 因此, 该方法成功了. + +这个类通常位于`.Domain.Tests`项目中, 因为它测试位于`.Domain`项目中的类. 它派生自`MyProjectDomainTestBase`, 并已经为正确运行测试进行了配置. + +编写这样一个集成测试类非常简单. 另一个好处是, 在以后向`IssueManager`类添加另一个依赖项时, 不需要更改测试类. + +### 示例: 测试应用服务 + +测试[应用服务](Application-Services.md)并没有太大的不同. 假设你已经创建了一个`IssueAppService`, 定义如下: + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Volo.Abp.Application.Services; + +namespace MyProject.Issues +{ + public class IssueAppService : ApplicationService, IIssueAppService + { + private readonly IIssueRepository _issueRepository; + + public IssueAppService(IIssueRepository issueRepository) + { + _issueRepository = issueRepository; + } + + public async Task> GetListAsync() + { + var issues = await _issueRepository.GetListAsync(); + + return ObjectMapper.Map, List>(issues); + } + } +} +```` + +*(假设你还定义了`IIssueAppService`和`IssueDto`, 并在`Issue`和`IssueDto`之间创建了[对象映射](Object-To-Object-Mapping.md))* + +现在, 你可以在`.Application.Tests`项目中编写一个测试类: + +````csharp +using System.Threading.Tasks; +using Shouldly; +using Xunit; + +namespace MyProject.Issues +{ + public class IssueAppService_Tests : MyProjectApplicationTestBase + { + private readonly IIssueAppService _issueAppService; + + public IssueAppService_Tests() + { + _issueAppService = GetRequiredService(); + } + + [Fact] + public async Task Should_Get_All_Issues() + { + //Act + var issueDtos = await _issueAppService.GetListAsync(); + + //Assert + issueDtos.Count.ShouldBeGreaterThan(0); + } + } +} +```` + +就这么简单. 此测试方法测试的所有内容, 包括应用服务、EF Core映射、对象到对象映射和仓储实现. 通过这种方式, 你可以完全测试解决方案的应用层和领域层. + +### 处理集成测试中的工作单元 + +ABP的[工作单元](Unit-Of-Work.md)系统控制应用程序中的数据库连接和事务管理. 它可以在你编写应用程序代码时无缝工作, 因此你可能没有意识到它. + +在ABP框架中, 所有数据库操作都必须在一个工作单元作用域内执行. 当你测试[应用服务](Application-Services.md)方法时, 工作单元的作用域将是应用服务方法的作用域. 如果你正在测试[仓储](Repositories.md)方法, 那么工作单元作用域将是你的仓储方法的作用域. + +在某些情况下, 你可能需要手动控制工作单元作用域. 可以考虑下面的测试方法: + +````csharp +public class IssueRepository_Tests : MyProjectDomainTestBase +{ + private readonly IRepository _issueRepository; + + public IssueRepository_Tests() + { + _issueRepository = GetRequiredService>(); + } + + public async Task Should_Query_By_Title() + { + IQueryable queryable = await _issueRepository.GetQueryableAsync(); + var issue = queryable.FirstOrDefaultAsync(i => i.Title == "My issue title"); + issue.ShouldNotBeNull(); + } +} +```` + +我们正在使用`_issueRepository.GetQueryableAsync`获取`IQueryable` 对象. 然后, 我们使用`FirstOrDefaultAsync`方法按标题查询issue. 此时执行数据库查询, 你将会得到一个异常, 表明没有起作用的工作单元. + +要使该测试正常工作, 你应该手动启动工作单元作用域, 如下所示: + +````csharp +public class IssueRepository_Tests : MyProjectDomainTestBase +{ + private readonly IRepository _issueRepository; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public IssueRepository_Tests() + { + _issueRepository = GetRequiredService>(); + _unitOfWorkManager = GetRequiredService(); + } + + public async Task Should_Query_By_Title() + { + using (var uow = _unitOfWorkManager.Begin()) + { + IQueryable queryable = await _issueRepository.GetQueryableAsync(); + var issue = queryable.FirstOrDefaultAsync(i => i.Title == "My issue title"); + issue.ShouldNotBeNull(); + await uow.CompleteAsync(); + } + } +} +```` + +我们已经使用了`IUnitOfWorkManager`服务来创建一个工作单元作用域, 然后在该作用域内调用了`FirstOrDefaultAsync`方法, 所以不再有问题了. + +> 请注意, 我们测试了`FirstOrDefaultAsync`来演示工作单元的问题. 作为一个好的标准, 编写自己的代码. + +### 使用DbContext + +在某些情况下, 你可能希望使用Entity Framework的`DbContext`对象来执行测试方法中的数据库操作. 在这种情况下, 可以使用`IDbContextProvider`服务在工作单元内获取`DbContext`实例. + +下面的示例展示了如何在测试方法中创建`DbContext`对象: + +````csharp +public class MyDbContext_Tests : MyProjectDomainTestBase +{ + private readonly IDbContextProvider _dbContextProvider; + private readonly IUnitOfWorkManager _unitOfWorkManager; + + public IssueRepository_Tests() + { + _dbContextProvider = GetRequiredService>(); + _unitOfWorkManager = GetRequiredService(); + } + + public async Task Should_Query_By_Title() + { + using (var uow = _unitOfWorkManager.Begin()) + { + var dbContext = await _dbContextProvider.GetDbContextAsync(); + var issue = await dbContext.Issues.FirstOrDefaultAsync(i => i.Title == "My issue title"); + issue.ShouldNotBeNull(); + await uow.CompleteAsync(); + } + } +} +```` + +就像我们在*集成测试中处理工作单元*一节中所做的那样, 我们应该在起作用的工作单元内执行`DbContext`操作. + +对于[MongoDB](MongoDB.md), 你可以使用`IMongoDbContextProvider`服务获取`DbContext`对象, 并在测试方法中直接使用MongoDB APIs. + +## 用户界面测试 + +一般来说, 有两种类型的UI测试: + +### 非可视化测试 + +此类测试完全取决于UI框架的选择: + +* 对于MVC / Razor页面UI, 通常向服务器发出请求, 获取HTML, 并测试返回的结果中是否存在一些预期的DOM元素. +* Angular有自己的基础设施和实践来测试组件、视图和服务. + +请参阅以下文档以了解非可视化UI测试: + +* [Testing in ASP.NET Core MVC / Razor Pages](UI/AspNetCore/Testing.md) +* [Testing in Angular](UI/Angular/Testing.md) +* [Testing in Blazor](UI/Blazor/Testing.md) + +### 可视化测试 + +与真实用户一样, 可视化测试用于与应用程序UI交互. 它全面测试应用程序, 包括页面和组件的外观. + +可视化UI测试超出了ABP框架的范围. 行业中有很多工具(比如[Selenium](https://www.selenium.dev/))可以用来测试应用程序的UI. From bc6f1b6780b9c684428d30bd5e4078774487d9e9 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Mon, 25 Apr 2022 09:34:02 +0800 Subject: [PATCH 042/134] Update docs-nav.json --- docs/zh-Hans/docs-nav.json | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index db0fc10aed..6586fa978f 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -682,6 +682,10 @@ } ] }, + { + "text": "测试", + "path": "Testing.md" + }, { "text": "示例", "items": [ From 440ba40f14de84f0bebe0e2111de07a05fd777d4 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:24:09 +0800 Subject: [PATCH 043/134] Create UI/Angular/Testing.md --- docs/zh-Hans/UI/Angular/Testing.md | 380 +++++++++++++++++++++++++++++ 1 file changed, 380 insertions(+) create mode 100644 docs/zh-Hans/UI/Angular/Testing.md diff --git a/docs/zh-Hans/UI/Angular/Testing.md b/docs/zh-Hans/UI/Angular/Testing.md new file mode 100644 index 0000000000..c176d82247 --- /dev/null +++ b/docs/zh-Hans/UI/Angular/Testing.md @@ -0,0 +1,380 @@ +# Angular UI 单元测试 + +ABP Angular UI的测试与其他Angular应用程序一样. 所以, [这里的指南](https://angular.io/guide/testing)也适用于ABP. 也就是说, 我们想指出一些**特定于ABP Angular应用程序的单元测试内容**. + +## 设置 + +在Angular中, 单元测试默认使用[Karma](https://karma-runner.github.io/)和[Jasmine](https://jasmine.github.io). 虽然我们更喜欢Jest, 但我们选择不偏离这些默认设置, 因此**你下载的应用程序模板将预先配置Karma和Jasmine**. 你可以在根目录中的 _karma.conf.js_ 文件中找到Karma配置. 你什么都不用做. 添加一个spec文件并运行`npm test`即可. + +## 基础 + +简化版的spec文件如下所示: + +```js +import { CoreTestingModule } from "@abp/ng.core/testing"; +import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing"; +import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; +import { ComponentFixture, TestBed, waitForAsync } from "@angular/core/testing"; +import { NgxValidateCoreModule } from "@ngx-validate/core"; +import { MyComponent } from "./my.component"; + +describe("MyComponent", () => { + let fixture: ComponentFixture; + + beforeEach( + waitForAsync(() => { + TestBed.configureTestingModule({ + declarations: [MyComponent], + imports: [ + CoreTestingModule.withConfig(), + ThemeSharedTestingModule.withConfig(), + ThemeBasicTestingModule.withConfig(), + NgxValidateCoreModule, + ], + providers: [ + /* mock providers here */ + ], + }).compileComponents(); + }) + ); + + beforeEach(() => { + fixture = TestBed.createComponent(MyComponent); + fixture.detectChanges(); + }); + + it("should be initiated", () => { + expect(fixture.componentInstance).toBeTruthy(); + }); +}); +``` + +如果你看一下导入内容, 你会注意到我们已经准备了一些测试模块来取代内置的ABP模块. 这对于模拟某些特性是必要的, 否则这些特性会破坏你的测试. 请记住**使用测试模块**并**调用其`withConfig`静态方法**. + +## 提示 + +### Angular测试库 + +虽然你可以使用Angular TestBed测试代码, 但你可以找到一个好的替代品[Angular测试库](https://testing-library.com/docs/angular-testing-library/intro). + +上面的简单示例可以用Angular测试库编写, 如下所示: + +```js +import { CoreTestingModule } from "@abp/ng.core/testing"; +import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing"; +import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; +import { ComponentFixture } from "@angular/core/testing"; +import { NgxValidateCoreModule } from "@ngx-validate/core"; +import { render } from "@testing-library/angular"; +import { MyComponent } from "./my.component"; + +describe("MyComponent", () => { + let fixture: ComponentFixture; + + beforeEach(async () => { + const result = await render(MyComponent, { + imports: [ + CoreTestingModule.withConfig(), + ThemeSharedTestingModule.withConfig(), + ThemeBasicTestingModule.withConfig(), + NgxValidateCoreModule, + ], + providers: [ + /* mock providers here */ + ], + }); + + fixture = result.fixture; + }); + + it("should be initiated", () => { + expect(fixture.componentInstance).toBeTruthy(); + }); +}); +``` + +正如你所见, 二者非常相似. 当我们使用查询和触发事件时, 真正的区别就显现出来了. + +```js +// other imports +import { getByLabelText, screen } from "@testing-library/angular"; +import userEvent from "@testing-library/user-event"; + +describe("MyComponent", () => { + beforeEach(/* removed for sake of brevity */); + + it("should display advanced filters", () => { + const filters = screen.getByTestId("author-filters"); + const nameInput = getByLabelText(filters, /name/i) as HTMLInputElement; + expect(nameInput.offsetWidth).toBe(0); + + const advancedFiltersBtn = screen.getByRole("link", { name: /advanced/i }); + userEvent.click(advancedFiltersBtn); + + expect(nameInput.offsetWidth).toBeGreaterThan(0); + + userEvent.type(nameInput, "fooo{backspace}"); + expect(nameInput.value).toBe("foo"); + }); +}); +``` + +**Angular测试库中的查询遵循可维护测试**, 用户事件库提供了与DOM的**类人交互**, 并且该库通常有**清晰的API**简化组件测试. 下面提供一些有用的链接: + +- [查询](https://testing-library.com/docs/dom-testing-library/api-queries) +- [用户事件](https://testing-library.com/docs/ecosystem-user-event) +- [范例](https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples) + +### 在每个Spec之后清除DOM + +需要记住的一点是, Karma在真实的浏览器实例中运行测试. 这意味着, 你将能够看到测试代码的结果, 但也会遇到与文档正文连接的组件的问题, 这些组件可能无法在每次测试后都清除, 即使你配置了Karma也一样无法清除. + +我们准备了一个简单的函数, 可以在每次测试后清除所有剩余的DOM元素. + +```js +// other imports +import { clearPage } from "@abp/ng.core/testing"; + +describe("MyComponent", () => { + let fixture: ComponentFixture; + + afterEach(() => clearPage(fixture)); + + beforeEach(async () => { + const result = await render(MyComponent, { + /* removed for sake of brevity */ + }); + fixture = result.fixture; + }); + + // specs here +}); +``` + +请确保你使用它, 否则Karma将无法删除对话框, 并且你将有多个模态对话框、确认框等的副本. + +### 等待 + +一些组件, 特别是在检测周期之外工作的模态对话框. 换句话说, 你无法在打开这些组件后立即访问这些组件插入的DOM元素. 同样, 插入的元素在关闭时也不会立即销毁. + +为此, 我们准备了一个`wait`函数. + +```js +// other imports +import { wait } from "@abp/ng.core/testing"; + +describe("MyComponent", () => { + beforeEach(/* removed for sake of brevity */); + + it("should open a modal", async () => { + const openModalBtn = screen.getByRole("button", { name: "Open Modal" }); + userEvent.click(openModalBtn); + + await wait(fixture); + + const modal = screen.getByRole("dialog"); + + expect(modal).toBeTruthy(); + + /* wait again after closing the modal */ + }); +}); +``` + +`wait`函数接受第二个参数, 即超时(默认值为`0`). 但是尽量不要使用它. 使用大于`0`的超时通常表明某些不正确事情发生了. + +## 测试示例 + +下面是一个测试示例. 它并没有涵盖所有内容, 但却能够对测试有一个更好的了解. + +```js +import { clearPage, CoreTestingModule, wait } from "@abp/ng.core/testing"; +import { ThemeBasicTestingModule } from "@abp/ng.theme.basic/testing"; +import { ThemeSharedTestingModule } from "@abp/ng.theme.shared/testing"; +import { ComponentFixture } from "@angular/core/testing"; +import { + NgbCollapseModule, + NgbDatepickerModule, + NgbDropdownModule, +} from "@ng-bootstrap/ng-bootstrap"; +import { NgxValidateCoreModule } from "@ngx-validate/core"; +import { CountryService } from "@proxy/countries"; +import { + findByText, + getByLabelText, + getByRole, + getByText, + queryByRole, + render, + screen, +} from "@testing-library/angular"; +import userEvent from "@testing-library/user-event"; +import { BehaviorSubject, of } from "rxjs"; +import { CountryComponent } from "./country.component"; + +const list$ = new BehaviorSubject({ + items: [{ id: "ID_US", name: "United States of America" }], + totalCount: 1, +}); + +describe("Country", () => { + let fixture: ComponentFixture; + + afterEach(() => clearPage(fixture)); + + beforeEach(async () => { + const result = await render(CountryComponent, { + imports: [ + CoreTestingModule.withConfig(), + ThemeSharedTestingModule.withConfig(), + ThemeBasicTestingModule.withConfig(), + NgxValidateCoreModule, + NgbCollapseModule, + NgbDatepickerModule, + NgbDropdownModule, + ], + providers: [ + { + provide: CountryService, + useValue: { + getList: () => list$, + }, + }, + ], + }); + + fixture = result.fixture; + }); + + it("should display advanced filters", () => { + const filters = screen.getByTestId("country-filters"); + const nameInput = getByLabelText(filters, /name/i) as HTMLInputElement; + expect(nameInput.offsetWidth).toBe(0); + + const advancedFiltersBtn = screen.getByRole("link", { name: /advanced/i }); + userEvent.click(advancedFiltersBtn); + + expect(nameInput.offsetWidth).toBeGreaterThan(0); + + userEvent.type(nameInput, "fooo{backspace}"); + expect(nameInput.value).toBe("foo"); + + userEvent.click(advancedFiltersBtn); + expect(nameInput.offsetWidth).toBe(0); + }); + + it("should have a heading", () => { + const heading = screen.getByRole("heading", { name: "Countries" }); + expect(heading).toBeTruthy(); + }); + + it("should render list in table", async () => { + const table = await screen.findByTestId("country-table"); + + const name = getByText(table, "United States of America"); + expect(name).toBeTruthy(); + }); + + it("should display edit modal", async () => { + const actionsBtn = screen.queryByRole("button", { name: /actions/i }); + userEvent.click(actionsBtn); + + const editBtn = screen.getByRole("button", { name: /edit/i }); + userEvent.click(editBtn); + + await wait(fixture); + + const modal = screen.getByRole("dialog"); + const modalHeading = queryByRole(modal, "heading", { name: /edit/i }); + expect(modalHeading).toBeTruthy(); + + const closeBtn = getByText(modal, "×"); + userEvent.click(closeBtn); + + await wait(fixture); + + expect(screen.queryByRole("dialog")).toBeFalsy(); + }); + + it("should display create modal", async () => { + const newBtn = screen.getByRole("button", { name: /new/i }); + userEvent.click(newBtn); + + await wait(fixture); + + const modal = screen.getByRole("dialog"); + const modalHeading = queryByRole(modal, "heading", { name: /new/i }); + + expect(modalHeading).toBeTruthy(); + }); + + it("should validate required name field", async () => { + const newBtn = screen.getByRole("button", { name: /new/i }); + userEvent.click(newBtn); + + await wait(fixture); + + const modal = screen.getByRole("dialog"); + const nameInput = getByRole(modal, "textbox", { + name: /^name/i, + }) as HTMLInputElement; + + userEvent.type(nameInput, "x"); + userEvent.type(nameInput, "{backspace}"); + + const nameError = await findByText(modal, /required/i); + expect(nameError).toBeTruthy(); + }); + + it("should delete a country", () => { + const getSpy = spyOn(fixture.componentInstance.list, "get"); + const deleteSpy = jasmine.createSpy().and.returnValue(of(null)); + fixture.componentInstance.service.delete = deleteSpy; + + const actionsBtn = screen.queryByRole("button", { name: /actions/i }); + userEvent.click(actionsBtn); + + const deleteBtn = screen.getByRole("button", { name: /delete/i }); + userEvent.click(deleteBtn); + + const confirmText = screen.getByText("AreYouSure"); + expect(confirmText).toBeTruthy(); + + const confirmBtn = screen.getByRole("button", { name: "Yes" }); + userEvent.click(confirmBtn); + + expect(deleteSpy).toHaveBeenCalledWith(list$.value.items[0].id); + expect(getSpy).toHaveBeenCalledTimes(1); + }); +}); +``` + +## CI配置 + +你的CI环境需要不同的配置. 要为单元测试设置新的配置, 请在测试项目中找到 _angular.json_ 文件, 或者如下所示添加一个: + +```json +// angular.json + +"test": { + "builder": "@angular-devkit/build-angular:karma", + "options": { /* several options here */ }, + "configurations": { + "production": { + "karmaConfig": "karma.conf.prod.js" + } + } +} +``` + +现在你可以复制 _karma.conf.js_ 作为 _karma.conf.prod.js_ 并在其中使用你喜欢的任何配置. 请查看[Karma配置文档](http://karma-runner.github.io/5.2/config/configuration-file.html)配置选项. + +最后, 不要忘记使用以下命令运行CI测试: + +```sh +npm test -- --prod +``` + +## 另请参阅 + +- [ABP Community Video - Unit Testing with the Angular UI](https://community.abp.io/articles/unit-testing-with-the-angular-ui-p4l550q3) From 40a2ba7a45f96143e1fb27796c191a5e933078a9 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:25:26 +0800 Subject: [PATCH 044/134] Create UI/AspNetCore/Testing.md --- docs/zh-Hans/UI/AspNetCore/Testing.md | 220 ++++++++++++++++++++++++++ 1 file changed, 220 insertions(+) create mode 100644 docs/zh-Hans/UI/AspNetCore/Testing.md diff --git a/docs/zh-Hans/UI/AspNetCore/Testing.md b/docs/zh-Hans/UI/AspNetCore/Testing.md new file mode 100644 index 0000000000..e5b09353fb --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Testing.md @@ -0,0 +1,220 @@ +# ASP.NET Core MVC / Razor Pages: 测试 + +> 你可以参考[ASP.NET Core集成测试文档](https://docs.microsoft.com/en-us/aspnet/core/test/integration-tests)了解ASP.NET Core集成测试的详细内容. 本文档解释了ABP框架提供的附加测试基础设施. + +## 应用程序启动模板 + +应用程序启动模板的`.Web`项目其中包含应用程序的UI视图/页面/组件, 并提供`.Web.Tests`项目来测试这些内容. + +![aspnetcore-web-tests-in-solution](../../images/aspnetcore-web-tests-in-solution.png) + +## 测试Razor页面 + +假设你已经创建了一个名为`Issues.cshtml`的Razor页面, 包含以下内容; + +**Issues.cshtml.cs** + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.RazorPages; +using MyProject.Issues; + +namespace MyProject.Web.Pages +{ + public class IssuesModel : PageModel + { + public List Issues { get; set; } + + private readonly IIssueAppService _issueAppService; + + public IssuesModel(IIssueAppService issueAppService) + { + _issueAppService = issueAppService; + } + + public async Task OnGetAsync() + { + Issues = await _issueAppService.GetListAsync(); + } + } +} +```` + +**Issues.cshtml** + +````html +@page +@model MyProject.Web.Pages.IssuesModel +

Issue List

+ + + + + + + + + @foreach (var issue in Model.Issues) + { + + + + + } + +
IssueClosed?
@issue.Title + @if (issue.IsClosed) + { + Closed + } + else + { + Open + } +
+```` + +本页仅创建一个包含issue的表格: + +![issue-list](../../images/issue-list.png) + +你可以在`.Web.Tests`项目中编写一个测试类如下所示: + +````csharp +using System.Threading.Tasks; +using HtmlAgilityPack; +using Shouldly; +using Xunit; + +namespace MyProject.Pages +{ + public class Issues_Tests : MyProjectWebTestBase + { + [Fact] + public async Task Should_Get_Table_Of_Issues() + { + // Act + + var response = await GetResponseAsStringAsync("/Issues"); + + //Assert + + var htmlDocument = new HtmlDocument(); + htmlDocument.LoadHtml(response); + + var tableElement = htmlDocument.GetElementbyId("IssueTable"); + tableElement.ShouldNotBeNull(); + + var trNodes = tableElement.SelectNodes("//tbody/tr"); + trNodes.Count.ShouldBeGreaterThan(0); + } + } +} +```` + +`GetResponseAsStringAsync`是一个快捷方法, 它来自执行HTTP GET请求的基类, 检查生成的HTTP状态是否为`200`, 并将响应作为`string`返回. + +> 你可以使用`Client`对象(类型为`HttpClient`)对服务器执行任何类型的请求, 并读取响应.`GetResponseAsStringAsync`只是一种快捷方法. + +本例使用[HtmlAgilityPack](https://html-agility-pack.net/)库来解析传入的HTML并测试它是否包含issue表格. + +> 本例假设的数据库中存在一些初始issue. 请参阅[测试文档](../../Testing.md)的*种子数据*部分, 了解如何设置种子数据, 以便可以假定数据库中有一些可用的初始数据. + +## 控制器测试 + +测试控制器也不例外. 只需使用正确的URL向服务器执行请求, 获取响应并做出断言. + +### 查看结果 + +如果控制器返回一个视图, 你可以使用类似的代码来测试返回的HTML. 参见上面的Razor页面示例. + +### 对象结果 + +如果控制器返回对象结果, 则可以使用`GetResponseAsObjectAsync`方法. + +假设你有一个如下定义的控制器: + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using MyProject.Issues; +using Volo.Abp.AspNetCore.Mvc; + +namespace MyProject.Web.Controllers +{ + [Route("api/issues")] + public class IssueController : AbpController + { + private readonly IIssueAppService _issueAppService; + + public IssueController(IIssueAppService issueAppService) + { + _issueAppService = issueAppService; + } + + [HttpGet] + public async Task> GetAsync() + { + return await _issueAppService.GetListAsync(); + } + } +} +```` + +你可以编写测试代码来调用API并获得结果: + +````csharp +using System.Collections.Generic; +using System.Threading.Tasks; +using MyProject.Issues; +using Shouldly; +using Xunit; + +namespace MyProject.Pages +{ + public class Issues_Tests : MyProjectWebTestBase + { + [Fact] + public async Task Should_Get_Issues_From_Api() + { + var issues = await GetResponseAsObjectAsync>("/api/issues"); + + issues.ShouldNotBeNull(); + issues.Count.ShouldBeGreaterThan(0); + } + } +} +```` + +## 测试JavaScript代码 + +ABP框架不提供任何基础设施来测试JavaScript代码. 你可以使用任何测试框架和工具来测试JavaScript代码. + +## 测试基础设施 + +[Volo.Abp.AspNetCore.TestBase](https://www.nuget.org/packages/Volo.Abp.AspNetCore.TestBase) 提供了集成到ABP框架和ASP.NET Core的测试基础设施. + +> Volo.Abp.AspNetCore.TestBase 已经安装在 `.Web.Tests` 项目中. + +此包提供的`AbpAspNetCoreIntegratedTestBase`作为派生测试类的基类. 上面使用的`MyProjectWebTestBase`继承自`AbpAspNetCoreIntegratedTestBase`, 因此我们间接继承了`AbpAspNetCoreIntegratedTestBase`. + +### 基本属性 + +`AbpAspNetCoreIntegratedTestBase` 提供了测试中使用的以下基本属性: + +* `Server`: 在测试中托管web应用程序的`TestServer`实例. +* `Client`: 为执行对测试服务器的请求配置`HttpClient`实例. +* `ServiceProvider`: 可以在你需要时处理服务提供服务. + +### 基本方法 + +`AbpAspNetCoreIntegratedTestBase` 提供了以下方法, 如果需要自定义测试服务器, 可以重写这些方法: + +* `ConfigureServices` 仅为派生测试类注册/替换服务时可以重写使用. +* `CreateHostBuilder` 可用于自定义生成 `IHostBuilder`. + +另请参阅 + +* [总览/服务器端测试](../../Testing.md) From 82d8320c9010eeb90494a93562262254d4d3f367 Mon Sep 17 00:00:00 2001 From: hpstory <33348162+hpstory@users.noreply.github.com> Date: Mon, 25 Apr 2022 11:26:22 +0800 Subject: [PATCH 045/134] Update docs-nav.json --- docs/zh-Hans/docs-nav.json | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 6586fa978f..fa38a203d0 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -515,6 +515,10 @@ "text": "自定义/扩展UI", "path": "UI/AspNetCore/Customization-User-Interface.md" }, + { + "text": "测试", + "path": "UI/AspNetCore/Testing.md" + }, { "text": "主题化", "path": "UI/AspNetCore/Theming.md" @@ -536,6 +540,10 @@ "text": "服务代理", "path": "UI/Angular/Service-Proxies.md" }, + { + "text": "单元测试", + "path": "UI/Angular/Testing.md" + }, { "text": "HTTP请求", "path": "UI/Angular/HTTP-Requests.md" From 6efc1b56dd90a4e551d945ad8bbb5880c97a6d50 Mon Sep 17 00:00:00 2001 From: braim23 <94292623+braim23@users.noreply.github.com> Date: Mon, 25 Apr 2022 12:22:50 +0300 Subject: [PATCH 046/134] Update en.json --- .../AbpIoLocalization/Admin/Localization/Resources/en.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json index 18c5524d1f..ec9be431ba 100644 --- a/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json +++ b/abp_io/AbpIoLocalization/AbpIoLocalization/Admin/Localization/Resources/en.json @@ -382,6 +382,7 @@ "SuccessfullyAdded": "Successfully added", "PurchaseState": "Purchase State", "ShowBetweenDayCount": "Show Between Days", - "PurchaseOrder": "Purchase Order" + "PurchaseOrder": "Purchase Order", + "ShowCreateInvoiceOfOrganization": "Create Invoice" } } \ No newline at end of file From e48efa412eab56ec1a1570767eb4d3ad4ef87083 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Mon, 25 Apr 2022 15:07:34 +0300 Subject: [PATCH 047/134] Add Volo.Abp.Gdpr.Abstractions.csproj --- framework/Volo.Abp.sln | 7 ++++ .../FodyWeavers.xml | 3 ++ .../FodyWeavers.xsd | 30 ++++++++++++++ .../Volo.Abp.Gdpr.Abstractions.csproj | 15 +++++++ .../Abp/Gdpr/AbpGdprAbstractionsModule.cs | 9 ++++ .../Volo/Abp/Gdpr/GdprDataInfo.cs | 10 +++++ .../Gdpr/GdprUserDataDeleteRequestedEto.cs | 9 ++++ .../Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs | 13 ++++++ .../Volo/Abp/Gdpr/GdprUserDataProviderBase.cs | 8 ++++ .../Abp/Gdpr/GdprUserDataProviderContext.cs | 8 ++++ .../Gdpr/GdprUserDataRequestEventHandler.cs | 41 +++++++++++++++++++ .../Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs | 11 +++++ .../Volo/Abp/Gdpr/IGdprUserDataProvider.cs | 8 ++++ 13 files changed, 172 insertions(+) create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xml create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xsd create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprDataInfo.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeleteRequestedEto.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderBase.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderContext.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestEventHandler.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs create mode 100644 framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/IGdprUserDataProvider.cs diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index f3e495c814..94a2e79415 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -403,6 +403,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.DistributedLocking EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BackgroundWorkers.Hangfire", "src\Volo.Abp.BackgroundWorkers.Hangfire\Volo.Abp.BackgroundWorkers.Hangfire.csproj", "{E5FCE710-C5A3-4F94-B9C9-BD1E99252BFB}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Gdpr.Abstractions", "src\Volo.Abp.Gdpr.Abstractions\Volo.Abp.Gdpr.Abstractions.csproj", "{3683340D-92F5-4B14-B77B-34A163333309}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1201,6 +1203,10 @@ Global {E5FCE710-C5A3-4F94-B9C9-BD1E99252BFB}.Debug|Any CPU.Build.0 = Debug|Any CPU {E5FCE710-C5A3-4F94-B9C9-BD1E99252BFB}.Release|Any CPU.ActiveCfg = Release|Any CPU {E5FCE710-C5A3-4F94-B9C9-BD1E99252BFB}.Release|Any CPU.Build.0 = Release|Any CPU + {3683340D-92F5-4B14-B77B-34A163333309}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3683340D-92F5-4B14-B77B-34A163333309}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3683340D-92F5-4B14-B77B-34A163333309}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3683340D-92F5-4B14-B77B-34A163333309}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1404,6 +1410,7 @@ Global {CA805B77-D50C-431F-B3CB-1111C9C6E807} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {C4F54FB5-C828-414D-BA03-E8E7A10C784D} = {447C8A77-E5F0-4538-8687-7383196D04EA} {E5FCE710-C5A3-4F94-B9C9-BD1E99252BFB} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {3683340D-92F5-4B14-B77B-34A163333309} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xml b/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xml new file mode 100644 index 0000000000..00e1d9a1c1 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xsd b/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xsd new file mode 100644 index 0000000000..3f3946e282 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/FodyWeavers.xsd @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed. + + + + + A comma-separated list of error codes that can be safely ignored in assembly verification. + + + + + 'false' to turn off automatic generation of the XML Schema file. + + + + + \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj new file mode 100644 index 0000000000..3a5f8369b0 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo.Abp.Gdpr.Abstractions.csproj @@ -0,0 +1,15 @@ + + + + + + + netstandard2.0 + + + + + + + + diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs new file mode 100644 index 0000000000..9d1e95eae9 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/AbpGdprAbstractionsModule.cs @@ -0,0 +1,9 @@ +using Volo.Abp.EventBus; +using Volo.Abp.Modularity; + +namespace Volo.Abp.Gdpr; + +[DependsOn(typeof(AbpEventBusModule))] +public class AbpGdprAbstractionsModule : AbpModule +{ +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprDataInfo.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprDataInfo.cs new file mode 100644 index 0000000000..550f556a1c --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprDataInfo.cs @@ -0,0 +1,10 @@ +using System; +using System.Collections.Generic; + +namespace Volo.Abp.Gdpr; + +[Serializable] +public class GdprDataInfo : Dictionary +{ + +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeleteRequestedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeleteRequestedEto.cs new file mode 100644 index 0000000000..c5975e1496 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataDeleteRequestedEto.cs @@ -0,0 +1,9 @@ +using System; + +namespace Volo.Abp.Gdpr; + +[Serializable] +public class GdprUserDataDeleteRequestedEto +{ + public Guid UserId { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs new file mode 100644 index 0000000000..3a2c440f97 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataPreparedEto.cs @@ -0,0 +1,13 @@ +using System; + +namespace Volo.Abp.Gdpr; + +[Serializable] +public class GdprUserDataPreparedEto +{ + public Guid RequestId { get; set; } + + public string Provider { get; set; } + + public GdprDataInfo Data { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderBase.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderBase.cs new file mode 100644 index 0000000000..696cf16e74 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderBase.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Gdpr; + +public abstract class GdprUserDataProviderBase : IGdprUserDataProvider +{ + public abstract Task GetAsync(GdprUserDataProviderContext context); +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderContext.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderContext.cs new file mode 100644 index 0000000000..1ccd035b7e --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataProviderContext.cs @@ -0,0 +1,8 @@ +using System; + +namespace Volo.Abp.Gdpr; + +public class GdprUserDataProviderContext +{ + public Guid UserId { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestEventHandler.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestEventHandler.cs new file mode 100644 index 0000000000..d9453c7ef5 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestEventHandler.cs @@ -0,0 +1,41 @@ +using System.Linq; +using System.Threading.Tasks; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.DependencyInjection; +using Volo.Abp.EventBus.Distributed; + +namespace Volo.Abp.Gdpr; + +public class GdprUserDataRequestEventHandler + : IDistributedEventHandler, ITransientDependency +{ + protected IServiceScopeFactory ServiceScopeFactory { get; } + protected IDistributedEventBus EventBus { get; } + + public GdprUserDataRequestEventHandler(IServiceScopeFactory serviceScopeFactory, IDistributedEventBus eventBus) + { + ServiceScopeFactory = serviceScopeFactory; + EventBus = eventBus; + } + + public async Task HandleEventAsync(GdprUserDataRequestedEto eventData) + { + using (var scope = ServiceScopeFactory.CreateScope()) + { + var gdprDataProviders = scope.ServiceProvider.GetServices().ToList(); + + foreach (var gdprDataProvider in gdprDataProviders) + { + var gdprDataInfo = await gdprDataProvider.GetAsync(new GdprUserDataProviderContext { UserId = eventData.UserId}); + + await EventBus.PublishAsync( + new GdprUserDataPreparedEto + { + RequestId = eventData.RequestId, + Data = gdprDataInfo, + Provider = gdprDataProvider.GetType().FullName + }); + } + } + } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs new file mode 100644 index 0000000000..b341ab2659 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/GdprUserDataRequestedEto.cs @@ -0,0 +1,11 @@ +using System; + +namespace Volo.Abp.Gdpr; + +[Serializable] +public class GdprUserDataRequestedEto +{ + public Guid UserId { get; set; } + + public Guid RequestId { get; set; } +} \ No newline at end of file diff --git a/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/IGdprUserDataProvider.cs b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/IGdprUserDataProvider.cs new file mode 100644 index 0000000000..e04fc950d9 --- /dev/null +++ b/framework/src/Volo.Abp.Gdpr.Abstractions/Volo/Abp/Gdpr/IGdprUserDataProvider.cs @@ -0,0 +1,8 @@ +using System.Threading.Tasks; + +namespace Volo.Abp.Gdpr; + +public interface IGdprUserDataProvider +{ + Task GetAsync(GdprUserDataProviderContext context); +} \ No newline at end of file From f15ed95068d2e15fa63c50d9d3c336fdd276bca2 Mon Sep 17 00:00:00 2001 From: Ebicoglu Date: Tue, 26 Apr 2022 02:28:34 +0300 Subject: [PATCH 048/134] closes #12355 --- .../docs/src/Volo.Docs.Web/DocsWebModule.cs | 7 ++ .../Pages/Documents/Project/Index.cshtml | 110 +++++++++--------- .../Pages/Documents/Project/Index.cshtml.cs | 30 +++-- .../Shared/Components/Head/Default.cshtml | 4 + .../Components/Head/HeadViewComponent.cs | 15 +++ 5 files changed, 102 insertions(+), 64 deletions(-) create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Shared/Components/Head/Default.cshtml create mode 100644 modules/docs/src/Volo.Docs.Web/Pages/Shared/Components/Head/HeadViewComponent.cs diff --git a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs index 77f5bec551..42c1717690 100644 --- a/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs +++ b/modules/docs/src/Volo.Docs.Web/DocsWebModule.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Bundling; +using Volo.Abp.AspNetCore.Mvc.UI.Components.LayoutHook; using Volo.Abp.AspNetCore.Mvc.UI.Packages; using Volo.Abp.AspNetCore.Mvc.UI.Packages.Prismjs; using Volo.Abp.AspNetCore.Mvc.UI.Theme.Shared; @@ -16,6 +17,7 @@ using Volo.Docs.Bundling; using Volo.Docs.HtmlConverting; using Volo.Docs.Localization; using Volo.Docs.Markdown; +using Volo.Docs.Pages.Shared.Components.Head; namespace Volo.Docs { @@ -91,6 +93,11 @@ namespace Volo.Docs { options.DisableModule(DocsRemoteServiceConsts.ModuleName); }); + + Configure(options => + { + options.Add(LayoutHooks.Head.Last, typeof(HeadViewComponent)); + }); } } } diff --git a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml index c0ea0c4d7c..5ca2b7fbd9 100644 --- a/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml +++ b/modules/docs/src/Volo.Docs.Web/Pages/Documents/Project/Index.cshtml @@ -11,38 +11,42 @@ @using Volo.Docs.Localization @using Volo.Docs.Pages.Documents.Project @using Volo.Docs.Pages.Documents.Shared.ErrorComponent +@using Volo.Docs.Pages.Shared.Components.Head @inject IThemeManager ThemeManager @inject IPageLayout PageLayout @inject IHtmlLocalizer L + @model IndexModel @{ ViewBag.FluidLayout = true; Layout = ThemeManager.CurrentTheme.GetEmptyLayout(); PageLayout.Content.Title = Model.DocumentName?.Replace("-", " "); ViewBag.Description = Model.GetDescription(); + ViewBag.CanonicalUrl = Model.IsLatestVersion ? null : Model.GetFullUrlOfTheLatestDocument(); //issue #12355 } + @section styles { - - - - - - - + + + + + + + } @section scripts { - - - - - - - - - - - - + + + + + + + + + + + + } @if (Model.LoadSuccess) { @@ -93,8 +97,8 @@ @@ -112,11 +116,11 @@ - + @@ -131,11 +135,11 @@
@* - - *@ + + *@
@@ -149,11 +153,11 @@
+ id="filter" + type="search" + data-search-url="@Model." + placeholder="@L["FilterTopics"].Value" + aria-label="Filter">
@@ -168,13 +172,13 @@ else { + version="@(Model.LatestVersionInfo == null || Model.LatestVersionInfo.IsSelected ? DocsAppConsts.Latest : Model.Version)" + project-name="@Model.ProjectName" + project-format="@Model.Project.Format" + selected-document-name="@Model.DocumentNameWithExtension" + language="@Model.LanguageCode" + id="sidebar-scroll" + class="nav nav-list"> } @@ -194,11 +198,11 @@ + id="fullsearch" + type="search" + data-fullsearch-url="/search/@Model.LanguageCode/@Model.ProjectName/@Model.Version/" + placeholder="@L["FullSearch"].Value" + aria-label="Filter"> } @@ -210,15 +214,15 @@ @(L["Contributors"].Value) - @foreach (var contributor in Model.Document.Contributors.OrderByDescending(c=> c.CommitCount).ToList()) + @foreach (var contributor in Model.Document.Contributors.OrderByDescending(c => c.CommitCount).ToList()) { Avatar + class="rounded-circle" + alt="Avatar" + height="21" + width="21" + title="@contributor.Username" /> } } @@ -261,8 +265,8 @@
@(parameter.DisplayName)