diff --git a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs index 0e5e9d08a..18990457b 100644 --- a/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs +++ b/backend/extensions/Squidex.Extensions/Validation/CompositeUniqueValidator.cs @@ -27,7 +27,7 @@ namespace Squidex.Extensions.Validation this.contentRepository = contentRepository; } - public async Task ValidateAsync(object value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object value, ValidationContext context, AddError addError) { if (value is ContentData data) { diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs index 2e46f8b91..2d4277182 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/Schemas/SchemaScripts.cs @@ -20,5 +20,7 @@ namespace Squidex.Domain.Apps.Core.Schemas public string? Delete { get; init; } public string? Query { get; init; } + + public string? QueryPre { get; init; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs index 9c37463e4..fcfc4af71 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentDataObject.cs @@ -143,5 +143,15 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper } } } + + public override object ToObject() + { + if (TryUpdate(out var result)) + { + return result; + } + + return contentData; + } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs index a300d411a..34232897c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ContentWrapper/ContentFieldObject.cs @@ -161,5 +161,15 @@ namespace Squidex.Domain.Apps.Core.Scripting.ContentWrapper } } } + + public override object ToObject() + { + if (TryUpdate(out var result)) + { + return result!; + } + + return fieldData!; + } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs index 92f4eb0f8..0c04b0680 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptContext.cs @@ -17,6 +17,11 @@ namespace Squidex.Domain.Apps.Core.Scripting { } + public ScriptContext(ScriptContext source) + : base(source, StringComparer.OrdinalIgnoreCase) + { + } + public bool TryGetValue(string key, [MaybeNullWhen(false)] out T value) { Guard.NotNull(key, nameof(key)); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs index b80616eb6..fd87bd785 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptExecutionContext.cs @@ -68,18 +68,11 @@ namespace Squidex.Domain.Apps.Core.Scripting if (options.AsContext) { - var contextInstance = new ObjectInstance(engine); + var contextInstance = new WritableContext(engine, vars); - foreach (var (key, value) in vars) + foreach (var (key, value) in vars.Where(x => x.Value != null)) { - var property = key.ToCamelCase(); - - if (value != null) - { - contextInstance.FastAddProperty(property, JsValue.FromObject(engine, value), true, true, true); - - this[property] = value; - } + this[key.ToCamelCase()] = value; } engine.SetValue("ctx", contextInstance); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs index f3744f09c..7113eb459 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptVars.cs @@ -12,6 +12,15 @@ namespace Squidex.Domain.Apps.Core.Scripting { public sealed class ScriptVars : ScriptContext { + public ScriptVars() + { + } + + public ScriptVars(ScriptVars source) + : base(source) + { + } + public ContentData? Data { get => GetValue(); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs new file mode 100644 index 000000000..618b83fcb --- /dev/null +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/WritableContext.cs @@ -0,0 +1,44 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using Jint; +using Jint.Native; +using Jint.Native.Object; +using Squidex.Text; + +namespace Squidex.Domain.Apps.Core.Scripting +{ + internal class WritableContext : ObjectInstance + { + private readonly ScriptVars vars; + + public WritableContext(Engine engine, ScriptVars vars) + : base(engine) + { + this.vars = vars; + + foreach (var (key, value) in vars) + { + var property = key.ToCamelCase(); + + if (value != null) + { + FastAddProperty(property, FromObject(engine, value), true, true, true); + } + } + } + + public override bool Set(JsValue property, JsValue value, JsValue receiver) + { + var propertyName = property.AsString(); + + vars[propertyName] = value.ToObject(); + + return base.Set(property, value, receiver); + } + } +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs index 6e8df7af9..d5faf31c5 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/ContentValidator.cs @@ -50,7 +50,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent errors.Add(new ValidationError(message, pathString)); } - public Task ValidateInputPartialAsync(ContentData data) + public ValueTask ValidateInputPartialAsync(ContentData data) { Guard.NotNull(data, nameof(data)); @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return validator.ValidateAsync(data, context, AddError); } - public Task ValidateInputAsync(ContentData data) + public ValueTask ValidateInputAsync(ContentData data) { Guard.NotNull(data, nameof(data)); @@ -68,7 +68,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent return validator.ValidateAsync(data, context, AddError); } - public Task ValidateContentAsync(ContentData data) + public ValueTask ValidateContentAsync(ContentData data) { Guard.NotNull(data, nameof(data)); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IValidator.cs index 090f145de..39c0c812b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/IValidator.cs @@ -11,6 +11,6 @@ namespace Squidex.Domain.Apps.Core.ValidateContent public interface IValidator { - Task ValidateAsync(object? value, ValidationContext context, AddError addError); + ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError); } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs index c37c99399..c25411307 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AggregateValidator.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Translations; using Squidex.Log; @@ -22,13 +23,13 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.log = log; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { try { if (validators?.Length > 0) { - await Task.WhenAll(validators.Select(x => x.ValidateAsync(value, context, addError))); + await AsyncHelper.WhenAllThrottledAsync(validators, (x, _) => x.ValidateAsync(value, context, addError)); } } catch (Exception ex) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs index 6b0de6ec2..b2976f9a7 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AllowedValuesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -26,14 +26,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.allowedValues = allowedValues; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is TValue typedValue && !allowedValues.Contains(typedValue)) { addError(context.Path, T.Get("contents.validation.notAllowed")); } - return Task.CompletedTask; + return default; } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs index c09985f2a..d4a3639de 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/AssetsValidator.cs @@ -42,7 +42,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.checkAssets = checkAssets; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { var foundIds = new List(); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs index 9007c45c5..0e9fc6228 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionItemValidator.cs @@ -7,6 +7,7 @@ using System.Collections; using Squidex.Infrastructure; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { @@ -21,23 +22,21 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.itemValidator = itemValidator; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is ICollection { Count: > 0 } items) { - var innerTasks = new List(); - var index = 1; - - foreach (var item in items) + var targets = items.OfType().Select((item, index) => { - var innerContext = context.Nested($"[{index}]"); - - await itemValidator.ValidateAsync(item, innerContext, addError); + var innerContext = context.Nested($"[{index + 1}]"); - index++; - } + return (item, innerContext); + }); - await Task.WhenAll(innerTasks); + await AsyncHelper.WhenAllThrottledAsync(targets, async (x, _) => + { + await itemValidator.ValidateAsync(x.item, x.innerContext, addError); + }); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs index be6952ed3..fed65b9e4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/CollectionValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -28,7 +28,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxItems = maxItems; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is not ICollection items || items.Count == 0) { @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators addError(context.Path, T.Get("contents.validation.required")); } - return Task.CompletedTask; + return default; } if (minItems != null && maxItems != null) @@ -64,7 +64,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ComponentValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ComponentValidator.cs index d567bb8c3..74f833ea0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ComponentValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ComponentValidator.cs @@ -19,7 +19,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.validatorFactory = validatorFactory; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is Component component) { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs index 798d0921e..17b76177d 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/FieldValidator.cs @@ -14,19 +14,19 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class FieldValidator : IValidator { - private readonly IValidator fieldValueValidator; + private readonly IValidator validator; private readonly IField field; - public FieldValidator(IValidator fieldValueValidator, IField field) + public FieldValidator(IValidator validator, IField field) { Guard.NotNull(field, nameof(field)); - Guard.NotNull(fieldValueValidator, nameof(fieldValueValidator)); + Guard.NotNull(validator, nameof(validator)); this.field = field; - this.fieldValueValidator = fieldValueValidator; + this.validator = validator; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { var typedValue = value; @@ -59,7 +59,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators return; } - await fieldValueValidator.ValidateAsync(typedValue, context, addError); + await validator.ValidateAsync(typedValue, context, addError); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs index 108195af0..c5a460ee3 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/NoValueValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -17,14 +17,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (!value.IsUndefined()) { addError(context.Path, T.Get("contents.validation.mustBeEmpty")); } - return Task.CompletedTask; + return default; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs index d01fd51d4..9751fdc4c 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ObjectValidator.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.Infrastructure.Tasks; using Squidex.Infrastructure.Translations; namespace Squidex.Domain.Apps.Core.ValidateContent.Validators @@ -24,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.isPartial = isPartial; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value.IsNullOrUndefined()) { @@ -43,19 +44,17 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - var tasks = new List(); - - foreach (var (fieldName, fieldConfig) in fields) + await AsyncHelper.WhenAllThrottledAsync(fields, async (kvp, _) => { - var (isOptional, validator) = fieldConfig; + var (isOptional, validator) = kvp.Value; var fieldValue = Undefined.Value; - if (!values.TryGetValue(fieldName, out var nestedValue)) + if (!values.TryGetValue(kvp.Key, out var nestedValue)) { if (isPartial) { - continue; + return; } } else @@ -63,12 +62,10 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators fieldValue = nestedValue!; } - var fieldContext = context.Nested(fieldName).Optional(isOptional); - - tasks.Add(validator.ValidateAsync(fieldValue, fieldContext, addError)); - } + var fieldContext = context.Nested(kvp.Key).Optional(isOptional); - await Task.WhenAll(tasks); + await validator.ValidateAsync(fieldValue, fieldContext, addError); + }); } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs index 55887b951..4305302b0 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/PatternValidator.cs @@ -33,7 +33,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators regex = new Regex($"^{pattern}$", options, Timeout); } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is string stringValue) { @@ -60,7 +60,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs index be6b8a91e..9cf153425 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RangeValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.max = max; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is TValue typedValue) { @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs index fd913207f..5ca432637 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/ReferencesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.checkReferences = checkReferences; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { var foundIds = new List(); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs index 31914f6cb..d73d53e55 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredStringValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -18,11 +18,11 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.validateEmptyStrings = validateEmptyStrings; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (context.IsOptional) { - return Task.CompletedTask; + return default; } if (value.IsNullOrUndefined() || IsEmptyString(value)) @@ -30,7 +30,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators addError(context.Path, T.Get("contents.validation.required")); } - return Task.CompletedTask; + return default; } private bool IsEmptyString(object? value) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs index fa675f77b..b459860b8 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/RequiredValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,14 +11,14 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public class RequiredValidator : IValidator { - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value.IsNullOrUndefined() && !context.IsOptional) { addError(context.Path, T.Get("contents.validation.required")); } - return Task.CompletedTask; + return default; } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs index 44a0c817b..a95efa452 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringLengthValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -25,7 +25,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxLength = maxLength; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) { @@ -54,7 +54,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs index 1e27bb4fb..0f2de869f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/StringTextValidator.cs @@ -41,7 +41,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.maxWords = maxWords; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is string stringValue && !string.IsNullOrEmpty(stringValue)) { @@ -109,7 +109,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } } diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs index f3347a29c..81b82d04f 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueObjectValuesValidator.cs @@ -20,7 +20,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.fields = fields; } - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is IEnumerable objects && objects.Count() > 1) { @@ -31,7 +31,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators Validate(context, addError, components.Select(x => x.Data)); } - return Task.CompletedTask; + return default; } private void Validate(ValidationContext context, AddError addError, IEnumerable items) diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs index 5df1f9c9a..d90a31299 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators this.checkUniqueness = checkUniqueness; } - public async Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public async ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { var count = context.Path.Count(); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs index 344dc347d..6a97b3291 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/ValidateContent/Validators/UniqueValuesValidator.cs @@ -1,4 +1,4 @@ -// ========================================================================== +// ========================================================================== // Squidex Headless CMS // ========================================================================== // Copyright (c) Squidex UG (haftungsbeschraenkt) @@ -11,7 +11,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators { public sealed class UniqueValuesValidator : IValidator { - public Task ValidateAsync(object? value, ValidationContext context, AddError addError) + public ValueTask ValidateAsync(object? value, ValidationContext context, AddError addError) { if (value is IEnumerable items && items.Any()) { @@ -23,7 +23,7 @@ namespace Squidex.Domain.Apps.Core.ValidateContent.Validators } } - return Task.CompletedTask; + return default; } } -} \ No newline at end of file +} diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs index ee3bf0c9d..c9805aa3f 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ResolveReferences.cs @@ -37,25 +37,27 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { - if (ShouldEnrich(context)) + if (!ShouldEnrich(context)) { - var ids = new HashSet(); + return; + } - foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) - { - var (schema, components) = await schemas(group.Key); + var ids = new HashSet(); - AddReferenceIds(ids, schema, components, group); - } + foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) + { + var (schema, components) = await schemas(group.Key); - var references = await GetReferencesAsync(context, ids, ct); + AddReferenceIds(ids, schema, components, group); + } - foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) - { - var (schema, components) = await schemas(group.Key); + var references = await GetReferencesAsync(context, ids, ct); - await ResolveReferencesAsync(context, schema, components, group, references, schemas); - } + foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) + { + var (schema, components) = await schemas(group.Key); + + await ResolveReferencesAsync(context, schema, components, group, references, schemas); } } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs index fe9fb44a7..1a0903ec7 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Contents/Queries/Steps/ScriptContent.cs @@ -6,6 +6,7 @@ // ========================================================================== using Squidex.Domain.Apps.Core.Scripting; +using Squidex.Infrastructure.Tasks; namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps { @@ -31,32 +32,55 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries.Steps public async Task EnrichAsync(Context context, IEnumerable contents, ProvideSchema schemas, CancellationToken ct) { - if (ShouldEnrich(context)) + if (!ShouldEnrich(context)) { - foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) + return; + } + + foreach (var group in contents.GroupBy(x => x.SchemaId.Id)) + { + var (schema, _) = await schemas(group.Key); + + var script = schema.SchemaDef.Scripts.Query; + + if (string.IsNullOrWhiteSpace(script)) + { + continue; + } + + var vars = new ScriptVars { - var (schema, _) = await schemas(group.Key); + [ScriptKeys.AppId] = context.App.Id, + [ScriptKeys.AppName] = context.App.Name, + [ScriptKeys.User] = context.User + }; - var script = schema.SchemaDef.Scripts.Query; + var preScript = schema.SchemaDef.Scripts.QueryPre; - if (!string.IsNullOrWhiteSpace(script)) + if (!string.IsNullOrWhiteSpace(preScript)) + { + var options = new ScriptOptions { - await Task.WhenAll(group.Select(x => TransformAsync(context, script, x, ct))); - } + AsContext = true + }; + + await scriptEngine.ExecuteAsync(vars, preScript, options, ct); } + + await AsyncHelper.WhenAllThrottledAsync(group, async (content, _) => + { + await TransformAsync(vars, script, content, ct); + }, ct: ct); } } - private async Task TransformAsync(Context context, string script, ContentEntity content, + private async Task TransformAsync(ScriptVars rootVars, string script, ContentEntity content, CancellationToken ct) { - var vars = new ScriptVars + var vars = new ScriptVars(rootVars) { - [ScriptKeys.AppId] = context.App.Id, - [ScriptKeys.AppName] = context.App.Name, [ScriptKeys.ContentId] = content.Id, [ScriptKeys.Data] = content.Data, - [ScriptKeys.User] = context.User }; var options = new ScriptOptions diff --git a/backend/src/Squidex.Domain.Apps.Entities/Scripting/ScriptingCompletion.cs b/backend/src/Squidex.Domain.Apps.Entities/Scripting/ScriptingCompletion.cs index de81241e6..69857a602 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Scripting/ScriptingCompletion.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Scripting/ScriptingCompletion.cs @@ -17,25 +17,25 @@ namespace Squidex.Domain.Apps.Entities.Scripting public IReadOnlyList Content(Schema schema, PartitionResolver partitionResolver) { - AddObject("ctx", FieldDescriptions.Context, () => - { - AddFunction("replace()", - "Tell Squidex that you have modified the data and that the change should be applied."); + AddFunction("replace()", + "Tell Squidex that you have modified the data and that the change should be applied."); - AddFunction("getReferences(ids, callback)", - "Queries the content items with the specified IDs and invokes the callback with an array of contents."); + AddFunction("getReferences(ids, callback)", + "Queries the content items with the specified IDs and invokes the callback with an array of contents."); - AddFunction("getReference(ids, callback)", - "Queries the content item with the specified ID and invokes the callback with an array of contents."); + AddFunction("getReference(ids, callback)", + "Queries the content item with the specified ID and invokes the callback with an array of contents."); - AddFunction("getAssets(ids, callback)", - "Queries the assets with the specified IDs and invokes the callback with an array of assets."); + AddFunction("getAssets(ids, callback)", + "Queries the assets with the specified IDs and invokes the callback with an array of assets."); - AddFunction("getAsset(ids, callback)", - "Queries the asset with the specified ID and invokes the callback with an array of assets."); + AddFunction("getAsset(ids, callback)", + "Queries the asset with the specified ID and invokes the callback with an array of assets."); - AddShared(); + AddShared(); + AddObject("ctx", FieldDescriptions.Context, () => + { AddString("contentId", FieldDescriptions.EntityId); @@ -61,10 +61,10 @@ namespace Squidex.Domain.Apps.Entities.Scripting public IReadOnlyList Asset() { + AddShared(); + AddObject("ctx", FieldDescriptions.Context, () => { - AddShared(); - AddString("assetId", FieldDescriptions.EntityId); diff --git a/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs b/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs index 7cc8dc20b..f514adc83 100644 --- a/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs +++ b/backend/src/Squidex.Infrastructure/Tasks/AsyncHelper.cs @@ -51,6 +51,30 @@ namespace Squidex.Infrastructure.Tasks .GetResult(); } + public static async ValueTask WhenAllThrottledAsync(IEnumerable source, Func action, int maxDegreeOfParallelism = 0, + CancellationToken ct = default) + { + if (maxDegreeOfParallelism <= 0) + { + maxDegreeOfParallelism = Environment.ProcessorCount * 2; + } + + var semaphore = new SemaphoreSlim(maxDegreeOfParallelism); + + foreach (var item in source) + { + await semaphore.WaitAsync(ct); + try + { + await action(item, ct); + } + finally + { + semaphore.Release(); + } + } + } + public static void Batch(this Channel source, Channel target, Func, TOut> converter, int batchSize, int timeout, CancellationToken ct = default) { diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs index 8dae3e69f..f69122d11 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/Models/SchemaScriptsDto.cs @@ -14,10 +14,15 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models public sealed class SchemaScriptsDto { /// - /// The script that is executed for each query when querying contents. + /// The script that is executed for each content when querying contents. /// public string? Query { get; set; } + /// + /// The script that is executed for all contents when querying contents. + /// + public string? QueryPre { get; set; } + /// /// The script that is executed when creating a content. /// @@ -45,4 +50,4 @@ namespace Squidex.Areas.Api.Controllers.Schemas.Models return new ConfigureScripts { Scripts = scripts }; } } -} \ No newline at end of file +} diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs index 10c88ec16..23be4a6bf 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ContentDataObjectTests.cs @@ -27,7 +27,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(1.0)); - var result = ExecuteScript(original, @"data.number = { iv: 1 }"); + const string script = @" + data.number = { iv: 1 } + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -43,7 +47,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(1.0)); - var result = ExecuteScript(original, @"data.number.iv = 1"); + const string script = @" + data.number.iv = 1 + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -59,7 +67,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(1.0)); - var result = ExecuteScript(original, "Object.defineProperty(data, 'number', { value: { iv: 1 } })"); + const string script = @" + Object.defineProperty(data, 'number', { value: { iv: 1 } }) + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -69,7 +81,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var original = new ContentData(); - Assert.Throws(() => ExecuteScript(original, @"data.number = 1")); + const string script = @" + data.number = 1 + "; + + Assert.Throws(() => ExecuteScript(original, script)); } [Fact] @@ -83,7 +99,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting var expected = new ContentData(); - var result = ExecuteScript(original, @"delete data.number"); + const string script = @" + delete data.number + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -103,7 +123,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant("1new")); - var result = ExecuteScript(original, @"data.string.iv = data.string.iv + 'new'"); + const string script = @" + data.string.iv = data.string.iv + 'new' + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -123,7 +147,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(3.0)); - var result = ExecuteScript(original, @"data.number.iv = data.number.iv + 2"); + const string script = @" + data.number.iv = data.number.iv + 2 + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -143,7 +171,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(true)); - var result = ExecuteScript(original, @"data.boolean.iv = !data.boolean.iv"); + const string script = @" + data.boolean.iv = !data.boolean.iv + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -163,7 +195,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(JsonValue.Array(1.0, 4.0, 5.0))); - var result = ExecuteScript(original, @"data.number.iv = [data.number.iv[0], data.number.iv[1] + 2, 5]"); + const string script = @" + data.number.iv = [data.number.iv[0], data.number.iv[1] + 2, 5] + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -183,7 +219,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(JsonValue.Object().Add("lat", 1.0).Add("lon", 4.0))); - var result = ExecuteScript(original, @"data.geo.iv = { lat: data.geo.iv.lat, lon: data.geo.iv.lat + 3 }"); + const string script = @" + data.geo.iv = { lat: data.geo.iv.lat, lon: data.geo.iv.lat + 3 } + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -203,11 +243,14 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(JsonValue.Object().Add("lat", 2.0).Add("lon", 4.0))); - var result = ExecuteScript(original, @" + const string script = @" var nested = data.geo.iv; nested.lat = 2; nested.lon = 4; - data.geo.iv = nested"); + data.geo.iv = nested + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -221,11 +264,14 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(JsonValue.Object().Add("lat", 2.0).Add("lon", 4.0))); - var result = ExecuteScript(original, @" + const string script = @" var nested = data.geo.iv; nested.lat = 2; nested.lon = 4; - data.geo.iv = nested"); + data.geo.iv = nested + "; + + var result = ExecuteScript(original, script); Assert.Same(original, result); } @@ -244,7 +290,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(1.0)); - var result = ExecuteScript(original, "Object.defineProperty(data.number, 'iv', { value: 1 })"); + const string script = @" + Object.defineProperty(data.number, 'iv', { value: 1 }) + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -263,7 +313,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting .AddField("string", new ContentFieldData()); - var result = ExecuteScript(original, @"delete data.string.iv"); + const string script = @" + delete data.string.iv + "; + + var result = ExecuteScript(original, script); Assert.Equal(expected, result); } @@ -286,7 +340,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting engine.SetValue("data", new ContentDataObject(engine, content)); - var result = engine.Evaluate(@" + const string script = @" var result = []; for (var x in data) { var field = data[x]; @@ -295,7 +349,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting result.push(field[y]); } } - result;").ToObject(); + result; + "; + + var result = engine.Evaluate(script).ToObject(); Assert.Equal(new[] { "1", "2", "3", "4" }, result); } @@ -309,13 +366,21 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(JsonValue.Array())); - ExecuteScript(original, "data.obj.iv[0] = 1"); + const string script = @" + data.obj.iv[0] = 1 + "; + + ExecuteScript(original, script); } [Fact] public void Should_null_propagate_unknown_fields() { - ExecuteScript(new ContentData(), @"data.string.iv = 'hello'"); + const string script = @" + data.string.iv = 'hello' + "; + + ExecuteScript(new ContentData(), script); } private static ContentData ExecuteScript(ContentData original, string script) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs index 208fff153..9e8950e09 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineHelperTests.cs @@ -47,15 +47,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_convert_html_to_text() { - const string script = @" - return html2Text(value); - "; - var vars = new ScriptVars { ["value"] = "

Hello World

" }; + const string script = @" + return html2Text(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("Hello World", result); @@ -64,15 +64,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_convert_markdown_to_text() { - const string script = @" - return markdown2Text(value); - "; - var vars = new ScriptVars { ["value"] = "## Hello World" }; + const string script = @" + return markdown2Text(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("Hello World", result); @@ -81,15 +81,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_count_words() { - const string script = @" - return wordCount(value); - "; - var vars = new ScriptVars { ["value"] = "Hello, World" }; + const string script = @" + return wordCount(value); + "; + var result = ((JsonNumber)sut.Execute(vars, script)).Value; Assert.Equal(2, result); @@ -98,15 +98,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_count_characters() { - const string script = @" - return characterCount(value); - "; - var vars = new ScriptVars { ["value"] = "Hello, World" }; + const string script = @" + return characterCount(value); + "; + var result = ((JsonNumber)sut.Execute(vars, script)).Value; Assert.Equal(10, result); @@ -115,15 +115,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_camel_case_value() { - const string script = @" - return toCamelCase(value); - "; - var vars = new ScriptVars { ["value"] = "Hello World" }; + const string script = @" + return toCamelCase(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("helloWorld", result); @@ -132,15 +132,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_pascal_case_value() { - const string script = @" - return toPascalCase(value); - "; - var vars = new ScriptVars { ["value"] = "Hello World" }; + const string script = @" + return toPascalCase(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("HelloWorld", result); @@ -149,15 +149,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_slugify_value() { - const string script = @" - return slugify(value); - "; - var vars = new ScriptVars { ["value"] = "4 Häuser" }; + const string script = @" + return slugify(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("4-haeuser", result); @@ -166,15 +166,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_slugify_value_with_single_char() { - const string script = @" - return slugify(value, true); - "; - var vars = new ScriptVars { ["value"] = "4 Häuser" }; + const string script = @" + return slugify(value, true); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("4-hauser", result); @@ -183,15 +183,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_compute_sha256_hash() { - const string script = @" - return sha256(value); - "; - var vars = new ScriptVars { ["value"] = "HelloWorld" }; + const string script = @" + return sha256(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("HelloWorld".ToSha256(), result); @@ -200,15 +200,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_compute_sha512_hash() { - const string script = @" - return sha512(value); - "; - var vars = new ScriptVars { ["value"] = "HelloWorld" }; + const string script = @" + return sha512(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("HelloWorld".ToSha512(), result); @@ -217,15 +217,15 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_compute_md5_hash() { - const string script = @" - return md5(value); - "; - var vars = new ScriptVars { ["value"] = "HelloWorld" }; + const string script = @" + return md5(value); + "; + var result = sut.Execute(vars, script).ToString(); Assert.Equal("HelloWorld".ToMD5(), result); @@ -234,14 +234,14 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Should_compute_guid() { - const string script = @" - return guid(); - "; - var vars = new ScriptVars { }; + const string script = @" + return guid(); + "; + var result = sut.Execute(vars, script).ToString(); Assert.True(Guid.TryParse(result, out _)); @@ -250,16 +250,18 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task Should_throw_validation_exception_if_calling_reject() { - const string script = @" - reject() - "; - var options = new ScriptOptions { CanReject = true }; - var vars = new ScriptVars(); + var vars = new ScriptVars + { + }; + + const string script = @" + reject() + "; var ex = await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script, options)); @@ -269,16 +271,18 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task Should_throw_validation_exception_if_calling_reject_with_message() { - const string script = @" - reject('Not valid') - "; - var options = new ScriptOptions { CanReject = true }; - var vars = new ScriptVars(); + var vars = new ScriptVars + { + }; + + const string script = @" + reject('Not valid') + "; var ex = await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script, options)); @@ -288,16 +292,18 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task Should_throw_security_exception_if_calling_reject() { - const string script = @" - disallow() - "; - var options = new ScriptOptions { CanDisallow = true }; - var vars = new ScriptVars(); + var vars = new ScriptVars + { + }; + + const string script = @" + disallow() + "; var ex = await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script, options)); @@ -316,7 +322,9 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting CanDisallow = true }; - var vars = new ScriptVars(); + var vars = new ScriptVars + { + }; var ex = await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script, options)); @@ -326,28 +334,32 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task Should_throw_exception_if_getJson_url_is_null() { + var vars = new ScriptVars + { + }; + const string script = @" getJSON(null, function(result) { complete(result); }); "; - var vars = new ScriptVars(); - await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script)); } [Fact] public async Task Should_throw_exception_if_getJson_callback_is_null() { + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; getJSON(url, null); "; - var vars = new ScriptVars(); - await Assert.ThrowsAsync(() => sut.ExecuteAsync(vars, script)); } @@ -356,6 +368,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; @@ -364,8 +380,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Get); @@ -381,6 +395,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var headers = { 'X-Header1': 1, @@ -394,8 +412,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }, headers); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Get); @@ -413,6 +429,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; @@ -421,8 +441,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Delete); @@ -438,6 +456,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; @@ -448,8 +470,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Patch); @@ -466,6 +486,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; @@ -476,8 +500,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Post); @@ -494,6 +516,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var httpHandler = SetupRequest(); + var vars = new ScriptVars + { + }; + const string script = @" var url = 'http://squidex.io'; @@ -504,8 +530,6 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var vars = new ScriptVars(); - var result = await sut.ExecuteAsync(vars, script); httpHandler.ShouldBeMethod(HttpMethod.Put); diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs index 0336ebb2a..7cfa68e9d 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintScriptEngineTests.cs @@ -13,14 +13,16 @@ using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Core.Scripting.Extensions; +using Squidex.Domain.Apps.Core.TestHelpers; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Security; using Squidex.Infrastructure.Validation; using Xunit; namespace Squidex.Domain.Apps.Core.Operations.Scripting { - public class JintScriptEngineTests + public class JintScriptEngineTests : IClassFixture { private readonly ScriptOptions contentOptions = new ScriptOptions { @@ -85,13 +87,17 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting public async Task TransformAsync_should_return_original_content_if_script_failed() { var content = new ContentData(); - var context = new ScriptVars { ["data"] = content }; + + var vars = new ScriptVars + { + ["data"] = content + }; const string script = @" x => x "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Empty(result); } @@ -116,7 +122,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(10.0)); - var context = new ScriptVars { ["data"] = content }; + var vars = new ScriptVars + { + ["data"] = content + }; const string script = @" var data = ctx.data; @@ -129,7 +138,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting replace(data); "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Equal(expected, result); } @@ -147,27 +156,31 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_throw_exception_if_script_failed() { - var content = new ContentData(); - var context = new ScriptVars { ["data"] = content }; + var vars = new ScriptVars + { + ["data"] = new ContentData() + }; const string script = @" invalid((); "; - await Assert.ThrowsAsync(() => sut.TransformAsync(context, script, contentOptions)); + await Assert.ThrowsAsync(() => sut.TransformAsync(vars, script, contentOptions)); } [Fact] public async Task TransformAsync_should_return_original_content_if_not_replaced() { - var content = new ContentData(); - var context = new ScriptVars { ["data"] = content }; + var vars = new ScriptVars + { + ["data"] = new ContentData() + }; const string script = @" var x = 0; "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Empty(result); } @@ -175,8 +188,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_return_original_content_if_not_replaced_async() { - var content = new ContentData(); - var context = new ScriptVars { ["data"] = content }; + var vars = new ScriptVars + { + ["data"] = new ContentData() + }; const string script = @" async = true; @@ -188,7 +203,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Empty(result); } @@ -204,7 +219,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant("MyOperation")); - var context = new ScriptVars + var vars = new ScriptVars { ["data"] = content, ["dataOld"] = null, @@ -219,7 +234,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting replace(data); "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Equal(expected, result); } @@ -235,7 +250,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(42)); - var context = new ScriptVars + var vars = new ScriptVars { ["data"] = content, ["dataOld"] = null, @@ -255,7 +270,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Equal(expected, result); } @@ -263,10 +278,9 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_not_ignore_transformation_if_async_not_set() { - var content = new ContentData(); - var context = new ScriptVars + var vars = new ScriptVars { - ["data"] = content, + ["data"] = new ContentData(), ["dataOld"] = null, ["operation"] = "MyOperation" }; @@ -282,7 +296,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.NotEmpty(result); } @@ -290,10 +304,9 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public async Task TransformAsync_should_timeout_if_replace_never_called() { - var content = new ContentData(); - var context = new ScriptVars + var vars = new ScriptVars { - ["data"] = content, + ["data"] = new ContentData(), ["dataOld"] = null, ["operation"] = "MyOperation" }; @@ -308,7 +321,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting }); "; - await Assert.ThrowsAnyAsync(() => sut.TransformAsync(context, script, contentOptions)); + await Assert.ThrowsAnyAsync(() => sut.TransformAsync(vars, script, contentOptions)); } [Fact] @@ -331,7 +344,10 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting new ContentFieldData() .AddInvariant(10.0)); - var context = new ScriptVars { ["data"] = content }; + var vars = new ScriptVars + { + ["data"] = content + }; const string script = @" var data = ctx.data; @@ -344,7 +360,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting replace(data); "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Equal(expected, result); } @@ -375,7 +391,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting userIdentity.AddClaim(new Claim(OpenIdClaims.ClientId, "2")); - var context = new ScriptVars + var vars = new ScriptVars { ["data"] = content, ["dataOld"] = oldContent, @@ -388,7 +404,7 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting replace(ctx.data); "; - var result = await sut.TransformAsync(context, script, contentOptions); + var result = await sut.TransformAsync(vars, script, contentOptions); Assert.Equal(expected, result); } @@ -396,16 +412,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Evaluate_should_return_true_if_expression_match() { - const string script = @" - value.i == 2 - "; - - var context = new ScriptVars + var vars = new ScriptVars { ["value"] = new { i = 2 } }; - var result = ((IScriptEngine)sut).Evaluate(context, script); + const string script = @" + value.i == 2 + "; + + var result = ((IScriptEngine)sut).Evaluate(vars, script); Assert.True(result); } @@ -413,16 +429,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Evaluate_should_return_true_if_status_match() { - const string script = @" - value.status == 'Published' - "; - - var context = new ScriptVars + var vars = new ScriptVars { ["value"] = new { status = Status.Published } }; - var result = ((IScriptEngine)sut).Evaluate(context, script); + const string script = @" + value.status == 'Published' + "; + + var result = ((IScriptEngine)sut).Evaluate(vars, script); Assert.True(result); } @@ -430,16 +446,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Evaluate_should_return_false_if_expression_match() { - const string script = @" - value.i == 3 - "; - - var context = new ScriptVars + var vars = new ScriptVars { ["value"] = new { i = 2 } }; - var result = ((IScriptEngine)sut).Evaluate(context, script); + const string script = @" + value.i == 3 + "; + + var result = ((IScriptEngine)sut).Evaluate(vars, script); Assert.False(result); } @@ -447,16 +463,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting [Fact] public void Evaluate_should_return_false_if_script_is_invalid() { - const string script = @" - function(); - "; - - var context = new ScriptVars + var vars = new ScriptVars { ["value"] = new { i = 2 } }; - var result = ((IScriptEngine)sut).Evaluate(context, script); + const string script = @" + function(); + "; + + var result = ((IScriptEngine)sut).Evaluate(vars, script); Assert.False(result); } @@ -466,18 +482,93 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting { var id = DomainId.NewGuid(); + var vars = new ScriptVars + { + ["value"] = id + }; + const string script = @" return value; "; - var context = new ScriptVars + var result = sut.Execute(vars, script); + + Assert.Equal(id.ToString(), result.ToString()); + } + + [Fact] + public void Should_share_vars_between_executions() + { + var vars = new ScriptVars { - ["value"] = id + ["value"] = 13 }; - var result = sut.Execute(context, script); + const string script1 = @" + ctx.value = ctx.value * 2; + "; - Assert.Equal(id.ToString(), result.ToString()); + const string script2 = @" + return ctx.value + 2; + "; + + sut.Execute(vars, script1, new ScriptOptions { AsContext = true }); + + var result = sut.Execute(vars, script2, new ScriptOptions { AsContext = true }); + + Assert.Equal(JsonValue.Create(28), result); + } + + [Fact] + public void Should_share_complex_vars_between_executions() + { + var vars = new ScriptVars + { + ["value"] = 13 + }; + + const string script1 = @" + ctx.obj = { number: ctx.value * 2 }; + "; + + const string script2 = @" + return ctx.obj.number + 2; + "; + + sut.Execute(vars, script1, new ScriptOptions { AsContext = true }); + + var result = sut.Execute(vars, script2, new ScriptOptions { AsContext = true }); + + Assert.Equal(JsonValue.Create(28), result); + } + + [Fact] + public async Task Should_share_vars_between_execution_for_transform() + { + var vars = new ScriptVars + { + ["value"] = 13 + }; + + const string script1 = @" + ctx.obj = { number: ctx.value * 2 }; + "; + + const string script2 = @" + ctx.data.test = { iv: ctx.obj.number + 2 }; + replace(); + "; + + sut.Execute(vars, script1, new ScriptOptions { AsContext = true }); + + var vars2 = new ScriptVars(vars) + { + Data = new ContentData() + }; + + var result = await sut.TransformAsync(vars2, script2, new ScriptOptions { AsContext = true }); + + Assert.Equal(JsonValue.Create(28), result["test"]!["iv"]); } } } diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs index cf73d24f1..285762694 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/JintUserTests.cs @@ -44,7 +44,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim(OpenIdClaims.Email, "hello@squidex.io")); - Assert.Equal("hello@squidex.io", GetValue(identity, "user.email")); + const string script = @" + return user.email; + "; + + Assert.Equal("hello@squidex.io", GetValue(identity, script)); } [Fact] @@ -54,7 +58,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim(SquidexClaimTypes.PictureUrl, "my-picture")); - Assert.Equal(new[] { "my-picture" }, GetValue(identity, "user.claims.picture")); + const string script = @" + return user.claims.picture; + "; + + Assert.Equal(new[] { "my-picture" }, GetValue(identity, script)); } [Fact] @@ -64,7 +72,11 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim(ClaimTypes.Role, "my-role")); - Assert.Equal(new[] { "my-role" }, GetValue(identity, "user.claims.role")); + const string script = @" + return user.claims.role; + "; + + Assert.Equal(new[] { "my-role" }, GetValue(identity, script)); } [Fact] @@ -77,8 +89,16 @@ namespace Squidex.Domain.Apps.Core.Operations.Scripting identity.AddClaim(new Claim("claim2", "2a")); identity.AddClaim(new Claim("claim2", "2b")); - Assert.Equal(new[] { "1a", "1b" }, GetValue(identity, "user.claims.claim1")); - Assert.Equal(new[] { "2a", "2b" }, GetValue(identity, "user.claims.claim2")); + const string script1 = @" + return user.claims.claim1; + "; + + const string script2 = @" + return user.claims.claim2; + "; + + Assert.Equal(new[] { "1a", "1b" }, GetValue(identity, script1)); + Assert.Equal(new[] { "2a", "2b" }, GetValue(identity, script2)); } private static void AssetUser(ClaimsIdentity identity, string id, bool isClient, bool isUser) diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs index 1f1f1ba55..186b84e9e 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/ValidateContent/ValidationTestExtensions.cs @@ -24,7 +24,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent private static readonly NamedId AppId = NamedId.Of(DomainId.NewGuid(), "my-app"); private static readonly NamedId SchemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); - public static Task ValidateAsync(this IValidator validator, object? value, IList errors, + public static ValueTask ValidateAsync(this IValidator validator, object? value, IList errors, Schema? schema = null, ValidationMode mode = ValidationMode.Default, ValidationUpdater? updater = null, @@ -37,7 +37,7 @@ namespace Squidex.Domain.Apps.Core.Operations.ValidateContent return validator.ValidateAsync(value, context, CreateFormatter(errors)); } - public static Task ValidateAsync(this IField field, object? value, IList errors, + public static ValueTask ValidateAsync(this IField field, object? value, IList errors, Schema? schema = null, ValidationMode mode = ValidationMode.Default, ValidationUpdater? updater = null, diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs index 4606656c1..fb3692000 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Assets/AssetsJintExtensionTests.cs @@ -62,6 +62,10 @@ namespace Squidex.Domain.Apps.Entities.Assets { var (vars, asset) = SetupAssetVars(); + var expected = $@" + Text: {asset.FileName} {asset.Id} + "; + var script = @" getAsset(data.assets.iv[0], function (assets) { var result1 = `Text: ${assets[0].fileName} ${assets[0].id}`; @@ -69,10 +73,6 @@ namespace Squidex.Domain.Apps.Entities.Assets complete(`${result1}`); });"; - var expected = $@" - Text: {asset.FileName} {asset.Id} - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -83,6 +83,11 @@ namespace Squidex.Domain.Apps.Entities.Assets { var (vars, assets) = SetupAssetsVars(); + var expected = $@" + Text: {assets[0].FileName} {assets[0].Id} + Text: {assets[1].FileName} {assets[1].Id} + "; + var script = @" getAssets(data.assets.iv, function (assets) { var result1 = `Text: ${assets[0].fileName} ${assets[0].id}`; @@ -91,11 +96,6 @@ namespace Squidex.Domain.Apps.Entities.Assets complete(`${result1}\n${result2}`); });"; - var expected = $@" - Text: {assets[0].FileName} {assets[0].Id} - Text: {assets[1].FileName} {assets[1].Id} - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -108,6 +108,10 @@ namespace Squidex.Domain.Apps.Entities.Assets SetupText(asset.Id, Encoding.UTF8.GetBytes("Hello Asset")); + var expected = @" + Text: Hello Asset + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -117,10 +121,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }); });"; - var expected = @" - Text: Hello Asset - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -133,6 +133,10 @@ namespace Squidex.Domain.Apps.Entities.Assets SetupText(asset.Id, Encoding.UTF8.GetBytes("Hello Asset")); + var expected = @" + Text: Hello Asset + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -142,10 +146,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }, 'utf8'); });"; - var expected = @" - Text: Hello Asset - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -158,6 +158,10 @@ namespace Squidex.Domain.Apps.Entities.Assets SetupText(asset.Id, Encoding.Unicode.GetBytes("Hello Asset")); + var expected = @" + Text: Hello Asset + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -167,10 +171,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }, 'unicode'); });"; - var expected = @" - Text: Hello Asset - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -183,6 +183,10 @@ namespace Squidex.Domain.Apps.Entities.Assets SetupText(asset.Id, Encoding.ASCII.GetBytes("Hello Asset")); + var expected = @" + Text: Hello Asset + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -192,10 +196,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }, 'ascii'); });"; - var expected = @" - Text: Hello Asset - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -208,6 +208,10 @@ namespace Squidex.Domain.Apps.Entities.Assets SetupText(asset.Id, Encoding.UTF8.GetBytes("Hello Asset")); + var expected = @" + Text: SGVsbG8gQXNzZXQ= + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -217,10 +221,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }, 'base64'); });"; - var expected = @" - Text: SGVsbG8gQXNzZXQ= - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -231,6 +231,10 @@ namespace Squidex.Domain.Apps.Entities.Assets { var (vars, _) = SetupAssetVars(1_000_000); + var expected = @" + Text: ErrorTooBig + "; + var script = @" getAssets(data.assets.iv, function (assets) { getAssetText(assets[0], function (text) { @@ -240,10 +244,6 @@ namespace Squidex.Domain.Apps.Entities.Assets }); });"; - var expected = @" - Text: ErrorTooBig - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -270,6 +270,10 @@ namespace Squidex.Domain.Apps.Entities.Assets ["event"] = @event }; + var expected = @" + Text: Hello Asset + "; + var script = @" getAssetText(event, function (text) { var result = `Text: ${text}`; @@ -277,10 +281,6 @@ namespace Squidex.Domain.Apps.Entities.Assets complete(result); });"; - var expected = @" - Text: Hello Asset - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -302,6 +302,10 @@ namespace Squidex.Domain.Apps.Entities.Assets ["event"] = @event }; + var expected = @" + Text: ErrorTooBig + "; + var script = @" getAssetText(event, function (text) { var result = `Text: ${text}`; @@ -309,10 +313,6 @@ namespace Squidex.Domain.Apps.Entities.Assets complete(result); });"; - var expected = @" - Text: ErrorTooBig - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs index 853e86b12..984d2e0a0 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/Queries/ScriptContentTests.cs @@ -6,12 +6,15 @@ // ========================================================================== using FakeItEasy; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Options; using Squidex.Domain.Apps.Core.Contents; using Squidex.Domain.Apps.Core.Schemas; using Squidex.Domain.Apps.Core.Scripting; using Squidex.Domain.Apps.Entities.Contents.Queries.Steps; using Squidex.Domain.Apps.Entities.TestHelpers; using Squidex.Infrastructure; +using Squidex.Infrastructure.Json.Objects; using Xunit; namespace Squidex.Domain.Apps.Entities.Contents.Queries @@ -20,38 +23,10 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { private readonly IScriptEngine scriptEngine = A.Fake(); private readonly NamedId appId = NamedId.Of(DomainId.NewGuid(), "my-app"); - private readonly NamedId schemaId = NamedId.Of(DomainId.NewGuid(), "my-schema"); - private readonly NamedId schemaWithScriptId = NamedId.Of(DomainId.NewGuid(), "my-schema"); - private readonly ProvideSchema schemaProvider; private readonly ScriptContent sut; public ScriptContentTests() { - var schemaDef = new Schema(schemaId.Name); - - var schemaDefWithScript = - new Schema(schemaWithScriptId.Name) - .SetScripts(new SchemaScripts - { - Query = "my-query" - }); - - schemaProvider = x => - { - if (x == schemaId.Id) - { - return Task.FromResult((Mocks.Schema(appId, schemaId, schemaDef), ResolvedComponents.Empty)); - } - else if (x == schemaWithScriptId.Id) - { - return Task.FromResult((Mocks.Schema(appId, schemaWithScriptId, schemaDefWithScript), ResolvedComponents.Empty)); - } - else - { - throw new DomainObjectNotFoundException(x.ToString()); - } - }; - sut = new ScriptContent(scriptEngine); } @@ -60,9 +35,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var ctx = new Context(Mocks.ApiUser(), Mocks.App(appId)); - var content = new ContentEntity { SchemaId = schemaId }; + var (provider, schemaId) = CreateSchema( + queryPre: "my-pre-query"); + + var content = new ContentEntity { Data = new ContentData(), SchemaId = schemaId }; - await sut.EnrichAsync(ctx, new[] { content }, schemaProvider, default); + await sut.EnrichAsync(ctx, new[] { content }, provider, default); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -73,9 +51,12 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries { var ctx = new Context(Mocks.FrontendUser(), Mocks.App(appId)); - var content = new ContentEntity { SchemaId = schemaWithScriptId }; + var (provider, schemaId) = CreateSchema( + query: "my-query"); + + var content = new ContentEntity { Data = new ContentData(), SchemaId = schemaId }; - await sut.EnrichAsync(ctx, new[] { content }, schemaProvider, default); + await sut.EnrichAsync(ctx, new[] { content }, provider, default); A.CallTo(() => scriptEngine.TransformAsync(A._, A._, ScriptOptions(), A._)) .MustNotHaveHappened(); @@ -88,12 +69,15 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries var oldData = new ContentData(); - var content = new ContentEntity { SchemaId = schemaWithScriptId, Data = oldData }; + var (provider, schemaId) = CreateSchema( + query: "my-query"); + + var content = new ContentEntity { Data = oldData, SchemaId = schemaId }; A.CallTo(() => scriptEngine.TransformAsync(A._, "my-query", ScriptOptions(), A._)) .Returns(new ContentData()); - await sut.EnrichAsync(ctx, new[] { content }, schemaProvider, default); + await sut.EnrichAsync(ctx, new[] { content }, provider, default); Assert.NotSame(oldData, content.Data); @@ -107,6 +91,52 @@ namespace Squidex.Domain.Apps.Entities.Contents.Queries .MustHaveHappened(); } + [Fact] + public async Task Should_make_test_with_pre_query_script() + { + var ctx = new Context(Mocks.ApiUser(), Mocks.App(appId)); + + var (provider, id) = CreateSchema( + query: @" + ctx.data.test = { iv: ctx.custom }; + replace()", + queryPre: "ctx.custom = 123;"); + + var content = new ContentEntity { Data = new ContentData(), SchemaId = id }; + + var realScriptEngine = + new JintScriptEngine(new MemoryCache(Options.Create(new MemoryCacheOptions())), + Options.Create(new JintScriptOptions + { + TimeoutScript = TimeSpan.FromSeconds(20), + TimeoutExecution = TimeSpan.FromSeconds(100) + })); + + var sut2 = new ScriptContent(realScriptEngine); + + await sut2.EnrichAsync(ctx, new[] { content }, provider, default); + + Assert.Equal(JsonValue.Create(123), content.Data["test"]!["iv"]); + } + + private (ProvideSchema, NamedId) CreateSchema(string? query = null, string? queryPre = null) + { + var id = NamedId.Of(DomainId.NewGuid(), "my-schema"); + + return (_ => + { + var schemaDef = + new Schema(id.Name) + .SetScripts(new SchemaScripts + { + Query = query, + QueryPre = queryPre + }); + + return Task.FromResult((Mocks.Schema(appId, id, schemaDef), ResolvedComponents.Empty)); + }, id); + } + private static ScriptOptions ScriptOptions() { return A.That.Matches(x => x.AsContext); diff --git a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs index f74a7eee6..ab5143cbc 100644 --- a/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Entities.Tests/Contents/ReferencesJintExtensionTests.cs @@ -78,6 +78,10 @@ namespace Squidex.Domain.Apps.Entities.Contents ["user"] = user }; + var expected = @" + Text: Hello 1 World 1 + "; + var script = @" getReference(data.references.iv[0], function (references) { var result1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; @@ -85,10 +89,6 @@ namespace Squidex.Domain.Apps.Entities.Contents complete(`${result1}`); })"; - var expected = @" - Text: Hello 1 World 1 - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); @@ -122,6 +122,11 @@ namespace Squidex.Domain.Apps.Entities.Contents ["user"] = user }; + var expected = @" + Text: Hello 1 World 1 + Text: Hello 2 World 2 + "; + var script = @" getReferences(data.references.iv, function (references) { var result1 = `Text: ${references[0].data.field1.iv} ${references[0].data.field2.iv}`; @@ -130,11 +135,6 @@ namespace Squidex.Domain.Apps.Entities.Contents complete(`${result1}\n${result2}`); })"; - var expected = @" - Text: Hello 1 World 1 - Text: Hello 2 World 2 - "; - var result = (await sut.ExecuteAsync(vars, script)).ToString(); Assert.Equal(Cleanup(expected), Cleanup(result)); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs index eae2d7e75..89917a6c6 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/AppTests.cs @@ -114,75 +114,6 @@ namespace TestSuite.ApiTests Assert.False(app_2._links.ContainsKey("image")); } - [Fact] - public async Task Should_manage_roles() - { - // Use role name with hash to test previous bug. - var roleName = $"{Guid.NewGuid()}/1"; - var roleClient = Guid.NewGuid().ToString(); - var roleContributor1 = "hello@squidex.io"; - - // STEP 1: Add role. - var createRequest = new AddRoleDto { Name = roleName }; - - var roles_1 = await _.Apps.PostRoleAsync(_.AppName, createRequest); - var role_1 = roles_1.Items.Find(x => x.Name == roleName); - - // Should return role with correct name. - Assert.Empty(role_1.Permissions); - - - // STEP 2: Update role. - var updateRequest = new UpdateRoleDto { Permissions = new List { "a", "b" } }; - - var roles_2 = await _.Apps.PutRoleAsync(_.AppName, roleName, updateRequest); - var role_2 = roles_2.Items.Find(x => x.Name == roleName); - - // Should return role with correct name. - Assert.Equal(updateRequest.Permissions, role_2.Permissions); - - - // STEP 3: Assign client and contributor. - await _.Apps.PostClientAsync(_.AppName, new CreateClientDto { Id = roleClient }); - - // Add client to role. - await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = roleName }); - - // Add contributor to role. - await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor1, Role = roleName, Invite = true }); - - var roles_3 = await _.Apps.GetRolesAsync(_.AppName); - var role_3 = roles_3.Items.Find(x => x.Name == roleName); - - // Should return role with correct number of users and clients. - Assert.Equal(1, role_3.NumClients); - Assert.Equal(1, role_3.NumContributors); - - - // STEP 4: Try to delete role. - var ex = await Assert.ThrowsAnyAsync(() => _.Apps.DeleteRoleAsync(_.AppName, roleName)); - - Assert.Equal(400, ex.StatusCode); - - - // STEP 5: Remove after client and contributor removed. - var fallbackRole = "Developer"; - - // Remove client from role. - await _.Apps.PutClientAsync(_.AppName, roleClient, new UpdateClientDto { Role = fallbackRole }); - - // Remove contributor from role. - await _.Apps.PostContributorAsync(_.AppName, new AssignContributorDto { ContributorId = roleContributor1, Role = fallbackRole }); - - await _.Apps.DeleteRoleAsync(_.AppName, roleName); - - var roles_4 = await _.Apps.GetRolesAsync(_.AppName); - var role_4 = roles_4.Items.Find(x => x.Name == roleName); - - // Should not return deleted role. - Assert.Null(role_4); - } - [Fact] public async Task Should_get_settings() { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs index 2191555a2..e9fe9b0e6 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentCleanupTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.ClientLibrary; using TestSuite.Fixtures; using TestSuite.Model; using Xunit; @@ -35,7 +36,7 @@ namespace TestSuite.ApiTests // STEP 2: Create a content for this schema. - var data = new TestEntityData { Number1 = 12, String = "hello" }; + var data = new TestEntityData { Number = 12, String = "hello" }; var content_1 = await contents.CreateAsync(data); @@ -47,7 +48,10 @@ namespace TestSuite.ApiTests // STEP 4: Make any update. - var content_2 = await contents.ChangeStatusAsync(content_1.Id, "Published"); + var content_2 = await contents.ChangeStatusAsync(content_1.Id, new ChangeStatus + { + Status = "Published" + }); // Should not return deleted field. Assert.Null(content_2.Data.String); @@ -81,7 +85,10 @@ namespace TestSuite.ApiTests // STEP 4: Make any update. - var contentB_2 = await contents.ChangeStatusAsync(contentB_1.Id, "Published"); + var contentB_2 = await contents.ChangeStatusAsync(contentB_1.Id, new ChangeStatus + { + Status = "Published" + }); // Should not return deleted field. Assert.Empty(contentB_2.Data.References); diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs index 877fec382..aa0d877cf 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentQueryTests.cs @@ -46,7 +46,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { - OrderBy = "data/number1/iv asc" + OrderBy = "data/number/iv asc" }); var itemsById = await _.Contents.GetAsync(new HashSet(items.Items.Take(3).Select(x => x.Id))); @@ -66,7 +66,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { - OrderBy = "data/number1/iv asc" + OrderBy = "data/number/iv asc" }); AssertItems(items, 10, new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10 }); @@ -83,7 +83,7 @@ namespace TestSuite.ApiTests { new { - path = "data.number1.iv", order = "ascending" + path = "data.number.iv", order = "ascending" } } } @@ -97,7 +97,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { - Skip = 5, OrderBy = "data/number1/iv asc" + Skip = 5, OrderBy = "data/number/iv asc" }); AssertItems(items, 10, new[] { 6, 7, 8, 9, 10 }); @@ -114,7 +114,7 @@ namespace TestSuite.ApiTests { new { - path = "data.number1.iv", order = "ascending" + path = "data.number.iv", order = "ascending" } }, skip = 5 @@ -129,7 +129,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { - Skip = 2, Top = 5, OrderBy = "data/number1/iv asc" + Skip = 2, Top = 5, OrderBy = "data/number/iv asc" }); AssertItems(items, 10, new[] { 3, 4, 5, 6, 7 }); @@ -146,7 +146,7 @@ namespace TestSuite.ApiTests { new { - path = "data.number1.iv", order = "ascending" + path = "data.number.iv", order = "ascending" } }, skip = 2, top = 5 @@ -161,7 +161,7 @@ namespace TestSuite.ApiTests { var items = await _.Contents.GetAsync(new ContentQuery { - Filter = "data/number1/iv gt 3 and data/number1/iv lt 7", OrderBy = "data/number1/iv asc" + Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" }); AssertItems(items, 3, new[] { 4, 5, 6 }); @@ -178,7 +178,7 @@ namespace TestSuite.ApiTests { new { - path = "data.number1.iv", order = "ascending" + path = "data.number.iv", order = "ascending" } }, filter = new @@ -187,13 +187,13 @@ namespace TestSuite.ApiTests { new { - path = "data.number1.iv", + path = "data.number.iv", op = "gt", value = 3 }, new { - path = "data.number1.iv", + path = "data.number.iv", op = "lt", value = 7 } @@ -418,19 +418,13 @@ namespace TestSuite.ApiTests query = @" mutation { createMyReadsContent(data: { - number1: { + number: { iv: 555 - }, - number2: { - iv: 556 } }) { id, data { - number1 { - iv - }, - number2 { + number { iv } } @@ -440,11 +434,9 @@ namespace TestSuite.ApiTests var result = await _.Contents.GraphQlAsync(query); - var value1 = result["createMyReadsContent"]["data"]["number1"]["iv"].Value(); - var value2 = result["createMyReadsContent"]["data"]["number2"]["iv"].Value(); + var value = result["createMyReadsContent"]["data"]["number"]["iv"].Value(); - Assert.Equal(555, value1); - Assert.Equal(556, value2); + Assert.Equal(555, value); } [Fact] @@ -457,10 +449,7 @@ namespace TestSuite.ApiTests createMyReadsContent(data: $data) { id, data { - number1 { - iv - } - number2 { + number { iv } } @@ -470,13 +459,9 @@ namespace TestSuite.ApiTests { data = new { - number1 = new + number = new { iv = 998 - }, - number2 = new - { - iv = 999 } } } @@ -484,11 +469,9 @@ namespace TestSuite.ApiTests var result = await _.Contents.GraphQlAsync(query); - var value1 = result["createMyReadsContent"]["data"]["number1"]["iv"].Value(); - var value2 = result["createMyReadsContent"]["data"]["number2"]["iv"].Value(); + var value = result["createMyReadsContent"]["data"]["number"]["iv"].Value(); - Assert.Equal(998, value1); - Assert.Equal(999, value2); + Assert.Equal(998, value); } [Fact] @@ -498,10 +481,10 @@ namespace TestSuite.ApiTests { query = @" query ContentsQuery($filter: String!) { - queryMyReadsContents(filter: $filter, orderby: ""data/number1/iv asc"") { + queryMyReadsContents(filter: $filter, orderby: ""data/number/iv asc"") { id, data { - number1 { + number { iv } } @@ -509,7 +492,7 @@ namespace TestSuite.ApiTests }", variables = new { - filter = @"data/number1/iv gt 3 and data/number1/iv lt 7" + filter = @"data/number/iv gt 3 and data/number/iv lt 7" } }; @@ -517,10 +500,10 @@ namespace TestSuite.ApiTests { query = @" query ContentsQuery($filter: String!) { - queryMyReadsContents(filter: $filter, orderby: ""data/number1/iv asc"") { + queryMyReadsContents(filter: $filter, orderby: ""data/number/iv asc"") { id, data { - number1 { + number { iv } } @@ -528,7 +511,7 @@ namespace TestSuite.ApiTests }", variables = new { - filter = @"data/number1/iv gt 4 and data/number1/iv lt 7" + filter = @"data/number/iv gt 4 and data/number/iv lt 7" } }; @@ -537,8 +520,8 @@ namespace TestSuite.ApiTests var items1 = results.ElementAt(0).Data.Items; var items2 = results.ElementAt(1).Data.Items; - Assert.Equal(items1.Select(x => x.Data.Number1).ToArray(), new[] { 4, 5, 6 }); - Assert.Equal(items2.Select(x => x.Data.Number1).ToArray(), new[] { 5, 6 }); + Assert.Equal(items1.Select(x => x.Data.Number).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items2.Select(x => x.Data.Number).ToArray(), new[] { 5, 6 }); } [Fact] @@ -548,10 +531,10 @@ namespace TestSuite.ApiTests { query = @" query ContentsQuery($filter: String!) { - queryMyReadsContents(filter: $filter, orderby: ""data/number1/iv asc"") { + queryMyReadsContents(filter: $filter, orderby: ""data/number/iv asc"") { id, data { - number1 { + number { iv } } @@ -559,7 +542,7 @@ namespace TestSuite.ApiTests }", variables = new { - filter = @"data/number1/iv gt 3 and data/number1/iv lt 7" + filter = @"data/number/iv gt 3 and data/number/iv lt 7" } }; @@ -567,7 +550,7 @@ namespace TestSuite.ApiTests var items = result.Items; - Assert.Equal(items.Select(x => x.Data.Number1).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items.Select(x => x.Data.Number).ToArray(), new[] { 4, 5, 6 }); } [Fact] @@ -577,10 +560,10 @@ namespace TestSuite.ApiTests { query = @" query ContentsQuery($filter: String!) { - queryMyReadsContents(filter: $filter, orderby: ""data/number1/iv asc"") { + queryMyReadsContents(filter: $filter, orderby: ""data/number/iv asc"") { id, data { - number1 { + number { iv } } @@ -588,7 +571,7 @@ namespace TestSuite.ApiTests }", variables = new { - filter = @"data/number1/iv gt 3 and data/number1/iv lt 7" + filter = @"data/number/iv gt 3 and data/number/iv lt 7" } }; @@ -596,7 +579,7 @@ namespace TestSuite.ApiTests var items = result.Items; - Assert.Equal(items.Select(x => x.Data.Number1).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items.Select(x => x.Data.Number).ToArray(), new[] { 4, 5, 6 }); } [Fact] @@ -606,10 +589,10 @@ namespace TestSuite.ApiTests { query = @" { - queryMyReadsContents(filter: ""data/number1/iv gt 3 and data/number1/iv lt 7"", orderby: ""data/number1/iv asc"") { + queryMyReadsContents(filter: ""data/number/iv gt 3 and data/number/iv lt 7"", orderby: ""data/number/iv asc"") { id, data { - number1 { + number { iv } } @@ -621,7 +604,7 @@ namespace TestSuite.ApiTests var items = result["queryMyReadsContents"]; - Assert.Equal(items.Select(x => x["data"]["number1"]["iv"].Value()).ToArray(), new[] { 4, 5, 6 }); + Assert.Equal(items.Select(x => x["data"]["number"]["iv"].Value()).ToArray(), new[] { 4, 5, 6 }); } private sealed class QueryResult @@ -640,13 +623,13 @@ namespace TestSuite.ApiTests private sealed class QueryItemData { [JsonConverter(typeof(InvariantConverter))] - public int Number1 { get; set; } + public int Number { get; set; } } private static void AssertItems(ContentsResult entities, int total, int[] expected) { Assert.Equal(total, entities.Total); - Assert.Equal(expected, entities.Items.Select(x => x.Data.Number1).ToArray()); + Assert.Equal(expected, entities.Items.Select(x => x.Data.Number).ToArray()); } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs index e88c99ee8..a6c5fec39 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentReferencesTests.cs @@ -36,7 +36,7 @@ namespace TestSuite.ApiTests // STEP 2: Create a content with a reference. var dataB = new TestEntityWithReferencesData { References = new[] { contentA_1.Id } }; - var contentB_1 = await _.Contents.CreateAsync(dataB, true); + var contentB_1 = await _.Contents.CreateAsync(dataB, ContentCreateOptions.AsPublish); // STEP 3: Query new item @@ -46,7 +46,10 @@ namespace TestSuite.ApiTests // STEP 4: Publish reference - await _.Contents.ChangeStatusAsync(contentA_1.Id, "Published"); + await _.Contents.ChangeStatusAsync(contentA_1.Id, new ChangeStatus + { + Status = "Published" + }); // STEP 5: Query new item again @@ -61,21 +64,24 @@ namespace TestSuite.ApiTests // STEP 1: Create a referenced content. var dataA = new TestEntityWithReferencesData(); - var contentA_1 = await _.Contents.CreateAsync(dataA, true); + var contentA_1 = await _.Contents.CreateAsync(dataA, ContentCreateOptions.AsPublish); // STEP 2: Create a content with a reference. var dataB = new TestEntityWithReferencesData { References = new[] { contentA_1.Id } }; - await _.Contents.CreateAsync(dataB, true); + await _.Contents.CreateAsync(dataB, ContentCreateOptions.AsPublish); // STEP 3: Try to delete with referrer check. - await Assert.ThrowsAnyAsync(() => _.Contents.DeleteAsync(contentA_1.Id, checkReferrers: true)); + await Assert.ThrowsAnyAsync(() => _.Contents.DeleteAsync(contentA_1.Id, new ContentDeleteOptions + { + CheckReferrers = true + })); // STEP 4: Delete without referrer check - await _.Contents.DeleteAsync(contentA_1.Id, checkReferrers: false); + await _.Contents.DeleteAsync(contentA_1.Id); } [Fact] @@ -84,13 +90,13 @@ namespace TestSuite.ApiTests // STEP 1: Create a published referenced content. var dataA = new TestEntityWithReferencesData(); - var contentA_1 = await _.Contents.CreateAsync(dataA, true); + var contentA_1 = await _.Contents.CreateAsync(dataA, ContentCreateOptions.AsPublish); // STEP 2: Create a content with a reference. var dataB = new TestEntityWithReferencesData { References = new[] { contentA_1.Id } }; - await _.Contents.CreateAsync(dataB, true); + await _.Contents.CreateAsync(dataB, ContentCreateOptions.AsPublish); // STEP 3: Try to ThrowsAnyAsync with referrer check. @@ -115,13 +121,13 @@ namespace TestSuite.ApiTests // STEP 1: Create a referenced content. var dataA = new TestEntityWithReferencesData(); - var contentA_1 = await _.Contents.CreateAsync(dataA, true); + var contentA_1 = await _.Contents.CreateAsync(dataA, ContentCreateOptions.AsPublish); // STEP 2: Create a content with a reference. var dataB = new TestEntityWithReferencesData { References = new[] { contentA_1.Id } }; - await _.Contents.CreateAsync(dataB, true); + await _.Contents.CreateAsync(dataB, ContentCreateOptions.AsPublish); // STEP 3: Try to delete with referrer check. @@ -166,13 +172,13 @@ namespace TestSuite.ApiTests // STEP 1: Create a published referenced content. var dataA = new TestEntityWithReferencesData(); - var contentA_1 = await _.Contents.CreateAsync(dataA, true); + var contentA_1 = await _.Contents.CreateAsync(dataA, ContentCreateOptions.AsPublish); // STEP 2: Create a published content with a reference. var dataB = new TestEntityWithReferencesData { References = new[] { contentA_1.Id } }; - await _.Contents.CreateAsync(dataB, true); + await _.Contents.CreateAsync(dataB, ContentCreateOptions.AsPublish); // STEP 3: Try to delete with referrer check. diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentScriptingTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentScriptingTests.cs index 39ec21ef3..d08f20cb7 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentScriptingTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentScriptingTests.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.ClientLibrary; using Squidex.ClientLibrary.Management; using TestSuite.Fixtures; using TestSuite.Model; @@ -25,36 +26,177 @@ namespace TestSuite.ApiTests } [Fact] - public async Task Should_use_creating_and_query_tests() + public async Task Should_create_content_with_scripting() { var schemaName = $"schema-{Guid.NewGuid()}"; + var scripts = new SchemaScriptsDto + { + Create = @$" + ctx.data.{TestEntityData.NumberField}.iv *= 2; + replace()" + }; + // STEP 1: Create a schema. - await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName); + await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName, scripts); + + // STEP 2: Create content + var contents = _.ClientManager.CreateContentsClient(schemaName); + + var content = await contents.CreateAsync(new TestEntityData { Number = 13 }); + + Assert.Equal(26, content.Data.Number); + } - // STEP 2: Set scripts - await _.Schemas.PutScriptsAsync(_.AppName, schemaName, new SchemaScriptsDto + [Fact] + public async Task Should_query_content_with_scripting() + { + var schemaName = $"schema-{Guid.NewGuid()}"; + + var scripts = new SchemaScriptsDto { Query = @$" - ctx.data.{TestEntityData.StringField}.iv = ctx.data.{TestEntityData.StringField}.iv + '_Updated' + ctx.data.{TestEntityData.NumberField}.iv *= 2; + replace()", + }; + + // STEP 1: Create a schema. + await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName, scripts); + + // STEP 2: Create content + var contents = _.ClientManager.CreateContentsClient(schemaName); + + var content = await contents.CreateAsync(new TestEntityData { Number = 13 }, ContentCreateOptions.AsPublish); + + Assert.Equal(26, content.Data.Number); + } + + [Fact] + public async Task Should_query_content_with_scripting_and_pre_query() + { + var schemaName = $"schema-{Guid.NewGuid()}"; + + var scripts = new SchemaScriptsDto + { + QueryPre = @$" + ctx.test = 17", + Query = @$" + ctx.data.{TestEntityData.NumberField}.iv = ctx.test + 2; replace()", + }; + + // STEP 1: Create a schema. + await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName, scripts); + + + // STEP 2: Create content + var contents = _.ClientManager.CreateContentsClient(schemaName); + + var content = await contents.CreateAsync(new TestEntityData { Number = 99 }, ContentCreateOptions.AsPublish); + + Assert.Equal(19, content.Data.Number); + } + + [Fact] + public async Task Should_create_bulk_content_with_scripting() + { + var schemaName = $"schema-{Guid.NewGuid()}"; + + // STEP 1: Create a schema. + var scripts = new SchemaScriptsDto + { Create = @$" - ctx.data.{TestEntityData.Number1Field}.iv *= 2; + ctx.data.{TestEntityData.NumberField}.iv = incrementCounter('${schemaName}'); replace()" + }; + + await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName, scripts); + + + // STEP 2: Create content with a value that triggers the schema. + var contents = _.ClientManager.CreateContentsClient(schemaName); + + var results = await contents.BulkUpdateAsync(new BulkUpdate + { + DoNotScript = false, + DoNotValidate = false, + Jobs = new List + { + new BulkUpdateJob + { + Type = BulkUpdateType.Upsert, + Data = new + { + number = new + { + iv = 99 + } + } + } + }, + Publish = true }); + Assert.Single(results); + Assert.Null(results[0].Error); + + + // STEP 2: Query content. + var content = await contents.GetAsync(results[0].ContentId); - // STEP 3: Create content + Assert.True(content.Data.Number > 0); + } + + [Fact] + public async Task Should_create_bulk_content_with_scripting_but_disabled() + { + var schemaName = $"schema-{Guid.NewGuid()}"; + + // STEP 1: Create a schema. + var scripts = new SchemaScriptsDto + { + Create = @$" + ctx.data.{TestEntityData.NumberField}.iv = incrementCounter('${schemaName}'); + replace()" + }; + + await TestEntity.CreateSchemaAsync(_.Schemas, _.AppName, schemaName, scripts); + + + // STEP 1: Create content with a value that triggers the schema. var contents = _.ClientManager.CreateContentsClient(schemaName); - var data = new TestEntityData { Number1 = 13, String = "Hello" }; + var results = await contents.BulkUpdateAsync(new BulkUpdate + { + DoNotScript = true, + DoNotValidate = false, + Jobs = new List + { + new BulkUpdateJob + { + Type = BulkUpdateType.Upsert, + Data = new + { + number = new + { + iv = 99 + } + } + } + }, + Publish = true + }); + + Assert.Single(results); + Assert.Null(results[0].Error); + - var content = await contents.CreateAsync(data); + // STEP 2: Query content. + var content = await contents.GetAsync(results[0].ContentId); - Assert.Equal(26, content.Data.Number1); - Assert.Equal("Hello_Updated", content.Data.String); + Assert.Equal(99, content.Data.Number); } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs index 42a0933c5..da721f696 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/ContentUpdateTests.cs @@ -34,11 +34,14 @@ namespace TestSuite.ApiTests try { // STEP 1: Create the item unpublished. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); // STEP 2: Publish the item. - await _.Contents.ChangeStatusAsync(content.Id, Status.Published); + await _.Contents.ChangeStatusAsync(content.Id, new ChangeStatus + { + Status = "Published" + }); // STEP 3: Retrieve the item. @@ -60,11 +63,14 @@ namespace TestSuite.ApiTests try { // STEP 1: Create the item published. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, ContentCreateOptions.AsPublish); // STEP 2: Archive the item. - await _.Contents.ChangeStatusAsync(content.Id, Status.Archived); + await _.Contents.ChangeStatusAsync(content.Id, new ChangeStatus + { + Status = "Archived" + }); // STEP 3. Get a 404 for the item because it is not published anymore. @@ -86,12 +92,18 @@ namespace TestSuite.ApiTests try { // STEP 1: Create the item unpublished. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); // STEP 2: Change the status to publiushed and then to draft. - await _.Contents.ChangeStatusAsync(content.Id, Status.Published); - await _.Contents.ChangeStatusAsync(content.Id, Status.Draft); + await _.Contents.ChangeStatusAsync(content.Id, new ChangeStatus + { + Status = "Published" + }); + await _.Contents.ChangeStatusAsync(content.Id, new ChangeStatus + { + Status = "Draft" + }); // STEP 3. Get a 404 for the item because it is not published anymore. @@ -115,7 +127,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a content item with a text that caused a bug before. - content = await _.Contents.CreateAsync(new TestEntityData { String = text }, true); + content = await _.Contents.CreateAsync(new TestEntityData { String = text }, ContentCreateOptions.AsPublish); // STEP 2: Get the item and ensure that the text is the same. @@ -145,7 +157,7 @@ namespace TestSuite.ApiTests { ["en"] = null } - }, true); + }, ContentCreateOptions.AsPublish); // STEP 2: Get the item and ensure that the text is the same. @@ -172,7 +184,7 @@ namespace TestSuite.ApiTests content = await _.Contents.CreateAsync(new TestEntityData { Localized = new Dictionary() - }, true); + }, ContentCreateOptions.AsPublish); // STEP 2: Get the item and ensure that the text is the same. @@ -189,115 +201,6 @@ namespace TestSuite.ApiTests } } - [Fact] - public async Task Should_create_content_with_scripting() - { - TestEntity content = null; - try - { - // STEP 1: Create a content item with a value that triggers the schema. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = -99 }, true); - - Assert.True(content.Data.Number1 > 0); - } - finally - { - if (content != null) - { - await _.Contents.DeleteAsync(content.Id); - } - } - } - - [Fact] - public async Task Should_create_bulk_content_with_scripting() - { - TestEntity content = null; - try - { - // STEP 1: Create content with a value that triggers the schema. - var results = await _.Contents.BulkUpdateAsync(new BulkUpdate - { - DoNotScript = false, - Jobs = new List - { - new BulkUpdateJob - { - Type = BulkUpdateType.Upsert, - Data = new - { - number1 = new - { - iv = TestEntity.ScriptTrigger - } - } - } - }, - Publish = true - }); - - Assert.Single(results); - Assert.Null(results[0].Error); - - - // STEP 2: Query content. - content = await _.Contents.GetAsync(results[0].ContentId); - - Assert.True(content.Data.Number1 > 0); - } - finally - { - if (content != null) - { - await _.Contents.DeleteAsync(content.Id); - } - } - } - - [Fact] - public async Task Should_create_bulk_content_with_scripting_but_disabled() - { - TestEntity content = null; - try - { - // STEP 1: Create content with a value that triggers the schema. - var results = await _.Contents.BulkUpdateAsync(new BulkUpdate - { - Jobs = new List - { - new BulkUpdateJob - { - Type = BulkUpdateType.Upsert, - Data = new - { - number1 = new - { - iv = TestEntity.ScriptTrigger - } - } - } - }, - Publish = true - }); - - Assert.Single(results); - Assert.Null(results[0].Error); - - - // STEP 2: Query content. - content = await _.Contents.GetAsync(results[0].ContentId); - - Assert.Equal(-99, content.Data.Number1); - } - finally - { - if (content != null) - { - await _.Contents.DeleteAsync(content.Id); - } - } - } - [Fact] public async Task Should_create_non_published_content() { @@ -305,7 +208,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create the item unpublished. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }); // STEP 2. Get a 404 for the item because it is not published. @@ -327,7 +230,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create the item published. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, ContentCreateOptions.AsPublish); // STEP 2: Get the item. @@ -351,7 +254,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item with a custom id. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, id, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, new ContentCreateOptions { Id = id, Publish = true }); Assert.Equal(id, content.Id); } @@ -373,13 +276,13 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item with a custom id. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, id, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, new ContentCreateOptions { Id = id, Publish = true }); Assert.Equal(id, content.Id); // STEP 2: Create a new item with a custom id. - var ex = await Assert.ThrowsAnyAsync(() => _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, id, true)); + var ex = await Assert.ThrowsAnyAsync(() => _.Contents.CreateAsync(new TestEntityData { Number = 1 }, new ContentCreateOptions { Id = id, Publish = true })); Assert.Contains("\"statusCode\":409", ex.Message, StringComparison.Ordinal); } @@ -401,21 +304,21 @@ namespace TestSuite.ApiTests try { // STEP 1: Upsert a new item with a custom id. - content = await _.Contents.UpsertAsync(id, new TestEntityData { Number1 = 1 }, true); + content = await _.Contents.UpsertAsync(id, new TestEntityData { Number = 1 }, ContentUpsertOptions.AsPublish); Assert.Equal(id, content.Id); // STEP 2: Make an update with the upsert endpoint. - content = await _.Contents.UpsertAsync(id, new TestEntityData { Number1 = 2 }); + content = await _.Contents.UpsertAsync(id, new TestEntityData { Number = 2 }); - Assert.Equal(2, content.Data.Number1); + Assert.Equal(2, content.Data.Number); // STEP 3: Make an update with the update endpoint. - content = await _.Contents.UpdateAsync(id, new TestEntityData { Number1 = 3 }); + content = await _.Contents.UpdateAsync(id, new TestEntityData { Number = 3 }); - Assert.Equal(3, content.Data.Number1); + Assert.Equal(3, content.Data.Number); } finally { @@ -433,15 +336,15 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 2 }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, ContentCreateOptions.AsPublish); // STEP 2: Update the item and ensure that the data has changed. - await _.Contents.UpdateAsync(content.Id, new TestEntityData { Number1 = 2 }); + await _.Contents.UpdateAsync(content.Id, new TestEntityData { Number = 2 }); var updated = await _.Contents.GetAsync(content.Id); - Assert.Equal(2, content.Data.Number1); + Assert.Equal(2, content.Data.Number); } finally { @@ -459,7 +362,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { String = "initial" }, true); + content = await _.Contents.CreateAsync(new TestEntityData { String = "initial" }, ContentCreateOptions.AsPublish); // STEP 2: Update the item and ensure that the data has changed. @@ -485,19 +388,19 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { String = "test" }, true); + content = await _.Contents.CreateAsync(new TestEntityData { String = "test" }, ContentCreateOptions.AsPublish); // STEP 2: Path an item. - await _.Contents.PatchAsync(content.Id, new TestEntityData { Number1 = 1 }); + await _.Contents.PatchAsync(content.Id, new TestEntityData { Number = 1 }); // STEP 3: Update the item and ensure that the data has changed. - await _.Contents.PatchAsync(content.Id, new TestEntityData { Number1 = 2 }); + await _.Contents.PatchAsync(content.Id, new TestEntityData { Number = 2 }); var updated = await _.Contents.GetAsync(content.Id); - Assert.Equal(2, updated.Data.Number1); + Assert.Equal(2, updated.Data.Number); // Should not change other value with patch. Assert.Equal("test", updated.Data.String); @@ -518,7 +421,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { Id = "id1" }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Id = "id1" }, ContentCreateOptions.AsPublish); // STEP 2: Update the item and ensure that the data has changed. @@ -544,7 +447,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { String = "initial" }, true); + content = await _.Contents.CreateAsync(new TestEntityData { String = "initial" }, ContentCreateOptions.AsPublish); // STEP 2: Update the item and ensure that the data has changed. @@ -570,7 +473,7 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, ContentCreateOptions.AsPublish); // STEP 2: Create draft. @@ -578,25 +481,28 @@ namespace TestSuite.ApiTests // STEP 3: Update the item and ensure that the data has not changed. - await _.Contents.PatchAsync(content.Id, new TestEntityData { Number1 = 2 }); + await _.Contents.PatchAsync(content.Id, new TestEntityData { Number = 2 }); var updated_1 = await _.Contents.GetAsync(content.Id); - Assert.Equal(1, updated_1.Data.Number1); + Assert.Equal(1, updated_1.Data.Number); // STEP 4: Get the unpublished version var unpublished = await _.Contents.GetAsync(content.Id, QueryContext.Default.Unpublished()); - Assert.Equal(2, unpublished.Data.Number1); + Assert.Equal(2, unpublished.Data.Number); // STEP 5: Publish draft and ensure that it has been updated. - await _.Contents.ChangeStatusAsync(content.Id, "Published"); + await _.Contents.ChangeStatusAsync(content.Id, new ChangeStatus + { + Status = "Published" + }); var updated_2 = await _.Contents.GetAsync(content.Id); - Assert.Equal(2, updated_2.Data.Number1); + Assert.Equal(2, updated_2.Data.Number); } finally { @@ -613,11 +519,11 @@ namespace TestSuite.ApiTests public async Task Should_delete_content(bool permanent) { // STEP 1: Create a new item. - var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number1 = 2 }, true); + var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, ContentCreateOptions.AsPublish); // STEP 2: Delete the item. - await _.Contents.DeleteAsync(content_1.Id, permanent); + await _.Contents.DeleteAsync(content_1.Id, new ContentDeleteOptions { Permanent = permanent }); // STEP 3: Retrieve all items and ensure that the deleted item does not exist. @@ -641,15 +547,15 @@ namespace TestSuite.ApiTests public async Task Should_recreate_deleted_content(bool permanent) { // STEP 1: Create a new item. - var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number1 = 2 }, true); + var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, ContentCreateOptions.AsPublish); // STEP 2: Delete the item. - await _.Contents.DeleteAsync(content_1.Id, permanent); + await _.Contents.DeleteAsync(content_1.Id, new ContentDeleteOptions { Permanent = permanent }); // STEP 3: Recreate the item with the same id. - var content_2 = await _.Contents.CreateAsync(new TestEntityData { Number1 = 2 }, content_1.Id, true); + var content_2 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, new ContentCreateOptions { Id = content_1.Id, Publish = true }); Assert.Equal(Status.Published, content_2.Status); @@ -666,15 +572,15 @@ namespace TestSuite.ApiTests public async Task Should_recreate_deleted_content_with_upsert(bool permanent) { // STEP 1: Create a new item. - var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number1 = 2 }, true); + var content_1 = await _.Contents.CreateAsync(new TestEntityData { Number = 2 }, ContentCreateOptions.AsPublish); // STEP 2: Delete the item. - await _.Contents.DeleteAsync(content_1.Id, permanent); + await _.Contents.DeleteAsync(content_1.Id, new ContentDeleteOptions { Permanent = permanent }); // STEP 3: Recreate the item with the same id. - var content_2 = await _.Contents.UpsertAsync(content_1.Id, new TestEntityData { Number1 = 2 }, true); + var content_2 = await _.Contents.UpsertAsync(content_1.Id, new TestEntityData { Number = 2 }, ContentUpsertOptions.AsPublish); Assert.Equal(Status.Published, content_2.Status); @@ -736,29 +642,29 @@ namespace TestSuite.ApiTests try { // STEP 1: Create a new item. - content = await _.Contents.CreateAsync(new TestEntityData { Number1 = 1 }, true); + content = await _.Contents.CreateAsync(new TestEntityData { Number = 1 }, ContentCreateOptions.AsPublish); // STEP 2: Update content. - content = await _.Contents.UpdateAsync(content.Id, new TestEntityData { Number1 = 2 }); + content = await _.Contents.UpdateAsync(content.Id, new TestEntityData { Number = 2 }); // STEP 3: Get current version. var content_latest = await _.Contents.GetAsync(content.Id); - Assert.Equal(2, content_latest.Data.Number1); + Assert.Equal(2, content_latest.Data.Number); // STEP 4: Get current version. var data_2 = await _.Contents.GetDataAsync(content.Id, content.Version); - Assert.Equal(2, data_2.Number1); + Assert.Equal(2, data_2.Number); // STEP 4: Get previous version var data_1 = await _.Contents.GetDataAsync(content.Id, content.Version - 1); - Assert.Equal(1, data_1.Number1); + Assert.Equal(1, data_1.Number); } finally { diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs index 897e13a50..da39f438d 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs +++ b/backend/tools/TestSuite/TestSuite.ApiTests/GraphQLTests.cs @@ -192,7 +192,7 @@ namespace TestSuite.ApiTests var citiesClient = _.ClientManager.CreateContentsClient("cities"); - var city = await citiesClient.CreateAsync(cityData, true); + var city = await citiesClient.CreateAsync(cityData, ContentCreateOptions.AsPublish); // STEP 2: Create city @@ -210,7 +210,7 @@ namespace TestSuite.ApiTests var statesClient = _.ClientManager.CreateContentsClient("states"); - var state = await statesClient.CreateAsync(stateData, true); + var state = await statesClient.CreateAsync(stateData, ContentCreateOptions.AsPublish); // STEP 3: Create country @@ -228,7 +228,7 @@ namespace TestSuite.ApiTests var countriesClient = _.ClientManager.CreateContentsClient("countries"); - await countriesClient.CreateAsync(countryData, true); + await countriesClient.CreateAsync(countryData, ContentCreateOptions.AsPublish); } } } diff --git a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj index f823b49f1..ceae05d55 100644 --- a/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj +++ b/backend/tools/TestSuite/TestSuite.ApiTests/TestSuite.ApiTests.csproj @@ -14,7 +14,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs index 3002bb77f..d785c5a12 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs +++ b/backend/tools/TestSuite/TestSuite.LoadTests/ReadingContentBenchmarks.cs @@ -61,7 +61,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ContentQuery { OrderBy = "data/number1/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { OrderBy = "data/number/iv asc" }); }); } @@ -71,7 +71,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ContentQuery { Skip = 5, OrderBy = "data/number1/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 5, OrderBy = "data/number/iv asc" }); }); } @@ -81,7 +81,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/number1/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv asc" }); }); } @@ -91,7 +91,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/number1/iv desc" }); + await _.Contents.GetAsync(new ContentQuery { Skip = 2, Top = 5, OrderBy = "data/number/iv desc" }); }); } @@ -101,7 +101,7 @@ namespace TestSuite.LoadTests { await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.GetAsync(new ContentQuery { Filter = "data/number1/iv gt 3 and data/number1/iv lt 7", OrderBy = "data/number1/iv asc" }); + await _.Contents.GetAsync(new ContentQuery { Filter = "data/number/iv gt 3 and data/number/iv lt 7", OrderBy = "data/number/iv asc" }); }); } } diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj b/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj index 0546edcef..155055ba4 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj +++ b/backend/tools/TestSuite/TestSuite.LoadTests/TestSuite.LoadTests.csproj @@ -6,7 +6,7 @@ enable - + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs b/backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs index 1b59cc4a8..fda4b898f 100644 --- a/backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs +++ b/backend/tools/TestSuite/TestSuite.LoadTests/WritingBenchmarks.cs @@ -5,6 +5,7 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== +using Squidex.ClientLibrary; using TestSuite.Model; using Xunit; @@ -61,7 +62,7 @@ namespace TestSuite.LoadTests await Run.Parallel(numUsers, numIterationsPerUser, async () => { - await _.Contents.CreateAsync(new TestEntityData { Number1 = random.Next() }, true); + await _.Contents.CreateAsync(new TestEntityData { Number = random.Next() }, ContentCreateOptions.AsPublish); }); } } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs index 417c54c7c..49796873e 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Fixtures/ContentQueryFixture1to10.cs @@ -7,6 +7,7 @@ using System.Globalization; using Newtonsoft.Json.Linq; +using Squidex.ClientLibrary; using TestSuite.Model; namespace TestSuite.Fixtures @@ -34,15 +35,14 @@ namespace TestSuite.Fixtures var data = new TestEntityData { String = text, - Number1 = i, - Number2 = i, Json = JObject.FromObject(new { nested1 = new { nested2 = i } - }) + }), + Number = i, }; if (i % 2 == 0) @@ -54,7 +54,7 @@ namespace TestSuite.Fixtures data.Geo = new { longitude = i, latitude = i }; } - await Contents.CreateAsync(data, true); + await Contents.CreateAsync(data, ContentCreateOptions.AsPublish); } }).Wait(); } diff --git a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs index 34dec776f..87c29c7c8 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs +++ b/backend/tools/TestSuite/TestSuite.Shared/Model/TestEntity.cs @@ -16,7 +16,7 @@ namespace TestSuite.Model { public const int ScriptTrigger = -99; - public static async Task CreateSchemaAsync(ISchemasClient schemas, string appName, string name) + public static async Task CreateSchemaAsync(ISchemasClient schemas, string appName, string name, SchemaScriptsDto scripts = null) { var schema = await schemas.PostSchemaAsync(appName, new CreateSchemaDto { @@ -25,21 +25,13 @@ namespace TestSuite.Model { new UpsertSchemaFieldDto { - Name = TestEntityData.Number1Field, + Name = TestEntityData.NumberField, Properties = new NumberFieldPropertiesDto { IsRequired = true } }, new UpsertSchemaFieldDto - { - Name = TestEntityData.Number2Field, - Properties = new NumberFieldPropertiesDto - { - IsRequired = false - } - }, - new UpsertSchemaFieldDto { Name = TestEntityData.StringField, Properties = new StringFieldPropertiesDto @@ -79,16 +71,9 @@ namespace TestSuite.Model { IsRequired = false } - }, - }, - Scripts = new SchemaScriptsDto - { - Create = $@" - if (ctx.data.{TestEntityData.Number1Field}.iv === {ScriptTrigger}) {{ - ctx.data.{TestEntityData.Number1Field}.iv = incrementCounter('my'); - replace(); - }}" + } }, + Scripts = scripts, IsPublished = true }); @@ -102,9 +87,7 @@ namespace TestSuite.Model public static readonly string StringField = nameof(String).ToLowerInvariant(); - public static readonly string Number1Field = nameof(Number1).ToLowerInvariant(); - - public static readonly string Number2Field = nameof(Number2).ToLowerInvariant(); + public static readonly string NumberField = nameof(Number).ToLowerInvariant(); public static readonly string JsonField = nameof(Json).ToLowerInvariant(); @@ -115,10 +98,7 @@ namespace TestSuite.Model public Dictionary Localized { get; set; } [JsonConverter(typeof(InvariantConverter))] - public int Number1 { get; set; } - - [JsonConverter(typeof(InvariantConverter))] - public int Number2 { get; set; } + public int Number { get; set; } [JsonConverter(typeof(InvariantConverter))] public string Id { get; set; } diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index f468db90a..7c24afb07 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -10,8 +10,8 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -21,7 +21,7 @@ - + diff --git a/frontend/src/app/features/schemas/declarations.ts b/frontend/src/app/features/schemas/declarations.ts index 655d6e576..b2da69974 100644 --- a/frontend/src/app/features/schemas/declarations.ts +++ b/frontend/src/app/features/schemas/declarations.ts @@ -41,6 +41,7 @@ export * from './pages/schema/preview/schema-preview-urls-form.component'; export * from './pages/schema/rules/schema-field-rules-form.component'; export * from './pages/schema/schema-page.component'; export * from './pages/schema/scripts/schema-scripts-form.component'; +export * from './pages/schema/scripts/schema-scripts.pipes'; export * from './pages/schema/ui/field-list.component'; export * from './pages/schema/ui/schema-ui-form.component'; export * from './pages/schemas/schema-form.component'; diff --git a/frontend/src/app/features/schemas/module.ts b/frontend/src/app/features/schemas/module.ts index 71096f048..06013aa95 100644 --- a/frontend/src/app/features/schemas/module.ts +++ b/frontend/src/app/features/schemas/module.ts @@ -8,7 +8,7 @@ import { NgModule } from '@angular/core'; import { RouterModule, Routes } from '@angular/router'; import { HelpComponent, LoadSchemasGuard, SchemaMustExistGuard, SqxFrameworkModule, SqxSharedModule } from '@app/shared'; -import { ArrayValidationComponent, AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, ComponentsUIComponent, ComponentsValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, FieldComponent, FieldFormCommonComponent, FieldFormComponent, FieldFormUIComponent, FieldFormValidationComponent, FieldListComponent, FieldWizardComponent, GeolocationUIComponent, GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, NumberValidationComponent, ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, SchemaExportFormComponent, SchemaFieldRulesFormComponent, SchemaFieldsComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, SchemaScriptsFormComponent, SchemasPageComponent, SchemaUIFormComponent, StringUIComponent, StringValidationComponent, TagsUIComponent, TagsValidationComponent } from './declarations'; +import { ArrayValidationComponent, AssetsUIComponent, AssetsValidationComponent, BooleanUIComponent, BooleanValidationComponent, ComponentsUIComponent, ComponentsValidationComponent, DateTimeUIComponent, DateTimeValidationComponent, FieldComponent, FieldFormCommonComponent, FieldFormComponent, FieldFormUIComponent, FieldFormValidationComponent, FieldListComponent, FieldWizardComponent, GeolocationUIComponent, GeolocationValidationComponent, JsonUIComponent, JsonValidationComponent, NumberUIComponent, NumberValidationComponent, ReferencesUIComponent, ReferencesValidationComponent, SchemaEditFormComponent, SchemaExportFormComponent, SchemaFieldRulesFormComponent, SchemaFieldsComponent, SchemaFormComponent, SchemaPageComponent, SchemaPreviewUrlsFormComponent, SchemaScriptNamePipe, SchemaScriptsFormComponent, SchemasPageComponent, SchemaUIFormComponent, StringUIComponent, StringValidationComponent, TagsUIComponent, TagsValidationComponent } from './declarations'; import { ComponentUIComponent } from './pages/schema/fields/types/component-ui.component'; import { ComponentValidationComponent } from './pages/schema/fields/types/component-validation.component'; @@ -80,6 +80,7 @@ const routes: Routes = [ SchemaPageComponent, SchemaPreviewUrlsFormComponent, SchemaScriptsFormComponent, + SchemaScriptNamePipe, SchemasPageComponent, SchemaUIFormComponent, StringUIComponent, diff --git a/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.html b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.html index 330a9497d..08aabcd67 100644 --- a/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.html +++ b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts-form.component.html @@ -3,7 +3,7 @@ diff --git a/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.spec.ts b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.spec.ts new file mode 100644 index 000000000..a85ae273a --- /dev/null +++ b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.spec.ts @@ -0,0 +1,24 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { SchemaScriptNamePipe } from './schema-scripts.pipes'; + +describe('SchemaScriptsPipe', () => { + const pipe = new SchemaScriptNamePipe(); + + it('should return titlecase for schema name', () => { + const actual = pipe.transform('create'); + + expect(actual).toEqual('Create'); + }); + + it('should return custom name for queryPre', () => { + const actual = pipe.transform('queryPre'); + + expect(actual).toEqual('Prepare Query'); + }); +}); \ No newline at end of file diff --git a/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.ts b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.ts new file mode 100644 index 000000000..523dfd157 --- /dev/null +++ b/frontend/src/app/features/schemas/pages/schema/scripts/schema-scripts.pipes.ts @@ -0,0 +1,22 @@ +/* + * Squidex Headless CMS + * + * @license + * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. + */ + +import { Pipe, PipeTransform } from '@angular/core'; + +@Pipe({ + name: 'sqxSchemaScriptName', + pure: true, +}) +export class SchemaScriptNamePipe implements PipeTransform { + public transform(value: string) { + if (value === 'queryPre') { + return 'Prepare Query'; + } else { + return value.substring(0, 1).toUpperCase() + value.substring(1); + } + } +} \ No newline at end of file diff --git a/frontend/src/app/shared/state/schemas.forms.ts b/frontend/src/app/shared/state/schemas.forms.ts index 0aa6fe02b..a551fb34b 100644 --- a/frontend/src/app/shared/state/schemas.forms.ts +++ b/frontend/src/app/shared/state/schemas.forms.ts @@ -181,6 +181,9 @@ export class EditSchemaScriptsForm extends Form { query: new FormControl('', Validators.nullValidator, ), + queryPre: new FormControl('', + Validators.nullValidator, + ), create: new FormControl('', Validators.nullValidator, ),