From 9512b1da6fbbc10cb1a802936c143b8cea78ee98 Mon Sep 17 00:00:00 2001 From: Sebastian Stehle Date: Mon, 22 Jul 2019 13:52:55 +0200 Subject: [PATCH] Progress. --- .../Contents/ContentFieldData.cs | 2 +- .../Contents/Workflows.cs | 1 - .../InvariantPartitioning.cs | 5 +- .../ConvertContent/FieldConverters.cs | 4 +- .../ContentReferencesExtensions.cs | 70 ++++++++++- .../ExtractReferenceIds/Ids.cs | 15 +++ .../ReferencesExtensions.cs | 5 +- .../ReferencesExtractor.cs | 13 +- .../ValidateContent/ObjectPath.cs | 2 +- .../Validators/UniqueValidator.cs | 2 +- .../CreateProfileCommandMiddleware.cs | 4 +- .../Contents/ContentCommandMiddleware.cs | 9 +- .../Contents/ContentEnricher.cs | 115 +++++++++++------- .../Contents/ContentQueryService.cs | 2 +- .../Contents/IContentEnricher.cs | 5 +- .../Contents/IEnrichedContentEntity.cs | 2 + .../Contents/Text/TextIndexerGrain.cs | 2 +- .../Api/Controllers/Apps/AppsController.cs | 1 - .../Apps/Models/ContributorsDto.cs | 1 - .../Controllers/Contents/Models/ContentDto.cs | 5 + .../Contents/Models/ContentsDto.cs | 1 - .../Controllers/History/HistoryController.cs | 1 - .../Controllers/Schemas/Models/SchemasDto.cs | 1 - src/Squidex/Config/Domain/EntitiesServices.cs | 3 + .../content/shared/content-item.component.ts | 30 +---- .../shared/references-dropdown.component.ts | 34 +----- .../shared/services/contents.service.spec.ts | 2 + .../app/shared/services/contents.service.ts | 11 +- .../app/shared/state/contents.forms.ts | 102 ++++++++++++---- src/Squidex/appsettings.json | 2 +- .../ReferenceExtractionTests.cs | 14 +++ .../ReferenceFormattingTests.cs | 81 ++++++++++++ .../Contents/ContentCommandMiddlewareTests.cs | 13 +- .../Contents/ContentEnricherTests.cs | 25 ++-- .../Contents/ContentQueryServiceTests.cs | 2 +- 35 files changed, 402 insertions(+), 185 deletions(-) create mode 100644 src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs create mode 100644 tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs index 8bc60388a..3b0697bc7 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs @@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Contents public ContentFieldData AddValue(object value) { - return AddJsonValue(InvariantPartitioning.Instance.Master.Key, JsonValue.Create(value)); + return AddJsonValue(InvariantPartitioning.Key, JsonValue.Create(value)); } public ContentFieldData AddValue(string key, object value) diff --git a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs index b5d86740c..353323008 100644 --- a/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs +++ b/src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs @@ -9,7 +9,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.Contracts; using System.Linq; -using System.Threading.Tasks; using Squidex.Infrastructure; using Squidex.Infrastructure.Collections; diff --git a/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs b/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs index f9c439b56..a50e9beff 100644 --- a/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs +++ b/src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs @@ -15,6 +15,7 @@ namespace Squidex.Domain.Apps.Core public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem { public static readonly InvariantPartitioning Instance = new InvariantPartitioning(); + public static readonly string Key = "iv"; public int Count { @@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core string IFieldPartitionItem.Key { - get { return "iv"; } + get { return Key; } } string IFieldPartitionItem.Name @@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Core public bool TryGetItem(string key, out IFieldPartitionItem item) { - var isFound = string.Equals(key, "iv", StringComparison.OrdinalIgnoreCase); + var isFound = string.Equals(key, Key, StringComparison.OrdinalIgnoreCase); item = isFound ? this : null; diff --git a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs index 253af69a7..f9e35bb58 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs @@ -95,7 +95,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent public static FieldConverter ResolveInvariant(LanguagesConfig config) { - var codeForInvariant = InvariantPartitioning.Instance.Master.Key; + var codeForInvariant = InvariantPartitioning.Key; var codeForMasterLanguage = config.Master.Language.Iso2Code; return (data, field) => @@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent public static FieldConverter ResolveLanguages(LanguagesConfig config) { - var codeForInvariant = InvariantPartitioning.Instance.Master.Key; + var codeForInvariant = InvariantPartitioning.Key; return (data, field) => { diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs index 6c7be128e..4d157cd78 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs @@ -8,15 +8,18 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; +using Squidex.Domain.Apps.Core.Apps; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public static class ContentReferencesExtensions { - public static IEnumerable GetReferencedIds(this IdContentData source, Schema schema) + public static IEnumerable GetReferencedIds(this IdContentData source, Schema schema, Ids strategy = Ids.All) { Guard.NotNull(schema, nameof(schema)); @@ -24,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds foreach (var field in schema.Fields) { - var ids = source.GetReferencedIds(field); + var ids = source.GetReferencedIds(field, strategy); foreach (var id in ids) { @@ -36,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds } } - public static IEnumerable GetReferencedIds(this IdContentData source, IField field) + public static IEnumerable GetReferencedIds(this IdContentData source, IField field, Ids strategy = Ids.All) { Guard.NotNull(field, nameof(field)); @@ -44,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { foreach (var partitionValue in fieldData) { - var ids = field.GetReferencedIds(partitionValue.Value); + var ids = field.GetReferencedIds(partitionValue.Value, strategy); foreach (var id in ids) { @@ -54,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds } } - public static IEnumerable GetReferencedIds(this NamedContentData source, IField field) + public static IEnumerable GetReferencedIds(this NamedContentData source, IField field, Ids strategy = Ids.All) { Guard.NotNull(field, nameof(field)); @@ -62,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { foreach (var partitionValue in fieldData) { - var ids = field.GetReferencedIds(partitionValue.Value); + var ids = field.GetReferencedIds(partitionValue.Value, strategy); foreach (var id in ids) { @@ -71,5 +74,60 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds } } } + + public static JsonObject FormatReferences(this NamedContentData data, Schema schema, LanguagesConfig languages, string separator = ", ") + { + Guard.NotNull(schema, nameof(schema)); + + var result = JsonValue.Object(); + + foreach (var language in languages) + { + result[language.Key] = JsonValue.Create(data.FormatReferenceFields(schema, language.Key, separator)); + } + + return result; + } + + private static string FormatReferenceFields(this NamedContentData data, Schema schema, string partition, string separator) + { + Guard.NotNull(schema, nameof(schema)); + + var sb = new StringBuilder(); + + void AddValue(object value) + { + if (sb.Length > 0) + { + sb.Append(separator); + } + + sb.Append(value); + } + + var referenceFields = schema.Fields.Where(x => x.RawProperties.IsReferenceField); + + if (!referenceFields.Any()) + { + referenceFields = schema.Fields.Take(1); + } + + foreach (var referenceField in referenceFields) + { + if (data.TryGetValue(referenceField.Name, out var fieldData)) + { + if (fieldData.TryGetValue(partition, out var value)) + { + AddValue(value); + } + else if (fieldData.TryGetValue(InvariantPartitioning.Key, out var value2)) + { + AddValue(value2); + } + } + } + + return sb.ToString(); + } } } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs new file mode 100644 index 000000000..037ee9747 --- /dev/null +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs @@ -0,0 +1,15 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +namespace Squidex.Domain.Apps.Core.ExtractReferenceIds +{ + public enum Ids + { + All, + ContentOnly + } +} diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs index 0f65b224d..47f032123 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs @@ -7,7 +7,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure.Json.Objects; @@ -15,9 +14,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { public static class ReferencesExtensions { - public static IEnumerable GetReferencedIds(this IField field, IJsonValue value) + public static IEnumerable GetReferencedIds(this IField field, IJsonValue value, Ids strategy = Ids.All) { - return ReferencesExtractor.ExtractReferences(field, value); + return ReferencesExtractor.ExtractReferences(field, value, strategy); } public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection oldReferences) diff --git a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs index b4373ac0d..df024acc0 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs @@ -16,15 +16,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds public sealed class ReferencesExtractor : IFieldVisitor> { private readonly IJsonValue value; + private readonly Ids strategy; - private ReferencesExtractor(IJsonValue value) + private ReferencesExtractor(IJsonValue value, Ids strategy) { this.value = value; + + this.strategy = strategy; } - public static IEnumerable ExtractReferences(IField field, IJsonValue value) + public static IEnumerable ExtractReferences(IField field, IJsonValue value, Ids strategy) { - return field.Accept(new ReferencesExtractor(value)); + return field.Accept(new ReferencesExtractor(value, strategy)); } public IEnumerable Visit(IArrayField field) @@ -39,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { if (item.TryGetValue(nestedField.Name, out var nestedValue)) { - result.AddRange(nestedField.Accept(new ReferencesExtractor(nestedValue))); + result.AddRange(nestedField.Accept(new ReferencesExtractor(nestedValue, strategy))); } } } @@ -59,7 +62,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds { var ids = value.ToGuidSet(); - if (field.Properties.SchemaId != Guid.Empty) + if (strategy == Ids.All && field.Properties.SchemaId != Guid.Empty) { ids.Add(field.Properties.SchemaId); } diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs index 3b1c216cb..1ce083fa0 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs @@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent } else if (index == 1) { - if (!property.Equals(InvariantPartitioning.Instance.Master.Key, StringComparison.OrdinalIgnoreCase)) + if (!property.Equals(InvariantPartitioning.Key, StringComparison.OrdinalIgnoreCase)) { sb.Append("("); sb.Append(property); diff --git a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs index 3c2b8f14a..06c4c6d39 100644 --- a/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs +++ b/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs @@ -18,7 +18,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { var count = context.Path.Count(); - if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Instance.Master.Key))) + if (value != null && (count == 0 || (count == 2 && context.Path.Last() == InvariantPartitioning.Key))) { FilterNode filter = null; diff --git a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs index 59e46b4f5..6cea0df6c 100644 --- a/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs @@ -150,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates private static async Task> CreateExperienceSchemaAsync(Func publish) { var schema = - SchemaBuilder.Create("experience") + SchemaBuilder.Create("Experience") .AddString("Position", f => f .Required() .ShowInList() @@ -177,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates private static async Task> CreateEducationSchemaAsync(Func publish) { var schema = - SchemaBuilder.Create("Experience") + SchemaBuilder.Create("Education") .AddString("Degree", f => f .Required() .ShowInList() diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs index 63bc61a96..4ebe87d7a 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs @@ -17,22 +17,25 @@ namespace Squidex.Domain.Apps.Entities.Contents public sealed class ContentCommandMiddleware : GrainCommandMiddleware { private readonly IContentEnricher contentEnricher; + private readonly IContextProvider contextProvider; - public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher) + public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher, IContextProvider contextProvider) : base(grainFactory) { Guard.NotNull(contentEnricher, nameof(contentEnricher)); + Guard.NotNull(contextProvider, nameof(contextProvider)); this.contentEnricher = contentEnricher; + this.contextProvider = contextProvider; } public override async Task HandleAsync(CommandContext context, Func next) { await base.HandleAsync(context, next); - if (context.Command is SquidexCommand command && context.PlainResult is IContentEntity content && NotEnriched(context)) + if (context.PlainResult is IContentEntity content && NotEnriched(context)) { - var enriched = await contentEnricher.EnrichAsync(content, command.User); + var enriched = await contentEnricher.EnrichAsync(content, contextProvider.Context); context.Complete(enriched); } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs index 70734aa89..c07d90cb8 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs @@ -8,7 +8,6 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Security.Claims; using System.Threading.Tasks; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.ExtractReferenceIds; @@ -23,41 +22,37 @@ namespace Squidex.Domain.Apps.Entities.Contents public sealed class ContentEnricher : IContentEnricher { private const string DefaultColor = StatusColors.Draft; - private readonly IAppProvider appProvider; - private readonly IContentQueryService contentQuery; + private static readonly Dictionary EmptyReferences = new Dictionary(); + private readonly Lazy contentQuery; private readonly IContentWorkflow contentWorkflow; - private readonly IContextProvider contextProvider; - public ContentEnricher( - IAppProvider appProvider, - IContentQueryService contentQuery, - IContentWorkflow contentWorkflow, - IContextProvider contextProvider) + private IContentQueryService ContentQuery + { + get { return contentQuery.Value; } + } + + public ContentEnricher(Lazy contentQuery, IContentWorkflow contentWorkflow) { - Guard.NotNull(appProvider, nameof(appProvider)); Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); - Guard.NotNull(contextProvider, nameof(contextProvider)); - this.appProvider = appProvider; this.contentQuery = contentQuery; this.contentWorkflow = contentWorkflow; - this.contextProvider = contextProvider; } - public async Task EnrichAsync(IContentEntity content, ClaimsPrincipal user) + public async Task EnrichAsync(IContentEntity content, Context context) { Guard.NotNull(content, nameof(content)); - var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), user); + var enriched = await EnrichAsync(Enumerable.Repeat(content, 1), context); return enriched[0]; } - public async Task> EnrichAsync(IEnumerable contents, ClaimsPrincipal user) + public async Task> EnrichAsync(IEnumerable contents, Context context) { Guard.NotNull(contents, nameof(contents)); - Guard.NotNull(user, nameof(user)); + Guard.NotNull(context, nameof(context)); using (Profiler.TraceMethod()) { @@ -73,20 +68,20 @@ namespace Squidex.Domain.Apps.Entities.Contents await ResolveColorAsync(content, result, cache); - if (ShouldEnrichWithStatuses()) + if (ShouldEnrichWithStatuses(context)) { - await ResolveNextsAsync(content, result, user); + await ResolveNextsAsync(content, result, context); await ResolveCanUpdateAsync(content, result); } results.Add(result); } - if (contextProvider.Context.IsFrontendClient) + if (ShouldEnrichWithReferences(context)) { - foreach (var group in results.GroupBy(x => x.SchemaId)) + foreach (var group in results.GroupBy(x => x.SchemaId.Id)) { - await ResolveReferencesAsync(group.Key, group); + await ResolveReferencesAsync(group.Key, group, context); } } } @@ -95,67 +90,85 @@ namespace Squidex.Domain.Apps.Entities.Contents } } - private async Task ResolveReferencesAsync(NamedId schemaId, IEnumerable contents) + private async Task ResolveReferencesAsync(Guid schemaId, IEnumerable contents, Context context) { var appId = contents.First().AppId.Id; - var schema = await appProvider.GetSchemaAsync(appId, schemaId.Id); + var schema = await ContentQuery.GetSchemaOrThrowAsync(context, schemaId.ToString()); var referenceFields = schema.SchemaDef.Fields.OfType>() .Where(x => + x.Properties.SchemaId != Guid.Empty && x.Properties.MinItems == 1 && x.Properties.MaxItems == 1 && - (x.Properties.IsListField || x.Properties.IsReferenceField)); + x.Properties.IsListField); + + var formatted = new Dictionary(); foreach (var field in referenceFields) { - var allIds = GetContentIds(contents, field); + foreach (var content in contents) + { + content.ReferenceData = new NamedContentData(); + content.ReferenceData.GetOrAddNew(field.Name); + } - if (allIds.Count > 0) + try { - var referenced = await contentQuery.QueryAsync(contextProvider.Context, schemaId.Id.ToString(), Q.Empty.WithIds(allIds)); + var referencedSchemaId = field.Properties.SchemaId; + var referencedSchema = await ContentQuery.GetSchemaOrThrowAsync(context, referencedSchemaId.ToString()); - var byId = referenced.ToDictionary(x => x.Id); + var references = await GetReferencesAsync(referencedSchemaId, contents, field, context); foreach (var content in contents) { - content.ReferenceData = content.ReferenceData ?? new NamedContentData(); + var fieldReference = content.ReferenceData[field.Name]; if (content.DataDraft.TryGetValue(field.Name, out var fieldData)) { - foreach (var partitionValue in fieldData.Where(x => x.Value.Type != JsonValueType.Null)) + foreach (var partition in fieldData) { - var ids = field.GetReferencedIds(partitionValue.Value).ToArray(); + var id = field.GetReferencedIds(partition.Value, Ids.ContentOnly).FirstOrDefault(); - if (ids.Length == 1) + if (references.TryGetValue(id, out var reference)) { - if (byId.TryGetValue(ids[0], out var reference)) - { - } + var value = + formatted.GetOrAdd(id, + _ => reference.DataDraft.FormatReferences(referencedSchema.SchemaDef, context.App.LanguagesConfig)); + + fieldReference[partition.Key] = JsonValue.Create(value); } } } } } + catch (DomainObjectNotFoundException) + { + continue; + } } } - private static HashSet GetContentIds(IEnumerable contents, IField field) + private async Task> GetReferencesAsync(Guid schemaId, IEnumerable contents, IField field, Context context) { - var allIds = new HashSet(); + var ids = new HashSet(); foreach (var content in contents) { - allIds.AddRange(content.DataDraft.GetReferencedIds(field)); + ids.AddRange(content.DataDraft.GetReferencedIds(field, Ids.ContentOnly)); } - return allIds; - } + if (ids.Count > 0) + { + var references = await ContentQuery.QueryAsync(context.Clone().WithNoEnrichment(true), schemaId.ToString(), Q.Empty.WithIds(ids)); - private bool ShouldEnrichWithStatuses() - { - return contextProvider.Context.IsFrontendClient || contextProvider.Context.IsResolveFlow(); + return references.ToDictionary(x => x.Id); + } + else + { + return EmptyReferences; + } } private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result) @@ -163,9 +176,9 @@ namespace Squidex.Domain.Apps.Entities.Contents result.CanUpdate = await contentWorkflow.CanUpdateAsync(content); } - private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result, ClaimsPrincipal user) + private async Task ResolveNextsAsync(IContentEntity content, ContentEntity result, Context context) { - result.Nexts = await contentWorkflow.GetNextsAsync(content, user); + result.Nexts = await contentWorkflow.GetNextsAsync(content, context.User); } private async Task ResolveColorAsync(IContentEntity content, ContentEntity result, Dictionary<(Guid, Status), StatusInfo> cache) @@ -189,5 +202,15 @@ namespace Squidex.Domain.Apps.Entities.Contents return info.Color; } + + private static bool ShouldEnrichWithStatuses(Context context) + { + return context.IsFrontendClient || context.IsResolveFlow(); + } + + private static bool ShouldEnrichWithReferences(Context context) + { + return context.IsFrontendClient && !context.IsNoEnrichment(); + } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs index e88a08f67..750d964a5 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs @@ -186,7 +186,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var scriptText = schema.SchemaDef.Scripts.Query; var scripting = !string.IsNullOrWhiteSpace(scriptText); - var enriched = await contentEnricher.EnrichAsync(contents, context.User); + var enriched = await contentEnricher.EnrichAsync(contents, context); foreach (var content in enriched) { diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs b/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs index e73e49c16..e8b55d520 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs @@ -6,15 +6,14 @@ // ========================================================================== using System.Collections.Generic; -using System.Security.Claims; using System.Threading.Tasks; namespace Squidex.Domain.Apps.Entities.Contents { public interface IContentEnricher { - Task EnrichAsync(IContentEntity content, ClaimsPrincipal user); + Task EnrichAsync(IContentEntity content, Context context); - Task> EnrichAsync(IEnumerable contents, ClaimsPrincipal user); + Task> EnrichAsync(IEnumerable contents, Context context); } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs b/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs index 45b3506a4..c4f6580e8 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs @@ -16,5 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Contents string StatusColor { get; } StatusInfo[] Nexts { get; } + + NamedContentData ReferenceData { get; } } } diff --git a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs index 926f8e510..f234726fe 100644 --- a/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs +++ b/src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Entities.Contents.Text private static readonly TimeSpan CommitDelay = TimeSpan.FromSeconds(10); private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler(); private static readonly Analyzer Analyzer = new MultiLanguageAnalyzer(Version); - private static readonly string[] Invariant = { InvariantPartitioning.Instance.Master.Key }; + private static readonly string[] Invariant = { InvariantPartitioning.Key }; private readonly SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy()); private readonly IAssetStore assetStore; private IDisposable timer; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs index 8a1b950df..0c8c0d76c 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs @@ -14,7 +14,6 @@ using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Services; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Security; using Squidex.Shared; diff --git a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs index b9e264241..540725c75 100644 --- a/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs @@ -10,7 +10,6 @@ using System.Linq; using Newtonsoft.Json; using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps.Services; -using Squidex.Infrastructure; using Squidex.Shared; using Squidex.Web; diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs index ce728612f..dede3c34d 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs @@ -49,6 +49,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models /// public object DataDraft { get; set; } + /// + /// The reference data for the frontend UI. + /// + public NamedContentData ReferenceData { get; set; } + /// /// Indicates if the draft data is pending. /// diff --git a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs index f665c4d40..2f2b2afcc 100644 --- a/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs @@ -8,7 +8,6 @@ using System.ComponentModel.DataAnnotations; using System.Linq; using System.Threading.Tasks; -using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Schemas; diff --git a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs index 4a208143c..d50a2def7 100644 --- a/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs +++ b/src/Squidex/Areas/Api/Controllers/History/HistoryController.cs @@ -10,7 +10,6 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Squidex.Areas.Api.Controllers.History.Models; using Squidex.Domain.Apps.Entities.History; -using Squidex.Infrastructure; using Squidex.Infrastructure.Commands; using Squidex.Shared; using Squidex.Web; diff --git a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs index ebdaa95ab..de939823c 100644 --- a/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs +++ b/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs @@ -8,7 +8,6 @@ using System.Collections.Generic; using System.Linq; using Squidex.Domain.Apps.Entities.Schemas; -using Squidex.Infrastructure; using Squidex.Shared; using Squidex.Web; diff --git a/src/Squidex/Config/Domain/EntitiesServices.cs b/src/Squidex/Config/Domain/EntitiesServices.cs index b9a9813c3..1b480e088 100644 --- a/src/Squidex/Config/Domain/EntitiesServices.cs +++ b/src/Squidex/Config/Domain/EntitiesServices.cs @@ -105,6 +105,9 @@ namespace Squidex.Config.Domain services.AddSingletonAs() .As(); + services.AddSingletonAs(c => new Lazy(() => c.GetRequiredService())) + .AsSelf(); + services.AddSingletonAs() .As(); diff --git a/src/Squidex/app/features/content/shared/content-item.component.ts b/src/Squidex/app/features/content/shared/content-item.component.ts index 650ae0501..4dd5e0182 100644 --- a/src/Squidex/app/features/content/shared/content-item.component.ts +++ b/src/Squidex/app/features/content/shared/content-item.component.ts @@ -13,13 +13,11 @@ import { ContentsState, fadeAnimation, FieldDto, - FieldFormatter, - fieldInvariant, + getContentValue, ModalModel, PatchContentForm, RootFieldDto, - SchemaDetailsDto, - Types + SchemaDetailsDto } from '@app/shared'; /* tslint:disable:component-selector */ @@ -152,13 +150,9 @@ export class ContentItemComponent implements OnChanges { this.values = []; for (let field of this.schemaFields) { - const value = this.getRawValue(field); + const { value, formatted } = getContentValue(this.content, this.language, field); - if (Types.isUndefined(value)) { - this.values.push(''); - } else { - this.values.push(FieldFormatter.format(field, value, true)); - } + this.values.push(formatted); if (this.patchForm) { const formControl = this.patchForm.form.controls[field.name]; @@ -170,21 +164,7 @@ export class ContentItemComponent implements OnChanges { } } - private getRawValue(field: RootFieldDto): any { - const contentField = this.content.dataDraft[field.name]; - - if (contentField) { - if (field.isLocalizable) { - return contentField[this.language.iso2Code]; - } else { - return contentField[fieldInvariant]; - } - } - - return undefined; - } - - public trackByField(index: number, field: FieldDto) { + public trackByField(field: FieldDto) { return field.fieldId + this.schema.id; } } diff --git a/src/Squidex/app/features/content/shared/references-dropdown.component.ts b/src/Squidex/app/features/content/shared/references-dropdown.component.ts index b4ad8191d..b27d4debc 100644 --- a/src/Squidex/app/features/content/shared/references-dropdown.component.ts +++ b/src/Squidex/app/features/content/shared/references-dropdown.component.ts @@ -15,11 +15,9 @@ import { AppsState, ContentDto, ContentsService, - FieldFormatter, - fieldInvariant, + getContentValue, ImmutableArray, MathHelper, - RootFieldDto, SchemaDetailsDto, SchemasService, StatefulControlComponent, @@ -125,32 +123,12 @@ export class ReferencesDropdownComponent extends StatefulControlComponent { - const values: any[] = []; - - for (let field of schema.referenceFields) { - const value = getRawValue(field, content.data, this.languageField); - - if (!Types.isUndefined(value)) { - values.push(FieldFormatter.format(field, value, false)); - } - } - - const name = values.join(', '); + const name = + schema.referenceFields + .map(f => getContentValue(content, this.languageField, f, false)) + .map(v => v.formatted) + .join(', '); return { name, id: content.id }; }); diff --git a/src/Squidex/app/shared/services/contents.service.spec.ts b/src/Squidex/app/shared/services/contents.service.spec.ts index 6e16aa5ce..f839787fa 100644 --- a/src/Squidex/app/shared/services/contents.service.spec.ts +++ b/src/Squidex/app/shared/services/contents.service.spec.ts @@ -366,6 +366,7 @@ describe('ContentsService', () => { isPending: true, data: {}, dataDraft: {}, + referenceData: {}, version: `${id}`, _links: { update: { method: 'PUT', href: `/contents/id${id}` } @@ -389,5 +390,6 @@ export function createContent(id: number, suffix = '') { true, {}, {}, + {}, new Version(`${id}`)); } \ No newline at end of file diff --git a/src/Squidex/app/shared/services/contents.service.ts b/src/Squidex/app/shared/services/contents.service.ts index 1120f6b99..0f9b6da69 100644 --- a/src/Squidex/app/shared/services/contents.service.ts +++ b/src/Squidex/app/shared/services/contents.service.ts @@ -55,6 +55,11 @@ export class ContentsDto extends ResultSet { } } +export type ContentReferencesValue = { [partition: string]: string }; +export type ContentReferences = { [fieldName: string ]: ContentFieldData }; +export type ContentFieldData = { [partition: string]: T }; +export type ContentData = { [fieldName: string ]: ContentFieldData }; + export class ContentDto { public readonly _links: ResourceLinks; @@ -77,8 +82,9 @@ export class ContentDto { public readonly lastModifiedBy: string, public readonly scheduleJob: ScheduleDto | null, public readonly isPending: boolean, - public readonly data: object | any, - public readonly dataDraft: object, + public readonly data: ContentData | undefined, + public readonly dataDraft: ContentData, + public readonly referenceData: ContentReferences, public readonly version: Version ) { this._links = links; @@ -299,5 +305,6 @@ function parseContent(response: any) { response.isPending === true, response.data, response.dataDraft, + response.referenceData, new Version(response.version.toString())); } \ No newline at end of file diff --git a/src/Squidex/app/shared/state/contents.forms.ts b/src/Squidex/app/shared/state/contents.forms.ts index da3a2084c..31745c6d8 100644 --- a/src/Squidex/app/shared/state/contents.forms.ts +++ b/src/Squidex/app/shared/state/contents.forms.ts @@ -19,6 +19,8 @@ import { value$ } from '@app/framework'; +import { ContentDto, ContentReferencesValue } from '../services/contents.service'; +import { LanguageDto } from '../services/languages.service'; import { AppLanguageDto } from './../services/app-languages.service'; import { FieldDto, RootFieldDto, SchemaDetailsDto } from './../services/schemas.service'; import { @@ -56,7 +58,55 @@ export class SaveQueryForm extends Form { } } -export class FieldFormatter implements FieldPropertiesVisitor { +export type FieldValue = string | HtmlValue; + +export function getContentValue(content: ContentDto, language: LanguageDto, field: RootFieldDto, allowHtml = true): { value: any, formatted: FieldValue } { + const reference = content.referenceData[field.name]; + + if (reference) { + let fieldValue: ContentReferencesValue; + + if (field.isLocalizable) { + fieldValue = reference[language.iso2Code]; + } else { + fieldValue = reference[fieldInvariant]; + } + + let value = ''; + + if (fieldValue) { + value = fieldValue[language.iso2Code] || ''; + } + + return { value, formatted: value }; + } + + const contentField = content.dataDraft[field.name]; + + if (contentField) { + let value: any; + + if (field.isLocalizable) { + value = contentField[language.iso2Code]; + } else { + value = contentField[fieldInvariant]; + } + + let formatted: any; + + if (Types.isUndefined(value)) { + formatted = value || ''; + } else { + formatted = FieldFormatter.format(field, value, allowHtml); + } + + return { value, formatted }; + } + + return { value: undefined, formatted: '' }; +} + +export class FieldFormatter implements FieldPropertiesVisitor { constructor( private readonly value: any, private readonly allowHtml: boolean @@ -71,7 +121,7 @@ export class FieldFormatter implements FieldPropertiesVisitor { return field.properties.accept(new FieldFormatter(value, allowHtml)); } - public visitDateTime(properties: DateTimeFieldPropertiesDto): string | any { + public visitDateTime(properties: DateTimeFieldPropertiesDto): FieldValue { try { const parsed = DateTime.parseISO_UTC(this.value); @@ -85,7 +135,7 @@ export class FieldFormatter implements FieldPropertiesVisitor { } } - public visitArray(properties: ArrayFieldPropertiesDto): string { + public visitArray(_: ArrayFieldPropertiesDto): string { if (this.value.length) { return `${this.value.length} Item(s)`; } else { @@ -93,7 +143,7 @@ export class FieldFormatter implements FieldPropertiesVisitor { } } - public visitAssets(properties: AssetsFieldPropertiesDto): string { + public visitAssets(_: AssetsFieldPropertiesDto): string { if (this.value.length) { return `${this.value.length} Asset(s)`; } else { @@ -101,7 +151,7 @@ export class FieldFormatter implements FieldPropertiesVisitor { } } - public visitReferences(properties: ReferencesFieldPropertiesDto): string { + public visitReferences(_: ReferencesFieldPropertiesDto): string { if (this.value.length) { return `${this.value.length} Reference(s)`; } else { @@ -109,7 +159,7 @@ export class FieldFormatter implements FieldPropertiesVisitor { } } - public visitTags(properties: TagsFieldPropertiesDto): string { + public visitTags(_: TagsFieldPropertiesDto): string { if (this.value.length) { return this.value.join(', '); } else { @@ -117,19 +167,19 @@ export class FieldFormatter implements FieldPropertiesVisitor { } } - public visitBoolean(properties: BooleanFieldPropertiesDto): string { + public visitBoolean(_: BooleanFieldPropertiesDto): string { return this.value ? 'Yes' : 'No'; } - public visitGeolocation(properties: GeolocationFieldPropertiesDto): string { + public visitGeolocation(_: GeolocationFieldPropertiesDto): string { return `${this.value.longitude}, ${this.value.latitude}`; } - public visitJson(properties: JsonFieldPropertiesDto): string { + public visitJson(_: JsonFieldPropertiesDto): string { return ''; } - public visitNumber(properties: NumberFieldPropertiesDto): string | HtmlValue | number { + public visitNumber(properties: NumberFieldPropertiesDto): FieldValue { if (Types.isNumber(this.value) && properties.editor === 'Stars' && this.allowHtml) { if (this.value <= 0 || this.value > 6) { return new HtmlValue(`★ ${this.value}`); @@ -143,15 +193,15 @@ export class FieldFormatter implements FieldPropertiesVisitor { return new HtmlValue(html); } } - return this.value; + return `${this.value}`; } - public visitString(properties: StringFieldPropertiesDto): any { + public visitString(_: StringFieldPropertiesDto): any { return this.value; } - public visitUI(properties: UIFieldPropertiesDto): any { - return this.value; + public visitUI(_: UIFieldPropertiesDto): any { + return ''; } } @@ -257,23 +307,23 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor { return field.properties.accept(new FieldDefaultValue(now)); } - public visitArray(properties: ArrayFieldPropertiesDto): any { + public visitArray(_: ArrayFieldPropertiesDto): any { return null; } - public visitAssets(properties: AssetsFieldPropertiesDto): any { + public visitAssets(_: AssetsFieldPropertiesDto): any { return null; } @@ -312,11 +362,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor { return properties.defaultValue; } - public visitGeolocation(properties: GeolocationFieldPropertiesDto): any { + public visitGeolocation(_: GeolocationFieldPropertiesDto): any { return null; } - public visitJson(properties: JsonFieldPropertiesDto): any { + public visitJson(_: JsonFieldPropertiesDto): any { return null; } @@ -324,7 +374,7 @@ export class FieldDefaultValue implements FieldPropertiesVisitor { return properties.defaultValue; } - public visitReferences(properties: ReferencesFieldPropertiesDto): any { + public visitReferences(_: ReferencesFieldPropertiesDto): any { return null; } @@ -332,11 +382,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor { return properties.defaultValue; } - public visitTags(properties: TagsFieldPropertiesDto): any { + public visitTags(_: TagsFieldPropertiesDto): any { return null; } - public visitUI(properties: UIFieldPropertiesDto): any { + public visitUI(_: UIFieldPropertiesDto): any { return null; } } diff --git a/src/Squidex/appsettings.json b/src/Squidex/appsettings.json index b483bf879..20c442512 100644 --- a/src/Squidex/appsettings.json +++ b/src/Squidex/appsettings.json @@ -296,7 +296,7 @@ * * Supported: MongoDB, Development */ - "clustering": "Development", + "clustering": "MongoDB", /* * The port is used to share messages between all cluster members. Must be accessible within your cluster or network. */ diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs index 13ca9d77c..4fa3ef5f9 100644 --- a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs @@ -194,6 +194,20 @@ namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds Assert.Equal(new[] { id1, id2, schemaId }, result); } + [Fact] + public void Should_return_ids_from_references_field_without_schema_id() + { + var id1 = Guid.NewGuid(); + var id2 = Guid.NewGuid(); + + var sut = Fields.References(1, "my-refs", Partitioning.Invariant, + new ReferencesFieldProperties { SchemaId = schemaId }); + + var result = sut.GetReferencedIds(CreateValue(id1, id2), Ids.ContentOnly).ToArray(); + + Assert.Equal(new[] { id1, id2 }, result); + } + [Fact] public void Should_return_list_from_references_field_with_schema_id_list_for_referenced_ids_when_null() { diff --git a/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs new file mode 100644 index 000000000..abfa9bc9f --- /dev/null +++ b/tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs @@ -0,0 +1,81 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Squidex.Domain.Apps.Core.Apps; +using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.ExtractReferenceIds; +using Squidex.Domain.Apps.Core.Schemas; +using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.ExtractReferenceIds +{ + public class ReferenceFormattingTests + { + private readonly LanguagesConfig languages = LanguagesConfig.English.Set(Language.DE); + + [Fact] + public void Should_format_data_with_reference_fields() + { + var data = CreateData(); + + var schema = + new Schema("my-schema") + .AddString(1, "ref1", Partitioning.Invariant, + new StringFieldProperties { IsReferenceField = true }) + .AddString(2, "ref2", Partitioning.Invariant, + new StringFieldProperties { IsReferenceField = true }) + .AddString(3, "non-ref", Partitioning.Invariant); + + var formatted = data.FormatReferences(schema, languages); + + var expected = + JsonValue.Object() + .Add("en", "EN, 12") + .Add("de", "DE, 12"); + + Assert.Equal(expected, formatted); + } + + [Fact] + public void Should_format_data_with_first_field_if_no_reference_field_defined() + { + var data = CreateData(); + + var schema = + new Schema("my-schema") + .AddString(1, "ref1", Partitioning.Invariant) + .AddString(2, "ref2", Partitioning.Invariant) + .AddString(3, "non-ref", Partitioning.Invariant); + + var formatted = data.FormatReferences(schema, languages); + + var expected = + JsonValue.Object() + .Add("en", "EN") + .Add("de", "DE"); + + Assert.Equal(expected, formatted); + } + + private static NamedContentData CreateData() + { + return new NamedContentData() + .AddField("ref1", + new ContentFieldData() + .AddValue("en", "EN") + .AddValue("de", "DE")) + .AddField("ref2", + new ContentFieldData() + .AddValue("iv", 12)) + .AddField("non-ref", + new ContentFieldData() + .AddValue("iv", "Ignored")); + } + } +} diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs index 21d46e431..ac4199efd 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs @@ -19,7 +19,9 @@ namespace Squidex.Domain.Apps.Entities.Contents public sealed class ContentCommandMiddlewareTests : HandlerTestBase { private readonly IContentEnricher contentEnricher = A.Fake(); + private readonly IContextProvider contextProvider = A.Fake(); private readonly Guid contentId = Guid.NewGuid(); + private readonly Context requestContext = new Context(); private readonly ContentCommandMiddleware sut; public sealed class MyCommand : SquidexCommand @@ -33,7 +35,10 @@ namespace Squidex.Domain.Apps.Entities.Contents public ContentCommandMiddlewareTests() { - sut = new ContentCommandMiddleware(A.Fake(), contentEnricher); + A.CallTo(() => contextProvider.Context) + .Returns(requestContext); + + sut = new ContentCommandMiddleware(A.Fake(), contentEnricher, contextProvider); } [Fact] @@ -46,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Contents await sut.HandleAsync(context); - A.CallTo(() => contentEnricher.EnrichAsync(A.Ignored, User)) + A.CallTo(() => contentEnricher.EnrichAsync(A.Ignored, requestContext)) .MustNotHaveHappened(); } @@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents Assert.Same(result, context.Result()); - A.CallTo(() => contentEnricher.EnrichAsync(A.Ignored, User)) + A.CallTo(() => contentEnricher.EnrichAsync(A.Ignored, requestContext)) .MustNotHaveHappened(); } @@ -80,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents var enriched = new ContentEntity(); - A.CallTo(() => contentEnricher.EnrichAsync(result, User)) + A.CallTo(() => contentEnricher.EnrichAsync(result, requestContext)) .Returns(enriched); await sut.HandleAsync(context); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs index 83f406dd9..d795ec571 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs @@ -6,7 +6,6 @@ // ========================================================================== using System; -using System.Security.Claims; using System.Threading.Tasks; using FakeItEasy; using Squidex.Domain.Apps.Core.Contents; @@ -18,18 +17,14 @@ namespace Squidex.Domain.Apps.Entities.Contents public class ContentEnricherTests { private readonly IContentWorkflow contentWorkflow = A.Fake(); - private readonly IContextProvider contextProvider = A.Fake(); - private readonly ClaimsPrincipal user = new ClaimsPrincipal(); - private readonly Context context = new Context(); + private readonly IContentQueryService contentQuery = A.Fake(); + private readonly Context requestContext = new Context(); private readonly NamedId schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly ContentEnricher sut; public ContentEnricherTests() { - A.CallTo(() => contextProvider.Context) - .Returns(context); - - sut = new ContentEnricher(contentWorkflow, contextProvider); + sut = new ContentEnricher(new Lazy(() => contentQuery), contentWorkflow); } [Fact] @@ -40,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentWorkflow.GetInfoAsync(source)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); - var result = await sut.EnrichAsync(source, user); + var result = await sut.EnrichAsync(source, requestContext); Assert.Equal(StatusColors.Published, result.StatusColor); } @@ -53,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentWorkflow.GetInfoAsync(source)) .Returns(Task.FromResult(null)); - var result = await sut.EnrichAsync(source, user); + var result = await sut.EnrichAsync(source, requestContext); Assert.Equal(StatusColors.Draft, result.StatusColor); } @@ -61,14 +56,14 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_enrich_content_with_can_update() { - context.WithResolveFlow(true); + requestContext.WithResolveFlow(true); var source = new ContentEntity { SchemaId = schemaId }; A.CallTo(() => contentWorkflow.CanUpdateAsync(source)) .Returns(true); - var result = await sut.EnrichAsync(source, user); + var result = await sut.EnrichAsync(source, requestContext); Assert.True(result.CanUpdate); } @@ -76,11 +71,11 @@ namespace Squidex.Domain.Apps.Entities.Contents [Fact] public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context() { - context.WithResolveFlow(false); + requestContext.WithResolveFlow(false); var source = new ContentEntity { SchemaId = schemaId }; - var result = await sut.EnrichAsync(source, user); + var result = await sut.EnrichAsync(source, requestContext); Assert.False(result.CanUpdate); @@ -97,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents A.CallTo(() => contentWorkflow.GetInfoAsync(source1)) .Returns(new StatusInfo(Status.Published, StatusColors.Published)); - var result = await sut.EnrichAsync(new[] { source1, source2 }, user); + var result = await sut.EnrichAsync(new[] { source1, source2 }, requestContext); Assert.Equal(StatusColors.Published, result[0].StatusColor); Assert.Equal(StatusColors.Published, result[1].StatusColor); diff --git a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs index b98cdea04..9c917e4b4 100644 --- a/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs +++ b/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs @@ -527,7 +527,7 @@ namespace Squidex.Domain.Apps.Entities.Contents private void SetupEnricher() { - A.CallTo(() => contentEnricher.EnrichAsync(A>.Ignored, user)) + A.CallTo(() => contentEnricher.EnrichAsync(A>.Ignored, context)) .ReturnsLazily(x => { var input = (IEnumerable)x.Arguments[0];