Browse Source

Group features on the feature management modal

pull/5334/head
liangshiwei 6 years ago
parent
commit
5c173a692e
  1. 9
      framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs
  2. 4
      framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs
  3. 18
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs
  4. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs
  5. 9
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs
  6. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs
  7. 55
      modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs
  8. 2
      modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs
  9. 98
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml
  10. 19
      modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs
  11. 4
      modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs
  12. 82
      modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs

9
framework/src/Volo.Abp.Features/Volo/Abp/Features/FeatureDefinitionManager.cs

@ -62,6 +62,11 @@ namespace Volo.Abp.Features
return FeatureDefinitions.GetOrDefault(name);
}
public IReadOnlyList<FeatureGroupDefinition> GetGroups()
{
return FeatureGroupDefinitions.Values.ToImmutableList();
}
protected virtual Dictionary<string, FeatureDefinition> CreateFeatureDefinitions()
{
var features = new Dictionary<string, FeatureDefinition>();
@ -78,7 +83,7 @@ namespace Volo.Abp.Features
}
protected virtual void AddFeatureToDictionaryRecursively(
Dictionary<string, FeatureDefinition> features,
Dictionary<string, FeatureDefinition> features,
FeatureDefinition feature)
{
if (features.ContainsKey(feature.Name))
@ -114,4 +119,4 @@ namespace Volo.Abp.Features
return context.Groups;
}
}
}
}

4
framework/src/Volo.Abp.Features/Volo/Abp/Features/IFeatureDefinitionManager.cs

@ -11,5 +11,7 @@ namespace Volo.Abp.Features
IReadOnlyList<FeatureDefinition> GetAll();
FeatureDefinition GetOrNull(string name);
IReadOnlyList<FeatureGroupDefinition> GetGroups();
}
}
}

18
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureGroupDto.cs

@ -0,0 +1,18 @@
using System.Collections.Generic;
namespace Volo.Abp.FeatureManagement
{
public class FeatureGroupDto
{
public string Name { get; set; }
public string DisplayName { get; set; }
public List<FeatureDto> Features { get; set; }
public string GetNormalizedGroupName()
{
return Name.Replace(".", "_");
}
}
}

9
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/FeatureListDto.cs

@ -1,9 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.FeatureManagement
{
public class FeatureListDto
{
public List<FeatureDto> Features { get; set; }
}
}

9
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/GetFeatureListResultDto.cs

@ -0,0 +1,9 @@
using System.Collections.Generic;
namespace Volo.Abp.FeatureManagement
{
public class GetFeatureListResultDto
{
public List<FeatureGroupDto> Groups { get; set; }
}
}

2
modules/feature-management/src/Volo.Abp.FeatureManagement.Application.Contracts/Volo/Abp/FeatureManagement/IFeatureAppService.cs

@ -6,7 +6,7 @@ namespace Volo.Abp.FeatureManagement
{
public interface IFeatureAppService : IApplicationService
{
Task<FeatureListDto> GetAsync([NotNull] string providerName, [NotNull] string providerKey);
Task<GetFeatureListResultDto> GetAsync([NotNull] string providerName, [NotNull] string providerKey);
Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input);
}

55
modules/feature-management/src/Volo.Abp.FeatureManagement.Application/Volo/Abp/FeatureManagement/FeatureAppService.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using JetBrains.Annotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Features;
namespace Volo.Abp.FeatureManagement
@ -24,35 +25,49 @@ namespace Volo.Abp.FeatureManagement
Options = options.Value;
}
public virtual async Task<FeatureListDto> GetAsync([NotNull] string providerName, [NotNull] string providerKey)
public virtual async Task<GetFeatureListResultDto> GetAsync([NotNull] string providerName, [NotNull] string providerKey)
{
await CheckProviderPolicy(providerName);
var featureDefinitions = FeatureDefinitionManager.GetAll();
var features = new List<FeatureDto>();
var result = new GetFeatureListResultDto
{
Groups = new List<FeatureGroupDto>()
};
foreach (var featureDefinition in featureDefinitions)
foreach (var group in FeatureDefinitionManager.GetGroups())
{
var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey);
features.Add(new FeatureDto
var groupDto = new FeatureGroupDto
{
Name = group.Name,
DisplayName = group.DisplayName.Localize(StringLocalizerFactory),
Features = new List<FeatureDto>()
};
foreach (var featureDefinition in group.GetFeaturesWithChildren())
{
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
var feature = await FeatureManager.GetOrNullWithProviderAsync(featureDefinition.Name, providerName, providerKey);
groupDto.Features.Add(new FeatureDto
{
Name = feature.Provider?.Name,
Key = feature.Provider?.Key
}
});
}
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(groupDto.Features, providerName, providerKey);
SetFeatureDepth(features, providerName, providerKey);
result.Groups.Add(groupDto);
}
return new FeatureListDto { Features = features };
return result;
}
public virtual async Task UpdateAsync([NotNull] string providerName, [NotNull] string providerKey, UpdateFeaturesDto input)

2
modules/feature-management/src/Volo.Abp.FeatureManagement.HttpApi/Volo/Abp/FeatureManagement/FeaturesController.cs

@ -17,7 +17,7 @@ namespace Volo.Abp.FeatureManagement
}
[HttpGet]
public virtual Task<FeatureListDto> GetAsync(string providerName, string providerKey)
public virtual Task<GetFeatureListResultDto> GetAsync(string providerName, string providerKey)
{
return FeatureAppService.GetAsync(providerName, providerKey);
}

98
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml

@ -4,7 +4,6 @@
@using Volo.Abp.FeatureManagement.Localization
@using Volo.Abp.Validation.StringValues
@using Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement
@using Volo.Abp.Features
@model FeatureManagementModal
@inject IHtmlLocalizer<AbpFeatureManagementResource> L
@{
@ -13,53 +12,67 @@
<form method="post" asp-page="/FeatureManagement/FeatureManagementModal" data-script-class="abp.modals.FeatureManagement">
<abp-modal size="Large">
<abp-modal-header title="@(L["Features"].Value)"></abp-modal-header>
@if (Model.FeatureListDto?.Features != null && Model.FeatureListDto.Features.Any())
@if (Model.FeatureListResultDto != null && Model.FeatureListResultDto.Groups.Any())
{
<abp-modal-body class="ml-4">
<input asp-for="@Model.ProviderKey" />
<input asp-for="@Model.ProviderName" />
@for (var i = 0; i < Model.FeatureListDto.Features.Count; i++)
{
var feature = Model.FeatureListDto.Features[i];
var disabled = Model.IsDisabled(feature.Provider.Name);
<div class="mt-2" style="padding-left: @(feature.Depth * 20)px">
<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++)
{
<abp-tab title="@Model.FeatureListResultDto.Groups[i].DisplayName" name="v-pills-tab-@Model.FeatureListResultDto.Groups[i].GetNormalizedGroupName()">
<h4>@Model.FeatureListResultDto.Groups[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++)
{
var feature = Model.FeatureListResultDto.Groups[i].Features[j];
var disabled = Model.IsDisabled(feature.Provider.Name);
<div class="mt-2" style="padding-left: @(feature.Depth * 20)px">
<spam class="mr-2">@feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "")</spam>
<spam class="mr-2">@feature.DisplayName @(disabled ? $"({feature.Provider.Name})" : "")</spam>
<input type="text" name="Features[@i].ProviderName" value="@feature.Provider.Name" hidden />
<input type="text" name="Features[@i].Type" value="@feature.ValueType?.Name" hidden />
@if (feature.ValueType is FreeTextStringValueType)
{
<input type="text" name="Features[@i].Name" value="@feature.Name" hidden />
<input disabled="@disabled" type="text" name="Features[@i].Value" value="@feature.Value" />
}
@if (feature.ValueType is SelectionStringValueType)
{
<input type="text" name="Features[@i].Name" value="@feature.Name" hidden />
<select disabled="@disabled" name="Features[@i].Value">
@foreach (var item in (feature.ValueType as SelectionStringValueType).ItemSource.Items)
{
if (item.Value == feature.Value)
{
<option value="@item.Value" selected="selected"> @L.GetString(item.DisplayText.Name) </option>
}
else
{
<option value="@item.Value"> @L.GetString(item.DisplayText.Name) </option>
<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 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"/>
}
@if (feature.ValueType is SelectionStringValueType)
{
<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"> @L.GetString(item.DisplayText.Name) </option>
}
else
{
<option value="@item.Value"> @L.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" : "")/>
}
</div>
}
}
</select>
}
@if (feature.ValueType is ToggleStringValueType)
{
<input type="text" name="Features[@i].Name" value="@feature.Name" hidden />
<input disabled="@disabled" type="checkbox" class="FeatureValueCheckbox" name="Features[@i].BoolValue" value="@feature.Value"
@Html.Raw(feature.Value == "True" ? "checked" : "") />
}
</div>
}
</div>
</div>
</abp-tab>
}
</abp-tabs>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)"></abp-modal-footer>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel | AbpModalButtons.Save)"></abp-modal-footer>
}
else
{
@ -69,4 +82,3 @@
}
</abp-modal>
</form>

19
modules/feature-management/src/Volo.Abp.FeatureManagement.Web/Pages/FeatureManagement/FeatureManagementModal.cshtml.cs

@ -22,9 +22,9 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement
public string ProviderKey { get; set; }
[BindProperty]
public List<FeatureViewModel> Features { get; set; }
public List<FeatureGroupViewModel> FeatureGroups { get; set; }
public FeatureListDto FeatureListDto { get; set; }
public GetFeatureListResultDto FeatureListResultDto { get; set; }
protected IFeatureAppService FeatureAppService { get; }
@ -35,16 +35,20 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement
FeatureAppService = featureAppService;
}
public virtual async Task OnGetAsync()
public virtual async Task<IActionResult> OnGetAsync()
{
FeatureListDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey);
ValidateModel();
FeatureListResultDto = await FeatureAppService.GetAsync(ProviderName, ProviderKey);
return Page();
}
public virtual async Task<IActionResult> OnPostAsync()
{
var features = new UpdateFeaturesDto
{
Features = Features.Select(f => new UpdateFeatureDto
Features = FeatureGroups.SelectMany(g => g.Features).Select(f => new UpdateFeatureDto
{
Name = f.Name,
Value = f.Type == nameof(ToggleStringValueType) ? f.BoolValue.ToString() : f.Value
@ -68,6 +72,11 @@ namespace Volo.Abp.FeatureManagement.Web.Pages.FeatureManagement
public string ProviderKey { get; set; }
}
public class FeatureGroupViewModel
{
public List<FeatureViewModel> Features { get; set; }
}
public class FeatureViewModel
{
public string Name { get; set; }

4
modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/FeatureAppService_Tests.cs

@ -41,7 +41,7 @@ namespace Volo.Abp.FeatureManagement
TestEditionIds.Regular.ToString());
featureList.ShouldNotBeNull();
featureList.Features.ShouldContain(feature => feature.Name == TestFeatureDefinitionProvider.SocialLogins);
featureList.Groups.SelectMany(g =>g .Features).ShouldContain(feature => feature.Name == TestFeatureDefinitionProvider.SocialLogins);
}
[Fact]
@ -63,7 +63,7 @@ namespace Volo.Abp.FeatureManagement
});
(await _featureAppService.GetAsync(EditionFeatureValueProvider.ProviderName,
TestEditionIds.Regular.ToString())).Features.Any(x =>
TestEditionIds.Regular.ToString())).Groups.SelectMany(g => g.Features).Any(x =>
x.Name == TestFeatureDefinitionProvider.SocialLogins &&
x.Value == false.ToString().ToLowerInvariant())
.ShouldBeTrue();

82
modules/feature-management/test/Volo.Abp.FeatureManagement.Application.Tests/Volo/Abp/FeatureManagement/StringValueJsonConverter_Tests.cs

@ -20,38 +20,46 @@ namespace Volo.Abp.FeatureManagement
[Fact]
public void Should_Serialize_And_Deserialize()
{
var featureListDto = new FeatureListDto
var featureListDto = new GetFeatureListResultDto
{
Features = new List<FeatureDto>
Groups = new List<FeatureGroupDto>
{
new FeatureDto
new FeatureGroupDto
{
ValueType = new FreeTextStringValueType
Name = "MyGroup",
DisplayName = "MyGroup",
Features = new List<FeatureDto>
{
Validator = new BooleanValueValidator()
}
},
new FeatureDto
{
ValueType = new SelectionStringValueType
{
ItemSource = new StaticSelectionStringValueItemSource(
new LocalizableSelectionStringValueItem
new FeatureDto
{
ValueType = new FreeTextStringValueType
{
Value = "TestValue",
DisplayText = new LocalizableStringInfo("TestResourceName", "TestName")
}),
Validator = new AlwaysValidValueValidator()
}
},
new FeatureDto
{
ValueType = new ToggleStringValueType
{
Validator = new NumericValueValidator
Validator = new BooleanValueValidator()
}
},
new FeatureDto
{
MaxValue = 1000,
MinValue = 10
ValueType = new SelectionStringValueType
{
ItemSource = new StaticSelectionStringValueItemSource(
new LocalizableSelectionStringValueItem
{
Value = "TestValue",
DisplayText = new LocalizableStringInfo("TestResourceName", "TestName")
}),
Validator = new AlwaysValidValueValidator()
}
},
new FeatureDto
{
ValueType = new ToggleStringValueType
{
Validator = new NumericValueValidator
{
MaxValue = 1000,
MinValue = 10
}
}
}
}
}
@ -59,22 +67,22 @@ namespace Volo.Abp.FeatureManagement
};
var serialized = _jsonSerializer.Serialize(featureListDto, indented: true);
var featureListDto2 = _jsonSerializer.Deserialize<FeatureListDto>(serialized);
var featureListDto2 = _jsonSerializer.Deserialize<GetFeatureListResultDto>(serialized);
featureListDto2.Features[0].ValueType.ShouldBeOfType<FreeTextStringValueType>();
featureListDto2.Features[0].ValueType.Validator.ShouldBeOfType<BooleanValueValidator>();
featureListDto2.Groups[0].Features[0].ValueType.ShouldBeOfType<FreeTextStringValueType>();
featureListDto2.Groups[0].Features[0].ValueType.Validator.ShouldBeOfType<BooleanValueValidator>();
featureListDto2.Features[1].ValueType.ShouldBeOfType<SelectionStringValueType>();
featureListDto2.Features[1].ValueType.Validator.ShouldBeOfType<AlwaysValidValueValidator>();
featureListDto2.Features[1].ValueType.As<SelectionStringValueType>().ItemSource.Items.ShouldBeOfType<LocalizableSelectionStringValueItem[]>();
featureListDto2.Features[1].ValueType.As<SelectionStringValueType>().ItemSource.Items.ShouldContain(x =>
featureListDto2.Groups[0].Features[1].ValueType.ShouldBeOfType<SelectionStringValueType>();
featureListDto2.Groups[0].Features[1].ValueType.Validator.ShouldBeOfType<AlwaysValidValueValidator>();
featureListDto2.Groups[0].Features[1].ValueType.As<SelectionStringValueType>().ItemSource.Items.ShouldBeOfType<LocalizableSelectionStringValueItem[]>();
featureListDto2.Groups[0].Features[1].ValueType.As<SelectionStringValueType>().ItemSource.Items.ShouldContain(x =>
x.Value == "TestValue" && x.DisplayText.ResourceName == "TestResourceName" &&
x.DisplayText.Name == "TestName");
featureListDto2.Features[2].ValueType.ShouldBeOfType<ToggleStringValueType>();
featureListDto2.Features[2].ValueType.Validator.ShouldBeOfType<NumericValueValidator>();
featureListDto2.Features[2].ValueType.Validator.As<NumericValueValidator>().MaxValue.ShouldBe(1000);
featureListDto2.Features[2].ValueType.Validator.As<NumericValueValidator>().MinValue.ShouldBe(10);
featureListDto2.Groups[0].Features[2].ValueType.ShouldBeOfType<ToggleStringValueType>();
featureListDto2.Groups[0].Features[2].ValueType.Validator.ShouldBeOfType<NumericValueValidator>();
featureListDto2.Groups[0].Features[2].ValueType.Validator.As<NumericValueValidator>().MaxValue.ShouldBe(1000);
featureListDto2.Groups[0].Features[2].ValueType.Validator.As<NumericValueValidator>().MinValue.ShouldBe(10);
}
}
}

Loading…
Cancel
Save