From af56812c07a385fd8ed9e59866fd570feb0d47dd Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Sat, 10 Oct 2020 22:22:29 +0200 Subject: [PATCH 1/4] Caching extracted (#587) * Caching extracted * Tests cleanup --- .../ContentWrapper/ContentDataObject.cs | 1 - .../Operations/QueryReferrersAsync.cs | 2 - .../AppProvider.cs | 2 +- .../Apps/Indexes/AppsIndex.cs | 14 +- .../Contents/ContentOperationContext.cs | 2 - .../Contents/GraphQL/CachingGraphQLService.cs | 9 +- .../Contents/Queries/ContentQueryParser.cs | 12 +- .../Text/State/CachingTextIndexerState.cs | 2 +- .../Rules/RuleEnqueuer.cs | 2 +- .../Schemas/Indexes/SchemasIndex.cs | 14 +- .../Squidex.Infrastructure.Amazon.csproj | 2 +- .../Caching/AsyncLocalCache.cs | 79 ---------- .../Caching/CachingProviderBase.cs | 28 ---- .../Caching/ILocalCache.cs | 22 --- .../Squidex.Infrastructure/Caching/IPubSub.cs | 18 --- .../Caching/IPubSubSubscription.cs | 13 -- .../Caching/IReplicatedCache.cs | 20 --- .../Caching/LRUCache.cs | 123 --------------- .../Caching/LRUCacheItem.cs | 19 --- .../Caching/LocalCacheExtensions.cs | 36 ----- .../Caching/ReplicatedCache.cs | 103 ------------- .../Caching/ReplicatedCacheOptions.cs | 14 -- .../Caching/SimplePubSub.cs | 32 ---- .../Commands/Rebuilder.cs | 2 +- .../Orleans/ActivationLimiter.cs | 2 +- .../Orleans/IPubSubGrain.cs | 19 --- .../Orleans/IPubSubGrainObserver.cs | 19 --- .../Orleans/LocalCacheFilter.cs | 2 +- .../Orleans/OrleansPubSub.cs | 79 ---------- .../Orleans/OrleansPubSubGrain.cs | 42 ------ .../Squidex.Infrastructure.csproj | 3 +- .../UsageTracking/CachingUsageTracker.cs | 11 +- .../Pipeline/LocalCacheMiddleware.cs | 2 +- .../Config/Domain/InfrastructureServices.cs | 14 +- .../Squidex/Config/Orleans/OrleansServices.cs | 2 + backend/src/Squidex/Squidex.csproj | 3 +- .../Apps/Indexes/AppsIndexTests.cs | 5 +- .../ContentChangedTriggerHandlerTests.cs | 2 +- .../Rules/RuleEnqueuerTests.cs | 2 +- .../Schemas/Indexes/SchemasIndexTests.cs | 5 +- .../Caching/AsyncLocalCacheTests.cs | 123 --------------- .../Caching/LRUCacheTests.cs | 98 ------------ .../Caching/ReplicatedCacheTests.cs | 141 ------------------ .../Orleans/PubSubTests.cs | 82 ---------- 44 files changed, 57 insertions(+), 1170 deletions(-) delete mode 100644 backend/src/Squidex.Infrastructure/Caching/AsyncLocalCache.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/CachingProviderBase.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/ILocalCache.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/IPubSub.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/IPubSubSubscription.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/IReplicatedCache.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/LRUCache.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/LocalCacheExtensions.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs delete mode 100644 backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs delete mode 100644 backend/src/Squidex.Infrastructure/Orleans/IPubSubGrain.cs delete mode 100644 backend/src/Squidex.Infrastructure/Orleans/IPubSubGrainObserver.cs delete mode 100644 backend/src/Squidex.Infrastructure/Orleans/OrleansPubSub.cs delete mode 100644 backend/src/Squidex.Infrastructure/Orleans/OrleansPubSubGrain.cs delete mode 100644 backend/tests/Squidex.Infrastructure.Tests/Caching/AsyncLocalCacheTests.cs delete mode 100644 backend/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs delete mode 100644 backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs delete mode 100644 backend/tests/Squidex.Infrastructure.Tests/Orleans/PubSubTests.cs diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs index 813cc2c1a..148434072 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs @@ -13,7 +13,6 @@ using Jint.Native; using Jint.Native.Object; using Jint.Runtime; using Jint.Runtime.Descriptors; -using Orleans; using Squidex.Domain.Apps.Core.Contents; using Squidex.Infrastructure; diff --git a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrersAsync.cs b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrersAsync.cs index 7456f4b53..cd2665146 100644 --- a/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrersAsync.cs +++ b/backend/src/Squidex.Domain.Apps.Entities.MongoDb/Contents/Operations/QueryReferrersAsync.cs @@ -6,8 +6,6 @@ // ========================================================================== using System; -using System.Collections.Generic; -using System.Linq; using System.Threading; using System.Threading.Tasks; using MongoDB.Driver; diff --git a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs index dc51bfc54..ef760f710 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/AppProvider.cs @@ -9,6 +9,7 @@ using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using Squidex.Caching; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Indexes; using Squidex.Domain.Apps.Entities.Rules; @@ -16,7 +17,6 @@ using Squidex.Domain.Apps.Entities.Rules.Indexes; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Indexes; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Security; namespace Squidex.Domain.Apps.Entities diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs index 7cd694b26..cb0b65f26 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Indexes/AppsIndex.cs @@ -10,9 +10,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans; +using Squidex.Caching; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Orleans; @@ -36,7 +36,6 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes Guard.NotNull(replicatedCache, nameof(replicatedCache)); this.grainFactory = grainFactory; - this.replicatedCache = replicatedCache; } @@ -149,7 +148,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes if (app != null) { - CacheIt(app, false); + await CacheItAsync(app, false); } return app; @@ -227,7 +226,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes if (app != null) { - CacheIt(app, true); + await CacheItAsync(app, true); switch (context.Command) { @@ -319,10 +318,11 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes return $"APPS_NAME_{name}"; } - private void CacheIt(IAppEntity app, bool publish) + private Task CacheItAsync(IAppEntity app, bool publish) { - replicatedCache.Add(GetCacheKey(app.Id), app, CacheDuration, publish); - replicatedCache.Add(GetCacheKey(app.Name), app, CacheDuration, publish); + return Task.WhenAll( + replicatedCache.AddAsync(GetCacheKey(app.Id), app, CacheDuration, publish), + replicatedCache.AddAsync(GetCacheKey(app.Name), app, CacheDuration, publish)); } } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs index 5908d4174..48c3a669a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentOperationContext.cs @@ -16,11 +16,9 @@ using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.ValidateContent; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Contents.Commands; -using Squidex.Domain.Apps.Entities.Contents.Repositories; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Log; -using Squidex.Infrastructure.Translations; using Squidex.Infrastructure.Validation; #pragma warning disable IDE0016 // Use 'throw' expression diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs index 51f687712..10f58739b 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/GraphQL/CachingGraphQLService.cs @@ -15,20 +15,21 @@ using Squidex.Domain.Apps.Core; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Assets; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { - public sealed class CachingGraphQLService : CachingProviderBase, IGraphQLService + public sealed class CachingGraphQLService : IGraphQLService { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(10); + private readonly IMemoryCache cache; private readonly IServiceProvider resolver; public CachingGraphQLService(IMemoryCache cache, IServiceProvider resolver) - : base(cache) { + Guard.NotNull(cache, nameof(cache)); Guard.NotNull(resolver, nameof(resolver)); + this.cache = cache; this.resolver = resolver; } @@ -83,7 +84,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.GraphQL { var cacheKey = CreateCacheKey(app.Id, app.Version.ToString()); - return Cache.GetOrCreateAsync(cacheKey, async entry => + return cache.GetOrCreateAsync(cacheKey, async entry => { entry.AbsoluteExpirationRelativeToNow = CacheDuration; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs index 48f404124..c73fb238d 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/ContentQueryParser.cs @@ -20,7 +20,6 @@ using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Json; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Log; @@ -33,20 +32,21 @@ using Squidex.Text; namespace Squidex.Domain.Apps.Entities.Contents.Queries { - public class ContentQueryParser : CachingProviderBase + public class ContentQueryParser { private static readonly TimeSpan CacheTime = TimeSpan.FromMinutes(60); + private readonly IMemoryCache cache; private readonly IJsonSerializer jsonSerializer; private readonly ContentOptions options; public ContentQueryParser(IMemoryCache cache, IJsonSerializer jsonSerializer, IOptions options) - : base(cache) { Guard.NotNull(jsonSerializer, nameof(jsonSerializer)); + Guard.NotNull(cache, nameof(cache)); Guard.NotNull(options, nameof(options)); this.jsonSerializer = jsonSerializer; - + this.cache = cache; this.options = options.Value; } @@ -138,7 +138,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var cacheKey = BuildJsonCacheKey(context.App, schema, context.IsFrontendClient); - var result = Cache.GetOrCreate(cacheKey, entry => + var result = cache.GetOrCreate(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = CacheTime; @@ -152,7 +152,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var cacheKey = BuildEmdCacheKey(context.App, schema, context.IsFrontendClient); - var result = Cache.GetOrCreate(cacheKey, entry => + var result = cache.GetOrCreate(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = CacheTime; diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/State/CachingTextIndexerState.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/State/CachingTextIndexerState.cs index e9dacb3be..2bb1a874f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/State/CachingTextIndexerState.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Text/State/CachingTextIndexerState.cs @@ -8,8 +8,8 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; +using Squidex.Caching; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; namespace Squidex.Domain.Apps.Entities.Contents.Text.State { diff --git a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs index fc01ca334..3335bbeb1 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Rules/RuleEnqueuer.cs @@ -9,12 +9,12 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; +using Squidex.Caching; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Events; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.EventSourcing; namespace Squidex.Domain.Apps.Entities.Rules diff --git a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs index a9a1646ab..2a84f7fc3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Schemas/Indexes/SchemasIndex.cs @@ -10,9 +10,9 @@ using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using Orleans; +using Squidex.Caching; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Log; using Squidex.Infrastructure.Translations; @@ -33,7 +33,6 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes Guard.NotNull(replicatedCache, nameof(replicatedCache)); this.grainFactory = grainFactory; - this.replicatedCache = replicatedCache; } @@ -99,7 +98,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes if (schema != null) { - CacheIt(schema, false); + await CacheItAsync(schema, false); } return schema; @@ -159,7 +158,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes if (schema != null) { - CacheIt(schema, true); + await CacheItAsync(schema, true); if (context.Command is DeleteSchema) { @@ -221,10 +220,11 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes return $"SCHEMAS_ID_{appId}_{id}"; } - private void CacheIt(ISchemaEntity schema, bool publish) + private Task CacheItAsync(ISchemaEntity schema, bool publish) { - replicatedCache.Add(GetCacheKey(schema.AppId.Id, schema.Id), schema, CacheDuration, publish); - replicatedCache.Add(GetCacheKey(schema.AppId.Id, schema.SchemaDef.Name), schema, CacheDuration, publish); + return Task.WhenAll( + replicatedCache.AddAsync(GetCacheKey(schema.AppId.Id, schema.Id), schema, CacheDuration, publish), + replicatedCache.AddAsync(GetCacheKey(schema.AppId.Id, schema.SchemaDef.Name), schema, CacheDuration, publish)); } } } diff --git a/backend/src/Squidex.Infrastructure.Amazon/Squidex.Infrastructure.Amazon.csproj b/backend/src/Squidex.Infrastructure.Amazon/Squidex.Infrastructure.Amazon.csproj index 6ab072ae1..ad0e92c2b 100644 --- a/backend/src/Squidex.Infrastructure.Amazon/Squidex.Infrastructure.Amazon.csproj +++ b/backend/src/Squidex.Infrastructure.Amazon/Squidex.Infrastructure.Amazon.csproj @@ -6,7 +6,7 @@ enable - + diff --git a/backend/src/Squidex.Infrastructure/Caching/AsyncLocalCache.cs b/backend/src/Squidex.Infrastructure/Caching/AsyncLocalCache.cs deleted file mode 100644 index 9a7611c10..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/AsyncLocalCache.cs +++ /dev/null @@ -1,79 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Concurrent; -using System.Threading; -using Squidex.Infrastructure.Tasks; - -#pragma warning disable CS8601 // Possible null reference assignment. - -namespace Squidex.Infrastructure.Caching -{ - public sealed class AsyncLocalCache : ILocalCache - { - private static readonly AsyncLocal> LocalCache = new AsyncLocal>(); - private static readonly AsyncLocalCleaner> Cleaner; - - static AsyncLocalCache() - { - Cleaner = new AsyncLocalCleaner>(LocalCache); - } - - public IDisposable StartContext() - { - LocalCache.Value = new ConcurrentDictionary(); - - return Cleaner; - } - - public void Add(object key, object? value) - { - var cacheKey = GetCacheKey(key); - - var cache = LocalCache.Value; - - if (cache != null) - { - cache[cacheKey] = value; - } - } - - public void Remove(object key) - { - var cacheKey = GetCacheKey(key); - - var cache = LocalCache.Value; - - if (cache != null) - { - cache.TryRemove(cacheKey, out _); - } - } - - public bool TryGetValue(object key, out object? value) - { - var cacheKey = GetCacheKey(key); - - var cache = LocalCache.Value; - - if (cache != null) - { - return cache.TryGetValue(cacheKey, out value); - } - - value = null; - - return false; - } - - private static string GetCacheKey(object key) - { - return $"CACHE_{key}"; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/CachingProviderBase.cs b/backend/src/Squidex.Infrastructure/Caching/CachingProviderBase.cs deleted file mode 100644 index 9ff773e51..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/CachingProviderBase.cs +++ /dev/null @@ -1,28 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using Microsoft.Extensions.Caching.Memory; - -namespace Squidex.Infrastructure.Caching -{ - public abstract class CachingProviderBase - { - private readonly IMemoryCache cache; - - protected IMemoryCache Cache - { - get { return cache; } - } - - protected CachingProviderBase(IMemoryCache cache) - { - Guard.NotNull(cache, nameof(cache)); - - this.cache = cache; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/ILocalCache.cs b/backend/src/Squidex.Infrastructure/Caching/ILocalCache.cs deleted file mode 100644 index 8be615ca3..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/ILocalCache.cs +++ /dev/null @@ -1,22 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Infrastructure.Caching -{ - public interface ILocalCache - { - IDisposable StartContext(); - - void Add(object key, object? value); - - void Remove(object key); - - bool TryGetValue(object key, out object? value); - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/IPubSub.cs b/backend/src/Squidex.Infrastructure/Caching/IPubSub.cs deleted file mode 100644 index a2dbad92e..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/IPubSub.cs +++ /dev/null @@ -1,18 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Infrastructure.Caching -{ - public interface IPubSub - { - void Publish(object message); - - void Subscribe(Action handler); - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/IPubSubSubscription.cs b/backend/src/Squidex.Infrastructure/Caching/IPubSubSubscription.cs deleted file mode 100644 index c735e6d08..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/IPubSubSubscription.cs +++ /dev/null @@ -1,13 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.Caching -{ - internal interface IPubSubSubscription - { - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure/Caching/IReplicatedCache.cs b/backend/src/Squidex.Infrastructure/Caching/IReplicatedCache.cs deleted file mode 100644 index 075e515f9..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/IReplicatedCache.cs +++ /dev/null @@ -1,20 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; - -namespace Squidex.Infrastructure.Caching -{ - public interface IReplicatedCache - { - void Add(string key, object? value, TimeSpan expiration, bool invalidate); - - void Remove(string key); - - bool TryGetValue(string key, out object? value); - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/LRUCache.cs b/backend/src/Squidex.Infrastructure/Caching/LRUCache.cs deleted file mode 100644 index cfb1236d5..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/LRUCache.cs +++ /dev/null @@ -1,123 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; - -namespace Squidex.Infrastructure.Caching -{ - public sealed class LRUCache where TKey : notnull - { - private readonly Dictionary>> cacheMap = new Dictionary>>(); - private readonly LinkedList> cacheHistory = new LinkedList>(); - private readonly int capacity; - private readonly Action itemEvicted; - - public int Count - { - get { return cacheMap.Count; } - } - - public IEnumerable Keys - { - get { return cacheMap.Keys; } - } - - public LRUCache(int capacity, Action? itemEvicted = null) - { - Guard.GreaterThan(capacity, 0, nameof(capacity)); - - this.capacity = capacity; - - this.itemEvicted = itemEvicted ?? ((key, value) => { }); - } - - public void Clear() - { - cacheHistory.Clear(); - cacheMap.Clear(); - } - - public bool Set(TKey key, TValue value) - { - if (cacheMap.TryGetValue(key, out var node)) - { - node.Value.Value = value; - - cacheHistory.Remove(node); - cacheHistory.AddLast(node); - - cacheMap[key] = node; - - return true; - } - - if (cacheMap.Count >= capacity) - { - RemoveFirst(); - } - - var cacheItem = new LRUCacheItem { Key = key, Value = value }; - - node = new LinkedListNode>(cacheItem); - - cacheMap.Add(key, node); - cacheHistory.AddLast(node); - - return false; - } - - public bool Remove(TKey key) - { - if (cacheMap.TryGetValue(key, out var node)) - { - cacheMap.Remove(key); - cacheHistory.Remove(node); - - return true; - } - - return false; - } - - public bool TryGetValue(TKey key, [MaybeNullWhen(false)] out TValue value) - { - value = default!; - - if (cacheMap.TryGetValue(key, out var node)) - { - value = node.Value.Value; - - cacheHistory.Remove(node); - cacheHistory.AddLast(node); - - return true; - } - - return false; - } - - public bool Contains(TKey key) - { - return cacheMap.ContainsKey(key); - } - - private void RemoveFirst() - { - var node = cacheHistory.First; - - if (node != null) - { - itemEvicted(node.Value.Key, node.Value.Value); - - cacheMap.Remove(node.Value.Key); - cacheHistory.RemoveFirst(); - } - } - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs b/backend/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs deleted file mode 100644 index 3a6b20ef4..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/LRUCacheItem.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -#pragma warning disable SA1401 // Fields must be private -#pragma warning disable CS8618 // Non-nullable field is uninitialized. Consider declaring as nullable. - -namespace Squidex.Infrastructure.Caching -{ - internal class LRUCacheItem - { - public TKey Key; - - public TValue Value; - } -} \ No newline at end of file diff --git a/backend/src/Squidex.Infrastructure/Caching/LocalCacheExtensions.cs b/backend/src/Squidex.Infrastructure/Caching/LocalCacheExtensions.cs deleted file mode 100644 index aa50178bb..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/LocalCacheExtensions.cs +++ /dev/null @@ -1,36 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; - -namespace Squidex.Infrastructure.Caching -{ - public static class LocalCacheExtensions - { - public static async Task GetOrCreateAsync(this ILocalCache cache, object key, Func> task) - { - if (cache.TryGetValue(key, out var value)) - { - if (value is T typed) - { - return typed; - } - else - { - return default!; - } - } - - var result = await task(); - - cache.Add(key, result); - - return result; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs deleted file mode 100644 index 81c87e7ba..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCache.cs +++ /dev/null @@ -1,103 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; - -namespace Squidex.Infrastructure.Caching -{ - public sealed class ReplicatedCache : IReplicatedCache - { - private readonly Guid instanceId = Guid.NewGuid(); - private readonly IMemoryCache memoryCache; - private readonly IPubSub pubSub; - private readonly ReplicatedCacheOptions options; - - public class InvalidateMessage - { - public Guid Source { get; set; } - - public string Key { get; set; } - } - - public ReplicatedCache(IMemoryCache memoryCache, IPubSub pubSub, IOptions options) - { - Guard.NotNull(memoryCache, nameof(memoryCache)); - Guard.NotNull(pubSub, nameof(pubSub)); - Guard.NotNull(options, nameof(options)); - - this.memoryCache = memoryCache; - - this.pubSub = pubSub; - - if (options.Value.Enable) - { - this.pubSub.Subscribe(OnMessage); - } - - this.options = options.Value; - } - - private void OnMessage(object message) - { - if (message is InvalidateMessage invalidate && invalidate.Source != instanceId) - { - memoryCache.Remove(invalidate.Key); - } - } - - public void Add(string key, object? value, TimeSpan expiration, bool invalidate) - { - if (!options.Enable) - { - return; - } - - memoryCache.Set(key, value, expiration); - - if (invalidate) - { - Invalidate(key); - } - } - - public void Remove(string key) - { - if (!options.Enable) - { - return; - } - - memoryCache.Remove(key); - - Invalidate(key); - } - - public bool TryGetValue(string key, out object? value) - { - if (!options.Enable) - { - value = null; - - return false; - } - - return memoryCache.TryGetValue(key, out value); - } - - private void Invalidate(string key) - { - if (!options.Enable) - { - return; - } - - pubSub.Publish(new InvalidateMessage { Key = key, Source = instanceId }); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs b/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs deleted file mode 100644 index d1d1d9f48..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/ReplicatedCacheOptions.cs +++ /dev/null @@ -1,14 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -namespace Squidex.Infrastructure.Caching -{ - public sealed class ReplicatedCacheOptions - { - public bool Enable { get; set; } - } -} diff --git a/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs b/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs deleted file mode 100644 index 21383ee0d..000000000 --- a/backend/src/Squidex.Infrastructure/Caching/SimplePubSub.cs +++ /dev/null @@ -1,32 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; - -namespace Squidex.Infrastructure.Caching -{ - public class SimplePubSub : IPubSub - { - private readonly List> handlers = new List>(); - - public virtual void Publish(object message) - { - foreach (var handler in handlers) - { - handler(message); - } - } - - public virtual void Subscribe(Action handler) - { - Guard.NotNull(handler, nameof(handler)); - - handlers.Add(handler); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs index 433afcd60..3c41d36e9 100644 --- a/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs +++ b/backend/src/Squidex.Infrastructure/Commands/Rebuilder.cs @@ -10,7 +10,7 @@ using System.Collections.Generic; using System.Threading; using System.Threading.Tasks; using System.Threading.Tasks.Dataflow; -using Squidex.Infrastructure.Caching; +using Squidex.Caching; using Squidex.Infrastructure.EventSourcing; using Squidex.Infrastructure.States; diff --git a/backend/src/Squidex.Infrastructure/Orleans/ActivationLimiter.cs b/backend/src/Squidex.Infrastructure/Orleans/ActivationLimiter.cs index af4f44b2e..0d2367c0f 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/ActivationLimiter.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/ActivationLimiter.cs @@ -8,7 +8,7 @@ using System; using System.Collections.Concurrent; using System.Threading; -using Squidex.Infrastructure.Caching; +using Squidex.Caching; namespace Squidex.Infrastructure.Orleans { diff --git a/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrain.cs b/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrain.cs deleted file mode 100644 index 3c45bd87d..000000000 --- a/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrain.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Orleans; - -namespace Squidex.Infrastructure.Orleans -{ - public interface IPubSubGrain : IGrainWithStringKey - { - Task SubscribeAsync(IPubSubGrainObserver observer); - - Task PublishAsync(object message); - } -} diff --git a/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrainObserver.cs b/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrainObserver.cs deleted file mode 100644 index a7ea60e54..000000000 --- a/backend/src/Squidex.Infrastructure/Orleans/IPubSubGrainObserver.cs +++ /dev/null @@ -1,19 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using Orleans; - -namespace Squidex.Infrastructure.Orleans -{ - public interface IPubSubGrainObserver : IGrainObserver - { - void Handle(object message); - - void Subscribe(Action handler); - } -} diff --git a/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs b/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs index 6e7dec26a..e839eeb37 100644 --- a/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs +++ b/backend/src/Squidex.Infrastructure/Orleans/LocalCacheFilter.cs @@ -8,7 +8,7 @@ using System; using System.Threading.Tasks; using Orleans; -using Squidex.Infrastructure.Caching; +using Squidex.Caching; namespace Squidex.Infrastructure.Orleans { diff --git a/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSub.cs b/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSub.cs deleted file mode 100644 index a222c09d9..000000000 --- a/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSub.cs +++ /dev/null @@ -1,79 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading; -using System.Threading.Tasks; -using Orleans; -using Squidex.Infrastructure.Caching; -using Squidex.Infrastructure.Tasks; - -namespace Squidex.Infrastructure.Orleans -{ - public sealed class OrleansPubSub : IBackgroundProcess, IPubSub - { - private readonly IPubSubGrain pubSubGrain; - private readonly IPubSubGrainObserver pubSubGrainObserver = new Observer(); - private readonly IGrainFactory grainFactory; - - private sealed class Observer : IPubSubGrainObserver - { - private readonly List> subscriptions = new List>(); - - public void Handle(object message) - { - foreach (var subscription in subscriptions) - { - try - { - subscription(message); - } - catch - { - continue; - } - } - } - - public void Subscribe(Action handler) - { - subscriptions.Add(handler); - } - } - - public OrleansPubSub(IGrainFactory grainFactory) - { - Guard.NotNull(grainFactory, nameof(grainFactory)); - - this.grainFactory = grainFactory; - - pubSubGrain = grainFactory.GetGrain(SingleGrain.Id); - } - - public async Task StartAsync(CancellationToken ct) - { - var reference = await grainFactory.CreateObjectReference(pubSubGrainObserver); - - await pubSubGrain.SubscribeAsync(reference); - } - - public void Publish(object message) - { - Guard.NotNull(message, nameof(message)); - - pubSubGrain.PublishAsync(message).Forget(); - } - - public void Subscribe(Action handler) - { - Guard.NotNull(handler, nameof(handler)); - - pubSubGrainObserver.Subscribe(handler); - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSubGrain.cs b/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSubGrain.cs deleted file mode 100644 index 776287d13..000000000 --- a/backend/src/Squidex.Infrastructure/Orleans/OrleansPubSubGrain.cs +++ /dev/null @@ -1,42 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using System.Threading.Tasks; -using Orleans; - -namespace Squidex.Infrastructure.Orleans -{ - public sealed class OrleansPubSubGrain : Grain, IPubSubGrain - { - private readonly List subscriptions = new List(); - - public Task PublishAsync(object message) - { - foreach (var subscription in subscriptions) - { - try - { - subscription.Handle(message); - } - catch - { - continue; - } - } - - return Task.CompletedTask; - } - - public Task SubscribeAsync(IPubSubGrainObserver observer) - { - subscriptions.Add(observer); - - return Task.CompletedTask; - } - } -} diff --git a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj index 9ce729126..415fd3cd8 100644 --- a/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj +++ b/backend/src/Squidex.Infrastructure/Squidex.Infrastructure.csproj @@ -15,7 +15,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -37,6 +37,7 @@ + diff --git a/backend/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs b/backend/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs index 830403891..9713175bd 100644 --- a/backend/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs +++ b/backend/src/Squidex.Infrastructure/UsageTracking/CachingUsageTracker.cs @@ -9,21 +9,22 @@ using System; using System.Collections.Generic; using System.Threading.Tasks; using Microsoft.Extensions.Caching.Memory; -using Squidex.Infrastructure.Caching; namespace Squidex.Infrastructure.UsageTracking { - public sealed class CachingUsageTracker : CachingProviderBase, IUsageTracker + public sealed class CachingUsageTracker : IUsageTracker { private static readonly TimeSpan CacheDuration = TimeSpan.FromMinutes(5); private readonly IUsageTracker inner; + private readonly IMemoryCache cache; public CachingUsageTracker(IUsageTracker inner, IMemoryCache cache) - : base(cache) { Guard.NotNull(inner, nameof(inner)); + Guard.NotNull(cache, nameof(cache)); this.inner = inner; + this.cache = cache; } public Task>> QueryAsync(string key, DateTime fromDate, DateTime toDate) @@ -46,7 +47,7 @@ namespace Squidex.Infrastructure.UsageTracking var cacheKey = string.Join("$", "Usage", nameof(GetForMonthAsync), key, date, category); - return Cache.GetOrCreateAsync(cacheKey, entry => + return cache.GetOrCreateAsync(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = CacheDuration; @@ -60,7 +61,7 @@ namespace Squidex.Infrastructure.UsageTracking var cacheKey = string.Join("$", "Usage", nameof(GetAsync), key, fromDate, toDate, category); - return Cache.GetOrCreateAsync(cacheKey, entry => + return cache.GetOrCreateAsync(cacheKey, entry => { entry.AbsoluteExpirationRelativeToNow = CacheDuration; diff --git a/backend/src/Squidex.Web/Pipeline/LocalCacheMiddleware.cs b/backend/src/Squidex.Web/Pipeline/LocalCacheMiddleware.cs index b9f050191..3a69604aa 100644 --- a/backend/src/Squidex.Web/Pipeline/LocalCacheMiddleware.cs +++ b/backend/src/Squidex.Web/Pipeline/LocalCacheMiddleware.cs @@ -7,8 +7,8 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; +using Squidex.Caching; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; namespace Squidex.Web.Pipeline { diff --git a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs index d855656a8..17cab2d16 100644 --- a/backend/src/Squidex/Config/Domain/InfrastructureServices.cs +++ b/backend/src/Squidex/Config/Domain/InfrastructureServices.cs @@ -15,6 +15,7 @@ using Squidex.Areas.Api.Controllers.Contents.Generator; using Squidex.Areas.Api.Controllers.News; using Squidex.Areas.Api.Controllers.News.Service; using Squidex.Areas.Api.Controllers.UI; +using Squidex.Caching; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting.Extensions; using Squidex.Domain.Apps.Core.Tags; @@ -24,7 +25,6 @@ using Squidex.Domain.Apps.Entities.Contents.Counter; using Squidex.Domain.Apps.Entities.Rules.UsageTracking; using Squidex.Domain.Apps.Entities.Tags; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.EventSourcing.Grains; using Squidex.Infrastructure.Orleans; @@ -49,6 +49,9 @@ namespace Squidex.Config.Domain services.Configure( config.GetSection("caching:replicated")); + services.AddReplicatedCache(); + services.AddAsyncLocalCache(); + services.AddSingletonAs(_ => SystemClock.Instance) .As(); @@ -58,15 +61,6 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As(); - - services.AddSingletonAs() - .As(); - services.AddSingletonAs() .AsOptional(); diff --git a/backend/src/Squidex/Config/Orleans/OrleansServices.cs b/backend/src/Squidex/Config/Orleans/OrleansServices.cs index 41ec6a8ce..ce836ed82 100644 --- a/backend/src/Squidex/Config/Orleans/OrleansServices.cs +++ b/backend/src/Squidex/Config/Orleans/OrleansServices.cs @@ -28,6 +28,8 @@ namespace Squidex.Config.Orleans { public static void ConfigureForSquidex(this ISiloBuilder builder, IConfiguration config) { + builder.AddOrleansPubSub(); + builder.ConfigureServices(siloServices => { siloServices.AddSingleton(); diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index 2e3edee69..2fb0153d2 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -58,7 +58,8 @@ - + + diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs index 38fb6b7aa..1c7335a9f 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Apps/Indexes/AppsIndexTests.cs @@ -10,12 +10,13 @@ using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans; +using Squidex.Caching; using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Security; @@ -45,7 +46,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Indexes .Returns(indexByUser); var cache = - new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(), + new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(A.Fake>()), Options.Create(new ReplicatedCacheOptions { Enable = true })); sut = new AppsIndex(grainFactory, cache); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs index 85cf72960..ed7237d03 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentChangedTriggerHandlerTests.cs @@ -11,6 +11,7 @@ using System.Collections.ObjectModel; using System.Linq; using System.Threading.Tasks; using FakeItEasy; +using Squidex.Caching; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules.EnrichedEvents; @@ -20,7 +21,6 @@ using Squidex.Domain.Apps.Events; using Squidex.Domain.Apps.Events.Assets; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.EventSourcing; using Xunit; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs index cbff2b81a..8b672689d 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEnqueuerTests.cs @@ -12,13 +12,13 @@ using FakeItEasy; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Options; using NodaTime; +using Squidex.Caching; using Squidex.Domain.Apps.Core.HandleRules; using Squidex.Domain.Apps.Core.Rules; using Squidex.Domain.Apps.Core.Rules.Triggers; using Squidex.Domain.Apps.Entities.Rules.Repositories; using Squidex.Domain.Apps.Events.Contents; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.EventSourcing; using Xunit; diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs index 832e0b513..a805dd7f1 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Schemas/Indexes/SchemasIndexTests.cs @@ -10,12 +10,13 @@ using System.Collections.Generic; using System.Threading.Tasks; using FakeItEasy; using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using Orleans; +using Squidex.Caching; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Entities.Schemas.Commands; using Squidex.Infrastructure; -using Squidex.Infrastructure.Caching; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Orleans; using Squidex.Infrastructure.Validation; @@ -40,7 +41,7 @@ namespace Squidex.Domain.Apps.Entities.Schemas.Indexes .Returns(index); var cache = - new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(), + new ReplicatedCache(new MemoryCache(Options.Create(new MemoryCacheOptions())), new SimplePubSub(A.Fake>()), Options.Create(new ReplicatedCacheOptions { Enable = true })); sut = new SchemasIndex(grainFactory, cache); diff --git a/backend/tests/Squidex.Infrastructure.Tests/Caching/AsyncLocalCacheTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Caching/AsyncLocalCacheTests.cs deleted file mode 100644 index 4ac0b04a9..000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/Caching/AsyncLocalCacheTests.cs +++ /dev/null @@ -1,123 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Threading.Tasks; -using Xunit; - -namespace Squidex.Infrastructure.Caching -{ - public class AsyncLocalCacheTests - { - private readonly ILocalCache sut = new AsyncLocalCache(); - private int called; - - [Fact] - public async Task Should_add_item_to_cache_when_context_exists() - { - using (sut.StartContext()) - { - sut.Add("Key", 1); - - await Task.Delay(5); - - AssertCache(sut, "Key", 1, true); - - await Task.Delay(5); - - sut.Remove("Key"); - - AssertCache(sut, "Key", null, false); - } - } - - [Fact] - public async Task Should_not_add_item_to_cache_when_context_not_exists() - { - sut.Add("Key", 1); - - await Task.Delay(5); - - AssertCache(sut, "Key", null, false); - - sut.Remove("Key"); - - await Task.Delay(5); - - AssertCache(sut, "Key", null, false); - } - - [Fact] - public async Task Should_call_creator_once_when_context_exists() - { - using (sut.StartContext()) - { - var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - await Task.Delay(5); - - var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - Assert.Equal(1, called); - Assert.Equal(1, value1); - Assert.Equal(1, value2); - } - } - - [Fact] - public async Task Should_call_creator_twice_when_context_not_exists() - { - var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - await Task.Delay(5); - - var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - Assert.Equal(2, called); - Assert.Equal(1, value1); - Assert.Equal(2, value2); - } - - [Fact] - public async Task Should_call_async_creator_once_when_context_exists() - { - using (sut.StartContext()) - { - var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - await Task.Delay(5); - - var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - Assert.Equal(1, called); - Assert.Equal(1, value1); - Assert.Equal(1, value2); - } - } - - [Fact] - public async Task Should_call_async_creator_twice_when_context_not_exists() - { - var value1 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - await Task.Delay(5); - - var value2 = await sut.GetOrCreateAsync("Key", () => Task.FromResult(++called)); - - Assert.Equal(2, called); - Assert.Equal(1, value1); - Assert.Equal(2, value2); - } - - private static void AssertCache(ILocalCache cache, string key, object? expectedValue, bool expectedFound) - { - var found = cache.TryGetValue(key, out var value); - - Assert.Equal(expectedFound, found); - Assert.Equal(expectedValue, value); - } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs deleted file mode 100644 index 8f765f076..000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/Caching/LRUCacheTests.cs +++ /dev/null @@ -1,98 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschränkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System.Collections.Generic; -using Xunit; - -namespace Squidex.Infrastructure.Caching -{ - public class LRUCacheTests - { - private readonly LRUCache sut = new LRUCache(10); - private readonly string key = "Key"; - - [Fact] - public void Should_always_override_when_setting_value() - { - sut.Set(key, 1); - sut.Set(key, 2); - - Assert.True(sut.TryGetValue(key, out var value)); - Assert.True(sut.Contains(key)); - - Assert.Equal(2, value); - } - - [Fact] - public void Should_clear_items() - { - sut.Set("1", 1); - sut.Set("2", 2); - - Assert.Equal(2, sut.Count); - - sut.Clear(); - - Assert.Equal(0, sut.Count); - } - - [Fact] - public void Should_remove_old_items_whentC_capacity_reached() - { - for (var i = 0; i < 15; i++) - { - sut.Set(i.ToString(), i); - } - - for (var i = 0; i < 5; i++) - { - Assert.False(sut.TryGetValue(i.ToString(), out var value)); - Assert.Equal(0, value); - } - - for (var i = 5; i < 15; i++) - { - Assert.True(sut.TryGetValue(i.ToString(), out var value)); - Assert.Equal(i, value); - } - } - - [Fact] - public void Should_notify_about_evicted_items() - { - var evicted = new List(); - - var cache = new LRUCache(3, (key, _) => evicted.Add(key)); - - cache.Set(1, 1); - cache.Set(2, 2); - cache.Set(3, 3); - cache.Set(1, 1); - cache.Set(4, 4); - cache.Set(5, 5); - - Assert.Equal(new List { 2, 3 }, evicted); - } - - [Fact] - public void Should_return_false_when_item_to_remove_does_not_exist() - { - Assert.False(sut.Remove(key)); - } - - [Fact] - public void Should_remove_inserted_item() - { - sut.Set(key, 2); - - Assert.True(sut.Remove(key)); - Assert.False(sut.Contains(key)); - Assert.False(sut.TryGetValue(key, out var value)); - Assert.Equal(0, value); - } - } -} \ No newline at end of file diff --git a/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs deleted file mode 100644 index 58a60a841..000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/Caching/ReplicatedCacheTests.cs +++ /dev/null @@ -1,141 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Threading.Tasks; -using FakeItEasy; -using Microsoft.Extensions.Caching.Memory; -using Microsoft.Extensions.Options; -using Xunit; - -namespace Squidex.Infrastructure.Caching -{ - public class ReplicatedCacheTests - { - private readonly IPubSub pubSub = A.Fake(options => options.CallsBaseMethods()); - private readonly ReplicatedCacheOptions options = new ReplicatedCacheOptions { Enable = true }; - private readonly ReplicatedCache sut; - - public ReplicatedCacheTests() - { - sut = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - } - - [Fact] - public void Should_serve_from_cache() - { - sut.Add("Key", 1, TimeSpan.FromMinutes(10), true); - - AssertCache(sut, "Key", 1, true); - - sut.Remove("Key"); - - AssertCache(sut, "Key", null, false); - } - - [Fact] - public void Should_not_serve_from_cache_disabled() - { - options.Enable = false; - - sut.Add("Key", 1, TimeSpan.FromMilliseconds(100), true); - - AssertCache(sut, "Key", null, false); - } - - [Fact] - public async Task Should_not_serve_from_cache_when_expired() - { - sut.Add("Key", 1, TimeSpan.FromMilliseconds(1), true); - - await Task.Delay(100); - - AssertCache(sut, "Key", null, false); - } - - [Fact] - public void Should_not_invalidate_other_instances_when_item_added_and_flag_is_false() - { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - - cache1.Add("Key", 1, TimeSpan.FromMinutes(1), false); - cache2.Add("Key", 2, TimeSpan.FromMinutes(1), false); - - AssertCache(cache1, "Key", 1, true); - AssertCache(cache2, "Key", 2, true); - } - - [Fact] - public void Should_invalidate_other_instances_when_item_added_and_flag_is_true() - { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - - cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); - cache2.Add("Key", 2, TimeSpan.FromMinutes(1), true); - - AssertCache(cache1, "Key", null, false); - AssertCache(cache2, "Key", 2, true); - } - - [Fact] - public void Should_invalidate_other_instances_when_item_removed() - { - var cache1 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - var cache2 = new ReplicatedCache(CreateMemoryCache(), pubSub, Options.Create(options)); - - cache1.Add("Key", 1, TimeSpan.FromMinutes(1), true); - cache2.Remove("Key"); - - AssertCache(cache1, "Key", null, false); - AssertCache(cache2, "Key", null, false); - } - - [Fact] - public void Should_send_invalidation_message_when_added_and_flag_is_true() - { - sut.Add("Key", 1, TimeSpan.FromMinutes(1), true); - - A.CallTo(() => pubSub.Publish(A._)) - .MustHaveHappened(); - } - - [Fact] - public void Should_not_send_invalidation_message_when_added_flag_is_false() - { - sut.Add("Key", 1, TimeSpan.FromMinutes(1), false); - - A.CallTo(() => pubSub.Publish(A._)) - .MustNotHaveHappened(); - } - - [Fact] - public void Should_not_send_invalidation_message_when_added_but_disabled() - { - options.Enable = false; - - sut.Add("Key", 1, TimeSpan.FromMinutes(1), true); - - A.CallTo(() => pubSub.Publish(A._)) - .MustNotHaveHappened(); - } - - private static void AssertCache(IReplicatedCache cache, string key, object? expectedValue, bool expectedFound) - { - var found = cache.TryGetValue(key, out var value); - - Assert.Equal(expectedFound, found); - Assert.Equal(expectedValue, value); - } - - private static MemoryCache CreateMemoryCache() - { - return new MemoryCache(Options.Create(new MemoryCacheOptions())); - } - } -} diff --git a/backend/tests/Squidex.Infrastructure.Tests/Orleans/PubSubTests.cs b/backend/tests/Squidex.Infrastructure.Tests/Orleans/PubSubTests.cs deleted file mode 100644 index dd40da06e..000000000 --- a/backend/tests/Squidex.Infrastructure.Tests/Orleans/PubSubTests.cs +++ /dev/null @@ -1,82 +0,0 @@ -// ========================================================================== -// Squidex Headless CMS -// ========================================================================== -// Copyright (c) Squidex UG (haftungsbeschraenkt) -// All rights reserved. Licensed under the MIT license. -// ========================================================================== - -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using Orleans; -using Orleans.TestingHost; -using Xunit; - -namespace Squidex.Infrastructure.Orleans -{ - [Trait("Category", "Dependencies")] - public class PubSubTests - { - [Fact] - public async Task Simple_pubsub_tests() - { - var cluster = - new TestClusterBuilder(3) - .Build(); - - await cluster.DeployAsync(); - - var sent = new HashSet - { - Guid.NewGuid(), - Guid.NewGuid(), - Guid.NewGuid() - }; - - var received1 = await CreateSubscriber(cluster.Client, sent.Count); - var received2 = await CreateSubscriber(cluster.Client, sent.Count); - - var pubSub = new OrleansPubSub(cluster.Client); - - foreach (var message in sent) - { - pubSub.Publish(message); - } - - await Task.WhenAny( - Task.WhenAll( - received1, - received2 - ), - Task.Delay(10000)); - - Assert.True(received1.Result.SetEquals(sent)); - Assert.True(received2.Result.SetEquals(sent)); - } - - private static async Task>> CreateSubscriber(IGrainFactory grainFactory, int expectedCount) - { - var pubSub = new OrleansPubSub(grainFactory); - - await pubSub.StartAsync(default); - - var received = new HashSet(); - var receivedCompleted = new TaskCompletionSource>(); - - pubSub.Subscribe(message => - { - if (message is Guid guid) - { - received.Add(guid); - } - - if (received.Count == expectedCount) - { - receivedCompleted.TrySetResult(received); - } - }); - - return receivedCompleted.Task; - } - } -} From 9f25000ebe2327c184922ebf351398290963cb53 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 12 Oct 2020 17:08:14 +0200 Subject: [PATCH 2/4] Allow updating data in change scripts. --- .../Contents/ContentDomainObject.cs | 15 +++++-- .../Contents/ContentDomainObjectTests.cs | 40 ++++++++++++++++--- .../settings/pages/roles/role.component.html | 2 +- 3 files changed, 47 insertions(+), 10 deletions(-) diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs index 98b125055..96612236c 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/ContentDomainObject.cs @@ -88,7 +88,7 @@ namespace Squidex.Domain.Apps.Entities.Contents if (!c.DoNotScript && c.Publish) { - await context.ExecuteScriptAsync(s => s.Change, + c.Data = await context.ExecuteScriptAndTransformAsync(s => s.Change, new ScriptVars { Operation = "Published", @@ -164,14 +164,23 @@ namespace Squidex.Domain.Apps.Entities.Contents if (!c.DoNotScript) { - await context.ExecuteScriptAsync(s => s.Change, + var data = Snapshot.Data.Clone(); + + var newData = await context.ExecuteScriptAndTransformAsync(s => s.Change, new ScriptVars { Operation = change.ToString(), - Data = Snapshot.Data, + Data = data, Status = c.Status, StatusOld = Snapshot.EditingStatus }); + + if (!newData.Equals(Snapshot.Data)) + { + var command = SimpleMapper.Map(c, new UpdateContent { Data = newData }); + + Update(command, newData); + } } ChangeStatus(c, change); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs index 7c569611e..0e84bd8b4 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentDomainObjectTests.cs @@ -166,7 +166,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft), "", ScriptOptions())) .MustHaveHappened(); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published), "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Published), "", ScriptOptions())) .MustHaveHappened(); } @@ -332,7 +332,7 @@ namespace Squidex.Domain.Apps.Entities.Contents CreateContentEvent(new ContentStatusChanged { Status = Status.Published, Change = StatusChange.Published }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Published, Status.Draft), "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Published, Status.Draft), "", ScriptOptions())) .MustHaveHappened(); } @@ -354,7 +354,7 @@ namespace Squidex.Domain.Apps.Entities.Contents CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "", ScriptOptions())) .MustHaveHappened(); } @@ -377,7 +377,35 @@ namespace Squidex.Domain.Apps.Entities.Contents CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Draft, Status.Published), "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft, Status.Published), "", ScriptOptions())) + .MustHaveHappened(); + } + + [Fact] + public async Task ChangeStatus_should_also_update_when_script_changes_data() + { + var command = new ChangeContentStatus { Status = Status.Draft }; + + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft, Status.Published), "", ScriptOptions())) + .Returns(otherData); + + await ExecuteCreateAsync(); + await ExecutePublishAsync(); + + var result = await PublishAsync(command); + + result.ShouldBeEquivalent(sut.Snapshot); + + Assert.Equal(Status.Draft, sut.Snapshot.CurrentVersion.Status); + Assert.Equal(otherData, sut.Snapshot.CurrentVersion.Data); + + LastEvents + .ShouldHaveSameEvents( + CreateContentEvent(new ContentUpdated { Data = otherData }), + CreateContentEvent(new ContentStatusChanged { Status = Status.Draft, Change = StatusChange.Unpublished }) + ); + + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Draft, Status.Published), "", ScriptOptions())) .MustHaveHappened(); } @@ -401,7 +429,7 @@ namespace Squidex.Domain.Apps.Entities.Contents CreateContentEvent(new ContentStatusChanged { Change = StatusChange.Change, Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(ScriptContext(data, null, Status.Archived, Status.Draft), "", ScriptOptions())) .MustHaveHappened(); } @@ -456,7 +484,7 @@ namespace Squidex.Domain.Apps.Entities.Contents CreateContentEvent(new ContentStatusChanged { Status = Status.Archived }) ); - A.CallTo(() => scriptEngine.ExecuteAsync(A._, "", ScriptOptions())) + A.CallTo(() => scriptEngine.TransformAsync(A._, "", ScriptOptions())) .MustHaveHappened(); } diff --git a/frontend/app/features/settings/pages/roles/role.component.html b/frontend/app/features/settings/pages/roles/role.component.html index 06a40c331..e50a25b6c 100644 --- a/frontend/app/features/settings/pages/roles/role.component.html +++ b/frontend/app/features/settings/pages/roles/role.component.html @@ -69,7 +69,7 @@ {{descriptions[role.name] | sqxTranslate}} - +
{{control.value}} From 7e47faf12551a50d08139af656892134ba33d5ed Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 12 Oct 2020 17:46:55 +0200 Subject: [PATCH 3/4] Half width fields. --- backend/i18n/frontend_en.json | 1 + backend/i18n/frontend_it.json | 1 + backend/i18n/frontend_nl.json | 3 ++- backend/i18n/source/backend__ignore.json | 4 ++-- backend/i18n/source/frontend__ignore.json | 4 ++-- backend/i18n/source/frontend_en.json | 1 + .../Schemas/FieldProperties.cs | 2 ++ .../Schemas/Models/FieldPropertiesDto.cs | 5 +++++ .../pages/content/content-field.component.html | 2 +- .../pages/content/content-field.component.ts | 11 ++++++++++- .../content/content-section.component.html | 2 +- .../content/content-section.component.scss | 13 +++++++++++++ .../fields/forms/field-form-ui.component.html | 17 +++++++++++++++-- .../fields/forms/field-form-ui.component.scss | 3 --- frontend/app/shared/services/schemas.types.ts | 1 + frontend/app/shared/state/schemas.forms.ts | 1 + 16 files changed, 58 insertions(+), 13 deletions(-) diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index fe11289b7..035f51931 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -662,6 +662,7 @@ "schemas.field.empty": "No field created yet.", "schemas.field.enable": "Enable in UI", "schemas.field.enabledMarker": "Enabled", + "schemas.field.halfWidth": "Half Width", "schemas.field.hiddenMarker": "Hidden", "schemas.field.hide": "Hide in API", "schemas.field.hintsHint": "Describe this schema for documentation and user interfaces.", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index ccc8d5834..8284cdfea 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -662,6 +662,7 @@ "schemas.field.empty": "Nessun campo è stato ancora creato.", "schemas.field.enable": "Abilita nella UI", "schemas.field.enabledMarker": "Abilitato", + "schemas.field.halfWidth": "Half Width", "schemas.field.hiddenMarker": "Nasconsto", "schemas.field.hide": "Nascondi nelle API", "schemas.field.hintsHint": "Descrivi questo schema per la documentazione e le interfacce utente.", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index 27d3f70bd..494565614 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -58,7 +58,7 @@ "assets.deleteFolderConfirmTitle": "Map verwijderen", "assets.deleteMetadataConfirmText": "Wil je deze metadata echt verwijderen?", "assets.deleteMetadataConfirmTitle": "Metadata verwijderen", - "assets.deleteReferrerConfirmText": "Er wordt naar het item verwezen door een contentitem.\n\nWil je het item echt verwijderen?", + "assets.deleteReferrerConfirmText": "Er wordt naar het item verwezen door een contentitem. \n \n Wil je het item echt verwijderen?", "assets.deleteReferrerConfirmTitle": "Verwijder bestand", "assets.downloadVersion": "Download deze versie", "assets.dropToUpdate": "Zet neer om te updaten", @@ -662,6 +662,7 @@ "schemas.field.empty": "Nog geen veld aangemaakt.", "schemas.field.enable": "Inschakelen in gebruikersinterface", "schemas.field.enabledMarker": "Ingeschakeld", + "schemas.field.halfWidth": "Half Width", "schemas.field.hiddenMarker": "Verborgen", "schemas.field.hide": "Verbergen in API", "schemas.field.hintsHint": "Beschrijf dit schema voor documentatie en gebruikersinterfaces.", diff --git a/backend/i18n/source/backend__ignore.json b/backend/i18n/source/backend__ignore.json index a678d4af6..e9596e8ee 100644 --- a/backend/i18n/source/backend__ignore.json +++ b/backend/i18n/source/backend__ignore.json @@ -151,10 +151,10 @@ "/Squidex.Domain.Apps.Entities/Contents/Text/Elastic/ElasticSearchTextIndex.cs": [ "*" ], - "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager_Impl.cs": [ + "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager.cs": [ "*" ], - "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager.cs": [ + "/Squidex.Domain.Apps.Entities/Contents/Text/Lucene/IndexManager_Impl.cs": [ "*" ], "/Squidex.Domain.Apps.Entities/Notifications/NotificationEmailSender.cs": [ diff --git a/backend/i18n/source/frontend__ignore.json b/backend/i18n/source/frontend__ignore.json index 4635dc8c0..737bd389f 100644 --- a/backend/i18n/source/frontend__ignore.json +++ b/backend/i18n/source/frontend__ignore.json @@ -19,8 +19,8 @@ "#{{index + 1}}" ], "/features/content/shared/forms/field-editor.component.html": [ - "{{field.displayName}} {{displaySuffix}}", - "*" + "*", + "{{field.displayName}} {{displaySuffix}}" ], "/features/content/shared/references/references-editor.component.html": [ "·" diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index fe11289b7..035f51931 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -662,6 +662,7 @@ "schemas.field.empty": "No field created yet.", "schemas.field.enable": "Enable in UI", "schemas.field.enabledMarker": "Enabled", + "schemas.field.halfWidth": "Half Width", "schemas.field.hiddenMarker": "Hidden", "schemas.field.hide": "Hide in API", "schemas.field.hintsHint": "Describe this schema for documentation and user interfaces.", diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs index 5765770c1..152688f2f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/FieldProperties.cs @@ -14,6 +14,8 @@ namespace Squidex.Domain.Apps.Core.Schemas { public bool IsRequired { get; set; } + public bool IsHalfWidth { get; set; } + public string? Placeholder { get; set; } public string? EditorUrl { get; set; } diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs index 1d2888b4b..be00a4f25 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/FieldPropertiesDto.cs @@ -43,6 +43,11 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models /// public bool IsRequired { get; set; } + /// + /// Indicates if the field should be rendered with half width only. + /// + public bool IsHalfWidth { get; set; } + /// /// Optional url to the editor. /// diff --git a/frontend/app/features/content/pages/content/content-field.component.html b/frontend/app/features/content/pages/content/content-field.component.html index 1a6c2a83e..69e1239d4 100644 --- a/frontend/app/features/content/pages/content/content-field.component.html +++ b/frontend/app/features/content/pages/content/content-field.component.html @@ -86,4 +86,4 @@ - + \ No newline at end of file diff --git a/frontend/app/features/content/pages/content/content-field.component.ts b/frontend/app/features/content/pages/content/content-field.component.ts index 05181c54c..f7867d3b2 100644 --- a/frontend/app/features/content/pages/content/content-field.component.ts +++ b/frontend/app/features/content/pages/content/content-field.component.ts @@ -5,7 +5,7 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, EventEmitter, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; +import { Component, EventEmitter, HostBinding, Input, OnChanges, Output, SimpleChanges } from '@angular/core'; import { AppLanguageDto, AppsState, EditContentForm, FieldForm, invalid$, LocalStoreService, SchemaDto, Settings, StringFieldPropertiesDto, TranslationsService, Types, value$ } from '@app/shared'; import { combineLatest, Observable } from 'rxjs'; import { map } from 'rxjs/operators'; @@ -43,6 +43,15 @@ export class ContentFieldComponent implements OnChanges { @Input() public languages: ReadonlyArray; + @HostBinding('class') + public get class() { + return this.isHalfWidth ? 'col-6 half-field' : 'col-12'; + } + + public get isHalfWidth() { + return this.formModel.field.properties.isHalfWidth && !this.formCompare; + } + public showAllControls = false; public isDifferent: Observable; diff --git a/frontend/app/features/content/pages/content/content-section.component.html b/frontend/app/features/content/pages/content/content-section.component.html index b62a82228..34e2690e2 100644 --- a/frontend/app/features/content/pages/content/content-section.component.html +++ b/frontend/app/features/content/pages/content/content-section.component.html @@ -17,7 +17,7 @@ -
+
.col-6, + > .col-12 { + padding-left: .25rem; + padding-right: .25rem; + } + } +} + .btn { & { width: 2rem; diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html index 5b6edff4c..9ce287760 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.html @@ -1,5 +1,5 @@
-
+
@@ -40,4 +40,17 @@ - \ No newline at end of file + + +
+
+
+
+ + +
+
+
+
diff --git a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.scss b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.scss index 54750016b..e69de29bb 100644 --- a/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.scss +++ b/frontend/app/features/schemas/pages/schema/fields/forms/field-form-ui.component.scss @@ -1,3 +0,0 @@ -.form-group { - margin-bottom: 1rem; -} \ No newline at end of file diff --git a/frontend/app/shared/services/schemas.types.ts b/frontend/app/shared/services/schemas.types.ts index 86fceaf53..4fd44e9d2 100644 --- a/frontend/app/shared/services/schemas.types.ts +++ b/frontend/app/shared/services/schemas.types.ts @@ -135,6 +135,7 @@ export abstract class FieldPropertiesDto { public readonly editorUrl?: string; public readonly hints?: string; public readonly isRequired: boolean = false; + public readonly isHalfWidth: boolean = false; public readonly label?: string; public readonly placeholder?: string; public readonly tags?: ReadonlyArray; diff --git a/frontend/app/shared/state/schemas.forms.ts b/frontend/app/shared/state/schemas.forms.ts index 882d083d9..975583702 100644 --- a/frontend/app/shared/state/schemas.forms.ts +++ b/frontend/app/shared/state/schemas.forms.ts @@ -214,6 +214,7 @@ export class EditFieldForm extends Form { ], editorUrl: null, isRequired: false, + isHalfWidth: false, tags: [] })); } From 473e78dc401259d80943d480d11f45d5690e4913 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 12 Oct 2020 20:03:30 +0200 Subject: [PATCH 4/4] Make the selector dropdown more visible. --- .../content/pages/content/content-field.component.ts | 5 ++++- .../content/pages/content/content-section.component.html | 1 + .../content/pages/content/content-section.component.ts | 3 +++ .../shared/references/content-creator.component.html | 1 + .../shared/references/content-creator.component.scss | 4 ++++ .../shared/references/content-selector.component.scss | 4 ++++ frontend/app/theme/_forms.scss | 9 ++++++++- 7 files changed, 25 insertions(+), 2 deletions(-) diff --git a/frontend/app/features/content/pages/content/content-field.component.ts b/frontend/app/features/content/pages/content/content-field.component.ts index f7867d3b2..bea1b080f 100644 --- a/frontend/app/features/content/pages/content/content-field.component.ts +++ b/frontend/app/features/content/pages/content/content-field.component.ts @@ -19,6 +19,9 @@ export class ContentFieldComponent implements OnChanges { @Output() public languageChange = new EventEmitter(); + @Input() + public compact = false; + @Input() public form: EditContentForm; @@ -49,7 +52,7 @@ export class ContentFieldComponent implements OnChanges { } public get isHalfWidth() { - return this.formModel.field.properties.isHalfWidth && !this.formCompare; + return this.formModel.field.properties.isHalfWidth && !this.compact && !this.formCompare; } public showAllControls = false; diff --git a/frontend/app/features/content/pages/content/content-section.component.html b/frontend/app/features/content/pages/content/content-section.component.html index 34e2690e2..179d8f92a 100644 --- a/frontend/app/features/content/pages/content/content-section.component.html +++ b/frontend/app/features/content/pages/content/content-section.component.html @@ -20,6 +20,7 @@
(); + @Input() + public compact = false; + @Input() public form: EditContentForm; diff --git a/frontend/app/features/content/shared/references/content-creator.component.html b/frontend/app/features/content/shared/references/content-creator.component.html index 764b885c8..3c4a8fad8 100644 --- a/frontend/app/features/content/shared/references/content-creator.component.html +++ b/frontend/app/features/content/shared/references/content-creator.component.html @@ -38,6 +38,7 @@
") !default;; + // Dark form control for the dark panel. .form-control-dark { & { @include placeholder-color($color-dark2-placeholder); - background: $color-dark2-control; + background-color: $color-dark2-control; border: 1px solid $color-dark2-control; color: darken($color-dark-foreground, 20%); transition: background-color .3s ease; @@ -275,6 +278,10 @@ label { border-color: lighten($color-dark2-control, 2%); color: $color-dark2-focus-foreground; } + + &.custom-select { + background: $color-dark2-control escape-svg($select-indicator) no-repeat right .75rem center / 8px 10px; + } } input {