Browse Source

Define day range.

pull/342/head
Sebastian Stehle 8 years ago
parent
commit
4bf3badffd
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/UsageTrigger.cs
  2. 4
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs
  3. 14
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs
  4. 32
      src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs
  5. 12
      src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs
  6. 18
      src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs
  7. 2
      src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs
  8. 5
      src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageRuleTriggerDto.cs
  9. 24
      tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs
  10. 19
      tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs

2
src/Squidex.Domain.Apps.Core.Model/Rules/Triggers/UsageTrigger.cs

@ -16,6 +16,8 @@ namespace Squidex.Domain.Apps.Core.Rules.Triggers
public int Limit { get; set; } public int Limit { get; set; }
public int? NumDays { get; set; }
public override T Accept<T>(IRuleTriggerVisitor<T> visitor) public override T Accept<T>(IRuleTriggerVisitor<T> visitor)
{ {
return visitor.Visit(this); return visitor.Visit(this);

4
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/IUsageTrackerGrain.cs

@ -15,10 +15,10 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
{ {
public interface IUsageTrackerGrain : IGrainWithStringKey, IBackgroundGrain public interface IUsageTrackerGrain : IGrainWithStringKey, IBackgroundGrain
{ {
Task AddTargetAsync(Guid ruleId, NamedId<Guid> appId, int limits); Task AddTargetAsync(Guid ruleId, NamedId<Guid> appId, int limits, int? numDays);
Task RemoveTargetAsync(Guid ruleId); Task RemoveTargetAsync(Guid ruleId);
Task UpdateTargetAsync(Guid ruleId, int limits); Task UpdateTargetAsync(Guid ruleId, int limits, int? numDays);
} }
} }

14
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerCommandMiddleware.cs

@ -36,21 +36,23 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
break; break;
case CreateRule createRule: case CreateRule createRule:
{ {
if (createRule.Trigger is UsageTrigger createdTrigger) if (createRule.Trigger is UsageTrigger usage)
{ {
await usageTrackerGrain.AddTargetAsync(createRule.RuleId, createRule.AppId, createdTrigger.Limit); await usageTrackerGrain.AddTargetAsync(createRule.RuleId, createRule.AppId, usage.Limit, usage.NumDays);
} }
break; break;
} }
case UpdateRule ruleUpdated: case UpdateRule ruleUpdated:
if (ruleUpdated.Trigger is UsageTrigger updatedTrigger)
{ {
await usageTrackerGrain.UpdateTargetAsync(ruleUpdated.RuleId, updatedTrigger.Limit); if (ruleUpdated.Trigger is UsageTrigger usage)
} {
await usageTrackerGrain.UpdateTargetAsync(ruleUpdated.RuleId, usage.Limit, usage.NumDays);
}
break; break;
}
} }
await next(); await next();

32
src/Squidex.Domain.Apps.Entities/Rules/UsageTracking/UsageTrackerGrain.cs

@ -24,12 +24,15 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
[Reentrant] [Reentrant]
public sealed class UsageTrackerGrain : GrainOfString<UsageTrackerGrain.GrainState>, IRemindable, IUsageTrackerGrain public sealed class UsageTrackerGrain : GrainOfString<UsageTrackerGrain.GrainState>, IRemindable, IUsageTrackerGrain
{ {
private const int MaxDays = 30;
private readonly IUsageTracker usageTracker; private readonly IUsageTracker usageTracker;
public sealed class Target public sealed class Target
{ {
public int Limits { get; set; } public int Limits { get; set; }
public int? NumDays { get; set; }
public DateTime? Triggered { get; set; } public DateTime? Triggered { get; set; }
public NamedId<Guid> AppId { get; set; } public NamedId<Guid> AppId { get; set; }
@ -75,11 +78,13 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
foreach (var kvp in State.Targets) foreach (var kvp in State.Targets)
{ {
var appId = kvp.Value.AppId; var target = kvp.Value;
var (from, to) = GetDateRange(today, target.NumDays);
if (!kvp.Value.Triggered.HasValue || !IsSameMonth(today, kvp.Value.Triggered.Value)) if (!target.Triggered.HasValue || target.Triggered < from)
{ {
var usage = await usageTracker.GetMonthlyCallsAsync(appId.Id.ToString(), today); var usage = await usageTracker.GetMonthlyCallsAsync(target.AppId.Id.ToString(), today);
var limit = kvp.Value.Limits; var limit = kvp.Value.Limits;
@ -89,7 +94,7 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
var @event = new AppUsageExceeded var @event = new AppUsageExceeded
{ {
AppId = appId, AppId = target.AppId,
CallsCurrent = usage, CallsCurrent = usage,
CallsLimit = limit, CallsLimit = limit,
RuleId = kvp.Key RuleId = kvp.Key
@ -103,21 +108,28 @@ namespace Squidex.Domain.Apps.Entities.Rules.UsageTracking
await WriteStateAsync(); await WriteStateAsync();
} }
private static bool IsSameMonth(DateTime lhs, DateTime rhs) private (DateTime, DateTime) GetDateRange(DateTime today, int? numDays)
{ {
return lhs.Year == rhs.Year && lhs.Month == rhs.Month; if (numDays > 0 && numDays < MaxDays)
{
return (today.AddDays(-numDays.Value).AddDays(1), today);
}
else
{
return (new DateTime(today.Year, today.Month, 1), today);
}
} }
public Task AddTargetAsync(Guid ruleId, NamedId<Guid> appId, int limits) public Task AddTargetAsync(Guid ruleId, NamedId<Guid> appId, int limits, int? numDays)
{ {
UpdateTarget(ruleId, t => { t.Limits = limits; t.AppId = appId; }); UpdateTarget(ruleId, t => { t.Limits = limits; t.AppId = appId; t.NumDays = numDays; });
return WriteStateAsync(); return WriteStateAsync();
} }
public Task UpdateTargetAsync(Guid ruleId, int limits) public Task UpdateTargetAsync(Guid ruleId, int limits, int? numDays)
{ {
UpdateTarget(ruleId, t => t.Limits = limits); UpdateTarget(ruleId, t => { t.Limits = limits; t.NumDays = numDays; });
return WriteStateAsync(); return WriteStateAsync();
} }

12
src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs

@ -167,16 +167,18 @@ namespace Squidex.Infrastructure.UsageTracking
return result; return result;
} }
public async Task<long> GetMonthlyCallsAsync(string key, DateTime date) public Task<long> GetMonthlyCallsAsync(string key, DateTime date)
{
return GetPreviousCallsAsync(key, new DateTime(date.Year, date.Month, 1), date);
}
public async Task<long> GetPreviousCallsAsync(string key, DateTime fromDate, DateTime toDate)
{ {
key = GetKey(key); key = GetKey(key);
ThrowIfDisposed(); ThrowIfDisposed();
var dateFrom = new DateTime(date.Year, date.Month, 1); var originalUsages = await usageRepository.QueryAsync(key, fromDate, toDate);
var dateTo = dateFrom.AddMonths(1).AddDays(-1);
var originalUsages = await usageRepository.QueryAsync(key, dateFrom, dateTo);
return originalUsages.Sum(x => (long)x.Counters.Get(CounterTotalCalls)); return originalUsages.Sum(x => (long)x.Counters.Get(CounterTotalCalls));
} }

18
src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs

@ -14,7 +14,7 @@ namespace Squidex.Infrastructure.UsageTracking
{ {
public sealed class CachingUsageTracker : CachingProviderBase, IUsageTracker public sealed class CachingUsageTracker : CachingProviderBase, IUsageTracker
{ {
private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5);
private readonly IUsageTracker inner; private readonly IUsageTracker inner;
public CachingUsageTracker(IUsageTracker inner, IMemoryCache cache) public CachingUsageTracker(IUsageTracker inner, IMemoryCache cache)
@ -43,7 +43,7 @@ namespace Squidex.Infrastructure.UsageTracking
{ {
Guard.NotNull(key, nameof(key)); Guard.NotNull(key, nameof(key));
var cacheKey = string.Concat(key, date); var cacheKey = string.Join("$", "Usage", nameof(GetMonthlyCallsAsync), key, date);
return Cache.GetOrCreateAsync(cacheKey, entry => return Cache.GetOrCreateAsync(cacheKey, entry =>
{ {
@ -52,5 +52,19 @@ namespace Squidex.Infrastructure.UsageTracking
return inner.GetMonthlyCallsAsync(key, date); return inner.GetMonthlyCallsAsync(key, date);
}); });
} }
public Task<long> GetPreviousCallsAsync(string key, DateTime fromDate, DateTime toDate)
{
Guard.NotNull(key, nameof(key));
var cacheKey = string.Join("$", "Usage", nameof(GetPreviousCallsAsync), key, fromDate, toDate);
return Cache.GetOrCreateAsync(cacheKey, entry =>
{
entry.AbsoluteExpirationRelativeToNow = CacheDuration;
return inner.GetPreviousCallsAsync(key, fromDate, toDate);
});
}
} }
} }

2
src/Squidex.Infrastructure/UsageTracking/IUsageTracker.cs

@ -17,6 +17,8 @@ namespace Squidex.Infrastructure.UsageTracking
Task<long> GetMonthlyCallsAsync(string key, DateTime date); Task<long> GetMonthlyCallsAsync(string key, DateTime date);
Task<long> GetPreviousCallsAsync(string key, DateTime fromDate, DateTime toDate);
Task<IReadOnlyDictionary<string, IReadOnlyList<DateUsage>>> QueryAsync(string key, DateTime fromDate, DateTime toDate); Task<IReadOnlyDictionary<string, IReadOnlyList<DateUsage>>> QueryAsync(string key, DateTime fromDate, DateTime toDate);
} }
} }

5
src/Squidex/Areas/Api/Controllers/Rules/Models/Triggers/UsageRuleTriggerDto.cs

@ -18,6 +18,11 @@ namespace Squidex.Areas.Api.Controllers.Rules.Models.Triggers
/// </summary> /// </summary>
public int Limit { get; set; } public int Limit { get; set; }
/// <summary>
/// The number of days to check or null for the current month.
/// </summary>
public int? NumDays { get; set; }
public override RuleTrigger ToTrigger() public override RuleTrigger ToTrigger()
{ {
return SimpleMapper.Map(this, new UsageTrigger()); return SimpleMapper.Map(this, new UsageTrigger());

24
tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs

@ -64,7 +64,7 @@ namespace Squidex.Infrastructure.UsageTracking
new StoredUsage("category1", date.AddDays(7), Counters(17, 22)) new StoredUsage("category1", date.AddDays(7), Counters(17, 22))
}; };
A.CallTo(() => usageStore.QueryAsync($"{key}_API", new DateTime(2016, 1, 1), new DateTime(2016, 1, 31))) A.CallTo(() => usageStore.QueryAsync($"{key}_API", new DateTime(2016, 1, 1), new DateTime(2016, 1, 15)))
.Returns(originalData); .Returns(originalData);
var result = await sut.GetMonthlyCallsAsync(key, date); var result = await sut.GetMonthlyCallsAsync(key, date);
@ -72,6 +72,28 @@ namespace Squidex.Infrastructure.UsageTracking
Assert.Equal(55, result); Assert.Equal(55, result);
} }
[Fact]
public async Task Should_sum_up_when_getting_last_calls_calls()
{
var f = DateTime.Today;
var t = DateTime.Today.AddDays(10);
IReadOnlyList<StoredUsage> originalData = new List<StoredUsage>
{
new StoredUsage("category1", f.AddDays(1), Counters(10, 15)),
new StoredUsage("category1", f.AddDays(3), Counters(13, 18)),
new StoredUsage("category1", f.AddDays(5), Counters(15, 20)),
new StoredUsage("category1", f.AddDays(7), Counters(17, 22))
};
A.CallTo(() => usageStore.QueryAsync($"{key}_API", f, t))
.Returns(originalData);
var result = await sut.GetPreviousCallsAsync(key, f, t);
Assert.Equal(55, result);
}
[Fact] [Fact]
public async Task Should_fill_missing_days() public async Task Should_fill_missing_days()
{ {

19
tests/Squidex.Infrastructure.Tests/UsageTracking/CachingUsageTrackerTests.cs

@ -59,5 +59,24 @@ namespace Squidex.Infrastructure.UsageTracking
A.CallTo(() => inner.GetMonthlyCallsAsync(key, DateTime.Today)) A.CallTo(() => inner.GetMonthlyCallsAsync(key, DateTime.Today))
.MustHaveHappened(Repeated.Exactly.Once); .MustHaveHappened(Repeated.Exactly.Once);
} }
[Fact]
public async Task Should_cache_days_usage()
{
var f = DateTime.Today;
var t = DateTime.Today.AddDays(10);
A.CallTo(() => inner.GetPreviousCallsAsync(key, f, t))
.Returns(120);
var result1 = await sut.GetPreviousCallsAsync(key, f, t);
var result2 = await sut.GetPreviousCallsAsync(key, f, t);
Assert.Equal(120, result1);
Assert.Equal(120, result2);
A.CallTo(() => inner.GetPreviousCallsAsync(key, f, t))
.MustHaveHappened(Repeated.Exactly.Once);
}
} }
} }

Loading…
Cancel
Save