diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index 6d5d0430f5..535f3dbcd9 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -117,7 +117,7 @@ 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.SumUp(counters)); + jobs.AddOrUpdate((key, category, date), counters, (k, p) => p.SumpUpCloned(counters)); #pragma warning restore MA0105 // Use the lambda parameters instead of using a closure return Task.CompletedTask; @@ -191,7 +191,7 @@ public async Task GetAsync(string key, DateOnly fromDate, DateOnly toD foreach (var usage in queried) { - result.SumUp(usage.Counters); + result.SumpUp(usage.Counters); } return result; diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs b/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs index 903c1468cc..c13e34696d 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs @@ -42,15 +42,22 @@ public long GetInt64(string name) return (long)value; } - public Counters SumUp(Counters counters) + public Counters SumpUpCloned(Counters counters) { - foreach (var (key, value) in counters) + var result = new Counters(this); + + return result.SumpUp(counters); + } + + public Counters SumpUp(Counters source) + { + foreach (var (key, value) in source) { var newValue = value; - if (TryGetValue(key, out var temp)) + if (TryGetValue(key, out var existing)) { - newValue += temp; + newValue += existing; } this[key] = newValue; diff --git a/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs b/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs index a4e53b8f44..1fcf082aaa 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -243,6 +243,40 @@ public async Task Should_write_usage_in_batches() .MustHaveHappened(); } + [Fact] + public async Task Should_write_with_shared_counters() + { + var key1 = Guid.NewGuid().ToString(); + var key2 = Guid.NewGuid().ToString(); + + var counters1 = Counters(a: 1, b: 2); + + await sut.TrackAsync(date, key1, "my-category", counters1, ct); + await sut.TrackAsync(date, key2, "my-category", counters1, ct); + + await sut.TrackAsync(date, key2, "my-category", Counters(a: 0.3, b: 2000), ct); + + UsageUpdate[]? updates = null; + + A.CallTo(() => usageStore.TrackUsagesAsync(A._, A._)) + .Invokes(args => + { + updates = args.GetArgument(0)!; + }); + + // Wait for the timer to trigger. + await WaitForCompletion(); + + updates.Should().BeEquivalentTo(new[] + { + new UsageUpdate(date, key1, "my-category", Counters(a: 1.0, b: 2)), + new UsageUpdate(date, key2, "my-category", Counters(a: 1.3, b: 2002)), + }, o => o.ComparingByMembers()); + + A.CallTo(() => usageStore.TrackUsagesAsync(A._, A._)) + .MustHaveHappened(); + } + private async Task WaitForCompletion() { sut.Next(); diff --git a/frontend/src/app/features/content/shared/forms/field-editor.component.html b/frontend/src/app/features/content/shared/forms/field-editor.component.html index 38b758d6ef..2dde0a0184 100644 --- a/frontend/src/app/features/content/shared/forms/field-editor.component.html +++ b/frontend/src/app/features/content/shared/forms/field-editor.component.html @@ -106,7 +106,7 @@ - + @@ -195,7 +195,7 @@ - + - + \ No newline at end of file diff --git a/frontend/src/app/features/rules/shared/actions/generic-action.component.html b/frontend/src/app/features/rules/shared/actions/generic-action.component.html index 22e4550391..8e0bd6ccf5 100644 --- a/frontend/src/app/features/rules/shared/actions/generic-action.component.html +++ b/frontend/src/app/features/rules/shared/actions/generic-action.component.html @@ -25,7 +25,7 @@ - +
diff --git a/frontend/src/app/features/schemas/pages/schema/fields/types/json-more.component.html b/frontend/src/app/features/schemas/pages/schema/fields/types/json-more.component.html index 083088596d..14af1abf80 100644 --- a/frontend/src/app/features/schemas/pages/schema/fields/types/json-more.component.html +++ b/frontend/src/app/features/schemas/pages/schema/fields/types/json-more.component.html @@ -2,7 +2,7 @@
- + {{ 'schemas.field.graphQLSchemaHint' | sqxTranslate }} diff --git a/frontend/src/app/features/schemas/pages/schemas/schema-form.component.html b/frontend/src/app/features/schemas/pages/schemas/schema-form.component.html index e499d73716..a8ea94ac64 100644 --- a/frontend/src/app/features/schemas/pages/schemas/schema-form.component.html +++ b/frontend/src/app/features/schemas/pages/schemas/schema-form.component.html @@ -105,7 +105,7 @@ {{ 'common.hide' | sqxTranslate }} - +
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 52339bf745..1ff4b5aa79 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 @@ -61,7 +61,7 @@ export class CodeEditorComponent extends StatefulControlComponent<{}, any> imple @Input({ transform: booleanAttribute }) public wordWrap = false; - @Input({ transform: numberAttribute }) + @Input() public height: number | 'auto' | 'full' = 'full'; @Input({ transform: booleanAttribute }) diff --git a/frontend/src/app/framework/angular/forms/form-alert.component.ts b/frontend/src/app/framework/angular/forms/form-alert.component.ts index aedcc28c70..08c808ce31 100644 --- a/frontend/src/app/framework/angular/forms/form-alert.component.ts +++ b/frontend/src/app/framework/angular/forms/form-alert.component.ts @@ -18,10 +18,10 @@ export class FormAlertComponent { public class = ''; @Input({ transform: numberAttribute }) - public marginTop: number | string | undefined | null = 2; + public marginTop: number | undefined | null = 2; @Input({ transform: numberAttribute }) - public marginBottom: number | string | undefined | null = 4; + public marginBottom: number | undefined | null = 4; @Input({ transform: booleanAttribute }) public light?: boolean | null; diff --git a/frontend/src/app/shared/components/tour-hint.directive.ts b/frontend/src/app/shared/components/tour-hint.directive.ts index c742485706..5ecd2106f4 100644 --- a/frontend/src/app/shared/components/tour-hint.directive.ts +++ b/frontend/src/app/shared/components/tour-hint.directive.ts @@ -20,7 +20,7 @@ export class TourHintDirective extends ResourceOwner implements OnDestroy, OnIni public hintText!: string; @Input({ transform: numberAttribute }) - public hintAfter: number | string = 1000; + public hintAfter: number = 1000; @Input() public hintPosition?: FloatingPlacement; diff --git a/frontend/src/app/theme/_lists.scss b/frontend/src/app/theme/_lists.scss index 5ba1d80d2a..7e1ac84d78 100644 --- a/frontend/src/app/theme/_lists.scss +++ b/frontend/src/app/theme/_lists.scss @@ -38,6 +38,7 @@ thead { // Small font size for the table header, content is more important! th { + background: none; border: 0; color: $color-text-decent; font-size: .8rem;