From 70bfb0fafbabc4c5d27514a055b697efa66933ea Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 1 Sep 2020 23:02:35 +0800 Subject: [PATCH 01/35] Add IsAvailableToHost & HostFeatureValueProvider & HostFeatureManagementProvider. --- .../Volo/Abp/Features/AbpFeaturesModule.cs | 1 + .../Volo/Abp/Features/FeatureDefinition.cs | 30 ++++++---- .../Abp/Features/HostFeatureValueProvider.cs | 30 ++++++++++ .../AbpFeatureManagementDomainModule.cs | 3 +- .../HostFeatureManagementProvider.cs | 59 +++++++++++++++++++ 5 files changed, 111 insertions(+), 12 deletions(-) create mode 100644 framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs index f30017feed..7db32231e0 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -26,6 +26,7 @@ namespace Volo.Abp.Features context.Services.Configure(options => { options.ValueProviders.Add(); + options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); }); diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs index bd79480326..b2b11f0a18 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinition.cs @@ -51,6 +51,12 @@ namespace Volo.Abp.Features /// public bool IsVisibleToClients { get; set; } + /// + /// Can host use this feature. + /// Default: true. + /// + public bool IsAvailableToHost { get; set; } + /// /// A list of allowed providers to get/set value of this feature. /// An empty list indicates that all providers are allowed. @@ -93,7 +99,8 @@ namespace Volo.Abp.Features ILocalizableString displayName = null, ILocalizableString description = null, IStringValueType valueType = null, - bool isVisibleToClients = true) + bool isVisibleToClients = true, + bool isAvailableToHost = true) { Name = name; DefaultValue = defaultValue; @@ -101,6 +108,7 @@ namespace Volo.Abp.Features Description = description; ValueType = valueType; IsVisibleToClients = isVisibleToClients; + IsAvailableToHost = isAvailableToHost; Properties = new Dictionary(); AllowedProviders = new List(); @@ -136,20 +144,22 @@ namespace Volo.Abp.Features /// /// Returns a newly created child feature public FeatureDefinition CreateChild( - string name, - string defaultValue = null, - ILocalizableString displayName = null, + string name, + string defaultValue = null, + ILocalizableString displayName = null, ILocalizableString description = null, IStringValueType valueType = null, - bool isVisibleToClients = true) + bool isVisibleToClients = true, + bool isAvailableToHost = true) { var feature = new FeatureDefinition( - name, - defaultValue, - displayName, + name, + defaultValue, + displayName, description, valueType, - isVisibleToClients) + isVisibleToClients, + isAvailableToHost) { Parent = this }; @@ -175,4 +185,4 @@ namespace Volo.Abp.Features return $"[{nameof(FeatureDefinition)}: {Name}]"; } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs new file mode 100644 index 0000000000..d895813e6f --- /dev/null +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs @@ -0,0 +1,30 @@ +using System.Threading.Tasks; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.Features +{ + public class HostFeatureValueProvider : FeatureValueProvider + { + public const string ProviderName = "H"; + + public override string Name => ProviderName; + + protected ICurrentTenant CurrentTenant { get; } + + public HostFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) + : base(featureStore) + { + CurrentTenant = currentTenant; + } + + public override async Task GetOrNullAsync(FeatureDefinition feature) + { + if (CurrentTenant.Id.HasValue || !feature.IsAvailableToHost) + { + return null; + } + + return await FeatureStore.GetOrNullAsync(feature.Name, Name, null); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs index e8e961d3b4..17e0b9fc04 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs @@ -1,10 +1,8 @@ using Volo.Abp.Caching; using Volo.Abp.FeatureManagement.Localization; using Volo.Abp.Features; -using Volo.Abp.Localization; using Volo.Abp.Localization.ExceptionHandling; using Volo.Abp.Modularity; -using Volo.Abp.VirtualFileSystem; namespace Volo.Abp.FeatureManagement { @@ -20,6 +18,7 @@ namespace Volo.Abp.FeatureManagement Configure(options => { options.Providers.Add(); + options.Providers.Add(); options.Providers.Add(); //TODO: Should be moved to the Tenant Management module diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs new file mode 100644 index 0000000000..d40d2dfdec --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs @@ -0,0 +1,59 @@ +using System.Threading.Tasks; +using Volo.Abp.DependencyInjection; +using Volo.Abp.Features; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.FeatureManagement +{ + public class HostFeatureManagementProvider : FeatureManagementProvider, ITransientDependency + { + public override string Name => HostFeatureValueProvider.ProviderName; + + protected ICurrentTenant CurrentTenant { get; } + + public HostFeatureManagementProvider( + IFeatureManagementStore store, + ICurrentTenant currentTenant) + : base(store) + { + CurrentTenant = currentTenant; + } + + public override async Task GetOrNullAsync(FeatureDefinition feature, string providerKey) + { + if (IsHostSide(feature)) + { + return await Store.GetOrNullAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); + } + + return null; + } + + public override async Task SetAsync(FeatureDefinition feature, string value, string providerKey) + { + if (IsHostSide(feature)) + { + await Store.SetAsync(feature.Name, value, Name, NormalizeProviderKey(providerKey)); + } + } + + public override async Task ClearAsync(FeatureDefinition feature, string providerKey) + { + if (IsHostSide(feature)) + { + await Store.DeleteAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); + } + } + + protected override string NormalizeProviderKey(string providerKey) + { + return null; + } + + //TODO: Should throw an ex when there is not in the host side? + protected virtual bool IsHostSide(FeatureDefinition feature) + { + return feature.IsAvailableToHost && CurrentTenant.Id == null; + } + } +} From 7b09b7e5926db9f80b3f7d14c8b9ac08ad1caf88 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 2 Sep 2020 15:31:42 +0800 Subject: [PATCH 02/35] Manage Host features. --- .../FeatureManagementPermissions.cs | 16 +++++++ .../FeaturePermissionDefinitionProvider.cs | 28 +++++++++++ .../FeatureManagement/IFeatureAppService.cs | 8 +++- .../FeatureManagement/FeatureAppService.cs | 46 +++++++++++++++++++ .../Localization/Domain/en.json | 7 ++- .../Localization/Domain/tr.json | 7 ++- .../Localization/Domain/zh-Hans.json | 7 ++- .../Localization/Domain/zh-Hant.json | 7 ++- .../FeatureManagement/FeaturesController.cs | 16 ++++++- .../FeatureManagementModal.cshtml.cs | 18 +++++++- .../Localization/Resources/en.json | 7 +-- .../Localization/Resources/tr.json | 3 +- .../Localization/Resources/zh-Hans.json | 3 +- .../Localization/Resources/zh-Hant.json | 3 +- .../TenantManagement/Tenants/Index.cshtml | 9 +++- .../Pages/TenantManagement/Tenants/Index.js | 10 +++- 16 files changed, 173 insertions(+), 22 deletions(-) create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementPermissions.cs create mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeaturePermissionDefinitionProvider.cs diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementPermissions.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementPermissions.cs new file mode 100644 index 0000000000..86772c0a7b --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureManagementPermissions.cs @@ -0,0 +1,16 @@ +using Volo.Abp.Reflection; + +namespace Volo.Abp.FeatureManagement +{ + public class FeatureManagementPermissions + { + public const string GroupName = "FeatureManagement"; + + public const string ManageHostFeatures = GroupName + ".ManageHostFeatures"; + + public static string[] GetAll() + { + return ReflectionHelper.GetPublicConstantsRecursively(typeof(FeatureManagementPermissions)); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeaturePermissionDefinitionProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeaturePermissionDefinitionProvider.cs new file mode 100644 index 0000000000..27e159ee8a --- /dev/null +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeaturePermissionDefinitionProvider.cs @@ -0,0 +1,28 @@ +using Volo.Abp.Authorization.Permissions; +using Volo.Abp.FeatureManagement.Localization; +using Volo.Abp.Localization; +using Volo.Abp.MultiTenancy; + +namespace Volo.Abp.FeatureManagement +{ + public class FeaturePermissionDefinitionProvider : PermissionDefinitionProvider + { + public override void Define(IPermissionDefinitionContext context) + { + var featureManagementGroup = context.AddGroup( + FeatureManagementPermissions.GroupName, + L("Permission:FeatureManagement"), + multiTenancySide: MultiTenancySides.Host); + + featureManagementGroup.AddPermission( + FeatureManagementPermissions.ManageHostFeatures, + L("Permission:FeatureManagement.ManageHostFeatures"), + multiTenancySide: MultiTenancySides.Host); + } + + private static LocalizableString L(string name) + { + return LocalizableString.Create(name); + } + } +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs index 2c337f1b2a..a2a87b195b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs @@ -6,8 +6,12 @@ namespace Volo.Abp.FeatureManagement { public interface IFeatureAppService : IApplicationService { - Task GetAsync([NotNull] string providerName, [NotNull] string providerKey); + Task GetAsync([NotNull] string providerName, [NotNull] string providerKey); - Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input); + Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input); + + Task GetHostAsync(); + + Task UpdateHostAsync(UpdateFeaturesDto input); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index f0c310650d..6969739baa 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using JetBrains.Annotations; using Microsoft.AspNetCore.Authorization; @@ -65,6 +66,51 @@ namespace Volo.Abp.FeatureManagement } } + [Authorize(FeatureManagementPermissions.ManageHostFeatures)] + public async Task GetHostAsync() + { + var featureDefinitions = FeatureDefinitionManager.GetAll().Where(x => x.IsAvailableToHost); + var features = new List(); + + foreach (var featureDefinition in featureDefinitions) + { + var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, HostFeatureValueProvider.ProviderName, null); + features.Add(new FeatureDto + { + Name = featureDefinition.Name, + DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), + ValueType = featureDefinition.ValueType, + Description = featureDefinition.Description?.Localize(StringLocalizerFactory), + ParentName = featureDefinition.Parent?.Name, + Value = feature.Value, + Provider = new FeatureProviderDto + { + Name = feature.Provider?.Name, + Key = feature.Provider?.Key + } + }); + } + + SetFeatureDepth(features, HostFeatureValueProvider.ProviderName, null); + + return new FeatureListDto { Features = features }; + } + + [Authorize(FeatureManagementPermissions.ManageHostFeatures)] + public async Task UpdateHostAsync(UpdateFeaturesDto input) + { + foreach (var feature in input.Features) + { + var featureDefinition = FeatureDefinitionManager.GetOrNull(feature.Name); + if (featureDefinition == null || !featureDefinition.IsAvailableToHost) + { + throw new UserFriendlyException(L["FeatureNotAvailable"]); + } + + await FeatureManager.SetAsync(feature.Name, feature.Value, HostFeatureValueProvider.ProviderName, null); + } + } + protected virtual void SetFeatureDepth(List features, string providerName, string providerKey, FeatureDto parentFeature = null, int depth = 0) { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json index e5a2406aaf..6e17e8fc48 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json @@ -2,6 +2,9 @@ "culture": "en", "texts": { "Features": "Features", - "NoFeatureFoundMessage": "There isn't any available feature." + "NoFeatureFoundMessage": "There isn't any available feature.", + "FeatureNotAvailable": "Feature not available.", + "Permission:FeatureManagement": "Feature management", + "Permission:FeatureManagement.ManageHostFeatures": "Manage Host features" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json index b566f654ca..de5ad20624 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json @@ -2,6 +2,9 @@ "culture": "tr", "texts": { "Features": "Özellikler", - "NoFeatureFoundMessage": "Hiç özellik yok." + "NoFeatureFoundMessage": "Hiç özellik yok.", + "FeatureNotAvailable": "özelliği mevcut değil.", + "Permission:FeatureManagement": "Özellik yönetimi", + "Permission:FeatureManagement.ManageHostFeatures": "Yönetilen Ana Bilgisayar özellikleri" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json index 69b8ca89b1..2ecdb3997b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json @@ -2,6 +2,9 @@ "culture": "zh-Hans", "texts": { "Features": "功能", - "NoFeatureFoundMessage": "没有可用的功能." + "NoFeatureFoundMessage": "没有可用的功能.", + "FeatureNotAvailable": "功能不可用.", + "Permission:FeatureManagement": "特性管理", + "Permission:FeatureManagement.ManageHostFeatures": "管理Host特性" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json index bc4dbf268f..6a8ccd099b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json @@ -2,6 +2,9 @@ "culture": "zh-Hant", "texts": { "Features": "功能", - "NoFeatureFoundMessage": "沒有可用的功能." + "NoFeatureFoundMessage": "沒有可用的功能.", + "FeatureNotAvailable": "功能不可用.", + "Permission:FeatureManagement": "功能管理", + "Permission:FeatureManagement.ManageHostFeatures": "管理Host功能" } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs index 0ae78ed03f..a5c9c1c819 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs @@ -27,5 +27,19 @@ namespace Volo.Abp.FeatureManagement { return FeatureAppService.UpdateAsync(providerName, providerKey, input); } + + [HttpGet] + [Route("host")] + public virtual Task GetHostAsync() + { + return FeatureAppService.GetHostAsync(); + } + + [HttpPut] + [Route("host")] + public virtual Task UpdateHostAsync(UpdateFeaturesDto input) + { + return FeatureAppService.UpdateHostAsync(input); + } } -} \ No newline at end of file +} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs index e46d686e69..0fd5d756f7 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs @@ -37,7 +37,14 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement public virtual async Task OnGetAsync() { - FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + if (ProviderName == HostFeatureValueProvider.ProviderName) + { + FeatureListDto = await FeatureAppService.GetHostAsync(); + } + else + { + FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); + } } public virtual async Task OnPostAsync() @@ -51,7 +58,14 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement }).ToList() }; - await FeatureAppService.UpdateAsync(ProviderName, ProviderKey, features); + if (ProviderName == HostFeatureValueProvider.ProviderName) + { + await FeatureAppService.UpdateHostAsync(features); + } + else + { + await FeatureAppService.UpdateAsync(ProviderName, ProviderKey, features); + } return NoContent(); } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json index d6d1d7c155..ba4109c392 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/en.json @@ -10,13 +10,14 @@ "ConnectionStrings": "Connection Strings", "DisplayName:DefaultConnectionString": "Default Connection String", "DisplayName:UseSharedDatabase": "Use the Shared Database", + "ManageHostFeatures": "Manage Host features", "Permission:TenantManagement": "Tenant management", "Permission:Create": "Create", "Permission:Edit": "Edit", "Permission:Delete": "Delete", "Permission:ManageConnectionStrings": "Manage connection strings", "Permission:ManageFeatures": "Manage features", - "DisplayName:AdminEmailAddress": "Admin Email Address", - "DisplayName:AdminPassword": "Admin Password" + "DisplayName:AdminEmailAddress": "Admin Email Address", + "DisplayName:AdminPassword": "Admin Password" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json index 72b570f3f9..7315cccc3f 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/tr.json @@ -10,6 +10,7 @@ "ConnectionStrings": "Bağlantı cümlesi", "DisplayName:DefaultConnectionString": "Varsayılan bağlantı cümlesi", "DisplayName:UseSharedDatabase": "Paylaşılan veritabanını kullan", + "ManageHostFeatures": "Toplantı Sahibi özelliklerini yönetin", "Permission:TenantManagement": "Müşteri yönetimi", "Permission:Create": "Oluşturma", "Permission:Edit": "Düzenleme", @@ -19,4 +20,4 @@ "DisplayName:AdminEmailAddress": "Admin Eposta Adresi", "DisplayName:AdminPassword": "Admin Şifresi" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json index b60c68e2e0..f85f6cf638 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hans.json @@ -10,6 +10,7 @@ "ConnectionStrings": "连接字符串", "DisplayName:DefaultConnectionString": "默认连接字符串", "DisplayName:UseSharedDatabase": "使用共享数据库", + "ManageHostFeatures": "管理Host特性", "Permission:TenantManagement": "租户管理", "Permission:Create": "创建", "Permission:Edit": "编辑", @@ -17,4 +18,4 @@ "Permission:ManageConnectionStrings": "管理连接字符串", "Permission:ManageFeatures": "管理功能" } -} \ No newline at end of file +} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json index ea8be8ae0e..6950a1ca00 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Domain.Shared/Volo/Abp/TenantManagement/Localization/Resources/zh-Hant.json @@ -10,13 +10,14 @@ "ConnectionStrings": "資料庫連線字串", "DisplayName:DefaultConnectionString": "預設資料庫連線字串", "DisplayName:UseSharedDatabase": "使用共用資料庫", + "ManageHostFeatures": "管理Host功能", "Permission:TenantManagement": "租戶管理", "Permission:Create": "新增", "Permission:Edit": "編輯", "Permission:Delete": "刪除", "Permission:ManageConnectionStrings": "管理資料庫連線字串", "Permission:ManageFeatures": "管理功能", - "DisplayName:AdminEmailAddress": "管理者信箱", + "DisplayName:AdminEmailAddress": "管理者信箱", "DisplayName:AdminPassword": "管理者密碼" } } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml index eabc75c786..bb972b02d8 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.cshtml @@ -2,6 +2,7 @@ @using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Mvc.Localization @using Volo.Abp.AspNetCore.Mvc.UI.Layout +@using Volo.Abp.FeatureManagement @using Volo.Abp.TenantManagement @using Volo.Abp.TenantManagement.Localization @using Volo.Abp.TenantManagement.Web.Navigation @@ -29,9 +30,13 @@ @L["Tenants"] + @if (await Authorization.IsGrantedAsync(FeatureManagementPermissions.ManageHostFeatures)) + { + + } @if (await Authorization.IsGrantedAsync(TenantManagementPermissions.Tenants.Create)) { - + } @@ -39,4 +44,4 @@ - \ No newline at end of file + diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js index 4b19d0bbb7..828f163908 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js @@ -100,7 +100,7 @@ }, 0 //adds as the first contributor ); - + $(function () { var _$wrapper = $('#TenantsWrapper'); @@ -128,5 +128,13 @@ e.preventDefault(); _createModal.open(); }); + + _$wrapper.find('button[name=ManageHostFeatures]').click(function (e) { + e.preventDefault(); + _featuresModal.open({ + providerName: 'H', + providerKey: 'H' + }); + }); }); })(); From 2811de7a708fc6ce5461726a43f2a58824782632 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 7 Sep 2020 16:11:12 +0800 Subject: [PATCH 03/35] Add more property to user claims. --- .../CurrentUserDto.cs | 10 ++++ .../AbpApplicationConfigurationAppService.cs | 5 ++ .../Volo/Abp/Security/Claims/AbpClaimTypes.cs | 56 ++++++++++++------- .../Volo/Abp/Users/CurrentUser.cs | 6 +- .../Volo/Abp/Users/ICurrentUser.cs | 8 ++- .../Abp/Identity/AbpIdentityDomainModule.cs | 15 ++++- .../Identity/AbpUserClaimsPrincipalFactory.cs | 38 ++++++++++--- .../Abp/Identity/ClaimsIdentityExtensions.cs | 21 +++++++ 8 files changed, 126 insertions(+), 33 deletions(-) create mode 100644 modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ClaimsIdentityExtensions.cs diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs index ce08bebe87..5d1f6d8414 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc.Contracts/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/CurrentUserDto.cs @@ -13,8 +13,18 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations public string UserName { get; set; } + public string Name { get; set; } + + public string SurName { get; set; } + public string Email { get; set; } + public bool EmailVerified { get; set; } + + public string PhoneNumber { get; set; } + + public bool PhoneNumberVerified { get; set; } + public string[] Roles { get; set; } } } diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs index 54815d6ff5..89bf3e7cfc 100644 --- a/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs +++ b/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/ApplicationConfigurations/AbpApplicationConfigurationAppService.cs @@ -117,7 +117,12 @@ namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations Id = _currentUser.Id, TenantId = _currentUser.TenantId, UserName = _currentUser.UserName, + SurName = _currentUser.SurName, + Name = _currentUser.Name, Email = _currentUser.Email, + EmailVerified = _currentUser.EmailVerified, + PhoneNumber = _currentUser.PhoneNumber, + PhoneNumberVerified = _currentUser.PhoneNumberVerified, Roles = _currentUser.Roles }; } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index d0b883e4f3..a797b360ee 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -8,55 +8,71 @@ namespace Volo.Abp.Security.Claims /// public static class AbpClaimTypes { + public const string ClaimTypeNamespace = "abp_"; + /// - /// Default: + /// Default: abp_username /// - public static string UserName { get; set; } = ClaimTypes.Name; + public static string UserName { get; set; } = ClaimTypeNamespace + "username"; /// - /// Default: + /// Default: abp_name /// - public static string UserId { get; set; } = ClaimTypes.NameIdentifier; + public static string Name { get; set; } = ClaimTypeNamespace + "name"; /// - /// Default: + /// Default: abp_surname /// - public static string Role { get; set; } = ClaimTypes.Role; + public static string SurName { get; set; } = ClaimTypeNamespace + "surname"; /// - /// Default: + /// Default: abp_user_id /// - public static string Email { get; set; } = ClaimTypes.Email; + public static string UserId { get; set; } = ClaimTypeNamespace + "user_id"; /// - /// Default: "email_verified". + /// Default: abp_role /// - public static string EmailVerified { get; set; } = "email_verified"; + public static string Role { get; set; } = ClaimTypeNamespace + "role"; /// - /// Default: "phone_number". + /// Default: abp_security_stamp /// - public static string PhoneNumber { get; set; } = "phone_number"; + public static string SecurityStamp { get; set; } = ClaimTypeNamespace + "security_stamp"; /// - /// Default: "phone_number_verified". + /// Default: abp_email /// - public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; + public static string Email { get; set; } = ClaimTypeNamespace + "email"; /// - /// Default: "tenantid". + /// Default: abp_email_verified /// - public static string TenantId { get; set; } = "tenantid"; + public static string EmailVerified { get; set; } = ClaimTypeNamespace + "email_verified"; + /// + /// Default: abp_phone_number + /// + public static string PhoneNumber { get; set; } = ClaimTypeNamespace + "phone_number"; + + /// + /// Default: abp_phone_number_verified + /// + public static string PhoneNumberVerified { get; set; } = ClaimTypeNamespace + "phone_number_verified"; + + /// + /// Default: abp_tenant_id + /// + public static string TenantId { get; set; } = ClaimTypeNamespace + "tenant_id"; /// - /// Default: "editionid". + /// Default: abp_edition_id /// - public static string EditionId { get; set; } = "editionid"; + public static string EditionId { get; set; } = ClaimTypeNamespace + "edition_id"; /// - /// Default: "client_id". + /// Default: abp_client_id /// - public static string ClientId { get; set; } = "client_id"; + public static string ClientId { get; set; } = ClaimTypeNamespace + "client_id"; } } diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs index 487274860f..e769b9ec11 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Users/CurrentUser.cs @@ -17,6 +17,10 @@ namespace Volo.Abp.Users public virtual string UserName => this.FindClaimValue(AbpClaimTypes.UserName); + public virtual string Name => this.FindClaimValue(AbpClaimTypes.Name); + + public virtual string SurName => this.FindClaimValue(AbpClaimTypes.SurName); + public virtual string PhoneNumber => this.FindClaimValue(AbpClaimTypes.PhoneNumber); public virtual bool PhoneNumberVerified => string.Equals(this.FindClaimValue(AbpClaimTypes.PhoneNumberVerified), "true", StringComparison.InvariantCultureIgnoreCase); @@ -56,4 +60,4 @@ namespace Volo.Abp.Users return FindClaims(AbpClaimTypes.Role).Any(c => c.Value == roleName); } } -} \ No newline at end of file +} diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Users/ICurrentUser.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Users/ICurrentUser.cs index 18613406d8..528f176c94 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Users/ICurrentUser.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Users/ICurrentUser.cs @@ -14,9 +14,15 @@ namespace Volo.Abp.Users [CanBeNull] string UserName { get; } + [CanBeNull] + string Name { get; } + + [CanBeNull] + string SurName { get; } + [CanBeNull] string PhoneNumber { get; } - + bool PhoneNumberVerified { get; } [CanBeNull] diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index d21e945f60..685f66a9ea 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -8,6 +8,7 @@ using Volo.Abp.Domain.Entities.Events.Distributed; using Volo.Abp.Modularity; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectExtending.Modularity; +using Volo.Abp.Security.Claims; using Volo.Abp.Users; namespace Volo.Abp.Identity @@ -36,7 +37,7 @@ namespace Volo.Abp.Identity options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); options.EtoMappings.Add(typeof(AbpIdentityDomainModule)); }); - + var identityBuilder = context.Services.AddAbpIdentity(options => { options.User.RequireUniqueEmail = true; @@ -45,6 +46,14 @@ namespace Volo.Abp.Identity context.Services.AddObjectAccessor(identityBuilder); context.Services.ExecutePreConfiguredActions(identityBuilder); + Configure(options => + { + options.ClaimsIdentity.UserIdClaimType = AbpClaimTypes.UserId; + options.ClaimsIdentity.UserNameClaimType = AbpClaimTypes.UserName; + options.ClaimsIdentity.RoleClaimType = AbpClaimTypes.Role; + options.ClaimsIdentity.SecurityStampClaimType = AbpClaimTypes.SecurityStamp; + }); + AddAbpIdentityOptionsFactory(context.Services); } @@ -67,7 +76,7 @@ namespace Volo.Abp.Identity IdentityModuleExtensionConsts.EntityNames.ClaimType, typeof(IdentityClaimType) ); - + ModuleExtensionConfigurationHelper.ApplyEntityConfigurationToEntity( IdentityModuleExtensionConsts.ModuleName, IdentityModuleExtensionConsts.EntityNames.OrganizationUnit, @@ -81,4 +90,4 @@ namespace Volo.Abp.Identity services.Replace(ServiceDescriptor.Scoped, OptionsManager>()); } } -} \ No newline at end of file +} diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs index 8db2b95942..c9e266bf23 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs @@ -1,4 +1,5 @@ -using System.Linq; +using System; +using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; @@ -6,6 +7,7 @@ using Microsoft.Extensions.Options; using Volo.Abp.DependencyInjection; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; +using Volo.Abp.Users; namespace Volo.Abp.Identity { @@ -13,11 +15,11 @@ namespace Volo.Abp.Identity { public AbpUserClaimsPrincipalFactory( UserManager userManager, - RoleManager roleManager, - IOptions options) + RoleManager roleManager, + IOptions options) : base( - userManager, - roleManager, + userManager, + roleManager, options) { } @@ -26,14 +28,34 @@ namespace Volo.Abp.Identity public override async Task CreateAsync(IdentityUser user) { var principal = await base.CreateAsync(user); + var identity = principal.Identities.First(); if (user.TenantId.HasValue) { - principal.Identities - .First() - .AddClaim(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())); + identity.AddIfNotContains(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())); } + if (!user.Name.IsNullOrWhiteSpace()) + { + identity.AddIfNotContains(new Claim(AbpClaimTypes.Name, user.Name)); + } + if (!user.Surname.IsNullOrWhiteSpace()) + { + identity.AddIfNotContains(new Claim(AbpClaimTypes.SurName, user.Surname)); + } + + if (!user.PhoneNumber.IsNullOrWhiteSpace()) + { + identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber)); + } + identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed.ToString())); + + if (!user.Email.IsNullOrWhiteSpace()) + { + identity.AddIfNotContains(new Claim(AbpClaimTypes.Email, user.Email)); + } + identity.AddIfNotContains(new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString())); + return principal; } } diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ClaimsIdentityExtensions.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ClaimsIdentityExtensions.cs new file mode 100644 index 0000000000..156f092f9e --- /dev/null +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/ClaimsIdentityExtensions.cs @@ -0,0 +1,21 @@ +using System; +using System.Linq; +using System.Security.Claims; + +namespace Volo.Abp.Identity +{ + public static class ClaimsIdentityExtensions + { + public static ClaimsIdentity AddIfNotContains(this ClaimsIdentity claimsIdentity, Claim claim) + { + if (!claimsIdentity.Claims.Any(existClaim => + existClaim != null && + string.Equals(existClaim.Type, claim.Type, StringComparison.OrdinalIgnoreCase))) + { + claimsIdentity.AddClaim(claim); + } + + return claimsIdentity; + } + } +} From 2c6cd37af2f7e7a70565e4f91f971fd281074e70 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 7 Sep 2020 16:54:00 +0800 Subject: [PATCH 04/35] Fix unit test. --- .../Volo/Abp/Authorization/AuthorizationTestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs index 5cf9976e46..89f45600f4 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs @@ -23,7 +23,7 @@ namespace Volo.Abp.Authorization new Claim(AbpClaimTypes.Role, "MyRole") }; - var identity = new ClaimsIdentity(claims); + var identity = new ClaimsIdentity(claims,null, AbpClaimTypes.UserName, AbpClaimTypes.Role); var claimsPrincipal = new ClaimsPrincipal(identity); var principalAccessor = Substitute.For(); principalAccessor.Principal.Returns(ci => claimsPrincipal); From 6845b0a58804ba1a61c73504985fbb01446282c6 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 7 Sep 2020 20:18:24 +0800 Subject: [PATCH 05/35] Update modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Halil İbrahim Kalkan --- .../Volo/Abp/FeatureManagement/Localization/Domain/tr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json index de5ad20624..a7dab6dac3 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json @@ -3,7 +3,7 @@ "texts": { "Features": "Özellikler", "NoFeatureFoundMessage": "Hiç özellik yok.", - "FeatureNotAvailable": "özelliği mevcut değil.", + "FeatureNotAvailable": "Özellik bulunamadı.", "Permission:FeatureManagement": "Özellik yönetimi", "Permission:FeatureManagement.ManageHostFeatures": "Yönetilen Ana Bilgisayar özellikleri" } From e2bba2941eb8125276c5024100ec0260c3dc2a8c Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Mon, 7 Sep 2020 20:18:41 +0800 Subject: [PATCH 06/35] Update modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Halil İbrahim Kalkan --- .../Volo/Abp/FeatureManagement/Localization/Domain/tr.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json index a7dab6dac3..285b4143cf 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json @@ -5,6 +5,6 @@ "NoFeatureFoundMessage": "Hiç özellik yok.", "FeatureNotAvailable": "Özellik bulunamadı.", "Permission:FeatureManagement": "Özellik yönetimi", - "Permission:FeatureManagement.ManageHostFeatures": "Yönetilen Ana Bilgisayar özellikleri" + "Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle" } } From 40978470c6b5bc8f032d320aa59535d237c646b8 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 8 Sep 2020 11:17:34 +0800 Subject: [PATCH 07/35] Refactor --- .../FeatureManagement/IFeatureAppService.cs | 8 +-- .../FeatureManagement/FeatureAppService.cs | 52 ++++--------------- .../AbpFeatureManagementDomainModule.cs | 1 + .../FeatureManagement/FeaturesController.cs | 14 ----- .../FeatureManagementModal.cshtml.cs | 21 +------- .../Pages/TenantManagement/Tenants/Index.js | 3 +- 6 files changed, 15 insertions(+), 84 deletions(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs index a2a87b195b..ce9609976e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs @@ -6,12 +6,8 @@ namespace Volo.Abp.FeatureManagement { public interface IFeatureAppService : IApplicationService { - Task GetAsync([NotNull] string providerName, [NotNull] string providerKey); + Task GetAsync([NotNull] string providerName, string providerKey); - Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input); - - Task GetHostAsync(); - - Task UpdateHostAsync(UpdateFeaturesDto input); + Task UpdateAsync([NotNull] string providerName, string providerKey, UpdateFeaturesDto input); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 6969739baa..7bb305054e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -25,13 +25,18 @@ namespace Volo.Abp.FeatureManagement Options = options.Value; } - public virtual async Task GetAsync([NotNull] string providerName, [NotNull] string providerKey) + public virtual async Task GetAsync([NotNull] string providerName, string providerKey) { await CheckProviderPolicy(providerName); var featureDefinitions = FeatureDefinitionManager.GetAll(); var features = new List(); + if (providerName == HostFeatureValueProvider.ProviderName) + { + featureDefinitions = featureDefinitions.Where(x => x.IsAvailableToHost).ToList(); + } + foreach (var featureDefinition in featureDefinitions) { var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey); @@ -56,58 +61,19 @@ namespace Volo.Abp.FeatureManagement return new FeatureListDto { Features = features }; } - public virtual async Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input) + public virtual async Task UpdateAsync([NotNull] string providerName, string providerKey, UpdateFeaturesDto input) { await CheckProviderPolicy(providerName); - foreach (var feature in input.Features) - { - await FeatureManager.SetAsync(feature.Name, feature.Value, providerName, providerKey); - } - } - - [Authorize(FeatureManagementPermissions.ManageHostFeatures)] - public async Task GetHostAsync() - { - var featureDefinitions = FeatureDefinitionManager.GetAll().Where(x => x.IsAvailableToHost); - var features = new List(); - - foreach (var featureDefinition in featureDefinitions) - { - var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, HostFeatureValueProvider.ProviderName, null); - features.Add(new FeatureDto - { - Name = featureDefinition.Name, - DisplayName = featureDefinition.DisplayName?.Localize(StringLocalizerFactory), - ValueType = featureDefinition.ValueType, - Description = featureDefinition.Description?.Localize(StringLocalizerFactory), - ParentName = featureDefinition.Parent?.Name, - Value = feature.Value, - Provider = new FeatureProviderDto - { - Name = feature.Provider?.Name, - Key = feature.Provider?.Key - } - }); - } - - SetFeatureDepth(features, HostFeatureValueProvider.ProviderName, null); - - return new FeatureListDto { Features = features }; - } - - [Authorize(FeatureManagementPermissions.ManageHostFeatures)] - public async Task UpdateHostAsync(UpdateFeaturesDto input) - { foreach (var feature in input.Features) { var featureDefinition = FeatureDefinitionManager.GetOrNull(feature.Name); - if (featureDefinition == null || !featureDefinition.IsAvailableToHost) + if (featureDefinition == null || (providerName == HostFeatureValueProvider.ProviderName && !featureDefinition.IsAvailableToHost)) { throw new UserFriendlyException(L["FeatureNotAvailable"]); } - await FeatureManager.SetAsync(feature.Name, feature.Value, HostFeatureValueProvider.ProviderName, null); + await FeatureManager.SetAsync(feature.Name, feature.Value, providerName, providerKey); } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs index 17e0b9fc04..b9992309dd 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs @@ -19,6 +19,7 @@ namespace Volo.Abp.FeatureManagement { options.Providers.Add(); options.Providers.Add(); + options.ProviderPolicies[HostFeatureValueProvider.ProviderName] = "FeatureManagement.ManageHostFeatures"; options.Providers.Add(); //TODO: Should be moved to the Tenant Management module diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs index a5c9c1c819..1c956e1fcf 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs @@ -27,19 +27,5 @@ namespace Volo.Abp.FeatureManagement { return FeatureAppService.UpdateAsync(providerName, providerKey, input); } - - [HttpGet] - [Route("host")] - public virtual Task GetHostAsync() - { - return FeatureAppService.GetHostAsync(); - } - - [HttpPut] - [Route("host")] - public virtual Task UpdateHostAsync(UpdateFeaturesDto input) - { - return FeatureAppService.UpdateHostAsync(input); - } } } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs index 0fd5d756f7..ecc59d11f9 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs @@ -16,7 +16,6 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement [BindProperty(SupportsGet = true)] public string ProviderName { get; set; } - [Required] [HiddenInput] [BindProperty(SupportsGet = true)] public string ProviderKey { get; set; } @@ -37,14 +36,7 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement public virtual async Task OnGetAsync() { - if (ProviderName == HostFeatureValueProvider.ProviderName) - { - FeatureListDto = await FeatureAppService.GetHostAsync(); - } - else - { - FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); - } + FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey); } public virtual async Task OnPostAsync() @@ -58,14 +50,7 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement }).ToList() }; - if (ProviderName == HostFeatureValueProvider.ProviderName) - { - await FeatureAppService.UpdateHostAsync(features); - } - else - { - await FeatureAppService.UpdateAsync(ProviderName, ProviderKey, features); - } + await FeatureAppService.UpdateAsync(ProviderName, ProviderKey, features); return NoContent(); } @@ -88,8 +73,6 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement public string Value { get; set; } - public string ProviderName { get; set; } - public bool BoolValue { get; set; } public string Type { get; set; } diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js index 828f163908..ecb2309f36 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js @@ -132,8 +132,7 @@ _$wrapper.find('button[name=ManageHostFeatures]').click(function (e) { e.preventDefault(); _featuresModal.open({ - providerName: 'H', - providerKey: 'H' + providerName: 'H' }); }); }); From fcdcc3e6708feaaf8532a655106f65fe4940fafd Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:39:55 +0300 Subject: [PATCH 08/35] feat: create replaceable-components.service --- .../packages/core/src/lib/services/index.ts | 1 + .../replaceable-components.service.ts | 65 +++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts diff --git a/npm/ng-packs/packages/core/src/lib/services/index.ts b/npm/ng-packs/packages/core/src/lib/services/index.ts index c084fd4808..d3d13de20b 100644 --- a/npm/ng-packs/packages/core/src/lib/services/index.ts +++ b/npm/ng-packs/packages/core/src/lib/services/index.ts @@ -9,6 +9,7 @@ export * from './localization.service'; export * from './multi-tenancy.service'; export * from './profile-state.service'; export * from './profile.service'; +export * from './replaceable-components.service'; export * from './rest.service'; export * from './routes.service'; export * from './session-state.service'; diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts new file mode 100644 index 0000000000..418e608c49 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -0,0 +1,65 @@ +import { Injectable, NgZone } from '@angular/core'; +import { Router } from '@angular/router'; +import { ReplaceableComponents } from '../models/replaceable-components'; +import { BehaviorSubject, Observable } from 'rxjs'; +import { noop } from '../utils/common-utils'; +import { map, filter } from 'rxjs/operators'; + +@Injectable({ providedIn: 'root' }) +export class ReplaceableComponentsService { + private components$ = new BehaviorSubject([]); + + get replaceableComponents$(): Observable { + return this.components$.asObservable(); + } + + get replaceableComponents(): ReplaceableComponents.ReplaceableComponent[] { + return this.components$.value; + } + + constructor(private ngZone: NgZone, private router: Router) {} + + // TODO: Create a shared service for route reload and more + private reloadRoute() { + const { shouldReuseRoute } = this.router.routeReuseStrategy; + const setRouteReuse = (reuse: typeof shouldReuseRoute) => { + this.router.routeReuseStrategy.shouldReuseRoute = reuse; + }; + + setRouteReuse(() => false); + this.router.navigated = false; + + this.ngZone.run(async () => { + await this.router.navigateByUrl(this.router.url).catch(noop); + setRouteReuse(shouldReuseRoute); + }); + } + + add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { + let replaceableComponents = this.components$.value; + + const index = replaceableComponents.findIndex( + component => component.key === replaceableComponent.key, + ); + + if (index > -1) { + replaceableComponents[index] = replaceableComponent; + } else { + replaceableComponents = [...replaceableComponents, replaceableComponent]; + } + + this.components$.next(replaceableComponents); + + if (reload) this.reloadRoute(); + } + + get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent { + return this.replaceableComponents.find(component => component.key === replaceableComponentKey); + } + + get$(replaceableComponentKey: string): Observable { + return this.replaceableComponents$.pipe( + map(components => components.find(component => component.key === replaceableComponentKey)), + ); + } +} From a2f6235225001958db5151dbf4f2217c2c0d8d18 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:40:27 +0300 Subject: [PATCH 09/35] chore: deprecate replaceable-components.state --- .../actions/replaceable-components.actions.ts | 3 +- .../states/replaceable-components.state.ts | 30 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts index 8df6a384ad..a800ab858a 100644 --- a/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts +++ b/npm/ng-packs/packages/core/src/lib/actions/replaceable-components.actions.ts @@ -1,7 +1,8 @@ import { ReplaceableComponents } from '../models/replaceable-components'; +// tslint:disable: max-line-length /** - * @see usage: https://github.com/abpframework/abp/pull/2522#issue-358333183 + * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) */ export class AddReplaceableComponent { static readonly type = '[ReplaceableComponents] Add'; diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 82096c5472..e5caa9f95b 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,11 +1,14 @@ -import { Injectable, NgZone } from '@angular/core'; -import { Router } from '@angular/router'; +import { Injectable } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; import snq from 'snq'; import { AddReplaceableComponent } from '../actions/replaceable-components.actions'; import { ReplaceableComponents } from '../models/replaceable-components'; -import { noop } from '../utils/common-utils'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +// tslint:disable: max-line-length +/** + * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) + */ @State({ name: 'ReplaceableComponentsState', defaults: { replaceableComponents: [] } as ReplaceableComponents.State, @@ -30,23 +33,7 @@ export class ReplaceableComponentsState { return selector; } - constructor(private ngZone: NgZone, private router: Router) {} - - // TODO: Create a shared service for route reload and more - private reloadRoute() { - const { shouldReuseRoute } = this.router.routeReuseStrategy; - const setRouteReuse = (reuse: typeof shouldReuseRoute) => { - this.router.routeReuseStrategy.shouldReuseRoute = reuse; - }; - - setRouteReuse(() => false); - this.router.navigated = false; - - this.ngZone.run(async () => { - await this.router.navigateByUrl(this.router.url).catch(noop); - setRouteReuse(shouldReuseRoute); - }); - } + constructor(private service: ReplaceableComponentsService) {} @Action(AddReplaceableComponent) replaceableComponentsAction( @@ -69,6 +56,7 @@ export class ReplaceableComponentsState { replaceableComponents, }); - if (reload) this.reloadRoute(); + console.log(this.service); + this.service.add(payload, reload); } } From 7e7d86a745343c2e9c377a1c1e3752a45776c6ed Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 10:42:07 +0300 Subject: [PATCH 10/35] feat: implement the new service to project --- .../components/dynamic-layout.component.ts | 9 ++- .../replaceable-route-container.component.ts | 9 ++- .../replaceable-template.directive.ts | 9 ++- .../tests/dynamic-layout.component.spec.ts | 63 +++++++++---------- .../src/lib/providers/styles.provider.ts | 39 ++++++------ 5 files changed, 61 insertions(+), 68 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts index c47b3c02fd..3d6870f577 100644 --- a/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/dynamic-layout.component.ts @@ -1,13 +1,12 @@ import { Component, Injector, Optional, SkipSelf, Type } from '@angular/core'; import { ActivatedRoute, NavigationEnd, Router } from '@angular/router'; -import { Store } from '@ngxs/store'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; import { ReplaceableComponents } from '../models/replaceable-components'; import { LocalizationService } from '../services/localization.service'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { RoutesService } from '../services/routes.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; import { findRoute, getRoutePath } from '../utils/route-utils'; import { TreeNode } from '../utils/tree-utils'; @@ -37,7 +36,7 @@ export class DynamicLayoutComponent { constructor( injector: Injector, private localizationService: LocalizationService, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, @Optional() @SkipSelf() dynamicLayoutComponent: DynamicLayoutComponent, ) { @@ -67,7 +66,7 @@ export class DynamicLayoutComponent { if (!expectedLayout) expectedLayout = eLayoutType.empty; const key = this.layouts.get(expectedLayout); - this.layout = this.getComponent(key).component; + this.layout = this.getComponent(key)?.component; } }); @@ -82,6 +81,6 @@ export class DynamicLayoutComponent { } private getComponent(key: string): ReplaceableComponents.ReplaceableComponent { - return this.store.selectSnapshot(ReplaceableComponentsState.getComponent(key)); + return this.replaceableComponents.get(key); } } diff --git a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts index 04c596d398..6c0dabd511 100644 --- a/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts +++ b/npm/ng-packs/packages/core/src/lib/components/replaceable-route-container.component.ts @@ -1,10 +1,9 @@ import { Component, OnInit, Type } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngxs/store'; import { distinctUntilChanged } from 'rxjs/operators'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; @Component({ selector: 'abp-replaceable-route-container', @@ -22,7 +21,7 @@ export class ReplaceableRouteContainerComponent implements OnInit { constructor( private route: ActivatedRoute, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, ) {} @@ -31,8 +30,8 @@ export class ReplaceableRouteContainerComponent implements OnInit { this.componentKey = (this.route.snapshot.data .replaceableComponent as ReplaceableComponents.RouteData).key; - const component$ = this.store - .select(ReplaceableComponentsState.getComponent(this.componentKey)) + const component$ = this.replaceableComponents + .get$(this.componentKey) .pipe(distinctUntilChanged()); this.subscription.addOne( diff --git a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts index fd9051c64e..489d1afabc 100644 --- a/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts +++ b/npm/ng-packs/packages/core/src/lib/directives/replaceable-template.directive.ts @@ -10,15 +10,14 @@ import { Type, ViewContainerRef, } from '@angular/core'; -import { Store } from '@ngxs/store'; import compare from 'just-compare'; import { Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import snq from 'snq'; import { ABP } from '../models/common'; import { ReplaceableComponents } from '../models/replaceable-components'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; import { SubscriptionService } from '../services/subscription.service'; -import { ReplaceableComponentsState } from '../states/replaceable-components.state'; @Directive({ selector: '[abpReplaceableTemplate]', providers: [SubscriptionService] }) export class ReplaceableTemplateDirective implements OnInit, OnChanges { @@ -45,7 +44,7 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges { private templateRef: TemplateRef, private cfRes: ComponentFactoryResolver, private vcRef: ViewContainerRef, - private store: Store, + private replaceableComponents: ReplaceableComponentsService, private subscription: SubscriptionService, ) { this.context = { @@ -58,8 +57,8 @@ export class ReplaceableTemplateDirective implements OnInit, OnChanges { } ngOnInit() { - const component$ = this.store - .select(ReplaceableComponentsState.getComponent(this.data.componentKey)) + const component$ = this.replaceableComponents + .get$(this.data.componentKey) .pipe( filter( (res = {} as ReplaceableComponents.ReplaceableComponent) => diff --git a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts index 4d5f53a9ba..43ad6f87b1 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/dynamic-layout.component.spec.ts @@ -7,7 +7,11 @@ import { NEVER } from 'rxjs'; import { DynamicLayoutComponent, RouterOutletComponent } from '../components'; import { eLayoutType } from '../enums/common'; import { ABP } from '../models'; -import { ApplicationConfigurationService, RoutesService } from '../services'; +import { + ApplicationConfigurationService, + RoutesService, + ReplaceableComponentsService, +} from '../services'; import { ReplaceableComponentsState } from '../states'; @Component({ @@ -78,33 +82,7 @@ const routes: ABP.Route[] = [ }, ]; -const storeData = { - ReplaceableComponentsState: { - replaceableComponents: [ - { - key: 'Theme.ApplicationLayoutComponent', - component: DummyApplicationLayoutComponent, - }, - { - key: 'Theme.AccountLayoutComponent', - component: DummyAccountLayoutComponent, - }, - { - key: 'Theme.EmptyLayoutComponent', - component: DummyEmptyLayoutComponent, - }, - ], - }, -}; - describe('DynamicLayoutComponent', () => { - const mockActions: Actions = NEVER; - const mockStore = ({ - selectSnapshot() { - return true; - }, - } as unknown) as Store; - const createComponent = createRoutingFactory({ component: RouterOutletComponent, stubsEnabled: false, @@ -113,10 +91,16 @@ describe('DynamicLayoutComponent', () => { providers: [ { provide: RoutesService, - useFactory: () => new RoutesService(mockActions, mockStore), + useFactory: () => + new RoutesService(NEVER, ({ + selectSnapshot() { + return true; + }, + } as unknown) as Store), }, + ReplaceableComponentsService, ], - imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot([ReplaceableComponentsState])], + imports: [RouterModule, DummyLayoutModule, NgxsModule.forRoot()], routes: [ { path: '', component: RouterOutletComponent }, { @@ -163,15 +147,26 @@ describe('DynamicLayoutComponent', () => { }); let spectator: SpectatorRouting; - let store: Store; + let replaceableComponents: ReplaceableComponentsService; beforeEach(async () => { spectator = createComponent(); - store = spectator.inject(Store); + replaceableComponents = spectator.inject(ReplaceableComponentsService); const routesService = spectator.inject(RoutesService); routesService.add(routes); - store.reset(storeData); + replaceableComponents.add({ + key: 'Theme.ApplicationLayoutComponent', + component: DummyApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.AccountLayoutComponent', + component: DummyAccountLayoutComponent, + }); + replaceableComponents.add({ + key: 'Theme.EmptyLayoutComponent', + component: DummyEmptyLayoutComponent, + }); }); it('should handle application layout from parent abp route and display it', async () => { @@ -204,8 +199,8 @@ describe('DynamicLayoutComponent', () => { }); it('should not display any layout when layouts are empty', async () => { - store.reset({ ...storeData, ReplaceableComponentsState: {} }); - + const spy = jest.spyOn(replaceableComponents, 'get'); + spy.mockReturnValue(null); spectator.detectChanges(); spectator.router.navigateByUrl('/withoutLayout'); diff --git a/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts b/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts index ab6ea9cd1e..b823503912 100644 --- a/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts +++ b/npm/ng-packs/packages/theme-basic/src/lib/providers/styles.provider.ts @@ -1,4 +1,4 @@ -import { AddReplaceableComponent, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core'; +import { ReplaceableComponentsService, CONTENT_STRATEGY, DomInsertionService } from '@abp/ng.core'; import { APP_INITIALIZER } from '@angular/core'; import { Store } from '@ngxs/store'; import { AccountLayoutComponent } from '../components/account-layout/account-layout.component'; @@ -11,32 +11,33 @@ export const BASIC_THEME_STYLES_PROVIDERS = [ { provide: APP_INITIALIZER, useFactory: configureStyles, - deps: [DomInsertionService, Store], + deps: [DomInsertionService, ReplaceableComponentsService], multi: true, }, ]; -export function configureStyles(domInsertion: DomInsertionService, store: Store) { +export function configureStyles( + domInsertion: DomInsertionService, + replaceableComponents: ReplaceableComponentsService, +) { return () => { domInsertion.insertContent(CONTENT_STRATEGY.AppendStyleToHead(styles)); - initLayouts(store); + initLayouts(replaceableComponents); }; } -function initLayouts(store: Store) { - store.dispatch([ - new AddReplaceableComponent({ - key: eThemeBasicComponents.ApplicationLayout, - component: ApplicationLayoutComponent, - }), - new AddReplaceableComponent({ - key: eThemeBasicComponents.AccountLayout, - component: AccountLayoutComponent, - }), - new AddReplaceableComponent({ - key: eThemeBasicComponents.EmptyLayout, - component: EmptyLayoutComponent, - }), - ]); +function initLayouts(replaceableComponents: ReplaceableComponentsService) { + replaceableComponents.add({ + key: eThemeBasicComponents.ApplicationLayout, + component: ApplicationLayoutComponent, + }); + replaceableComponents.add({ + key: eThemeBasicComponents.AccountLayout, + component: AccountLayoutComponent, + }); + replaceableComponents.add({ + key: eThemeBasicComponents.EmptyLayout, + component: EmptyLayoutComponent, + }); } From 2104c86eab18ce32a0f74ecd6d3356f4d21e9c3b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 11:07:37 +0300 Subject: [PATCH 11/35] chore: remove console.log --- .../packages/core/src/lib/states/replaceable-components.state.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index e5caa9f95b..61a5fef5f6 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -56,7 +56,6 @@ export class ReplaceableComponentsState { replaceableComponents, }); - console.log(this.service); this.service.add(payload, reload); } } From 89df9fdde0a3f5a33ae6c4c01001ad4b4469c42f Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 12:05:54 +0300 Subject: [PATCH 12/35] test: fix testing errors --- ...laceable-route-container.component.spec.ts | 18 +++++++-------- .../replaceable-template.directive.spec.ts | 22 +++++++++++-------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index f5874bca46..16973c17c8 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -5,6 +5,7 @@ import { Store } from '@ngxs/store'; import { of, Subject, BehaviorSubject } from 'rxjs'; import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component'; import { ReplaceableComponentsState } from '../states'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; @Component({ selector: 'abp-external-component', @@ -30,16 +31,15 @@ const activatedRouteMock = { }; describe('ReplaceableRouteContainerComponent', () => { - const selectResponse = new BehaviorSubject(undefined); - const mockSelect = jest.fn(() => selectResponse); - let spectator: SpectatorHost; + const get$Res = new BehaviorSubject(undefined); + const replaceableComponents = spectator.inject(ReplaceableComponentsService); + const spy = jest.spyOn(replaceableComponents, 'get$'); + spy.mockReturnValue(get$Res as any); + const createHost = createHostFactory({ component: ReplaceableRouteContainerComponent, - providers: [ - { provide: ActivatedRoute, useValue: activatedRouteMock }, - { provide: Store, useValue: { select: mockSelect } }, - ], + providers: [{ provide: ActivatedRoute, useValue: activatedRouteMock }], declarations: [ExternalComponent, DefaultComponent], entryComponents: [DefaultComponent, ExternalComponent], }); @@ -55,11 +55,11 @@ describe('ReplaceableRouteContainerComponent', () => { }); it("should display the external component if it's available in store.", () => { - selectResponse.next({ component: ExternalComponent }); + get$Res.next({ component: ExternalComponent }); spectator.detectChanges(); expect(spectator.query('p')).toHaveText('external'); - selectResponse.next({ component: null }); + get$Res.next({ component: null }); spectator.detectChanges(); expect(spectator.query('p')).toHaveText('default'); }); diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 613508f56b..2923dd473b 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -4,6 +4,8 @@ import { Store } from '@ngxs/store'; import { Subject } from 'rxjs'; import { ReplaceableTemplateDirective } from '../directives'; import { ReplaceableComponents } from '../models'; +import { Router } from '@angular/router'; +import { ReplaceableComponentsService } from '../services/replaceable-components.service'; @Component({ selector: 'abp-default-component', @@ -48,15 +50,17 @@ class ExternalComponent { } describe('ReplaceableTemplateDirective', () => { - const selectResponse = new Subject(); - const mockSelect = jest.fn(() => selectResponse); - let spectator: SpectatorDirective; + const get$Res = new Subject(); + + const replaceableComponents = spectator.inject(ReplaceableComponentsService); + const spy = jest.spyOn(replaceableComponents, 'get$'); + spy.mockReturnValue(get$Res as any); const createDirective = createDirectiveFactory({ directive: ReplaceableTemplateDirective, - providers: [{ provide: Store, useValue: { select: mockSelect } }], declarations: [DefaultComponent, ExternalComponent], entryComponents: [ExternalComponent], + mocks: [Router], }); describe('without external component', () => { @@ -72,7 +76,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); - selectResponse.next(undefined); + get$Res.next(undefined); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); spectator.detectChanges(); @@ -114,7 +118,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); - selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); }); afterEach(() => twoWayChange.mockClear()); @@ -150,7 +154,7 @@ describe('ReplaceableTemplateDirective', () => { const externalComponent = spectator.query(ExternalComponent); spectator.setHostInput({ oneWay: 'test' }); externalComponent.data.inputs.twoWay = true; - selectResponse.next({ component: null, key: 'TestModule.TestComponent' }); + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); spectator.detectChanges(); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); @@ -161,14 +165,14 @@ describe('ReplaceableTemplateDirective', () => { }); it('should reset default component subscriptions', () => { - selectResponse.next({ component: null, key: 'TestModule.TestComponent' }); + get$Res.next({ component: null, key: 'TestModule.TestComponent' }); const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); spectator.detectChanges(); const unsubscribe = jest.fn(() => {}); spectator.directive.defaultComponentSubscriptions.twoWayChange.unsubscribe = unsubscribe; - selectResponse.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); expect(unsubscribe).toHaveBeenCalled(); }); }); From 431d02f177153c79ae6330a45c8c27de3d95a1fa Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Tue, 8 Sep 2020 14:26:45 +0300 Subject: [PATCH 13/35] chore: add deprecation message logger to replaceable-components.state --- .../lib/states/replaceable-components.state.ts | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts index 61a5fef5f6..8b861f144e 100644 --- a/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts +++ b/npm/ng-packs/packages/core/src/lib/states/replaceable-components.state.ts @@ -1,10 +1,19 @@ -import { Injectable } from '@angular/core'; +import { Injectable, isDevMode } from '@angular/core'; import { Action, createSelector, Selector, State, StateContext } from '@ngxs/store'; import snq from 'snq'; import { AddReplaceableComponent } from '../actions/replaceable-components.actions'; import { ReplaceableComponents } from '../models/replaceable-components'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; +function logDeprecationMsg() { + if (isDevMode()) { + console.warn(` + ReplacableComponentsState has been deprecated. Use ReplaceableComponentsService instead. + See the doc https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement + `); + } +} + // tslint:disable: max-line-length /** * @deprecated To be deleted in v4.0. Use ReplaceableComponentsService instead. See the doc (https://docs.abp.io/en/abp/latest/UI/Angular/Component-Replacement) @@ -19,6 +28,7 @@ export class ReplaceableComponentsState { static getAll({ replaceableComponents, }: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent[] { + logDeprecationMsg(); return replaceableComponents || []; } @@ -26,6 +36,7 @@ export class ReplaceableComponentsState { const selector = createSelector( [ReplaceableComponentsState], (state: ReplaceableComponents.State): ReplaceableComponents.ReplaceableComponent => { + logDeprecationMsg(); return snq(() => state.replaceableComponents.find(component => component.key === key)); }, ); @@ -40,6 +51,8 @@ export class ReplaceableComponentsState { { getState, patchState }: StateContext, { payload, reload }: AddReplaceableComponent, ) { + logDeprecationMsg(); + let { replaceableComponents } = getState(); const index = snq( From 9b44c00c1afd16b1499fa3d196858416bbc5987f Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 8 Sep 2020 21:27:58 +0800 Subject: [PATCH 14/35] Use standard name as abp claim type. --- .../Volo/Abp/Security/Claims/AbpClaimTypes.cs | 54 +++++++++---------- .../Authorization/AuthorizationTestBase.cs | 2 +- 2 files changed, 27 insertions(+), 29 deletions(-) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index a797b360ee..e829ecddf3 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -8,71 +8,69 @@ namespace Volo.Abp.Security.Claims /// public static class AbpClaimTypes { - public const string ClaimTypeNamespace = "abp_"; - /// - /// Default: abp_username + /// Default: /// - public static string UserName { get; set; } = ClaimTypeNamespace + "username"; + public static string UserName { get; set; } = ClaimTypes.Name; /// - /// Default: abp_name + /// Default: /// - public static string Name { get; set; } = ClaimTypeNamespace + "name"; + public static string Name { get; set; } = ClaimTypes.GivenName; /// - /// Default: abp_surname + /// Default: /// - public static string SurName { get; set; } = ClaimTypeNamespace + "surname"; + public static string SurName { get; set; } = ClaimTypes.Surname; /// - /// Default: abp_user_id + /// Default: security_stamp /// - public static string UserId { get; set; } = ClaimTypeNamespace + "user_id"; + public static string SecurityStamp { get; set; } = "security_stamp"; /// - /// Default: abp_role + /// Default: /// - public static string Role { get; set; } = ClaimTypeNamespace + "role"; + public static string UserId { get; set; } = ClaimTypes.NameIdentifier; /// - /// Default: abp_security_stamp + /// Default: /// - public static string SecurityStamp { get; set; } = ClaimTypeNamespace + "security_stamp"; + public static string Role { get; set; } = ClaimTypes.Role; /// - /// Default: abp_email + /// Default: /// - public static string Email { get; set; } = ClaimTypeNamespace + "email"; + public static string Email { get; set; } = ClaimTypes.Email; /// - /// Default: abp_email_verified + /// Default: "email_verified". /// - public static string EmailVerified { get; set; } = ClaimTypeNamespace + "email_verified"; + public static string EmailVerified { get; set; } = "email_verified"; /// - /// Default: abp_phone_number + /// Default: "phone_number". /// - public static string PhoneNumber { get; set; } = ClaimTypeNamespace + "phone_number"; + public static string PhoneNumber { get; set; } = "phone_number"; /// - /// Default: abp_phone_number_verified + /// Default: "phone_number_verified". /// - public static string PhoneNumberVerified { get; set; } = ClaimTypeNamespace + "phone_number_verified"; + public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; /// - /// Default: abp_tenant_id + /// Default: "tenantid". /// - public static string TenantId { get; set; } = ClaimTypeNamespace + "tenant_id"; + public static string TenantId { get; set; } = "tenant_id"; /// - /// Default: abp_edition_id + /// Default: "editionid". /// - public static string EditionId { get; set; } = ClaimTypeNamespace + "edition_id"; + public static string EditionId { get; set; } = "edition_id"; /// - /// Default: abp_client_id + /// Default: "client_id". /// - public static string ClientId { get; set; } = ClaimTypeNamespace + "client_id"; + public static string ClientId { get; set; } = "client_id"; } } diff --git a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs index 89f45600f4..5cf9976e46 100644 --- a/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs +++ b/framework/test/Volo.Abp.Authorization.Tests/Volo/Abp/Authorization/AuthorizationTestBase.cs @@ -23,7 +23,7 @@ namespace Volo.Abp.Authorization new Claim(AbpClaimTypes.Role, "MyRole") }; - var identity = new ClaimsIdentity(claims,null, AbpClaimTypes.UserName, AbpClaimTypes.Role); + var identity = new ClaimsIdentity(claims); var claimsPrincipal = new ClaimsPrincipal(identity); var principalAccessor = Substitute.For(); principalAccessor.Principal.Returns(ci => claimsPrincipal); From 69f6caca9171604549da09235883784d2a23f563 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 8 Sep 2020 21:29:32 +0800 Subject: [PATCH 15/35] Update AbpClaimTypes.cs --- .../Volo/Abp/Security/Claims/AbpClaimTypes.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index e829ecddf3..6d6672ec2b 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -59,12 +59,12 @@ namespace Volo.Abp.Security.Claims public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; /// - /// Default: "tenantid". + /// Default: "tenant_id". /// public static string TenantId { get; set; } = "tenant_id"; /// - /// Default: "editionid". + /// Default: "edition_id". /// public static string EditionId { get; set; } = "edition_id"; From 835e5466c0e62377d948ed9deece0be00bc46e36 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Tue, 8 Sep 2020 22:20:24 +0800 Subject: [PATCH 16/35] Refactor --- .../Volo/Abp/Features/AbpFeaturesModule.cs | 1 - .../Abp/Features/HostFeatureValueProvider.cs | 30 ---------- .../FeatureManagement/FeatureAppService.cs | 33 +++++++---- .../Localization/Domain/en.json | 1 - .../Localization/Domain/tr.json | 1 - .../Localization/Domain/zh-Hans.json | 1 - .../Localization/Domain/zh-Hant.json | 1 - .../AbpFeatureManagementDomainModule.cs | 3 - .../HostFeatureManagementProvider.cs | 59 ------------------- .../Pages/TenantManagement/Tenants/Index.js | 2 +- 10 files changed, 21 insertions(+), 111 deletions(-) delete mode 100644 framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs delete mode 100644 modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs index 7db32231e0..f30017feed 100644 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs +++ b/framework/src/Volo.Abp.Features/Volo/Abp/Features/AbpFeaturesModule.cs @@ -26,7 +26,6 @@ namespace Volo.Abp.Features context.Services.Configure(options => { options.ValueProviders.Add(); - options.ValueProviders.Add(); options.ValueProviders.Add(); options.ValueProviders.Add(); }); diff --git a/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs b/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs deleted file mode 100644 index d895813e6f..0000000000 --- a/framework/src/Volo.Abp.Features/Volo/Abp/Features/HostFeatureValueProvider.cs +++ /dev/null @@ -1,30 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.MultiTenancy; - -namespace Volo.Abp.Features -{ - public class HostFeatureValueProvider : FeatureValueProvider - { - public const string ProviderName = "H"; - - public override string Name => ProviderName; - - protected ICurrentTenant CurrentTenant { get; } - - public HostFeatureValueProvider(IFeatureStore featureStore, ICurrentTenant currentTenant) - : base(featureStore) - { - CurrentTenant = currentTenant; - } - - public override async Task GetOrNullAsync(FeatureDefinition feature) - { - if (CurrentTenant.Id.HasValue || !feature.IsAvailableToHost) - { - return null; - } - - return await FeatureStore.GetOrNullAsync(feature.Name, Name, null); - } - } -} diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 5c19df8751..6f0fe4815e 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -28,7 +28,7 @@ namespace Volo.Abp.FeatureManagement public virtual async Task GetAsync([NotNull] string providerName, string providerKey) { - await CheckProviderPolicy(providerName); + await CheckProviderPolicy(providerName, providerKey); var result = new GetFeatureListResultDto { @@ -46,7 +46,10 @@ namespace Volo.Abp.FeatureManagement foreach (var featureDefinition in group.GetFeaturesWithChildren()) { - if (providerName == HostFeatureValueProvider.ProviderName && !featureDefinition.IsAvailableToHost) + if (providerName == TenantFeatureValueProvider.ProviderName && + CurrentTenant.Id == null && + providerKey == null && + !featureDefinition.IsAvailableToHost) { continue; } @@ -78,16 +81,10 @@ namespace Volo.Abp.FeatureManagement public virtual async Task UpdateAsync([NotNull] string providerName, string providerKey, UpdateFeaturesDto input) { - await CheckProviderPolicy(providerName); + await CheckProviderPolicy(providerName, providerKey); foreach (var feature in input.Features) { - var featureDefinition = FeatureDefinitionManager.GetOrNull(feature.Name); - if (featureDefinition == null || (providerName == HostFeatureValueProvider.ProviderName && !featureDefinition.IsAvailableToHost)) - { - throw new UserFriendlyException(L["FeatureNotAvailable"]); - } - await FeatureManager.SetAsync(feature.Name, feature.Value, providerName, providerKey); } } @@ -105,12 +102,22 @@ namespace Volo.Abp.FeatureManagement } } - protected virtual async Task CheckProviderPolicy(string providerName) + protected virtual async Task CheckProviderPolicy(string providerName, string providerKey) { - var policyName = Options.ProviderPolicies.GetOrDefault(providerName); - if (policyName.IsNullOrEmpty()) + string policyName; + if (providerName == TenantFeatureValueProvider.ProviderName) { - throw new AbpException($"No policy defined to get/set permissions for the provider '{policyName}'. Use {nameof(FeatureManagementOptions)} to map the policy."); + policyName = CurrentTenant.Id == null && providerKey == null ? + "FeatureManagement.ManageHostFeatures" : + "AbpTenantManagement.Tenants.ManageFeatures"; + } + else + { + policyName = Options.ProviderPolicies.GetOrDefault(providerName); + if (policyName.IsNullOrEmpty()) + { + throw new AbpException($"No policy defined to get/set permissions for the provider '{policyName}'. Use {nameof(FeatureManagementOptions)} to map the policy."); + } } await AuthorizationService.CheckAsync(policyName); diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json index 6e17e8fc48..26f5dc736b 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/en.json @@ -3,7 +3,6 @@ "texts": { "Features": "Features", "NoFeatureFoundMessage": "There isn't any available feature.", - "FeatureNotAvailable": "Feature not available.", "Permission:FeatureManagement": "Feature management", "Permission:FeatureManagement.ManageHostFeatures": "Manage Host features" } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json index 285b4143cf..8233674933 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/tr.json @@ -3,7 +3,6 @@ "texts": { "Features": "Özellikler", "NoFeatureFoundMessage": "Hiç özellik yok.", - "FeatureNotAvailable": "Özellik bulunamadı.", "Permission:FeatureManagement": "Özellik yönetimi", "Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle" } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json index 2ecdb3997b..9737a9a59c 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hans.json @@ -3,7 +3,6 @@ "texts": { "Features": "功能", "NoFeatureFoundMessage": "没有可用的功能.", - "FeatureNotAvailable": "功能不可用.", "Permission:FeatureManagement": "特性管理", "Permission:FeatureManagement.ManageHostFeatures": "管理Host特性" } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json index 6a8ccd099b..7d68fd5bc2 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain.Shared/Volo/Abp/FeatureManagement/Localization/Domain/zh-Hant.json @@ -3,7 +3,6 @@ "texts": { "Features": "功能", "NoFeatureFoundMessage": "沒有可用的功能.", - "FeatureNotAvailable": "功能不可用.", "Permission:FeatureManagement": "功能管理", "Permission:FeatureManagement.ManageHostFeatures": "管理Host功能" } diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs index b9992309dd..34a0e198c7 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs @@ -18,13 +18,10 @@ namespace Volo.Abp.FeatureManagement Configure(options => { options.Providers.Add(); - options.Providers.Add(); - options.ProviderPolicies[HostFeatureValueProvider.ProviderName] = "FeatureManagement.ManageHostFeatures"; options.Providers.Add(); //TODO: Should be moved to the Tenant Management module options.Providers.Add(); - options.ProviderPolicies[TenantFeatureValueProvider.ProviderName] = "AbpTenantManagement.Tenants.ManageFeatures"; }); Configure(options => diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs deleted file mode 100644 index d40d2dfdec..0000000000 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/HostFeatureManagementProvider.cs +++ /dev/null @@ -1,59 +0,0 @@ -using System.Threading.Tasks; -using Volo.Abp.DependencyInjection; -using Volo.Abp.Features; -using Volo.Abp.MultiTenancy; - -namespace Volo.Abp.FeatureManagement -{ - public class HostFeatureManagementProvider : FeatureManagementProvider, ITransientDependency - { - public override string Name => HostFeatureValueProvider.ProviderName; - - protected ICurrentTenant CurrentTenant { get; } - - public HostFeatureManagementProvider( - IFeatureManagementStore store, - ICurrentTenant currentTenant) - : base(store) - { - CurrentTenant = currentTenant; - } - - public override async Task GetOrNullAsync(FeatureDefinition feature, string providerKey) - { - if (IsHostSide(feature)) - { - return await Store.GetOrNullAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); - } - - return null; - } - - public override async Task SetAsync(FeatureDefinition feature, string value, string providerKey) - { - if (IsHostSide(feature)) - { - await Store.SetAsync(feature.Name, value, Name, NormalizeProviderKey(providerKey)); - } - } - - public override async Task ClearAsync(FeatureDefinition feature, string providerKey) - { - if (IsHostSide(feature)) - { - await Store.DeleteAsync(feature.Name, Name, NormalizeProviderKey(providerKey)); - } - } - - protected override string NormalizeProviderKey(string providerKey) - { - return null; - } - - //TODO: Should throw an ex when there is not in the host side? - protected virtual bool IsHostSide(FeatureDefinition feature) - { - return feature.IsAvailableToHost && CurrentTenant.Id == null; - } - } -} diff --git a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js index ecb2309f36..69bbb0b955 100644 --- a/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js +++ b/modules/tenant-management/src/Volo.Abp.TenantManagement.Web/Pages/TenantManagement/Tenants/Index.js @@ -132,7 +132,7 @@ _$wrapper.find('button[name=ManageHostFeatures]').click(function (e) { e.preventDefault(); _featuresModal.open({ - providerName: 'H' + providerName: 'T' }); }); }); From d4dd369bfa7e30173f72de247597c147d33acef2 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 8 Sep 2020 18:51:02 +0300 Subject: [PATCH 17/35] feat: add DeepPartial to utility types --- npm/ng-packs/packages/core/src/lib/models/utility.ts | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/npm/ng-packs/packages/core/src/lib/models/utility.ts b/npm/ng-packs/packages/core/src/lib/models/utility.ts index e84ad15340..228cab0de3 100644 --- a/npm/ng-packs/packages/core/src/lib/models/utility.ts +++ b/npm/ng-packs/packages/core/src/lib/models/utility.ts @@ -1,4 +1,13 @@ import { TemplateRef, Type } from '@angular/core'; +export type DeepPartial = { + [P in keyof T]?: T[P] extends Serializable ? DeepPartial : T[P]; +}; + +type Serializable = Record< + string | number | symbol, + string | number | boolean | Record +>; + export type InferredInstanceOf = T extends Type ? U : never; export type InferredContextOf = T extends TemplateRef ? U : never; From 3db16151f1570a562050e7e8d76f14b5b78c4fe6 Mon Sep 17 00:00:00 2001 From: Arman Ozak Date: Tue, 8 Sep 2020 18:51:26 +0300 Subject: [PATCH 18/35] feat: add InternalStore utility class --- .../core/src/lib/tests/internal-store.spec.ts | 108 ++++++++++++++++++ .../packages/core/src/lib/utils/index.ts | 1 + .../src/lib/utils/internal-store-utils.ts | 36 ++++++ 3 files changed, 145 insertions(+) create mode 100644 npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts create mode 100644 npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts diff --git a/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts new file mode 100644 index 0000000000..e0eed44eb6 --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/tests/internal-store.spec.ts @@ -0,0 +1,108 @@ +import clone from 'just-clone'; +import { take } from 'rxjs/operators'; +import { DeepPartial } from '../models'; +import { InternalStore } from '../utils'; + +const mockInitialState = { + foo: { + bar: { + baz: [() => {}], + qux: null as Promise, + }, + n: 0, + }, + x: '', + a: false, +}; + +type MockState = typeof mockInitialState; + +const patch1: DeepPartial = { foo: { bar: { baz: [() => {}] } } }; +const expected1: MockState = clone(mockInitialState); +expected1.foo.bar.baz = patch1.foo.bar.baz; + +const patch2: DeepPartial = { foo: { bar: { qux: Promise.resolve() } } }; +const expected2: MockState = clone(mockInitialState); +expected2.foo.bar.qux = patch2.foo.bar.qux; + +const patch3: DeepPartial = { foo: { n: 1 } }; +const expected3: MockState = clone(mockInitialState); +expected3.foo.n = patch3.foo.n; + +const patch4: DeepPartial = { x: 'X' }; +const expected4: MockState = clone(mockInitialState); +expected4.x = patch4.x; + +const patch5: DeepPartial = { a: true }; +const expected5: MockState = clone(mockInitialState); +expected5.a = patch5.a; + +describe('Internal Store', () => { + describe('sliceState', () => { + test.each` + selector | expected + ${(state: MockState) => state.a} | ${mockInitialState.a} + ${(state: MockState) => state.x} | ${mockInitialState.x} + ${(state: MockState) => state.foo.n} | ${mockInitialState.foo.n} + ${(state: MockState) => state.foo.bar} | ${mockInitialState.foo.bar} + ${(state: MockState) => state.foo.bar.baz} | ${mockInitialState.foo.bar.baz} + ${(state: MockState) => state.foo.bar.qux} | ${mockInitialState.foo.bar.qux} + `( + 'should return observable $expected when selector is $selector', + async ({ selector, expected }) => { + const store = new InternalStore(mockInitialState); + + const value = await store + .sliceState(selector) + .pipe(take(1)) + .toPromise(); + + expect(value).toEqual(expected); + }, + ); + }); + + describe('patchState', () => { + test.each` + patch | expected + ${patch1} | ${expected1} + ${patch2} | ${expected2} + ${patch3} | ${expected3} + ${patch4} | ${expected4} + ${patch5} | ${expected5} + `('should set state as $expected when patch is $patch', ({ patch, expected }) => { + const store = new InternalStore(mockInitialState); + + store.patch(patch); + + expect(store.state).toEqual(expected); + }); + }); + + describe('sliceUpdate', () => { + it('should return slice of update$ based on selector', done => { + const store = new InternalStore(mockInitialState); + + const onQux$ = store.sliceUpdate(state => state.foo.bar.qux); + + onQux$.pipe(take(1)).subscribe(value => { + expect(value).toEqual(patch2.foo.bar.qux); + done(); + }); + + store.patch(patch1); + store.patch(patch2); + }); + }); + + describe('reset', () => { + it('should reset state to initialState', () => { + const store = new InternalStore(mockInitialState); + + store.patch(patch1); + store.reset(); + + expect(store.state).toEqual(mockInitialState); + }); + }); +}); diff --git a/npm/ng-packs/packages/core/src/lib/utils/index.ts b/npm/ng-packs/packages/core/src/lib/utils/index.ts index 383261fc07..429a59d0b1 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/index.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/index.ts @@ -6,6 +6,7 @@ export * from './factory-utils'; export * from './form-utils'; export * from './generator-utils'; export * from './initial-utils'; +export * from './internal-store-utils'; export * from './lazy-load-utils'; export * from './localization-utils'; export * from './multi-tenancy-utils'; diff --git a/npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts new file mode 100644 index 0000000000..7a481121ee --- /dev/null +++ b/npm/ng-packs/packages/core/src/lib/utils/internal-store-utils.ts @@ -0,0 +1,36 @@ +import compare from 'just-compare'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { distinctUntilChanged, filter, map } from 'rxjs/operators'; +import { DeepPartial } from '../models'; +import { deepMerge } from './object-utils'; + +export class InternalStore { + private state$ = new BehaviorSubject(this.initialState); + + private update$ = new Subject>(); + + get state() { + return this.state$.value; + } + + sliceState = ( + selector: (state: State) => Slice, + compareFn: (s1: Slice, s2: Slice) => boolean = compare, + ) => this.state$.pipe(map(selector), distinctUntilChanged(compareFn)); + + sliceUpdate = ( + selector: (state: DeepPartial) => Slice, + filterFn = (x: Slice) => x !== undefined, + ) => this.update$.pipe(map(selector), filter(filterFn)); + + constructor(private initialState: State) {} + + patch(state: DeepPartial) { + this.state$.next(deepMerge(this.state, state)); + this.update$.next(state); + } + + reset() { + this.patch(this.initialState); + } +} From 3316f1aa7cd1945a188d9d1752c45cb47f1db4a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 9 Sep 2020 09:01:21 +0300 Subject: [PATCH 19/35] Add link to abp community --- docs/en/Index.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/en/Index.md b/docs/en/Index.md index 5fe44625ad..f1354ec21e 100644 --- a/docs/en/Index.md +++ b/docs/en/Index.md @@ -8,7 +8,19 @@ Explore the navigation menu to deep dive in the documentation. The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) tutorial. -Then you can continue with the [web application development tutorial](Tutorials/Part-1.md). +## Tutorials / Articles + +### Web Application Development + +[Web application development tutorial](Tutorials/Part-1.md) is a complete guide to develop application using the ABP Framework. + +### ABP Community Articles + +See also the [ABP Community](https://community.abp.io/) articles. + +## Samples + +See the [sample projects](Samples/Index.md) built with the ABP Framework. ## Source Code From 841af8028e38b3e18524330ec34736ddb3a0d112 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 9 Sep 2020 09:04:07 +0300 Subject: [PATCH 20/35] Update Index.md --- docs/en/Index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/en/Index.md b/docs/en/Index.md index f1354ec21e..949c04eb24 100644 --- a/docs/en/Index.md +++ b/docs/en/Index.md @@ -6,13 +6,13 @@ Explore the navigation menu to deep dive in the documentation. ## Getting Started -The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) tutorial. +The easiest way to start a new web application with the ABP Framework is to use the [getting started](Getting-Started.md) guide. ## Tutorials / Articles ### Web Application Development -[Web application development tutorial](Tutorials/Part-1.md) is a complete guide to develop application using the ABP Framework. +[Web application development tutorial](Tutorials/Part-1.md) is a complete tutorial to develop a full stack application using the ABP Framework. ### ABP Community Articles From 7653b020014147ea563d38dc44c8746caf829cb4 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 10:29:52 +0300 Subject: [PATCH 21/35] feat: implement internal store to ReplaceableComponentsService --- .../replaceable-components.service.ts | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 418e608c49..3f2b2cd17c 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -4,20 +4,27 @@ import { ReplaceableComponents } from '../models/replaceable-components'; import { BehaviorSubject, Observable } from 'rxjs'; import { noop } from '../utils/common-utils'; import { map, filter } from 'rxjs/operators'; +import { InternalStore } from '../utils/internal-store-utils'; @Injectable({ providedIn: 'root' }) export class ReplaceableComponentsService { - private components$ = new BehaviorSubject([]); + private state: InternalStore; get replaceableComponents$(): Observable { - return this.components$.asObservable(); + return this.state.sliceState(state => state); } get replaceableComponents(): ReplaceableComponents.ReplaceableComponent[] { - return this.components$.value; + return this.state.state; } - constructor(private ngZone: NgZone, private router: Router) {} + get onUpdate$(): Observable { + return this.state.sliceUpdate(state => state); + } + + constructor(private ngZone: NgZone, private router: Router) { + this.state = new InternalStore([]); + } // TODO: Create a shared service for route reload and more private reloadRoute() { @@ -36,7 +43,7 @@ export class ReplaceableComponentsService { } add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { - let replaceableComponents = this.components$.value; + let replaceableComponents = this.state.state; const index = replaceableComponents.findIndex( component => component.key === replaceableComponent.key, @@ -48,7 +55,7 @@ export class ReplaceableComponentsService { replaceableComponents = [...replaceableComponents, replaceableComponent]; } - this.components$.next(replaceableComponents); + this.state.patch(replaceableComponents); if (reload) this.reloadRoute(); } From 1661d58a4aa2dac5e4263e24504f37ee8e1ec9ba Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 11:16:03 +0300 Subject: [PATCH 22/35] test: fix some errors --- ...eplaceable-route-container.component.spec.ts | 17 ++++++++--------- .../replaceable-template.directive.spec.ts | 11 +++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts index 16973c17c8..9b8b62ef46 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-route-container.component.spec.ts @@ -1,10 +1,8 @@ -import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; import { Component } from '@angular/core'; -import { ActivatedRoute } from '@angular/router'; -import { Store } from '@ngxs/store'; -import { of, Subject, BehaviorSubject } from 'rxjs'; +import { ActivatedRoute, Router } from '@angular/router'; +import { createHostFactory, SpectatorHost } from '@ngneat/spectator/jest'; +import { BehaviorSubject } from 'rxjs'; import { ReplaceableRouteContainerComponent } from '../components/replaceable-route-container.component'; -import { ReplaceableComponentsState } from '../states'; import { ReplaceableComponentsService } from '../services/replaceable-components.service'; @Component({ @@ -33,15 +31,16 @@ const activatedRouteMock = { describe('ReplaceableRouteContainerComponent', () => { let spectator: SpectatorHost; const get$Res = new BehaviorSubject(undefined); - const replaceableComponents = spectator.inject(ReplaceableComponentsService); - const spy = jest.spyOn(replaceableComponents, 'get$'); - spy.mockReturnValue(get$Res as any); const createHost = createHostFactory({ component: ReplaceableRouteContainerComponent, - providers: [{ provide: ActivatedRoute, useValue: activatedRouteMock }], + providers: [ + { provide: ActivatedRoute, useValue: activatedRouteMock }, + { provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }, + ], declarations: [ExternalComponent, DefaultComponent], entryComponents: [DefaultComponent, ExternalComponent], + mocks: [Router], }); beforeEach(() => { diff --git a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts index 2923dd473b..2c1432e525 100644 --- a/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts +++ b/npm/ng-packs/packages/core/src/lib/tests/replaceable-template.directive.spec.ts @@ -1,7 +1,7 @@ import { Component, EventEmitter, Inject, Input, OnInit, Optional, Output } from '@angular/core'; import { createDirectiveFactory, SpectatorDirective } from '@ngneat/spectator/jest'; import { Store } from '@ngxs/store'; -import { Subject } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { ReplaceableTemplateDirective } from '../directives'; import { ReplaceableComponents } from '../models'; import { Router } from '@angular/router'; @@ -51,16 +51,14 @@ class ExternalComponent { describe('ReplaceableTemplateDirective', () => { let spectator: SpectatorDirective; - const get$Res = new Subject(); + const get$Res = new BehaviorSubject(undefined); - const replaceableComponents = spectator.inject(ReplaceableComponentsService); - const spy = jest.spyOn(replaceableComponents, 'get$'); - spy.mockReturnValue(get$Res as any); const createDirective = createDirectiveFactory({ directive: ReplaceableTemplateDirective, declarations: [DefaultComponent, ExternalComponent], entryComponents: [ExternalComponent], mocks: [Router], + providers: [{ provide: ReplaceableComponentsService, useValue: { get$: () => get$Res } }], }); describe('without external component', () => { @@ -76,7 +74,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); - get$Res.next(undefined); + const component = spectator.query(DefaultComponent); spectator.directive.context.initTemplate(component); spectator.detectChanges(); @@ -118,6 +116,7 @@ describe('ReplaceableTemplateDirective', () => { `, { hostProps: { oneWay: { label: 'Test' }, twoWay: false, twoWayChange, someOutput } }, ); + get$Res.next({ component: ExternalComponent, key: 'TestModule.TestComponent' }); }); From 1246a419926671ad03bd71cd79c449533a6cd53b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 11:51:50 +0300 Subject: [PATCH 23/35] refactor: change a variable name --- .../lib/services/replaceable-components.service.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 3f2b2cd17c..486f1bcd44 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -8,22 +8,22 @@ import { InternalStore } from '../utils/internal-store-utils'; @Injectable({ providedIn: 'root' }) export class ReplaceableComponentsService { - private state: InternalStore; + private store: InternalStore; get replaceableComponents$(): Observable { - return this.state.sliceState(state => state); + return this.store.sliceState(state => state); } get replaceableComponents(): ReplaceableComponents.ReplaceableComponent[] { - return this.state.state; + return this.store.state; } get onUpdate$(): Observable { - return this.state.sliceUpdate(state => state); + return this.store.sliceUpdate(state => state); } constructor(private ngZone: NgZone, private router: Router) { - this.state = new InternalStore([]); + this.store = new InternalStore([]); } // TODO: Create a shared service for route reload and more @@ -43,7 +43,7 @@ export class ReplaceableComponentsService { } add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { - let replaceableComponents = this.state.state; + let replaceableComponents = this.store.state; const index = replaceableComponents.findIndex( component => component.key === replaceableComponent.key, @@ -55,7 +55,7 @@ export class ReplaceableComponentsService { replaceableComponents = [...replaceableComponents, replaceableComponent]; } - this.state.patch(replaceableComponents); + this.store.patch(replaceableComponents); if (reload) this.reloadRoute(); } From 77156e8f0d5293107fc6d17158799b0e05a394fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Halil=20=C4=B0brahim=20Kalkan?= Date: Wed, 9 Sep 2020 11:54:08 +0300 Subject: [PATCH 24/35] Resolves #5346: Add global.json to specify the dotnet core version --- global.json | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 global.json diff --git a/global.json b/global.json new file mode 100644 index 0000000000..6156170057 --- /dev/null +++ b/global.json @@ -0,0 +1,6 @@ +{ + "sdk": { + "version": "3.1.102", + "rollForward": "latestFeature" + } +} \ No newline at end of file From 1c55f1cec87fa8c18db6044136acecef59d8176b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 12:06:14 +0300 Subject: [PATCH 25/35] chore: upgrade ngxs to latest version --- npm/ng-packs/package.json | 10 +++---- npm/ng-packs/packages/core/package.json | 6 ++-- npm/ng-packs/yarn.lock | 37 +++++++++++++++++++------ 3 files changed, 37 insertions(+), 16 deletions(-) diff --git a/npm/ng-packs/package.json b/npm/ng-packs/package.json index 32ff245f95..7d749a8d9c 100644 --- a/npm/ng-packs/package.json +++ b/npm/ng-packs/package.json @@ -55,11 +55,11 @@ "@ng-bootstrap/ng-bootstrap": "^7.0.0", "@ngneat/spectator": "^5.13.0", "@ngx-validate/core": "^0.0.11", - "@ngxs/devtools-plugin": "^3.6.2", - "@ngxs/logger-plugin": "^3.6.2", - "@ngxs/router-plugin": "^3.6.2", - "@ngxs/storage-plugin": "^3.6.2", - "@ngxs/store": "^3.6.2", + "@ngxs/devtools-plugin": "^3.7.0", + "@ngxs/logger-plugin": "^3.7.0", + "@ngxs/router-plugin": "^3.7.0", + "@ngxs/storage-plugin": "^3.7.0", + "@ngxs/store": "^3.7.0", "@schematics/angular": "~10.0.5", "@swimlane/ngx-datatable": "^17.1.0", "@types/jest": "^25.2.3", diff --git a/npm/ng-packs/packages/core/package.json b/npm/ng-packs/packages/core/package.json index 06eeb447a5..c3531c750b 100644 --- a/npm/ng-packs/packages/core/package.json +++ b/npm/ng-packs/packages/core/package.json @@ -9,9 +9,9 @@ "dependencies": { "@abp/utils": "^3.1.0", "@angular/localize": "~10.0.10", - "@ngxs/router-plugin": "^3.6.2", - "@ngxs/storage-plugin": "^3.6.2", - "@ngxs/store": "^3.6.2", + "@ngxs/router-plugin": "^3.7.0", + "@ngxs/storage-plugin": "^3.7.0", + "@ngxs/store": "^3.7.0", "angular-oauth2-oidc": "^10.0.0", "just-clone": "^3.1.0", "just-compare": "^1.3.0", diff --git a/npm/ng-packs/yarn.lock b/npm/ng-packs/yarn.lock index 512eb7d30b..1af94b7c24 100644 --- a/npm/ng-packs/yarn.lock +++ b/npm/ng-packs/yarn.lock @@ -2338,17 +2338,17 @@ dependencies: tslib "^1.9.0" -"@ngxs/devtools-plugin@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@ngxs/devtools-plugin/-/devtools-plugin-3.6.2.tgz#aa0a4835f90fb905951d7712dc3ce508cbc15a2c" - integrity sha512-0UUZlpXgEtrHoWNeVQXEvUyC6pW8nTACpqJgecuBjYJMa5imCCUSXdrpear8ztJuWwpLqMUSGk5cICNhKqK59g== +"@ngxs/devtools-plugin@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@ngxs/devtools-plugin/-/devtools-plugin-3.7.0.tgz#5b6b3e63411da527fcee1e8280714e1b95a838c7" + integrity sha512-jjq91AbnnhzSm4QRUd7M0Y+HnUYnsSTVwUy8c1BsH8rGQ9c77xIveQaF2UBngPaDKQzTyzdVO2rV7twy3W2/fg== dependencies: tslib "^1.9.0" -"@ngxs/logger-plugin@^3.6.2": - version "3.6.2" - resolved "https://registry.yarnpkg.com/@ngxs/logger-plugin/-/logger-plugin-3.6.2.tgz#1a8132a0b1eb95cad79736120fd50a331fe7347b" - integrity sha512-qF4ci9Ua7jL7r5EhZBEb0JB0HuGsTgFShK3OVTYSXU9+XfSb+5vrDLtmTshmBqQy6dWCd+2xq4LzBgc8IKMRuQ== +"@ngxs/logger-plugin@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@ngxs/logger-plugin/-/logger-plugin-3.7.0.tgz#a27d6bb27360fc2677773c57868740b8cd33cc41" + integrity sha512-yr3NXXJEqJnxSuKiHaTbNmoprKKcrIS1PUIlOm9nvKsOmyrGskxu+MYCzUifemXDIyXSP2u0OBNPrVUumcXyhg== dependencies: tslib "^1.9.0" @@ -2359,6 +2359,13 @@ dependencies: tslib "^1.9.0" +"@ngxs/router-plugin@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@ngxs/router-plugin/-/router-plugin-3.7.0.tgz#9d0595d8ec12e6143eb9f2f726fcc7cdec8cd635" + integrity sha512-nmArryNIBLWqIKLWelOQhgxEC3evLPDNDkE+PEYvwoASC4NP5rHIqJv0borJAWBCwo10t5wPeT1417vBQex5aQ== + dependencies: + tslib "^1.9.0" + "@ngxs/storage-plugin@^3.6.2": version "3.6.2" resolved "https://registry.yarnpkg.com/@ngxs/storage-plugin/-/storage-plugin-3.6.2.tgz#6fe2168891382c635406df02308f67b585efc60a" @@ -2366,6 +2373,13 @@ dependencies: tslib "^1.9.0" +"@ngxs/storage-plugin@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@ngxs/storage-plugin/-/storage-plugin-3.7.0.tgz#3a0bc75f5dbba17a18e37b69cfde5d470cfceb45" + integrity sha512-j5rGYfhi1S+sAky956DIs+6AYP9FMBWL2Uz+omKBS/i42mjhyD26UfApORjwCOyW5PCb4Tq3B14ZPxyAaSi/OA== + dependencies: + tslib "^1.9.0" + "@ngxs/store@^3.6.2": version "3.6.2" resolved "https://registry.yarnpkg.com/@ngxs/store/-/store-3.6.2.tgz#cfba63dc1e5bd422e89e54b3332cd69818510624" @@ -2373,6 +2387,13 @@ dependencies: tslib "^1.9.0" +"@ngxs/store@^3.7.0": + version "3.7.0" + resolved "https://registry.yarnpkg.com/@ngxs/store/-/store-3.7.0.tgz#e46387219dae610c685accc119ae42e351afcaa4" + integrity sha512-w9fG/DhKBgH1VJMKSoeNW9x9ycD9/Dzy+VkpFD8Jv0JBNX0MRgP+5KQQe3ZKwnJ+7S0UV/99JvJaWgxc/WOvPw== + dependencies: + tslib "^1.9.0" + "@nodelib/fs.scandir@2.1.3": version "2.1.3" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.3.tgz#3a582bdb53804c6ba6d146579c46e52130cf4a3b" From 0c5fbee1c38a58cebd82acb7e21b059b39f0d3ba Mon Sep 17 00:00:00 2001 From: Mehmet Erim <34455572+mehmet-erim@users.noreply.github.com> Date: Wed, 9 Sep 2020 12:40:17 +0300 Subject: [PATCH 26/35] Update npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Levent Arman Özak --- .../core/src/lib/services/replaceable-components.service.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 486f1bcd44..fd04c1c511 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -43,7 +43,7 @@ export class ReplaceableComponentsService { } add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { - let replaceableComponents = this.store.state; + const replaceableComponents = [...this.store.state]; const index = replaceableComponents.findIndex( component => component.key === replaceableComponent.key, @@ -52,7 +52,7 @@ export class ReplaceableComponentsService { if (index > -1) { replaceableComponents[index] = replaceableComponent; } else { - replaceableComponents = [...replaceableComponents, replaceableComponent]; + replaceableComponents.push(replaceableComponent); } this.store.patch(replaceableComponents); From 8edcac597a412ea9de38544801d2a872fd644d1b Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 12:46:42 +0300 Subject: [PATCH 27/35] refactor: move reloadRoute function to route-utils.ts --- .../services/replaceable-components.service.ts | 17 ++--------------- .../packages/core/src/lib/utils/route-utils.ts | 17 +++++++++++++++++ 2 files changed, 19 insertions(+), 15 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index fd04c1c511..7dfd1e5062 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -5,6 +5,7 @@ import { BehaviorSubject, Observable } from 'rxjs'; import { noop } from '../utils/common-utils'; import { map, filter } from 'rxjs/operators'; import { InternalStore } from '../utils/internal-store-utils'; +import { reloadRoute } from '../utils/route-utils'; @Injectable({ providedIn: 'root' }) export class ReplaceableComponentsService { @@ -27,20 +28,6 @@ export class ReplaceableComponentsService { } // TODO: Create a shared service for route reload and more - private reloadRoute() { - const { shouldReuseRoute } = this.router.routeReuseStrategy; - const setRouteReuse = (reuse: typeof shouldReuseRoute) => { - this.router.routeReuseStrategy.shouldReuseRoute = reuse; - }; - - setRouteReuse(() => false); - this.router.navigated = false; - - this.ngZone.run(async () => { - await this.router.navigateByUrl(this.router.url).catch(noop); - setRouteReuse(shouldReuseRoute); - }); - } add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { const replaceableComponents = [...this.store.state]; @@ -57,7 +44,7 @@ export class ReplaceableComponentsService { this.store.patch(replaceableComponents); - if (reload) this.reloadRoute(); + if (reload) reloadRoute(this.router, this.ngZone); } get(replaceableComponentKey: string): ReplaceableComponents.ReplaceableComponent { diff --git a/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts b/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts index a749090695..388e76cbf8 100644 --- a/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts +++ b/npm/ng-packs/packages/core/src/lib/utils/route-utils.ts @@ -2,6 +2,8 @@ import { PRIMARY_OUTLET, Router, UrlSegmentGroup } from '@angular/router'; import { ABP } from '../models/common'; import { RoutesService } from '../services/routes.service'; import { TreeNode } from './tree-utils'; +import { noop } from './common-utils'; +import { NgZone } from '@angular/core'; export function findRoute(routes: RoutesService, path: string): TreeNode { const node = routes.find(route => route.path === path); @@ -23,3 +25,18 @@ export function getRoutePath(router: Router, url = router.url) { return '/' + (primaryGroup || emptyGroup).segments.map(({ path }) => path).join('/'); } + +export function reloadRoute(router: Router, ngZone: NgZone) { + const { shouldReuseRoute } = router.routeReuseStrategy; + const setRouteReuse = (reuse: typeof shouldReuseRoute) => { + router.routeReuseStrategy.shouldReuseRoute = reuse; + }; + + setRouteReuse(() => false); + router.navigated = false; + + ngZone.run(async () => { + await router.navigateByUrl(router.url).catch(noop); + setRouteReuse(shouldReuseRoute); + }); +} From f34057593db2280ed5dd705a7ee69793160fe435 Mon Sep 17 00:00:00 2001 From: Mehmet Erim <34455572+mehmet-erim@users.noreply.github.com> Date: Wed, 9 Sep 2020 12:53:39 +0300 Subject: [PATCH 28/35] Update npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts Co-authored-by: Bunyamin Coskuner --- .../core/src/lib/services/replaceable-components.service.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts index 7dfd1e5062..674db83aea 100644 --- a/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts +++ b/npm/ng-packs/packages/core/src/lib/services/replaceable-components.service.ts @@ -27,8 +27,6 @@ export class ReplaceableComponentsService { this.store = new InternalStore([]); } - // TODO: Create a shared service for route reload and more - add(replaceableComponent: ReplaceableComponents.ReplaceableComponent, reload?: boolean): void { const replaceableComponents = [...this.store.state]; From 8bbbb24507bb0cbfb909683f3b444d3f1195bcf8 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 13:04:31 +0300 Subject: [PATCH 29/35] docs: add ReplaceableComponentsService example to Component-Replacement.md --- docs/en/UI/Angular/Component-Replacement.md | 77 ++++++++------------- 1 file changed, 28 insertions(+), 49 deletions(-) diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md index fd34fcbae7..d79a67829c 100644 --- a/docs/en/UI/Angular/Component-Replacement.md +++ b/docs/en/UI/Angular/Component-Replacement.md @@ -8,27 +8,22 @@ The reason that you **can replace** but **cannot customize** default ABP compone Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`. -Then, open the `app.component.ts` and dispatch the `AddReplaceableComponent` action to replace your component with an ABP component as shown below: +Then, open the `app.component.ts` and execute the `add` method of `ReplaceableComponentsService` to replace your component with an ABP component as shown below: ```js -import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent action +import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { eIdentityComponents } from '@abp/ng.identity'; // imported eIdentityComponents enum -import { Store } from '@ngxs/store'; // imported Store //... @Component(/* component metadata */) export class AppComponent { constructor( - private store: Store // injected Store - ) - { - // dispatched the AddReplaceableComponent action - this.store.dispatch( - new AddReplaceableComponent({ - component: YourNewRoleComponent, - key: eIdentityComponents.Roles, - }), - ); + private replaceableComponents: ReplaceableComponentsService, // injected the service + ) { + this.replaceableComponents.add({ + component: YourNewRoleComponent, + key: eIdentityComponents.Roles, + }); } } ``` @@ -59,28 +54,24 @@ Add the following code in your layout template (`my-layout.component.html`) wher Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent +import { ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents enum for component keys -import { Store } from '@ngxs/store'; // imported Store import { MyApplicationLayoutComponent } from './my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent @Component(/* component metadata */) export class AppComponent { constructor( - private store: Store, // injected Store + private replaceableComponents: ReplaceableComponentsService, // injected the service ) { - // dispatched the AddReplaceableComponent action - this.store.dispatch( - new AddReplaceableComponent({ - component: MyApplicationLayoutComponent, - key: eThemeBasicComponents.ApplicationLayout, - }), - ); + this.replaceableComponents.add({ + component: MyApplicationLayoutComponent, + key: eThemeBasicComponents.ApplicationLayout, + }); } } ``` -> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the AddReplaceableComponent action as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of AddReplaceableComponent is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated. +> If you like to replace a layout component at runtime (e.g: changing the layout by pressing a button), pass the second parameter of the `add` method of `ReplaceableComponentsService` as true. DynamicLayoutComponent loads content using a router-outlet. When the second parameter of the `add` method is true, the route will be refreshed, so use it with caution. Your component state will be gone and any initiation logic (including HTTP requests) will be repeated. ### Layout Components @@ -123,26 +114,22 @@ export class LogoComponent {} Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent -import { Store } from '@ngxs/store'; // imported Store +import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { LogoComponent } from './logo/logo.component'; // imported NavItemsComponent import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents //... @Component(/* component metadata */) export class AppComponent implements OnInit { - constructor(..., private store: Store) {} // injected Store + constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService ngOnInit() { //... - // added dispatch - this.store.dispatch( - new AddReplaceableComponent({ + this.replaceableComponents.add({ component: LogoComponent, key: eThemeBasicComponents.Logo, - }), - ); + }); } } ``` @@ -166,7 +153,7 @@ yarn ng generate component routes --entryComponent Open the generated `routes.component.ts` in `src/app/routes` folder and replace its content with the following: ```js -import { ABP, ReplaceableComponents } from '@abp/ng.core'; +import { ABP } from '@abp/ng.core'; import { Component, HostBinding, @@ -309,26 +296,22 @@ Open the generated `routes.component.html` in `src/app/routes` folder and replac Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent -import { Store } from '@ngxs/store'; // imported Store +import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { RoutesComponent } from './routes/routes.component'; // imported NavItemsComponent import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents //... @Component(/* component metadata */) export class AppComponent implements OnInit { - constructor(..., private store: Store) {} // injected Store + constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService ngOnInit() { //... - // added dispatch - this.store.dispatch( - new AddReplaceableComponent({ + this.replaceableComponents.add({ component: RoutesComponent, key: eThemeBasicComponents.Routes, - }), - ); + }); } } ``` @@ -511,26 +494,22 @@ Open the generated `nav-items.component.html` in `src/app/nav-items` folder and Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent -import { Store } from '@ngxs/store'; // imported Store +import { ..., ReplaceableComponentsService } from '@abp/ng.core'; // imported ReplaceableComponentsService import { NavItemsComponent } from './nav-items/nav-items.component'; // imported NavItemsComponent import { eThemeBasicComponents } from '@abp/ng.theme.basic'; // imported eThemeBasicComponents //... @Component(/* component metadata */) export class AppComponent implements OnInit { - constructor(..., private store: Store) {} // injected Store + constructor(..., private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService ngOnInit() { //... - // added dispatch - this.store.dispatch( - new AddReplaceableComponent({ + this.replaceableComponents.add({ component: NavItemsComponent, key: eThemeBasicComponents.NavItems, - }), - ); + }); } } ``` From c3be8dbdd5a77b7accf9c23a43cb7c1cf8f9c0c2 Mon Sep 17 00:00:00 2001 From: mehmet-erim Date: Wed, 9 Sep 2020 13:05:11 +0300 Subject: [PATCH 30/35] docs: use ReplaceableComponentsService in permissions modal replacement modal --- .../Permission-Management-Component-Replacement.md | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/docs/en/UI/Angular/Permission-Management-Component-Replacement.md b/docs/en/UI/Angular/Permission-Management-Component-Replacement.md index acdfffb948..9ae712cedd 100644 --- a/docs/en/UI/Angular/Permission-Management-Component-Replacement.md +++ b/docs/en/UI/Angular/Permission-Management-Component-Replacement.md @@ -473,24 +473,20 @@ Open the generated `permission-management.component.html` in `src/app/permission Open `app.component.ts` in `src/app` folder and modify it as shown below: ```js -import { AddReplaceableComponent } from '@abp/ng.core'; +import { ReplaceableComponentsService } from '@abp/ng.core'; import { ePermissionManagementComponents } from '@abp/ng.permission-management'; import { Component, OnInit } from '@angular/core'; -import { Store } from '@ngxs/store'; import { PermissionManagementComponent } from './permission-management/permission-management.component'; //... export class AppComponent implements OnInit { - constructor(private store: Store) {} // injected store + constructor(private replaceableComponents: ReplaceableComponentsService) {} // injected ReplaceableComponentsService ngOnInit() { - // added dispatching the AddReplaceableComponent action - this.store.dispatch( - new AddReplaceableComponent({ + this.replaceableComponents.add({ component: PermissionManagementComponent, key: ePermissionManagementComponents.PermissionManagement, - }) - ); + }); } } ``` From 2a26888ee6a38da52f4b877f5a675a02a85b0e42 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 9 Sep 2020 18:11:11 +0800 Subject: [PATCH 31/35] Update build-and-test.yml --- .github/workflows/build-and-test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml index 395f88351a..366972c165 100644 --- a/.github/workflows/build-and-test.yml +++ b/.github/workflows/build-and-test.yml @@ -17,7 +17,7 @@ jobs: - uses: actions/checkout@v2 - uses: actions/setup-dotnet@master with: - dotnet-version: 3.1.100 + dotnet-version: 3.1.102 - name: Build All run: .\build-all.ps1 From c0789c263c26f408fd1a2b97b1508826f2c020fc Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 9 Sep 2020 20:09:31 +0800 Subject: [PATCH 32/35] Revert renaming claim name. --- .../Volo/Abp/Security/Claims/AbpClaimTypes.cs | 13 ++++--------- .../Volo/Abp/Identity/AbpIdentityDomainModule.cs | 1 - 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs index 6d6672ec2b..271d5d3d94 100644 --- a/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs +++ b/framework/src/Volo.Abp.Security/Volo/Abp/Security/Claims/AbpClaimTypes.cs @@ -23,11 +23,6 @@ namespace Volo.Abp.Security.Claims /// public static string SurName { get; set; } = ClaimTypes.Surname; - /// - /// Default: security_stamp - /// - public static string SecurityStamp { get; set; } = "security_stamp"; - /// /// Default: /// @@ -59,14 +54,14 @@ namespace Volo.Abp.Security.Claims public static string PhoneNumberVerified { get; set; } = "phone_number_verified"; /// - /// Default: "tenant_id". + /// Default: "tenantid". /// - public static string TenantId { get; set; } = "tenant_id"; + public static string TenantId { get; set; } = "tenantid"; /// - /// Default: "edition_id". + /// Default: "editionid". /// - public static string EditionId { get; set; } = "edition_id"; + public static string EditionId { get; set; } = "editionid"; /// /// Default: "client_id". diff --git a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs index 685f66a9ea..6feddd125b 100644 --- a/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs +++ b/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpIdentityDomainModule.cs @@ -51,7 +51,6 @@ namespace Volo.Abp.Identity options.ClaimsIdentity.UserIdClaimType = AbpClaimTypes.UserId; options.ClaimsIdentity.UserNameClaimType = AbpClaimTypes.UserName; options.ClaimsIdentity.RoleClaimType = AbpClaimTypes.Role; - options.ClaimsIdentity.SecurityStampClaimType = AbpClaimTypes.SecurityStamp; }); AddAbpIdentityOptionsFactory(context.Services); From 747d2aadc8a7053a167043bbf04856111dc4a7c5 Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 9 Sep 2020 20:36:32 +0800 Subject: [PATCH 33/35] Update OrganizationUnitRepository_Tests.cs --- .../Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs index d60611b2b6..dc134f9974 100644 --- a/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs +++ b/modules/identity/test/Volo.Abp.Identity.MongoDB.Tests/Volo/Abp/Identity/MongoDB/OrganizationUnitRepository_Tests.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; using System.Text; +using Xunit; namespace Volo.Abp.Identity.MongoDB { + [Collection(MongoTestCollection.Name)] public class OrganizationUnitRepository_Tests : OrganizationUnitRepository_Tests { } From 67e0eb599d27fb96755721f234554f85a4ca2cf5 Mon Sep 17 00:00:00 2001 From: liangshiwei Date: Wed, 9 Sep 2020 20:42:27 +0800 Subject: [PATCH 34/35] Update HardDelete_Tests.cs --- .../Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs index 66c6e58b5c..699e6de5a3 100644 --- a/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs +++ b/framework/test/Volo.Abp.MongoDB.Tests/Volo/Abp/MongoDB/DataFiltering/HardDelete_Tests.cs @@ -2,9 +2,11 @@ using System.Collections.Generic; using System.Text; using Volo.Abp.TestApp.Testing; +using Xunit; namespace Volo.Abp.MongoDB.DataFiltering { + [Collection(MongoTestCollection.Name)] public class HardDelete_Tests : HardDelete_Tests { } From 3fa367455213636f564435862233788d0f535eec Mon Sep 17 00:00:00 2001 From: maliming <6908465+maliming@users.noreply.github.com> Date: Wed, 9 Sep 2020 22:43:56 +0800 Subject: [PATCH 35/35] Refactor. --- .../Volo/Abp/FeatureManagement/FeatureAppService.cs | 6 ++---- .../FeatureManagement/AbpFeatureManagementDomainModule.cs | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs index 6f0fe4815e..bfb6e65ea8 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs @@ -105,11 +105,9 @@ namespace Volo.Abp.FeatureManagement protected virtual async Task CheckProviderPolicy(string providerName, string providerKey) { string policyName; - if (providerName == TenantFeatureValueProvider.ProviderName) + if (providerName == TenantFeatureValueProvider.ProviderName && CurrentTenant.Id == null && providerKey == null ) { - policyName = CurrentTenant.Id == null && providerKey == null ? - "FeatureManagement.ManageHostFeatures" : - "AbpTenantManagement.Tenants.ManageFeatures"; + policyName = "FeatureManagement.ManageHostFeatures"; } else { diff --git a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs index 34a0e198c7..38a2fa3c56 100644 --- a/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs +++ b/modules/feature-management/src/Volo.Abp.FeatureManagement.Domain/Volo/Abp/FeatureManagement/AbpFeatureManagementDomainModule.cs @@ -22,6 +22,7 @@ namespace Volo.Abp.FeatureManagement //TODO: Should be moved to the Tenant Management module options.Providers.Add(); + options.ProviderPolicies[TenantFeatureValueProvider.ProviderName] = "AbpTenantManagement.Tenants.ManageFeatures"; }); Configure(options =>