Skip to content

Commit

Permalink
Web UI added cut-over dates
Browse files Browse the repository at this point in the history
Web UI added cut-over dates
  • Loading branch information
carlosgoias authored Jul 13, 2023
1 parent df38127 commit e5d4a2c
Show file tree
Hide file tree
Showing 13 changed files with 336 additions and 172 deletions.
20 changes: 9 additions & 11 deletions samples/Rules.Framework.WebUI.Sample/Enums/ContentTypes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
}
}
14 changes: 14 additions & 0 deletions src/Rules.Framework.WebUI/Dto/FilterDto.cs
Original file line number Diff line number Diff line change
@@ -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; }
}
}
3 changes: 2 additions & 1 deletion src/Rules.Framework.WebUI/Dto/RuleDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@ 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; }
public int? Priority { get; internal set; }
public string Status { get; internal set; }
public object Value { get; internal set; }
}
}
}
2 changes: 1 addition & 1 deletion src/Rules.Framework.WebUI/Dto/RuleStatusDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Rules.Framework.WebUI.Dto
{
internal enum RuleStatusDto : short
{
Inactive,
Expired,
Active,
Pending,
Deactivated
Expand Down
4 changes: 2 additions & 2 deletions src/Rules.Framework.WebUI/Dto/RuleStatusDtoAnalyzer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
}
}
3 changes: 2 additions & 1 deletion src/Rules.Framework.WebUI/Extensions/RuleDtoExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions src/Rules.Framework.WebUI/Handlers/GetContentTypeHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand All @@ -30,15 +30,15 @@ protected override async Task HandleRequestAsync(HttpRequest httpRequest, HttpRe
{
try
{
var contents = this.genericRulesEngineAdapter.GetContentTypes();
var contents = this.genericRulesEngine.GetContentTypes();

var contentTypes = new List<ContentTypeDto>();
var index = 0;
foreach (var identifier in contents.Select(c => c.Identifier))
{
var genericContentType = new GenericContentType { Identifier = identifier };

var genericRules = await this.genericRulesEngineAdapter
var genericRules = await this.genericRulesEngine
.SearchAsync(new SearchArgs<GenericContentType, GenericConditionType>(genericContentType,
DateTime.MinValue,
DateTime.MaxValue))
Expand Down
10 changes: 5 additions & 5 deletions src/Rules.Framework.WebUI/Handlers/GetIndexPageHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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("/")
Expand All @@ -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);
}
Expand All @@ -52,8 +52,8 @@ private IDictionary<string, string> GetIndexArguments()
{
return new Dictionary<string, string>
{
{ "%(DocumentTitle)", this.webUIOptions.DocumentTitle },
{ "%(HeadContent)", this.webUIOptions.HeadContent }
{ "%(DocumentTitle)", this.WebUIOptions.DocumentTitle },
{ "%(HeadContent)", this.WebUIOptions.HeadContent }
};
}

Expand All @@ -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);
Expand Down
132 changes: 106 additions & 26 deletions src/Rules.Framework.WebUI/Handlers/GetRulesHandler.cs
Original file line number Diff line number Diff line change
@@ -1,63 +1,63 @@
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;
using Rules.Framework.WebUI.Extensions;

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<GenericContentType, GenericConditionType>(
new GenericContentType { Identifier = contentTypeName },
DateTime.MinValue, DateTime.MaxValue))
.ConfigureAwait(false);

var rules = Enumerable.Empty<RuleDto>();

var priorityCriteria = this.rulesEngine.GetPriorityCriteria();
var rules = new List<RuleDto>();

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);
Expand All @@ -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<RuleDto> ApplyFilters(RulesFilterDto rulesFilter, IEnumerable<RuleDto> 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<string>().ToDictionary(k => k, v => string.IsNullOrWhiteSpace(parseQueryString[v]) ? null : parseQueryString[v]));
var rulesFilter = JsonSerializer.Deserialize<RulesFilterDto>(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<IEnumerable<RuleDto>> GetRulesForContentyType(string identifier, RulesFilterDto rulesFilter)
{
var genericRules = await this.genericRulesEngine.SearchAsync(
new SearchArgs<GenericContentType, GenericConditionType>(
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<RuleDto>();
}
}
}
11 changes: 5 additions & 6 deletions src/Rules.Framework.WebUI/WebUIRequestHandlerBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))
{
Expand Down Expand Up @@ -95,4 +94,4 @@ protected virtual async Task WriteResponseAsync<T>(HttpResponse httpResponse, T
}
}
}
}
}
Loading

0 comments on commit e5d4a2c

Please sign in to comment.