diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs index 6d5d0430f..535f3dbcd 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/BackgroundUsageTracker.cs @@ -117,7 +117,7 @@ public sealed class BackgroundUsageTracker : DisposableObjectBase, IUsageTracker 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 sealed class BackgroundUsageTracker : DisposableObjectBase, IUsageTracker 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 903c1468c..c13e34696 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/Counters.cs @@ -42,15 +42,22 @@ public sealed class Counters : Dictionary 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 a4e53b8f4..1fcf082aa 100644 --- a/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs +++ b/backend/tests/Squidex.Infrastructure.Tests/UsageTracking/BackgroundUsageTrackerTests.cs @@ -243,6 +243,40 @@ public class BackgroundUsageTrackerTests .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 38b758d6e..2dde0a018 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 22e455039..8e0bd6ccf 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 083088596..14af1abf8 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 e499d7371..a8ea94ac6 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 52339bf74..1ff4b5aa7 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 aedcc28c7..08c808ce3 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 c74248570..5ecd2106f 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 5ba1d80d2..7e1ac84d7 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;