Browse Source

Asset tracking repository.

pull/65/head
Sebastian Stehle 9 years ago
parent
commit
b15445c860
  1. 1
      src/Squidex.Events/Assets/AssetDeleted.cs
  2. 4
      src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs
  3. 40
      src/Squidex.Read.MongoDb/Assets/MongoAssetStatsEntity.cs
  4. 91
      src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository.cs
  5. 78
      src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs
  6. 21
      src/Squidex.Read/Assets/IAssetStatsEntity.cs
  7. 21
      src/Squidex.Read/Assets/Repositories/IAssetStatsRepository.cs
  8. 7
      src/Squidex.Write/Assets/AssetDomainObject.cs
  9. 8
      src/Squidex/Config/Domain/StoreMongoDbModule.cs
  10. 15
      src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts
  11. 10
      tests/Squidex.Write.Tests/Assets/AssetDomainObjectTests.cs

1
src/Squidex.Events/Assets/AssetDeleted.cs

@ -13,5 +13,6 @@ namespace Squidex.Events.Assets
[TypeName("AssetDeletedEvent")]
public sealed class AssetDeleted : AssetEvent
{
public long DeletedSize { get; set; }
}
}

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

@ -113,9 +113,11 @@ namespace Squidex.Infrastructure.UsageTracking
var originalUsages = await usageStore.FindAsync(key, fromDate, toDate);
var enrichedUsages = new List<StoredUsage>();
var usagesDictionary = originalUsages.ToDictionary(x => x.Date);
for (var date = fromDate; date <= toDate; date = date.AddDays(1))
{
enrichedUsages.Add(originalUsages.FirstOrDefault(x => x.Date == date) ?? new StoredUsage(date, 0, 0));
enrichedUsages.Add(usagesDictionary.GetOrDefault(date) ?? new StoredUsage(date, 0, 0));
}
return enrichedUsages;

40
src/Squidex.Read.MongoDb/Assets/MongoAssetStatsEntity.cs

@ -0,0 +1,40 @@
// ==========================================================================
// MongoAssetStatsEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using MongoDB.Bson;
using MongoDB.Bson.Serialization.Attributes;
using Squidex.Read.Assets;
namespace Squidex.Read.MongoDb.Assets
{
public sealed class MongoAssetStatsEntity : IAssetStatsEntity
{
[BsonId]
[BsonElement]
[BsonRepresentation(BsonType.String)]
public string Id { get; set; }
[BsonRequired]
[BsonElement]
[BsonDateTimeOptions(DateOnly = true)]
public DateTime Date { get; set; }
[BsonRequired]
[BsonElement]
public Guid AppId { get; set; }
[BsonRequired]
[BsonElement]
public long TotalSize { get; set; }
[BsonRequired]
[BsonElement]
public long TotalCount { get; set; }
}
}

91
src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository.cs

@ -0,0 +1,91 @@
// ==========================================================================
// MongoAssetStatsRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Infrastructure;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.MongoDb;
using Squidex.Read.Assets;
using Squidex.Read.Assets.Repositories;
namespace Squidex.Read.MongoDb.Assets
{
public partial class MongoAssetStatsRepository : MongoRepositoryBase<MongoAssetStatsEntity>, IAssetStatsRepository, IEventConsumer
{
public MongoAssetStatsRepository(IMongoDatabase database)
: base(database)
{
}
protected override string CollectionName()
{
return "Projections_AssetStats";
}
protected override Task SetupCollectionAsync(IMongoCollection<MongoAssetStatsEntity> collection)
{
return Task.WhenAll(
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Ascending(x => x.Date)),
collection.Indexes.CreateOneAsync(IndexKeys.Ascending(x => x.AppId).Descending(x => x.Date)));
}
public async Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate)
{
var originalSizes = await Collection.Find(x => x.AppId == appId && x.Date >= fromDate && x.Date <= toDate).SortBy(x => x.Date).ToListAsync();
var enrichedSizes = new List<MongoAssetStatsEntity>();
var sizesDictionary = originalSizes.ToDictionary(x => x.Date);
var previousSize = long.MinValue;
var previousCount = long.MinValue;
for (var date = fromDate; date <= toDate; date = date.AddDays(1))
{
var size = sizesDictionary.GetOrDefault(date);
if (size != null)
{
previousSize = size.TotalSize;
previousCount = size.TotalCount;
}
else
{
if (previousSize < 0)
{
var firstBeforeRange = await Collection.Find(x => x.AppId == appId && x.Date < fromDate).SortByDescending(x => x.Date).FirstOrDefaultAsync();
previousSize = firstBeforeRange?.TotalSize ?? 0L;
previousCount = firstBeforeRange?.TotalCount ?? 0L;
}
size = new MongoAssetStatsEntity
{
Date = date,
TotalSize = previousSize,
TotalCount = previousCount
};
}
enrichedSizes.Add(size);
}
return enrichedSizes;
}
public async Task<long> GetTotalSizeAsync(Guid appId)
{
var entity = await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date).FirstOrDefaultAsync();
return entity?.TotalSize ?? 0;
}
}
}

78
src/Squidex.Read.MongoDb/Assets/MongoAssetStatsRepository_EventHandling.cs

@ -0,0 +1,78 @@
// ==========================================================================
// MongoAssetStatsRepository_EventHandling.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Threading.Tasks;
using MongoDB.Driver;
using Squidex.Events.Assets;
using Squidex.Infrastructure.CQRS.Events;
using Squidex.Infrastructure.Dispatching;
namespace Squidex.Read.MongoDb.Assets
{
public partial class MongoAssetStatsRepository
{
private static readonly UpdateOptions Upsert = new UpdateOptions { IsUpsert = true };
public string Name
{
get { return GetType().Name; }
}
public string EventsFilter
{
get { return "^asset-"; }
}
public Task On(Envelope<IEvent> @event)
{
return this.DispatchActionAsync(@event.Payload, @event.Headers);
}
protected Task On(AssetCreated @event, EnvelopeHeaders headers)
{
return UpdateSizeAsync(@event.AppId.Id, headers.Timestamp().ToDateTimeUtc().Date, @event.FileSize, 1);
}
protected Task On(AssetUpdated @event, EnvelopeHeaders headers)
{
return UpdateSizeAsync(@event.AppId.Id, headers.Timestamp().ToDateTimeUtc().Date, @event.FileSize, 0);
}
protected Task On(AssetDeleted @event, EnvelopeHeaders headers)
{
return UpdateSizeAsync(@event.AppId.Id, headers.Timestamp().ToDateTimeUtc().Date, -@event.DeletedSize, -1);
}
private async Task UpdateSizeAsync(Guid appId, DateTime date, long size, long count)
{
var id = $"{appId}_{date:yyyy-MM-dd}";
var entity = await Collection.Find(x => x.Id == id).FirstOrDefaultAsync();
if (entity == null)
{
var last = await Collection.Find(x => x.AppId == appId).SortByDescending(x => x.Date).FirstOrDefaultAsync();
entity = new MongoAssetStatsEntity
{
Id = id,
Date = date,
AppId = appId,
TotalSize = last?.TotalSize ?? 0,
TotalCount = last?.TotalCount ?? 0
};
}
entity.TotalSize += size;
entity.TotalCount += count;
await Collection.ReplaceOneAsync(x => x.Id == id, entity, Upsert);
}
}
}

21
src/Squidex.Read/Assets/IAssetStatsEntity.cs

@ -0,0 +1,21 @@
// ==========================================================================
// IAssetDaySizeEntity.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
namespace Squidex.Read.Assets
{
public interface IAssetStatsEntity
{
DateTime Date { get; }
long TotalSize { get; }
long TotalCount { get; }
}
}

21
src/Squidex.Read/Assets/Repositories/IAssetStatsRepository.cs

@ -0,0 +1,21 @@
// ==========================================================================
// IAssetDaySizeRepository.cs
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex Group
// All rights reserved.
// ==========================================================================
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Squidex.Read.Assets.Repositories
{
public interface IAssetStatsRepository
{
Task<IReadOnlyList<IAssetStatsEntity>> QueryAsync(Guid appId, DateTime fromDate, DateTime toDate);
Task<long> GetTotalSizeAsync(Guid appId);
}
}

7
src/Squidex.Write/Assets/AssetDomainObject.cs

@ -23,6 +23,7 @@ namespace Squidex.Write.Assets
{
private bool isDeleted;
private long fileVersion = -1;
private long totalSize;
private string fileName;
public bool IsDeleted
@ -49,11 +50,15 @@ namespace Squidex.Write.Assets
{
fileVersion = @event.FileVersion;
fileName = @event.FileName;
totalSize += @event.FileSize;
}
protected void On(AssetUpdated @event)
{
fileVersion = @event.FileVersion;
totalSize += @event.FileSize;
}
protected void On(AssetRenamed @event)
@ -115,7 +120,7 @@ namespace Squidex.Write.Assets
VerifyCreatedAndNotDeleted();
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted()));
RaiseEvent(SimpleMapper.Map(command, new AssetDeleted { DeletedSize = totalSize }));
return this;
}

8
src/Squidex/Config/Domain/StoreMongoDbModule.cs

@ -142,6 +142,14 @@ namespace Squidex.Config.Domain
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoAssetStatsRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IAssetStatsRepository>()
.As<IEventConsumer>()
.As<IExternalSystem>()
.AsSelf()
.SingleInstance();
builder.RegisterType<MongoAssetRepository>()
.WithParameter(ResolvedParameter.ForNamed<IMongoDatabase>(MongoDatabaseRegistration))
.As<IAssetRepository>()

15
src/Squidex/app/features/dashboard/pages/dashboard-page.component.ts

@ -54,10 +54,19 @@ export class DashboardPageComponent extends AppComponentBase implements OnInit,
this.appName()
.switchMap(app => this.usagesService.getMonthlyCalls(app))
.subscribe(dto => {
if (dto.count > 1000) {
this.monthlyCalls = Math.round(dto.count / 1000) + 'k';
let count = dto.count;
if (count > 1000) {
count = count / 1000;
if (count < 10) {
count = Math.round(count * 10) / 10;
} else {
count = Math.round(count);
}
this.monthlyCalls = count + 'k';
} else {
this.monthlyCalls = dto.count.toString();
this.monthlyCalls = count.toString();
}
});

10
tests/Squidex.Write.Tests/Assets/AssetDomainObjectTests.cs

@ -187,6 +187,7 @@ namespace Squidex.Write.Assets
public void Delete_should_update_properties_create_events()
{
CreateAsset();
UpdateAsset();
sut.Delete(CreateAssetCommand(new DeleteAsset()));
@ -194,7 +195,7 @@ namespace Squidex.Write.Assets
sut.GetUncomittedEvents()
.ShouldHaveSameEvents(
CreateAssetEvent(new AssetDeleted())
CreateAssetEvent(new AssetDeleted { DeletedSize = 2048 })
);
}
@ -205,6 +206,13 @@ namespace Squidex.Write.Assets
((IAggregate)sut).ClearUncommittedEvents();
}
private void UpdateAsset()
{
sut.Update(CreateAssetCommand(new UpdateAsset { File = file }));
((IAggregate)sut).ClearUncommittedEvents();
}
private void DeleteAsset()
{
sut.Delete(CreateAssetCommand(new DeleteAsset()));

Loading…
Cancel
Save