Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature Management Modal improvements #5361

Merged
merged 4 commits into from
Sep 16, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using Volo.Abp.FeatureManagement.Localization;
using Volo.Abp.Localization;
using Volo.Abp.Localization.ExceptionHandling;
using Volo.Abp.Modularity;
using Volo.Abp.Validation;
using Volo.Abp.Validation.Localization;
Expand Down Expand Up @@ -27,6 +28,11 @@ public override void ConfigureServices(ServiceConfigurationContext context)
typeof(AbpValidationResource)
).AddVirtualJson("Volo/Abp/FeatureManagement/Localization/Domain");
});

Configure<AbpExceptionLocalizationOptions>(options =>
{
options.MapCodeNamespace("Volo.Abp.FeatureManagement", typeof(AbpFeatureManagementResource));
});
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
namespace Volo.Abp.FeatureManagement
using System;

namespace Volo.Abp.FeatureManagement
{
public static class FeatureManagementDomainErrorCodes
{

public const string FeatureValueInvalid = "Volo.Abp.FeatureManagement:InvalidFeatureValue";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
using System;

namespace Volo.Abp.FeatureManagement
{
[Serializable]
public class FeatureValueInvalidException : BusinessException
{
public FeatureValueInvalidException(string name) :
base(FeatureManagementDomainErrorCodes.FeatureValueInvalid)
{
WithData("0", name);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"culture": "ar",
"texts": {
"Features": "المميزات",
"NoFeatureFoundMessage": "لا توجد أي ميزة متاحة."
"NoFeatureFoundMessage": "لا توجد أي ميزة متاحة.",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} قيمة الميزة غير صالحة!"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"culture": "de",
"texts": {
"Features": "Funktionen",
"NoFeatureFoundMessage": "Es ist keine Funktion verfügbar."
"NoFeatureFoundMessage": "Es ist keine Funktion verfügbar.",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "Der {0} -Feature-Wert ist ungültig!"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Features": "Features",
"NoFeatureFoundMessage": "There isn't any available feature.",
"Permission:FeatureManagement": "Feature management",
"Permission:FeatureManagement.ManageHostFeatures": "Manage Host features"
"Permission:FeatureManagement.ManageHostFeatures": "Manage Host features",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} feature value is not valid!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
"culture": "ru",
"texts": {
"Features": "Функциональные возможности",
"NoFeatureFoundMessage": "Нет доступных функциональных возможностей."
"NoFeatureFoundMessage": "Нет доступных функциональных возможностей.",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "Недопустимое значение функции {0}!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Features": "Özellikler",
"NoFeatureFoundMessage": "Hiç özellik yok.",
"Permission:FeatureManagement": "Özellik yönetimi",
"Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle"
"Permission:FeatureManagement.ManageHostFeatures": "Host özelliklerini düzenle",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0} özellik değeri geçerli değil!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Features": "功能",
"NoFeatureFoundMessage": "没有可用的功能.",
"Permission:FeatureManagement": "特性管理",
"Permission:FeatureManagement.ManageHostFeatures": "管理Host特性"
"Permission:FeatureManagement.ManageHostFeatures": "管理Host特性",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0}功能的值无效!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
"Features": "功能",
"NoFeatureFoundMessage": "沒有可用的功能.",
"Permission:FeatureManagement": "功能管理",
"Permission:FeatureManagement.ManageHostFeatures": "管理Host功能"
"Permission:FeatureManagement.ManageHostFeatures": "管理Host功能",
"Volo.Abp.FeatureManagement:InvalidFeatureValue" : "{0}功能的值無效!"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Features;
Expand All @@ -14,15 +15,18 @@ public class FeatureManager : IFeatureManager, ISingletonDependency
protected IFeatureDefinitionManager FeatureDefinitionManager { get; }
protected List<IFeatureManagementProvider> Providers => _lazyProviders.Value;
protected FeatureManagementOptions Options { get; }
protected IStringLocalizerFactory StringLocalizerFactory { get; }

private readonly Lazy<List<IFeatureManagementProvider>> _lazyProviders;

public FeatureManager(
IOptions<FeatureManagementOptions> options,
IServiceProvider serviceProvider,
IFeatureDefinitionManager featureDefinitionManager)
IFeatureDefinitionManager featureDefinitionManager,
IStringLocalizerFactory stringLocalizerFactory)
{
FeatureDefinitionManager = featureDefinitionManager;
StringLocalizerFactory = stringLocalizerFactory;
Options = options.Value;

//TODO: Instead, use IHybridServiceScopeFactory and create a scope..?
Expand Down Expand Up @@ -57,15 +61,17 @@ public virtual async Task<List<FeatureNameValue>> GetAllAsync(
.Select(x => new FeatureNameValue(x.Name, x.Value)).ToList();
}

public async Task<FeatureNameValueWithGrantedProvider> GetOrNullWithProviderAsync(string name, string providerName, string providerKey, bool fallback = true)
public async Task<FeatureNameValueWithGrantedProvider> GetOrNullWithProviderAsync(string name,
string providerName, string providerKey, bool fallback = true)
{
Check.NotNull(name, nameof(name));
Check.NotNull(providerName, nameof(providerName));

return await GetOrNullInternalAsync(name, providerName, providerKey, fallback);
}

public async Task<List<FeatureNameValueWithGrantedProvider>> GetAllWithProviderAsync(string providerName, string providerKey, bool fallback = true)
public async Task<List<FeatureNameValueWithGrantedProvider>> GetAllWithProviderAsync(string providerName,
string providerKey, bool fallback = true)
{
Check.NotNull(providerName, nameof(providerName));

Expand Down Expand Up @@ -128,6 +134,11 @@ public virtual async Task SetAsync(

var feature = FeatureDefinitionManager.Get(name);

if (!feature.ValueType.Validator.IsValid(value))
{
throw new FeatureValueInvalidException(feature.DisplayName.Localize(StringLocalizerFactory));
}

var providers = Enumerable
.Reverse(Providers)
.SkipWhile(p => p.Name != providerName)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,55 +24,80 @@
<abp-modal-header title="@(L["Features"].Value)"></abp-modal-header>
@if (Model.FeatureListResultDto != null && Model.FeatureListResultDto.Groups.Any())
{
var featureGroups = Model.FeatureListResultDto.Groups;

<abp-modal-body class="ml-4">
<input asp-for="@Model.ProviderKey"/>
<input asp-for="@Model.ProviderName"/>
<abp-tabs name="FeaturesTabs" tab-style="PillVertical" vertical-header-size="_4" class="custom-scroll-container">
@for (var i = 0; i < Model.FeatureListResultDto.Groups.Count; i++)
@for (var i = 0; i < featureGroups.Count; i++)
{
<abp-tab title="@Model.FeatureListResultDto.Groups[i].DisplayName" name="v-pills-tab-@Model.FeatureListResultDto.Groups[i].GetNormalizedGroupName()">
<h4>@Model.FeatureListResultDto.Groups[i].DisplayName</h4>
<abp-tab title="@featureGroups[i].DisplayName" name="v-pills-tab-@featureGroups[i].GetNormalizedGroupName()">
<h4>@featureGroups[i].DisplayName</h4>
<hr class="mt-2 mb-3"/>
<div class="custom-scroll-content">
<div class="pl-1 pt-1">
@for (var j = 0; j < Model.FeatureListResultDto.Groups[i].Features.Count; j++)
@for (var j = 0; j < featureGroups[i].Features.Count; j++)
{
var feature = Model.FeatureListResultDto.Groups[i].Features[j];
var feature = featureGroups[i].Features[j];
var disabled = Model.IsDisabled(feature.Provider.Name);
<div class="mt-2" style="padding-left: @(feature.Depth * 20)px">

<span class="mr-2">@feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "")</span>
<div class="mt-2">

<input type="text" name="FeatureGroups[@i].Features[@j].ProviderName" value="@feature.Provider.Name" hidden/>
<input type="text" name="FeatureGroups[@i].Features[@j].Type" value="@feature.ValueType?.Name" hidden/>

@if (feature.ValueType is ToggleStringValueType)
{
<abp-input asp-for="@featureGroups[i].Features[j].Value"
type="checkbox"
name="FeatureGroups[@i].Features[@j].BoolValue"
label="@feature.DisplayName"
disabled="@disabled"
group-data-feature-name="@feature.Name"
group-data-parent-name="@(feature.ParentName ?? "")"
group-style="margin-left: @(feature.Depth * 20)px"/>

}

@if (feature.ValueType is FreeTextStringValueType)
{
<input type="text" name="FeatureGroups[@i].Features[@j].Name" value="@feature.Name" hidden/>
<input disabled="@disabled" type="text" name="FeatureGroups[@i].Features[@j].Value" value="@feature.Value"/>
var type = "text";
if(feature.ValueType.Validator is NumericValueValidator)
{
type = "number";
}

<abp-input asp-for="@featureGroups[i].Features[j].Value"
label="@feature.DisplayName"
disabled="@disabled"
type="@type"
group-data-feature-name="@feature.Name"
group-data-parent-name="@(feature.ParentName ?? "")"
group-style="margin-left: @(feature.Depth * 25)px"/>
}
@if (feature.ValueType is SelectionStringValueType)

@if (feature.ValueType is SelectionStringValueType selectType)
{
<input type="text" name="FeatureGroups[@i].Features[@j].Name" value="@feature.Name" hidden/>
<select disabled="@disabled" name="FeatureGroups[@i].Features[@j].Value">
@foreach (var item in (feature.ValueType as SelectionStringValueType).ItemSource.Items)
{
if (item.Value == feature.Value)
{
<option value="@item.Value" selected="selected"> @CreateHtmlLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) </option>
}
else
<div data-feature-name="@feature.Name" data-parent-name="@(feature.ParentName ?? "")" style="margin-left: @(feature.Depth * 25)px" class="form-group">
<label for="@feature.Name">@feature.DisplayName</label>

<select id="@feature.Name" name="FeatureGroups[@i].Features[@j].Value" class="custom-select form-control">
@foreach (var item in selectType.ItemSource.Items)
{
<option value="@item.Value"> @CreateHtmlLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) </option>
if (item.Value == feature.Value)
{
<option value="@item.Value" selected="selected"> @CreateHtmlLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) </option>
}
else
{
<option value="@item.Value"> @CreateHtmlLocalizer(item.DisplayText.ResourceName).GetString(item.DisplayText.Name) </option>
}
}
}
</select>
}
@if (feature.ValueType is ToggleStringValueType)
{
<input type="text" name="FeatureGroups[@i].Features[@j].Name" value="@feature.Name" hidden/>
<input disabled="@disabled" type="checkbox" class="FeatureValueCheckbox" name="FeatureGroups[@i].Features[@j].BoolValue" value="@feature.Value"
@Html.Raw(feature.Value == "True" ? "checked" : "")/>
</select>
</div>
}

<input value="@feature.Name" name="FeatureGroups[@i].Features[@j].Name" hidden=""/>
</div>
}
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,75 @@
abp.modals = abp.modals || {};

abp.modals.FeatureManagement = function () {
$('.FeatureValueCheckbox').change(function () {
if (this.checked) {
$(this).val('true');
} else {
$(this).val('false');
function checkParents($tab, $element, className) {
var parentName = $element
.closest(className)
.attr('data-parent-name');

if (!parentName) {
return;
}

$tab.find('.custom-checkbox')
.filter('[data-feature-name="' + parentName + '"]')
.find('input[type="checkbox"]')
.each(function () {
var $parent = $(this);
$parent.prop('checked', true);
checkParents($tab, $parent, className);
});
}

function uncheckChildren($tab, $checkBox) {
var featureName = $checkBox
.closest('.custom-checkbox')
.attr('data-feature-name');
if (!featureName) {
return;
}
});

$tab.find('.custom-checkbox')
.filter('[data-parent-name="' + featureName + '"]')
.find('input[type="checkbox"]')
.each(function () {
var $child = $(this);
$child.prop('checked', false);
uncheckChildren($tab, $child);
});
}

this.initDom = function ($el) {
$el.find('.tab-pane').each(function () {
var $tab = $(this);
$tab.find('input[type="checkbox"]')
.each(function () {
var $checkBox = $(this);
$checkBox.change(function () {
if ($checkBox.is(':checked')) {
checkParents($tab, $checkBox, '.custom-checkbox')
} else {
uncheckChildren($tab, $checkBox);
}
});
});

$tab.find('.form-control')
.each(function () {
var $element = $(this);
$element.change(function () {
checkParents($tab, $element, '.form-group')
});
});
});

$(function () {
$('.custom-scroll-content').mCustomScrollbar({
theme: 'minimal-dark',
});
$('.custom-scroll-container > .col-4').mCustomScrollbar({
theme: 'minimal-dark',
});
});
};
};
})(jQuery);