From e5d4a2c59374307e482d5973b963c65c9ab3dd85 Mon Sep 17 00:00:00 2001 From: Carlos Miranda Date: Thu, 13 Jul 2023 11:56:43 +0100 Subject: [PATCH] Web UI added cut-over dates Web UI added cut-over dates --- .../Enums/ContentTypes.cs | 20 +- src/Rules.Framework.WebUI/Dto/FilterDto.cs | 14 + src/Rules.Framework.WebUI/Dto/RuleDto.cs | 3 +- .../Dto/RuleStatusDto.cs | 2 +- .../Dto/RuleStatusDtoAnalyzer.cs | 4 +- .../Extensions/RuleDtoExtensions.cs | 3 +- .../Handlers/GetContentTypeHandler.cs | 10 +- .../Handlers/GetIndexPageHandler.cs | 10 +- .../Handlers/GetRulesHandler.cs | 132 ++++++-- .../WebUIRequestHandlerBase.cs | 11 +- src/Rules.Framework.WebUI/index.html | 289 +++++++++++------- .../Extensions/RuleDtoExtensionsTests.cs | 6 +- .../Handlers/GetRulesHandlerTests.cs | 4 +- 13 files changed, 336 insertions(+), 172 deletions(-) create mode 100644 src/Rules.Framework.WebUI/Dto/FilterDto.cs diff --git a/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs b/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs index 47db8f65..78d10b65 100644 --- a/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs +++ b/samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs @@ -2,22 +2,20 @@ namespace Rules.Framework.WebUI.Sample.Enums { public enum ContentTypes { - None = 0, + TestNumber = 0, - TestNumber = 1, + TestString = 1, - TestString = 2, + TestBoolean = 2, - TestBoolean = 3, + TestDecimal = 3, - TestDecimal = 4, + TestShort = 4, - TestShort = 5, + TestDateTime = 5, - TestDateTime = 6, + TestLong = 6, - TestLong = 7, - - TestBlob = 8 + TestBlob = 7 } -} \ No newline at end of file +} diff --git a/src/Rules.Framework.WebUI/Dto/FilterDto.cs b/src/Rules.Framework.WebUI/Dto/FilterDto.cs new file mode 100644 index 00000000..f90eb976 --- /dev/null +++ b/src/Rules.Framework.WebUI/Dto/FilterDto.cs @@ -0,0 +1,14 @@ +namespace Rules.Framework.WebUI.Dto +{ + using System; + + internal sealed class RulesFilterDto + { + public string Content { get; set; } + public string ContentType { get; set; } + public DateTime? DateBegin { get; set; } + public DateTime? DateEnd { get; set; } + public string Name { get; set; } + public RuleStatusDto? Status { get; set; } + } +} diff --git a/src/Rules.Framework.WebUI/Dto/RuleDto.cs b/src/Rules.Framework.WebUI/Dto/RuleDto.cs index 5e3de426..24ae31c6 100644 --- a/src/Rules.Framework.WebUI/Dto/RuleDto.cs +++ b/src/Rules.Framework.WebUI/Dto/RuleDto.cs @@ -3,6 +3,7 @@ namespace Rules.Framework.WebUI.Dto internal sealed class RuleDto { public ConditionNodeDto Conditions { get; internal set; } + public string ContentType { get; internal set; } public string DateBegin { get; internal set; } public string DateEnd { get; internal set; } public string Name { get; internal set; } @@ -10,4 +11,4 @@ internal sealed class RuleDto public string Status { get; internal set; } public object Value { get; internal set; } } -} \ No newline at end of file +} diff --git a/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs b/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs index 7858f4de..a0c2cc5f 100644 --- a/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs +++ b/src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs @@ -2,7 +2,7 @@ namespace Rules.Framework.WebUI.Dto { internal enum RuleStatusDto : short { - Inactive, + Expired, Active, Pending, Deactivated diff --git a/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs b/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs index 0dd60441..40e1d2b2 100644 --- a/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs +++ b/src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs @@ -18,10 +18,10 @@ public RuleStatusDto Analyze(DateTime dateBegin, DateTime? dateEnd) if (dateEnd.Value <= DateTime.UtcNow) { - return RuleStatusDto.Inactive; + return RuleStatusDto.Expired; } return RuleStatusDto.Active; } } -} \ No newline at end of file +} diff --git a/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs b/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs index ebd097b8..e50298fc 100644 --- a/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs +++ b/src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs @@ -41,11 +41,12 @@ public static ConditionNodeDto ToConditionNodeDto(this GenericConditionNode root }; } - public static RuleDto ToRuleDto(this GenericRule rule, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer) + public static RuleDto ToRuleDto(this GenericRule rule, string ContentType, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer) { return new RuleDto { Conditions = rule.RootCondition?.ToConditionNodeDto(), + ContentType = ContentType, Priority = rule.Priority, Name = rule.Name, Value = rule.Content, diff --git a/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs index 09760e0b..b4464799 100644 --- a/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs +++ b/src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs @@ -13,14 +13,14 @@ internal sealed class GetContentTypeHandler : WebUIRequestHandlerBase { private static readonly string[] resourcePath = new[] { "/{0}/api/v1/contentTypes" }; - private readonly IGenericRulesEngine genericRulesEngineAdapter; + private readonly IGenericRulesEngine genericRulesEngine; private readonly IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer; - public GetContentTypeHandler(IGenericRulesEngine rulesEngine, + public GetContentTypeHandler(IGenericRulesEngine genericRulesEngine, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer, WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) { - this.genericRulesEngineAdapter = rulesEngine; + this.genericRulesEngine = genericRulesEngine; this.ruleStatusDtoAnalyzer = ruleStatusDtoAnalyzer; } @@ -30,7 +30,7 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe { try { - var contents = this.genericRulesEngineAdapter.GetContentTypes(); + var contents = this.genericRulesEngine.GetContentTypes(); var contentTypes = new List(); var index = 0; @@ -38,7 +38,7 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe { var genericContentType = new GenericContentType { Identifier = identifier }; - var genericRules = await this.genericRulesEngineAdapter + var genericRules = await this.genericRulesEngine .SearchAsync(new SearchArgs(genericContentType, DateTime.MinValue, DateTime.MaxValue)) diff --git a/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs index ef5743d5..04734c5a 100644 --- a/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs +++ b/src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs @@ -23,7 +23,7 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe var path = httpRequest.Path.Value; var httpContext = httpRequest.HttpContext; - if (Regex.IsMatch(path, $"^/?{Regex.Escape(this.webUIOptions.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) + if (Regex.IsMatch(path, $"^/?{Regex.Escape(this.WebUIOptions.RoutePrefix)}/?$", RegexOptions.IgnoreCase)) { // Use relative redirect to support proxy environments var relativeIndexUrl = string.IsNullOrEmpty(path) || path.EndsWith("/") @@ -33,7 +33,7 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe RespondWithRedirect(httpContext.Response, relativeIndexUrl); } - if (Regex.IsMatch(path, $"^/{Regex.Escape(this.webUIOptions.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) + if (Regex.IsMatch(path, $"^/{Regex.Escape(this.WebUIOptions.RoutePrefix)}/?index.html$", RegexOptions.IgnoreCase)) { await this.RespondWithIndexHtmlAsync(httpContext.Response, next).ConfigureAwait(false); } @@ -52,8 +52,8 @@ private IDictionary GetIndexArguments() { return new Dictionary { - { "%(DocumentTitle)", this.webUIOptions.DocumentTitle }, - { "%(HeadContent)", this.webUIOptions.HeadContent } + { "%(DocumentTitle)", this.WebUIOptions.DocumentTitle }, + { "%(HeadContent)", this.WebUIOptions.HeadContent } }; } @@ -66,7 +66,7 @@ private async Task RespondWithIndexHtmlAsync(HttpResponse httpResponse, RequestD var originalBody = httpResponse.Body; - using (var stream = this.webUIOptions.IndexStream()) + using (var stream = this.WebUIOptions.IndexStream()) { httpResponse.Body = stream; await next(httpResponse.HttpContext).ConfigureAwait(false); diff --git a/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs b/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs index 3737b9c8..904d1942 100644 --- a/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs +++ b/src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs @@ -1,9 +1,12 @@ namespace Rules.Framework.WebUI.Handlers { using System; + using System.Collections.Generic; using System.Linq; using System.Net; + using System.Text.Json; using System.Threading.Tasks; + using System.Web; using Microsoft.AspNetCore.Http; using Rules.Framework.Generics; using Rules.Framework.WebUI.Dto; @@ -11,53 +14,50 @@ namespace Rules.Framework.WebUI.Handlers internal sealed class GetRulesHandler : WebUIRequestHandlerBase { - private const string dateFormat = "dd/MM/yyyy HH:mm:ss"; private static readonly string[] resourcePath = new[] { "/{0}/api/v1/rules" }; - private readonly IGenericRulesEngine rulesEngine; + private readonly IGenericRulesEngine genericRulesEngine; private readonly IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer; - public GetRulesHandler(IGenericRulesEngine rulesEngine, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer, WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) + public GetRulesHandler(IGenericRulesEngine genericRulesEngine, IRuleStatusDtoAnalyzer ruleStatusDtoAnalyzer, WebUIOptions webUIOptions) : base(resourcePath, webUIOptions) { - this.rulesEngine = rulesEngine; + this.genericRulesEngine = genericRulesEngine; this.ruleStatusDtoAnalyzer = ruleStatusDtoAnalyzer; } protected override HttpMethod HttpMethod => HttpMethod.GET; - protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpResponse httpResponse, RequestDelegate next) + protected override async Task HandleRequestAsync(HttpRequest httpRequest, + HttpResponse httpResponse, + RequestDelegate next) { - if (!httpRequest.Query.TryGetValue("contentType", out var contentTypeName)) + var rulesFilter = this.GetRulesFilterFromRequest(httpRequest); + + if (!IsValidFilterDates(rulesFilter)) { - await this.WriteResponseAsync(httpResponse, new { Message = "contentType is required" }, (int)HttpStatusCode.BadRequest) - .ConfigureAwait(false); + await this.WriteResponseAsync(httpResponse, new { Message = "Date begin cannot be greater than after" }, (int)HttpStatusCode.BadRequest) + .ConfigureAwait(false); return; } try { - var genericRules = await this.rulesEngine.SearchAsync( - new SearchArgs( - new GenericContentType { Identifier = contentTypeName }, - DateTime.MinValue, DateTime.MaxValue)) - .ConfigureAwait(false); - - var rules = Enumerable.Empty(); - - var priorityCriteria = this.rulesEngine.GetPriorityCriteria(); + var rules = new List(); - if (genericRules != null && genericRules.Any()) + if (rulesFilter.ContentType.Equals("all")) { - if (priorityCriteria == PriorityCriterias.BottommostRuleWins) - { - genericRules = genericRules.OrderByDescending(r => r.Priority); - } - else + var contents = this.genericRulesEngine.GetContentTypes(); + + foreach (var identifier in contents.Select(c => c.Identifier)) { - genericRules = genericRules.OrderBy(r => r.Priority); + var rulesForContentType = await this.GetRulesForContentyType(identifier, rulesFilter).ConfigureAwait(false); + rules.AddRange(rulesForContentType); } - - rules = genericRules.Select(g => g.ToRuleDto(this.ruleStatusDtoAnalyzer)); + } + else + { + var rulesForContentType = await this.GetRulesForContentyType(rulesFilter.ContentType, rulesFilter).ConfigureAwait(false); + rules.AddRange(rulesForContentType); } await this.WriteResponseAsync(httpResponse, rules, (int)HttpStatusCode.OK).ConfigureAwait(false); @@ -67,5 +67,85 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe await this.WriteExceptionResponseAsync(httpResponse, ex).ConfigureAwait(false); } } + + private static bool IsValidFilterDates(RulesFilterDto rulesFilter) + { + return (rulesFilter.DateBegin is null + || rulesFilter.DateEnd is null) || + (rulesFilter.DateBegin <= rulesFilter.DateEnd); + } + + private IEnumerable ApplyFilters(RulesFilterDto rulesFilter, IEnumerable genericRulesDto) + { + if (!string.IsNullOrWhiteSpace(rulesFilter.Content)) + { + genericRulesDto = genericRulesDto.Where(g => + { + return JsonSerializer.Serialize(g.Value).Contains(rulesFilter.Content, StringComparison.OrdinalIgnoreCase); + }); + } + + if (!string.IsNullOrWhiteSpace(rulesFilter.Name)) + { + genericRulesDto = genericRulesDto.Where(g => + { + return g.Name.Contains(rulesFilter.Name, StringComparison.OrdinalIgnoreCase); + }); + } + if (rulesFilter.Status != null) + { + genericRulesDto = genericRulesDto.Where(g => + { + return g.Status.Equals(rulesFilter.Status.ToString()); + }); + } + + return genericRulesDto; + } + + private RulesFilterDto GetRulesFilterFromRequest(HttpRequest httpRequest) + { + var parseQueryString = HttpUtility.ParseQueryString(httpRequest.QueryString.Value); + + var rulesFilterAsString = JsonSerializer.Serialize(parseQueryString.Cast().ToDictionary(k => k, v => string.IsNullOrWhiteSpace(parseQueryString[v]) ? null : parseQueryString[v])); + var rulesFilter = JsonSerializer.Deserialize(rulesFilterAsString, this.SerializerOptions); + + rulesFilter.ContentType = string.IsNullOrWhiteSpace(rulesFilter.ContentType) ? "all" : rulesFilter.ContentType; + + rulesFilter.DateEnd ??= DateTime.MaxValue; + + rulesFilter.DateBegin ??= DateTime.MinValue; + + return rulesFilter; + } + + private async Task> GetRulesForContentyType(string identifier, RulesFilterDto rulesFilter) + { + var genericRules = await this.genericRulesEngine.SearchAsync( + new SearchArgs( + new GenericContentType { Identifier = identifier }, + rulesFilter.DateBegin.Value, rulesFilter.DateEnd.Value)) + .ConfigureAwait(false); + + var priorityCriteria = this.genericRulesEngine.GetPriorityCriteria(); + + if (genericRules != null && genericRules.Any()) + { + if (priorityCriteria == PriorityCriterias.BottommostRuleWins) + { + genericRules = genericRules.OrderByDescending(r => r.Priority); + } + else + { + genericRules = genericRules.OrderBy(r => r.Priority); + } + + var genericRulesDto = this.ApplyFilters(rulesFilter, genericRules.Select(g => g.ToRuleDto(identifier, this.ruleStatusDtoAnalyzer))); + + return genericRulesDto; + } + + return Enumerable.Empty(); + } } } diff --git a/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs b/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs index 883a4cbd..9ec111c2 100644 --- a/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs +++ b/src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs @@ -13,14 +13,13 @@ namespace Rules.Framework.WebUI internal abstract class WebUIRequestHandlerBase : IHttpRequestHandler { - protected readonly WebUIOptions webUIOptions; - - private readonly JsonSerializerOptions SerializerOptions; + protected readonly JsonSerializerOptions SerializerOptions; + protected readonly WebUIOptions WebUIOptions; protected WebUIRequestHandlerBase(string[] resourcePath, WebUIOptions webUIOptions) { this.ResourcePath = resourcePath; - this.webUIOptions = webUIOptions; + this.WebUIOptions = webUIOptions; this.SerializerOptions = new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase, @@ -52,7 +51,7 @@ protected bool CanHandle(HttpRequest httpRequest) { var resource = httpRequest.Path.ToUriComponent(); - var resourcesPath = this.ResourcePath.Select(r => string.Format(r, this.webUIOptions.RoutePrefix)); + var resourcesPath = this.ResourcePath.Select(r => string.Format(r, this.WebUIOptions.RoutePrefix)); if (!resourcesPath.Contains(resource)) { @@ -95,4 +94,4 @@ protected virtual async Task WriteResponseAsync(HttpResponse httpResponse, T } } } -} \ No newline at end of file +} diff --git a/src/Rules.Framework.WebUI/index.html b/src/Rules.Framework.WebUI/index.html index 29c56def..bd04e6a1 100644 --- a/src/Rules.Framework.WebUI/index.html +++ b/src/Rules.Framework.WebUI/index.html @@ -109,7 +109,7 @@
-
+
@@ -161,32 +161,47 @@
Content Types
- -
- - -
-
- - -
- -
- - + +
+ +
+ + +
+
+ + +
+ +
+ + +
+
+ + +
- -
- - +
+
+ + +
+
+ + +
@@ -218,6 +233,7 @@
Rules
Priority + Content Type Name Date Begin Date End @@ -248,22 +264,18 @@
Rules