Browse Source

Progress.

pull/388/head
Sebastian Stehle 7 years ago
parent
commit
9512b1da6f
  1. 2
      src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs
  2. 1
      src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs
  3. 5
      src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs
  4. 4
      src/Squidex.Domain.Apps.Core.Operations/ConvertContent/FieldConverters.cs
  5. 70
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs
  6. 15
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/Ids.cs
  7. 5
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs
  8. 13
      src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs
  9. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs
  10. 2
      src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs
  11. 4
      src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs
  12. 9
      src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs
  13. 115
      src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs
  14. 2
      src/Squidex.Domain.Apps.Entities/Contents/ContentQueryService.cs
  15. 5
      src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs
  16. 2
      src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs
  17. 2
      src/Squidex.Domain.Apps.Entities/Contents/Text/TextIndexerGrain.cs
  18. 1
      src/Squidex/Areas/Api/Controllers/Apps/AppsController.cs
  19. 1
      src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs
  20. 5
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs
  21. 1
      src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs
  22. 1
      src/Squidex/Areas/Api/Controllers/History/HistoryController.cs
  23. 1
      src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs
  24. 3
      src/Squidex/Config/Domain/EntitiesServices.cs
  25. 30
      src/Squidex/app/features/content/shared/content-item.component.ts
  26. 34
      src/Squidex/app/features/content/shared/references-dropdown.component.ts
  27. 2
      src/Squidex/app/shared/services/contents.service.spec.ts
  28. 11
      src/Squidex/app/shared/services/contents.service.ts
  29. 102
      src/Squidex/app/shared/state/contents.forms.ts
  30. 2
      src/Squidex/appsettings.json
  31. 14
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceExtractionTests.cs
  32. 81
      tests/Squidex.Domain.Apps.Core.Tests/Operations/ExtractReferenceIds/ReferenceFormattingTests.cs
  33. 13
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs
  34. 25
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs
  35. 2
      tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

2
src/Squidex.Domain.Apps.Core.Model/Contents/ContentFieldData.cs

@ -21,7 +21,7 @@ namespace Squidex.Domain.Apps.Core.Contents
public ContentFieldData AddValue(object value) 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) public ContentFieldData AddValue(string key, object value)

1
src/Squidex.Domain.Apps.Core.Model/Contents/Workflows.cs

@ -9,7 +9,6 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics.Contracts; using System.Diagnostics.Contracts;
using System.Linq; using System.Linq;
using System.Threading.Tasks;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Collections; using Squidex.Infrastructure.Collections;

5
src/Squidex.Domain.Apps.Core.Model/InvariantPartitioning.cs

@ -15,6 +15,7 @@ namespace Squidex.Domain.Apps.Core
public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem public sealed class InvariantPartitioning : IFieldPartitioning, IFieldPartitionItem
{ {
public static readonly InvariantPartitioning Instance = new InvariantPartitioning(); public static readonly InvariantPartitioning Instance = new InvariantPartitioning();
public static readonly string Key = "iv";
public int Count public int Count
{ {
@ -28,7 +29,7 @@ namespace Squidex.Domain.Apps.Core
string IFieldPartitionItem.Key string IFieldPartitionItem.Key
{ {
get { return "iv"; } get { return Key; }
} }
string IFieldPartitionItem.Name string IFieldPartitionItem.Name
@ -52,7 +53,7 @@ namespace Squidex.Domain.Apps.Core
public bool TryGetItem(string key, out IFieldPartitionItem item) 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; item = isFound ? this : null;

4
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) public static FieldConverter ResolveInvariant(LanguagesConfig config)
{ {
var codeForInvariant = InvariantPartitioning.Instance.Master.Key; var codeForInvariant = InvariantPartitioning.Key;
var codeForMasterLanguage = config.Master.Language.Iso2Code; var codeForMasterLanguage = config.Master.Language.Iso2Code;
return (data, field) => return (data, field) =>
@ -126,7 +126,7 @@ namespace Squidex.Domain.Apps.Core.ConvertContent
public static FieldConverter ResolveLanguages(LanguagesConfig config) public static FieldConverter ResolveLanguages(LanguagesConfig config)
{ {
var codeForInvariant = InvariantPartitioning.Instance.Master.Key; var codeForInvariant = InvariantPartitioning.Key;
return (data, field) => return (data, field) =>
{ {

70
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ContentReferencesExtensions.cs

@ -8,15 +8,18 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Text;
using Squidex.Domain.Apps.Core.Apps;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure; using Squidex.Infrastructure;
using Squidex.Infrastructure.Json.Objects;
namespace Squidex.Domain.Apps.Core.ExtractReferenceIds namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ContentReferencesExtensions public static class ContentReferencesExtensions
{ {
public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, Schema schema) public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, Schema schema, Ids strategy = Ids.All)
{ {
Guard.NotNull(schema, nameof(schema)); Guard.NotNull(schema, nameof(schema));
@ -24,7 +27,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
foreach (var field in schema.Fields) foreach (var field in schema.Fields)
{ {
var ids = source.GetReferencedIds(field); var ids = source.GetReferencedIds(field, strategy);
foreach (var id in ids) foreach (var id in ids)
{ {
@ -36,7 +39,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, IField field) public static IEnumerable<Guid> GetReferencedIds(this IdContentData source, IField field, Ids strategy = Ids.All)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
@ -44,7 +47,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
foreach (var partitionValue in fieldData) foreach (var partitionValue in fieldData)
{ {
var ids = field.GetReferencedIds(partitionValue.Value); var ids = field.GetReferencedIds(partitionValue.Value, strategy);
foreach (var id in ids) foreach (var id in ids)
{ {
@ -54,7 +57,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
} }
} }
public static IEnumerable<Guid> GetReferencedIds(this NamedContentData source, IField field) public static IEnumerable<Guid> GetReferencedIds(this NamedContentData source, IField field, Ids strategy = Ids.All)
{ {
Guard.NotNull(field, nameof(field)); Guard.NotNull(field, nameof(field));
@ -62,7 +65,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
foreach (var partitionValue in fieldData) foreach (var partitionValue in fieldData)
{ {
var ids = field.GetReferencedIds(partitionValue.Value); var ids = field.GetReferencedIds(partitionValue.Value, strategy);
foreach (var id in ids) 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();
}
} }
} }

15
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
}
}

5
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtensions.cs

@ -7,7 +7,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Schemas;
using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Json.Objects;
@ -15,9 +14,9 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
public static class ReferencesExtensions public static class ReferencesExtensions
{ {
public static IEnumerable<Guid> GetReferencedIds(this IField field, IJsonValue value) public static IEnumerable<Guid> 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<Guid> oldReferences) public static IJsonValue CleanReferences(this IField field, IJsonValue value, ICollection<Guid> oldReferences)

13
src/Squidex.Domain.Apps.Core.Operations/ExtractReferenceIds/ReferencesExtractor.cs

@ -16,15 +16,18 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>> public sealed class ReferencesExtractor : IFieldVisitor<IEnumerable<Guid>>
{ {
private readonly IJsonValue value; private readonly IJsonValue value;
private readonly Ids strategy;
private ReferencesExtractor(IJsonValue value) private ReferencesExtractor(IJsonValue value, Ids strategy)
{ {
this.value = value; this.value = value;
this.strategy = strategy;
} }
public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value) public static IEnumerable<Guid> ExtractReferences(IField field, IJsonValue value, Ids strategy)
{ {
return field.Accept(new ReferencesExtractor(value)); return field.Accept(new ReferencesExtractor(value, strategy));
} }
public IEnumerable<Guid> Visit(IArrayField field) public IEnumerable<Guid> Visit(IArrayField field)
@ -39,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ExtractReferenceIds
{ {
if (item.TryGetValue(nestedField.Name, out var nestedValue)) 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(); var ids = value.ToGuidSet();
if (field.Properties.SchemaId != Guid.Empty) if (strategy == Ids.All && field.Properties.SchemaId != Guid.Empty)
{ {
ids.Add(field.Properties.SchemaId); ids.Add(field.Properties.SchemaId);
} }

2
src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ObjectPath.cs

@ -26,7 +26,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent
} }
else if (index == 1) else if (index == 1)
{ {
if (!property.Equals(InvariantPartitioning.Instance.Master.Key, StringComparison.OrdinalIgnoreCase)) if (!property.Equals(InvariantPartitioning.Key, StringComparison.OrdinalIgnoreCase))
{ {
sb.Append("("); sb.Append("(");
sb.Append(property); sb.Append(property);

2
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(); 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; FilterNode filter = null;

4
src/Squidex.Domain.Apps.Entities/Apps/Templates/CreateProfileCommandMiddleware.cs

@ -150,7 +150,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
private static async Task<NamedId<Guid>> CreateExperienceSchemaAsync(Func<ICommand, Task> publish) private static async Task<NamedId<Guid>> CreateExperienceSchemaAsync(Func<ICommand, Task> publish)
{ {
var schema = var schema =
SchemaBuilder.Create("experience") SchemaBuilder.Create("Experience")
.AddString("Position", f => f .AddString("Position", f => f
.Required() .Required()
.ShowInList() .ShowInList()
@ -177,7 +177,7 @@ namespace Squidex.Domain.Apps.Entities.Apps.Templates
private static async Task<NamedId<Guid>> CreateEducationSchemaAsync(Func<ICommand, Task> publish) private static async Task<NamedId<Guid>> CreateEducationSchemaAsync(Func<ICommand, Task> publish)
{ {
var schema = var schema =
SchemaBuilder.Create("Experience") SchemaBuilder.Create("Education")
.AddString("Degree", f => f .AddString("Degree", f => f
.Required() .Required()
.ShowInList() .ShowInList()

9
src/Squidex.Domain.Apps.Entities/Contents/ContentCommandMiddleware.cs

@ -17,22 +17,25 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentCommandMiddleware : GrainCommandMiddleware<ContentCommand, IContentGrain> public sealed class ContentCommandMiddleware : GrainCommandMiddleware<ContentCommand, IContentGrain>
{ {
private readonly IContentEnricher contentEnricher; private readonly IContentEnricher contentEnricher;
private readonly IContextProvider contextProvider;
public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher) public ContentCommandMiddleware(IGrainFactory grainFactory, IContentEnricher contentEnricher, IContextProvider contextProvider)
: base(grainFactory) : base(grainFactory)
{ {
Guard.NotNull(contentEnricher, nameof(contentEnricher)); Guard.NotNull(contentEnricher, nameof(contentEnricher));
Guard.NotNull(contextProvider, nameof(contextProvider));
this.contentEnricher = contentEnricher; this.contentEnricher = contentEnricher;
this.contextProvider = contextProvider;
} }
public override async Task HandleAsync(CommandContext context, Func<Task> next) public override async Task HandleAsync(CommandContext context, Func<Task> next)
{ {
await base.HandleAsync(context, 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); context.Complete(enriched);
} }

115
src/Squidex.Domain.Apps.Entities/Contents/ContentEnricher.cs

@ -8,7 +8,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Core.ExtractReferenceIds; using Squidex.Domain.Apps.Core.ExtractReferenceIds;
@ -23,41 +22,37 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentEnricher : IContentEnricher public sealed class ContentEnricher : IContentEnricher
{ {
private const string DefaultColor = StatusColors.Draft; private const string DefaultColor = StatusColors.Draft;
private readonly IAppProvider appProvider; private static readonly Dictionary<Guid, IEnrichedContentEntity> EmptyReferences = new Dictionary<Guid, IEnrichedContentEntity>();
private readonly IContentQueryService contentQuery; private readonly Lazy<IContentQueryService> contentQuery;
private readonly IContentWorkflow contentWorkflow; private readonly IContentWorkflow contentWorkflow;
private readonly IContextProvider contextProvider;
public ContentEnricher( private IContentQueryService ContentQuery
IAppProvider appProvider, {
IContentQueryService contentQuery, get { return contentQuery.Value; }
IContentWorkflow contentWorkflow, }
IContextProvider contextProvider)
public ContentEnricher(Lazy<IContentQueryService> contentQuery, IContentWorkflow contentWorkflow)
{ {
Guard.NotNull(appProvider, nameof(appProvider));
Guard.NotNull(contentQuery, nameof(contentQuery)); Guard.NotNull(contentQuery, nameof(contentQuery));
Guard.NotNull(contentWorkflow, nameof(contentWorkflow)); Guard.NotNull(contentWorkflow, nameof(contentWorkflow));
Guard.NotNull(contextProvider, nameof(contextProvider));
this.appProvider = appProvider;
this.contentQuery = contentQuery; this.contentQuery = contentQuery;
this.contentWorkflow = contentWorkflow; this.contentWorkflow = contentWorkflow;
this.contextProvider = contextProvider;
} }
public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, ClaimsPrincipal user) public async Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context)
{ {
Guard.NotNull(content, nameof(content)); 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]; return enriched[0];
} }
public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, ClaimsPrincipal user) public async Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context)
{ {
Guard.NotNull(contents, nameof(contents)); Guard.NotNull(contents, nameof(contents));
Guard.NotNull(user, nameof(user)); Guard.NotNull(context, nameof(context));
using (Profiler.TraceMethod<ContentEnricher>()) using (Profiler.TraceMethod<ContentEnricher>())
{ {
@ -73,20 +68,20 @@ namespace Squidex.Domain.Apps.Entities.Contents
await ResolveColorAsync(content, result, cache); await ResolveColorAsync(content, result, cache);
if (ShouldEnrichWithStatuses()) if (ShouldEnrichWithStatuses(context))
{ {
await ResolveNextsAsync(content, result, user); await ResolveNextsAsync(content, result, context);
await ResolveCanUpdateAsync(content, result); await ResolveCanUpdateAsync(content, result);
} }
results.Add(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<Guid> schemaId, IEnumerable<ContentEntity> contents) private async Task ResolveReferencesAsync(Guid schemaId, IEnumerable<ContentEntity> contents, Context context)
{ {
var appId = contents.First().AppId.Id; var appId = contents.First().AppId.Id;
var schema = await appProvider.GetSchemaAsync(appId, schemaId.Id); var schema = await ContentQuery.GetSchemaOrThrowAsync(context, schemaId.ToString());
var referenceFields = var referenceFields =
schema.SchemaDef.Fields.OfType<IField<ReferencesFieldProperties>>() schema.SchemaDef.Fields.OfType<IField<ReferencesFieldProperties>>()
.Where(x => .Where(x =>
x.Properties.SchemaId != Guid.Empty &&
x.Properties.MinItems == 1 && x.Properties.MinItems == 1 &&
x.Properties.MaxItems == 1 && x.Properties.MaxItems == 1 &&
(x.Properties.IsListField || x.Properties.IsReferenceField)); x.Properties.IsListField);
var formatted = new Dictionary<Guid, JsonObject>();
foreach (var field in referenceFields) 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) 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)) 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<Guid> GetContentIds(IEnumerable<ContentEntity> contents, IField<ReferencesFieldProperties> field) private async Task<Dictionary<Guid, IEnrichedContentEntity>> GetReferencesAsync(Guid schemaId, IEnumerable<ContentEntity> contents, IField field, Context context)
{ {
var allIds = new HashSet<Guid>(); var ids = new HashSet<Guid>();
foreach (var content in contents) 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 references.ToDictionary(x => x.Id);
{ }
return contextProvider.Context.IsFrontendClient || contextProvider.Context.IsResolveFlow(); else
{
return EmptyReferences;
}
} }
private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result) private async Task ResolveCanUpdateAsync(IContentEntity content, ContentEntity result)
@ -163,9 +176,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
result.CanUpdate = await contentWorkflow.CanUpdateAsync(content); 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) 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; 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();
}
} }
} }

2
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 scriptText = schema.SchemaDef.Scripts.Query;
var scripting = !string.IsNullOrWhiteSpace(scriptText); 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) foreach (var content in enriched)
{ {

5
src/Squidex.Domain.Apps.Entities/Contents/IContentEnricher.cs

@ -6,15 +6,14 @@
// ========================================================================== // ==========================================================================
using System.Collections.Generic; using System.Collections.Generic;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Squidex.Domain.Apps.Entities.Contents namespace Squidex.Domain.Apps.Entities.Contents
{ {
public interface IContentEnricher public interface IContentEnricher
{ {
Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, ClaimsPrincipal user); Task<IEnrichedContentEntity> EnrichAsync(IContentEntity content, Context context);
Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, ClaimsPrincipal user); Task<IReadOnlyList<IEnrichedContentEntity>> EnrichAsync(IEnumerable<IContentEntity> contents, Context context);
} }
} }

2
src/Squidex.Domain.Apps.Entities/Contents/IEnrichedContentEntity.cs

@ -16,5 +16,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
string StatusColor { get; } string StatusColor { get; }
StatusInfo[] Nexts { get; } StatusInfo[] Nexts { get; }
NamedContentData ReferenceData { get; }
} }
} }

2
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 TimeSpan CommitDelay = TimeSpan.FromSeconds(10);
private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler(); private static readonly MergeScheduler MergeScheduler = new ConcurrentMergeScheduler();
private static readonly Analyzer Analyzer = new MultiLanguageAnalyzer(Version); 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 SnapshotDeletionPolicy snapshotter = new SnapshotDeletionPolicy(new KeepOnlyLastCommitDeletionPolicy());
private readonly IAssetStore assetStore; private readonly IAssetStore assetStore;
private IDisposable timer; private IDisposable timer;

1
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;
using Squidex.Domain.Apps.Entities.Apps.Commands; using Squidex.Domain.Apps.Entities.Apps.Commands;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Security;
using Squidex.Shared; using Squidex.Shared;

1
src/Squidex/Areas/Api/Controllers/Apps/Models/ContributorsDto.cs

@ -10,7 +10,6 @@ using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Squidex.Domain.Apps.Entities.Apps; using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Apps.Services; using Squidex.Domain.Apps.Entities.Apps.Services;
using Squidex.Infrastructure;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Web; using Squidex.Web;

5
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentDto.cs

@ -49,6 +49,11 @@ namespace Squidex.Areas.Api.Controllers.Contents.Models
/// </summary> /// </summary>
public object DataDraft { get; set; } public object DataDraft { get; set; }
/// <summary>
/// The reference data for the frontend UI.
/// </summary>
public NamedContentData ReferenceData { get; set; }
/// <summary> /// <summary>
/// Indicates if the draft data is pending. /// Indicates if the draft data is pending.
/// </summary> /// </summary>

1
src/Squidex/Areas/Api/Controllers/Contents/Models/ContentsDto.cs

@ -8,7 +8,6 @@
using System.ComponentModel.DataAnnotations; using System.ComponentModel.DataAnnotations;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Squidex.Domain.Apps.Core.Contents;
using Squidex.Domain.Apps.Entities; using Squidex.Domain.Apps.Entities;
using Squidex.Domain.Apps.Entities.Contents; using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;

1
src/Squidex/Areas/Api/Controllers/History/HistoryController.cs

@ -10,7 +10,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Squidex.Areas.Api.Controllers.History.Models; using Squidex.Areas.Api.Controllers.History.Models;
using Squidex.Domain.Apps.Entities.History; using Squidex.Domain.Apps.Entities.History;
using Squidex.Infrastructure;
using Squidex.Infrastructure.Commands; using Squidex.Infrastructure.Commands;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Web; using Squidex.Web;

1
src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemasDto.cs

@ -8,7 +8,6 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Squidex.Domain.Apps.Entities.Schemas; using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Infrastructure;
using Squidex.Shared; using Squidex.Shared;
using Squidex.Web; using Squidex.Web;

3
src/Squidex/Config/Domain/EntitiesServices.cs

@ -105,6 +105,9 @@ namespace Squidex.Config.Domain
services.AddSingletonAs<ContentEnricher>() services.AddSingletonAs<ContentEnricher>()
.As<IContentEnricher>(); .As<IContentEnricher>();
services.AddSingletonAs(c => new Lazy<IContentEnricher>(() => c.GetRequiredService<IContentEnricher>()))
.AsSelf();
services.AddSingletonAs<ContentQueryService>() services.AddSingletonAs<ContentQueryService>()
.As<IContentQueryService>(); .As<IContentQueryService>();

30
src/Squidex/app/features/content/shared/content-item.component.ts

@ -13,13 +13,11 @@ import {
ContentsState, ContentsState,
fadeAnimation, fadeAnimation,
FieldDto, FieldDto,
FieldFormatter, getContentValue,
fieldInvariant,
ModalModel, ModalModel,
PatchContentForm, PatchContentForm,
RootFieldDto, RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto
Types
} from '@app/shared'; } from '@app/shared';
/* tslint:disable:component-selector */ /* tslint:disable:component-selector */
@ -152,13 +150,9 @@ export class ContentItemComponent implements OnChanges {
this.values = []; this.values = [];
for (let field of this.schemaFields) { 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(formatted);
this.values.push('');
} else {
this.values.push(FieldFormatter.format(field, value, true));
}
if (this.patchForm) { if (this.patchForm) {
const formControl = this.patchForm.form.controls[field.name]; const formControl = this.patchForm.form.controls[field.name];
@ -170,21 +164,7 @@ export class ContentItemComponent implements OnChanges {
} }
} }
private getRawValue(field: RootFieldDto): any { public trackByField(field: FieldDto) {
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) {
return field.fieldId + this.schema.id; return field.fieldId + this.schema.id;
} }
} }

34
src/Squidex/app/features/content/shared/references-dropdown.component.ts

@ -15,11 +15,9 @@ import {
AppsState, AppsState,
ContentDto, ContentDto,
ContentsService, ContentsService,
FieldFormatter, getContentValue,
fieldInvariant,
ImmutableArray, ImmutableArray,
MathHelper, MathHelper,
RootFieldDto,
SchemaDetailsDto, SchemaDetailsDto,
SchemasService, SchemasService,
StatefulControlComponent, StatefulControlComponent,
@ -125,32 +123,12 @@ export class ReferencesDropdownComponent extends StatefulControlComponent<State,
return ImmutableArray.empty(); return ImmutableArray.empty();
} }
function getRawValue(field: RootFieldDto, data: any, language: AppLanguageDto): any {
const contentField = data[field.name];
if (contentField) {
if (field.isLocalizable) {
return contentField[language.iso2Code];
} else {
return contentField[fieldInvariant];
}
}
return undefined;
}
return contents.map(content => { return contents.map(content => {
const values: any[] = []; const name =
schema.referenceFields
for (let field of schema.referenceFields) { .map(f => getContentValue(content, this.languageField, f, false))
const value = getRawValue(field, content.data, this.languageField); .map(v => v.formatted)
.join(', ');
if (!Types.isUndefined(value)) {
values.push(FieldFormatter.format(field, value, false));
}
}
const name = values.join(', ');
return { name, id: content.id }; return { name, id: content.id };
}); });

2
src/Squidex/app/shared/services/contents.service.spec.ts

@ -366,6 +366,7 @@ describe('ContentsService', () => {
isPending: true, isPending: true,
data: {}, data: {},
dataDraft: {}, dataDraft: {},
referenceData: {},
version: `${id}`, version: `${id}`,
_links: { _links: {
update: { method: 'PUT', href: `/contents/id${id}` } update: { method: 'PUT', href: `/contents/id${id}` }
@ -389,5 +390,6 @@ export function createContent(id: number, suffix = '') {
true, true,
{}, {},
{}, {},
{},
new Version(`${id}`)); new Version(`${id}`));
} }

11
src/Squidex/app/shared/services/contents.service.ts

@ -55,6 +55,11 @@ export class ContentsDto extends ResultSet<ContentDto> {
} }
} }
export type ContentReferencesValue = { [partition: string]: string };
export type ContentReferences = { [fieldName: string ]: ContentFieldData<ContentReferencesValue> };
export type ContentFieldData<T = any> = { [partition: string]: T };
export type ContentData = { [fieldName: string ]: ContentFieldData };
export class ContentDto { export class ContentDto {
public readonly _links: ResourceLinks; public readonly _links: ResourceLinks;
@ -77,8 +82,9 @@ export class ContentDto {
public readonly lastModifiedBy: string, public readonly lastModifiedBy: string,
public readonly scheduleJob: ScheduleDto | null, public readonly scheduleJob: ScheduleDto | null,
public readonly isPending: boolean, public readonly isPending: boolean,
public readonly data: object | any, public readonly data: ContentData | undefined,
public readonly dataDraft: object, public readonly dataDraft: ContentData,
public readonly referenceData: ContentReferences,
public readonly version: Version public readonly version: Version
) { ) {
this._links = links; this._links = links;
@ -299,5 +305,6 @@ function parseContent(response: any) {
response.isPending === true, response.isPending === true,
response.data, response.data,
response.dataDraft, response.dataDraft,
response.referenceData,
new Version(response.version.toString())); new Version(response.version.toString()));
} }

102
src/Squidex/app/shared/state/contents.forms.ts

@ -19,6 +19,8 @@ import {
value$ value$
} from '@app/framework'; } from '@app/framework';
import { ContentDto, ContentReferencesValue } from '../services/contents.service';
import { LanguageDto } from '../services/languages.service';
import { AppLanguageDto } from './../services/app-languages.service'; import { AppLanguageDto } from './../services/app-languages.service';
import { FieldDto, RootFieldDto, SchemaDetailsDto } from './../services/schemas.service'; import { FieldDto, RootFieldDto, SchemaDetailsDto } from './../services/schemas.service';
import { import {
@ -56,7 +58,55 @@ export class SaveQueryForm extends Form<FormGroup, any> {
} }
} }
export class FieldFormatter implements FieldPropertiesVisitor<any> { 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<FieldValue> {
constructor( constructor(
private readonly value: any, private readonly value: any,
private readonly allowHtml: boolean private readonly allowHtml: boolean
@ -71,7 +121,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
return field.properties.accept(new FieldFormatter(value, allowHtml)); return field.properties.accept(new FieldFormatter(value, allowHtml));
} }
public visitDateTime(properties: DateTimeFieldPropertiesDto): string | any { public visitDateTime(properties: DateTimeFieldPropertiesDto): FieldValue {
try { try {
const parsed = DateTime.parseISO_UTC(this.value); const parsed = DateTime.parseISO_UTC(this.value);
@ -85,7 +135,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
} }
} }
public visitArray(properties: ArrayFieldPropertiesDto): string { public visitArray(_: ArrayFieldPropertiesDto): string {
if (this.value.length) { if (this.value.length) {
return `${this.value.length} Item(s)`; return `${this.value.length} Item(s)`;
} else { } else {
@ -93,7 +143,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
} }
} }
public visitAssets(properties: AssetsFieldPropertiesDto): string { public visitAssets(_: AssetsFieldPropertiesDto): string {
if (this.value.length) { if (this.value.length) {
return `${this.value.length} Asset(s)`; return `${this.value.length} Asset(s)`;
} else { } else {
@ -101,7 +151,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
} }
} }
public visitReferences(properties: ReferencesFieldPropertiesDto): string { public visitReferences(_: ReferencesFieldPropertiesDto): string {
if (this.value.length) { if (this.value.length) {
return `${this.value.length} Reference(s)`; return `${this.value.length} Reference(s)`;
} else { } else {
@ -109,7 +159,7 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
} }
} }
public visitTags(properties: TagsFieldPropertiesDto): string { public visitTags(_: TagsFieldPropertiesDto): string {
if (this.value.length) { if (this.value.length) {
return this.value.join(', '); return this.value.join(', ');
} else { } else {
@ -117,19 +167,19 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
} }
} }
public visitBoolean(properties: BooleanFieldPropertiesDto): string { public visitBoolean(_: BooleanFieldPropertiesDto): string {
return this.value ? 'Yes' : 'No'; return this.value ? 'Yes' : 'No';
} }
public visitGeolocation(properties: GeolocationFieldPropertiesDto): string { public visitGeolocation(_: GeolocationFieldPropertiesDto): string {
return `${this.value.longitude}, ${this.value.latitude}`; return `${this.value.longitude}, ${this.value.latitude}`;
} }
public visitJson(properties: JsonFieldPropertiesDto): string { public visitJson(_: JsonFieldPropertiesDto): string {
return '<Json />'; return '<Json />';
} }
public visitNumber(properties: NumberFieldPropertiesDto): string | HtmlValue | number { public visitNumber(properties: NumberFieldPropertiesDto): FieldValue {
if (Types.isNumber(this.value) && properties.editor === 'Stars' && this.allowHtml) { if (Types.isNumber(this.value) && properties.editor === 'Stars' && this.allowHtml) {
if (this.value <= 0 || this.value > 6) { if (this.value <= 0 || this.value > 6) {
return new HtmlValue(`&#9733; ${this.value}`); return new HtmlValue(`&#9733; ${this.value}`);
@ -143,15 +193,15 @@ export class FieldFormatter implements FieldPropertiesVisitor<any> {
return new HtmlValue(html); return new HtmlValue(html);
} }
} }
return this.value; return `${this.value}`;
} }
public visitString(properties: StringFieldPropertiesDto): any { public visitString(_: StringFieldPropertiesDto): any {
return this.value; return this.value;
} }
public visitUI(properties: UIFieldPropertiesDto): any { public visitUI(_: UIFieldPropertiesDto): any {
return this.value; return '';
} }
} }
@ -257,23 +307,23 @@ export class FieldValidatorsFactory implements FieldPropertiesVisitor<ValidatorF
return validators; return validators;
} }
public visitBoolean(properties: BooleanFieldPropertiesDto): ValidatorFn[] { public visitBoolean(_: BooleanFieldPropertiesDto): ValidatorFn[] {
return []; return [];
} }
public visitDateTime(properties: DateTimeFieldPropertiesDto): ValidatorFn[] { public visitDateTime(_: DateTimeFieldPropertiesDto): ValidatorFn[] {
return []; return [];
} }
public visitGeolocation(properties: GeolocationFieldPropertiesDto): ValidatorFn[] { public visitGeolocation(_: GeolocationFieldPropertiesDto): ValidatorFn[] {
return []; return [];
} }
public visitJson(properties: JsonFieldPropertiesDto): ValidatorFn[] { public visitJson(_: JsonFieldPropertiesDto): ValidatorFn[] {
return []; return [];
} }
public visitUI(properties: UIFieldPropertiesDto): ValidatorFn[] { public visitUI(_: UIFieldPropertiesDto): ValidatorFn[] {
return []; return [];
} }
} }
@ -300,11 +350,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return field.properties.accept(new FieldDefaultValue(now)); return field.properties.accept(new FieldDefaultValue(now));
} }
public visitArray(properties: ArrayFieldPropertiesDto): any { public visitArray(_: ArrayFieldPropertiesDto): any {
return null; return null;
} }
public visitAssets(properties: AssetsFieldPropertiesDto): any { public visitAssets(_: AssetsFieldPropertiesDto): any {
return null; return null;
} }
@ -312,11 +362,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue; return properties.defaultValue;
} }
public visitGeolocation(properties: GeolocationFieldPropertiesDto): any { public visitGeolocation(_: GeolocationFieldPropertiesDto): any {
return null; return null;
} }
public visitJson(properties: JsonFieldPropertiesDto): any { public visitJson(_: JsonFieldPropertiesDto): any {
return null; return null;
} }
@ -324,7 +374,7 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue; return properties.defaultValue;
} }
public visitReferences(properties: ReferencesFieldPropertiesDto): any { public visitReferences(_: ReferencesFieldPropertiesDto): any {
return null; return null;
} }
@ -332,11 +382,11 @@ export class FieldDefaultValue implements FieldPropertiesVisitor<any> {
return properties.defaultValue; return properties.defaultValue;
} }
public visitTags(properties: TagsFieldPropertiesDto): any { public visitTags(_: TagsFieldPropertiesDto): any {
return null; return null;
} }
public visitUI(properties: UIFieldPropertiesDto): any { public visitUI(_: UIFieldPropertiesDto): any {
return null; return null;
} }
} }

2
src/Squidex/appsettings.json

@ -296,7 +296,7 @@
* *
* Supported: MongoDB, Development * 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. * The port is used to share messages between all cluster members. Must be accessible within your cluster or network.
*/ */

14
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); 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] [Fact]
public void Should_return_list_from_references_field_with_schema_id_list_for_referenced_ids_when_null() public void Should_return_list_from_references_field_with_schema_id_list_for_referenced_ids_when_null()
{ {

81
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"));
}
}
}

13
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentCommandMiddlewareTests.cs

@ -19,7 +19,9 @@ namespace Squidex.Domain.Apps.Entities.Contents
public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentState> public sealed class ContentCommandMiddlewareTests : HandlerTestBase<ContentState>
{ {
private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>(); private readonly IContentEnricher contentEnricher = A.Fake<IContentEnricher>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>();
private readonly Guid contentId = Guid.NewGuid(); private readonly Guid contentId = Guid.NewGuid();
private readonly Context requestContext = new Context();
private readonly ContentCommandMiddleware sut; private readonly ContentCommandMiddleware sut;
public sealed class MyCommand : SquidexCommand public sealed class MyCommand : SquidexCommand
@ -33,7 +35,10 @@ namespace Squidex.Domain.Apps.Entities.Contents
public ContentCommandMiddlewareTests() public ContentCommandMiddlewareTests()
{ {
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher); A.CallTo(() => contextProvider.Context)
.Returns(requestContext);
sut = new ContentCommandMiddleware(A.Fake<IGrainFactory>(), contentEnricher, contextProvider);
} }
[Fact] [Fact]
@ -46,7 +51,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
await sut.HandleAsync(context); await sut.HandleAsync(context);
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, User)) A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, requestContext))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -64,7 +69,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
Assert.Same(result, context.Result<IEnrichedContentEntity>()); Assert.Same(result, context.Result<IEnrichedContentEntity>());
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, User)) A.CallTo(() => contentEnricher.EnrichAsync(A<IEnrichedContentEntity>.Ignored, requestContext))
.MustNotHaveHappened(); .MustNotHaveHappened();
} }
@ -80,7 +85,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
var enriched = new ContentEntity(); var enriched = new ContentEntity();
A.CallTo(() => contentEnricher.EnrichAsync(result, User)) A.CallTo(() => contentEnricher.EnrichAsync(result, requestContext))
.Returns(enriched); .Returns(enriched);
await sut.HandleAsync(context); await sut.HandleAsync(context);

25
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentEnricherTests.cs

@ -6,7 +6,6 @@
// ========================================================================== // ==========================================================================
using System; using System;
using System.Security.Claims;
using System.Threading.Tasks; using System.Threading.Tasks;
using FakeItEasy; using FakeItEasy;
using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Contents;
@ -18,18 +17,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
public class ContentEnricherTests public class ContentEnricherTests
{ {
private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>(); private readonly IContentWorkflow contentWorkflow = A.Fake<IContentWorkflow>();
private readonly IContextProvider contextProvider = A.Fake<IContextProvider>(); private readonly IContentQueryService contentQuery = A.Fake<IContentQueryService>();
private readonly ClaimsPrincipal user = new ClaimsPrincipal(); private readonly Context requestContext = new Context();
private readonly Context context = new Context();
private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema"); private readonly NamedId<Guid> schemaId = NamedId.Of(Guid.NewGuid(), "my-schema");
private readonly ContentEnricher sut; private readonly ContentEnricher sut;
public ContentEnricherTests() public ContentEnricherTests()
{ {
A.CallTo(() => contextProvider.Context) sut = new ContentEnricher(new Lazy<IContentQueryService>(() => contentQuery), contentWorkflow);
.Returns(context);
sut = new ContentEnricher(contentWorkflow, contextProvider);
} }
[Fact] [Fact]
@ -40,7 +35,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentWorkflow.GetInfoAsync(source)) A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(new StatusInfo(Status.Published, StatusColors.Published)); .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); Assert.Equal(StatusColors.Published, result.StatusColor);
} }
@ -53,7 +48,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentWorkflow.GetInfoAsync(source)) A.CallTo(() => contentWorkflow.GetInfoAsync(source))
.Returns(Task.FromResult<StatusInfo>(null)); .Returns(Task.FromResult<StatusInfo>(null));
var result = await sut.EnrichAsync(source, user); var result = await sut.EnrichAsync(source, requestContext);
Assert.Equal(StatusColors.Draft, result.StatusColor); Assert.Equal(StatusColors.Draft, result.StatusColor);
} }
@ -61,14 +56,14 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_enrich_content_with_can_update() public async Task Should_enrich_content_with_can_update()
{ {
context.WithResolveFlow(true); requestContext.WithResolveFlow(true);
var source = new ContentEntity { SchemaId = schemaId }; var source = new ContentEntity { SchemaId = schemaId };
A.CallTo(() => contentWorkflow.CanUpdateAsync(source)) A.CallTo(() => contentWorkflow.CanUpdateAsync(source))
.Returns(true); .Returns(true);
var result = await sut.EnrichAsync(source, user); var result = await sut.EnrichAsync(source, requestContext);
Assert.True(result.CanUpdate); Assert.True(result.CanUpdate);
} }
@ -76,11 +71,11 @@ namespace Squidex.Domain.Apps.Entities.Contents
[Fact] [Fact]
public async Task Should_not_enrich_content_with_can_update_if_disabled_in_context() 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 source = new ContentEntity { SchemaId = schemaId };
var result = await sut.EnrichAsync(source, user); var result = await sut.EnrichAsync(source, requestContext);
Assert.False(result.CanUpdate); Assert.False(result.CanUpdate);
@ -97,7 +92,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
A.CallTo(() => contentWorkflow.GetInfoAsync(source1)) A.CallTo(() => contentWorkflow.GetInfoAsync(source1))
.Returns(new StatusInfo(Status.Published, StatusColors.Published)); .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[0].StatusColor);
Assert.Equal(StatusColors.Published, result[1].StatusColor); Assert.Equal(StatusColors.Published, result[1].StatusColor);

2
tests/Squidex.Domain.Apps.Entities.Tests/Contents/ContentQueryServiceTests.cs

@ -527,7 +527,7 @@ namespace Squidex.Domain.Apps.Entities.Contents
private void SetupEnricher() private void SetupEnricher()
{ {
A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored, user)) A.CallTo(() => contentEnricher.EnrichAsync(A<IEnumerable<IContentEntity>>.Ignored, context))
.ReturnsLazily(x => .ReturnsLazily(x =>
{ {
var input = (IEnumerable<IContentEntity>)x.Arguments[0]; var input = (IEnumerable<IContentEntity>)x.Arguments[0];

Loading…
Cancel
Save