diff --git a/backend/src/Migrations/MigrationPath.cs b/backend/src/Migrations/MigrationPath.cs index 42d55f02ee..caafeb8113 100644 --- a/backend/src/Migrations/MigrationPath.cs +++ b/backend/src/Migrations/MigrationPath.cs @@ -15,7 +15,7 @@ namespace Migrations; public sealed class MigrationPath : IMigrationPath { - private const int CurrentVersion = 25; + private const int CurrentVersion = 26; private readonly IServiceProvider serviceProvider; public MigrationPath(IServiceProvider serviceProvider) @@ -114,10 +114,16 @@ public MigrationPath(IServiceProvider serviceProvider) } } - // Version 13: Json refactoring + // Version 13: Json refactoring. if (version < 13) { yield return serviceProvider.GetRequiredService(); } + + // Version 27: New rule statistics using normal usage collection. + if (version < 26) + { + yield return serviceProvider.GetRequiredService(); + } } } diff --git a/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs b/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs new file mode 100644 index 0000000000..d97b51ca28 --- /dev/null +++ b/backend/src/Migrations/Migrations/MongoDb/CopyRuleStatistics.cs @@ -0,0 +1,66 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; +using MongoDB.Driver; +using Squidex.Domain.Apps.Entities.Rules; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Migrations; +using Squidex.Infrastructure.MongoDb; + +namespace Migrations.Migrations.MongoDb; + +public sealed class CopyRuleStatistics : IMigration +{ + private readonly IMongoDatabase database; + private readonly IRuleUsageTracker ruleUsageTracker; + + [BsonIgnoreExtraElements] + public class Document + { + public DomainId AppId { get; private set; } + + public DomainId RuleId { get; private set; } + + public int NumFailed { get; private set; } + + public int NumSucceeded { get; private set; } + } + + public CopyRuleStatistics(IMongoDatabase database, IRuleUsageTracker ruleUsageTracker) + { + this.database = database; + this.ruleUsageTracker = ruleUsageTracker; + } + + public async Task UpdateAsync( + CancellationToken ct) + { + var collectionName = "RuleStatistics"; + + // Do not create the collection if not needed. + if (!await database.CollectionExistsAsync(collectionName, ct)) + { + return; + } + + var collection = database.GetCollection(collectionName); + + await foreach (var document in collection.Find(new BsonDocument()).ToAsyncEnumerable(ct)) + { + await ruleUsageTracker.TrackAsync( + document.AppId, + document.RuleId, + default, + 0, + document.NumSucceeded, + document.NumFailed, + ct); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.Rules.cs b/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.Rules.cs index 79cfba15e1..5e04d5be9a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.Rules.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Billing/UsageGate.Rules.cs @@ -103,18 +103,26 @@ async Task IRuleUsageTracker.TrackAsync(DomainId appId, DomainId ruleId, DateOnl var tasks = new List { - usageTracker.TrackAsync(date, appKey, ruleId.ToString(), counters, ct), usageTracker.TrackAsync(SummaryDate, appKey, ruleId.ToString(), counters, ct) }; + if (date != default) + { + tasks.Add(usageTracker.TrackAsync(date, appKey, ruleId.ToString(), counters, ct)); + } + var (_, _, teamId) = await GetPlanForAppAsync(appId, true, ct); if (teamId != null) { var teamKey = TeamRulesKey(teamId.Value); - tasks.Add(usageTracker.TrackAsync(date, teamKey, appId.ToString(), counters, ct)); tasks.Add(usageTracker.TrackAsync(SummaryDate, teamKey, appId.ToString(), counters, ct)); + + if (date != default) + { + tasks.Add(usageTracker.TrackAsync(date, teamKey, appId.ToString(), counters, ct)); + } } await Task.WhenAll(tasks); diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index 535f3dbcd9..3867f1a79c 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -116,9 +116,8 @@ public Task TrackAsync(DateOnly date, string key, string? category, Counters cou category = GetCategory(category); -#pragma warning disable MA0105 // Use the lambda parameters instead of using a closure - jobs.AddOrUpdate((key, category, date), counters, (k, p) => p.SumpUpCloned(counters)); -#pragma warning restore MA0105 // Use the lambda parameters instead of using a closure + // Create a copy of the counters on add, so that we do not share it. + jobs.AddOrUpdate((key, category, date), (_, args) => new Counters(args), (_, v, args) => v.Merge(args), counters); return Task.CompletedTask; } @@ -191,7 +190,7 @@ public async Task GetAsync(string key, DateOnly fromDate, DateOnly toD foreach (var usage in queried) { - result.SumpUp(usage.Counters); + result.Merge(usage.Counters); } return result; diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs b/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs index c13e34696d..2a99271669 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs @@ -42,14 +42,7 @@ public long GetInt64(string name) return (long)value; } - public Counters SumpUpCloned(Counters counters) - { - var result = new Counters(this); - - return result.SumpUp(counters); - } - - public Counters SumpUp(Counters source) + public Counters Merge(Counters source) { foreach (var (key, value) in source) { diff --git a/backend/src/Squidex/Config/Domain/StoreServices.cs b/backend/src/Squidex/Config/Domain/StoreServices.cs index 168b59cb0e..a73d7f5546 100644 --- a/backend/src/Squidex/Config/Domain/StoreServices.cs +++ b/backend/src/Squidex/Config/Domain/StoreServices.cs @@ -80,6 +80,9 @@ public static void AddSquidexStoreServices(this IServiceCollection services, ICo services.AddTransientAs() .As(); + services.AddTransientAs() + .As(); + services.AddTransientAs(c => new DeleteContentCollections(GetDatabase(c, mongoContentDatabaseName))) .As(); diff --git a/frontend/src/app/framework/angular/forms/editors/code-editor.component.ts b/frontend/src/app/framework/angular/forms/editors/code-editor.component.ts index 1ff4b5aa79..bb5386b24a 100644 --- a/frontend/src/app/framework/angular/forms/editors/code-editor.component.ts +++ b/frontend/src/app/framework/angular/forms/editors/code-editor.component.ts @@ -294,6 +294,8 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple printMargin: !this.singleLine, showGutter: !this.singleLine, }); + + this.aceEditor.commands.bindKey('Enter|Shift-Enter', this.singleLine ? 'null' : undefined); } private setValue(value: string) {