diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f68065be7..c7116b3cc2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,49 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [7.2.0] - 2022-11-11 + +### Fixed + +* **Assets**: Configured timeout for queries was ignored. +* **Assets**: Several fixes for tags which was causing duplicate tag names when a tag was renamed. +* **Backups**: Fix timeout handling for backups to mark timed out backups as failed. +* **Contents**: Configured timeout for queries was ignored. +* **Contents**: Disable component fields when field is disabled. +* **Contents**: Disable drag and drop for array editors, when the array field is disabled. +* **Contents**: Fix generated OpenApi specs. +* **Contents**: Fix geo queries (latitude and longitude) was swapped. +* **Rules**: Fixes cache duration for rule handling. +* **Templates**: Updated the template system to create a new temporary folder for each operation to query the repository. +* **UI**: More help pages. +* **UI**: More history pages. +* **UI**: Several fixes to handle schema fields. +* **UI**: Use a fallback image, if the app image cannot be loaded. + +### Changed + +* **API**: Better status handling for exceptions. +* **API**: Move to file scoped namespaces. +* **Backups**: More logs for the backup. +* **Contents**: Mark content-version endpoint as obsolete. +* **Helm**: Document helm parameters. +* **Rules**: Dedicated Rule action for OpenSearch. +* **Rules**: More logs for rule enqueuer. +* **UI**: Updated Angular to 15.0 +* **UI**: Updated Bootstrap. + +### Added + +* **Billing**: Introducing teams to manage subscriptions across teams. +* **Billing**: Several changes to introduce a referral program. +* **Contents**: Button to show or hide the input for custom ID. +* **Contents**: Column to show the translation status. +* **Contents**: Define a custom GraphQL schema for JSON fields. +* **Contents**: New endpoint to fetch a specific content by version. +* **Contents**: OpenAPI definitions for the bulkd endpoint. +* **OpenAPI**: Add more controllers to OpenAPI spec. +* **Users**: Added tests for user management. + ## [7.1.0] - 2022-09-01 ### Fixed diff --git a/backend/i18n/frontend_en.json b/backend/i18n/frontend_en.json index 8445ac5f94..69dcec1d99 100644 --- a/backend/i18n/frontend_en.json +++ b/backend/i18n/frontend_en.json @@ -100,6 +100,7 @@ "assets.metadataAdd": "Add Metadata", "assets.moveFailed": "Failed to move asset. Please reload.", "assets.protected": "Protected", + "assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.", "assets.refreshTooltip": "Refresh Assets", "assets.reloaded": "Assets reloaded.", "assets.removeConfirmText": "Do you really want to remove the asset?", diff --git a/backend/i18n/frontend_it.json b/backend/i18n/frontend_it.json index 55fc79946f..017d15e91f 100644 --- a/backend/i18n/frontend_it.json +++ b/backend/i18n/frontend_it.json @@ -100,6 +100,7 @@ "assets.metadataAdd": "Aggiungi un metadato", "assets.moveFailed": "Non è stato possibile spostare la risorsa. Per favore ricarica.", "assets.protected": "Protetto", + "assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.", "assets.refreshTooltip": "Aggiorna le risorse", "assets.reloaded": "Risorse ricaricate.", "assets.removeConfirmText": "Sei sicuro di voler cancellare la risorsa?", diff --git a/backend/i18n/frontend_nl.json b/backend/i18n/frontend_nl.json index b09b68907d..67b81d8be0 100644 --- a/backend/i18n/frontend_nl.json +++ b/backend/i18n/frontend_nl.json @@ -100,6 +100,7 @@ "assets.metadataAdd": "Metadata toevoegen", "assets.moveFailed": "Verplaatsen van item is mislukt. Laad opnieuw.", "assets.protected": "Beschermd", + "assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.", "assets.refreshTooltip": "Bestanden vernieuwen", "assets.reloaded": "Bestanden herladen.", "assets.removeConfirmText": "Wil je het bestand echt verwijderen?", diff --git a/backend/i18n/frontend_zh.json b/backend/i18n/frontend_zh.json index f1c1c7981e..7b6f0be355 100644 --- a/backend/i18n/frontend_zh.json +++ b/backend/i18n/frontend_zh.json @@ -100,6 +100,7 @@ "assets.metadataAdd": "添加元数据", "assets.moveFailed": "资源移动失败。请重新加载。", "assets.protected": "受保护", + "assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.", "assets.refreshTooltip": "刷新资源", "assets.reloaded": "资源重新加载。", "assets.removeConfirmText": "你真的要移除资源吗?", diff --git a/backend/i18n/source/frontend_en.json b/backend/i18n/source/frontend_en.json index 8445ac5f94..69dcec1d99 100644 --- a/backend/i18n/source/frontend_en.json +++ b/backend/i18n/source/frontend_en.json @@ -100,6 +100,7 @@ "assets.metadataAdd": "Add Metadata", "assets.moveFailed": "Failed to move asset. Please reload.", "assets.protected": "Protected", + "assets.protectedHint": "Assets are public by default. Everybody with the link can download the file. If you make an asset protected, only authenticated users (usually a client) can download the asset.", "assets.refreshTooltip": "Refresh Assets", "assets.reloaded": "Assets reloaded.", "assets.removeConfirmText": "Do you really want to remove the asset?", diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs index 2c98c4fb67..6f33a2b1f3 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/DefaultAppLogStore.cs @@ -33,8 +33,7 @@ public sealed class DefaultAppLogStore : IAppLogStore, IDeleter private static readonly CsvConfiguration CsvConfiguration = new CsvConfiguration(CultureInfo.InvariantCulture) { DetectDelimiter = false, - Delimiter = "|", - LeaveOpen = true + Delimiter = "|" }; private readonly IRequestLogStore requestLogStore; @@ -88,7 +87,7 @@ public async Task ReadLogAsync(DomainId appId, DateTime fromDate, DateTime toDat var writer = new StreamWriter(stream, Encoding.UTF8, 4096, true); try { - await using (var csv = new CsvWriter(writer, CsvConfiguration)) + await using (var csv = new CsvWriter(writer, CsvConfiguration, true)) { csv.WriteField(FieldTimestamp); csv.WriteField(FieldRequestPath); diff --git a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs index 95aed0701d..4d9e63212a 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs +++ b/backend/src/Squidex.Domain.Apps.Entities/Apps/Templates/TemplateCommandMiddleware.cs @@ -105,7 +105,7 @@ private async Task ApplyTemplateAsync(IAppEntity app, string? template) private static async Task CreateSyncServiceAsync(string repository, ISession session) { - var fs = await FileSystems.CreateAsync(repository, session.WorkingDirectory); + var fs = await FileSystems.CreateAsync(repository); return new SyncService(fs, session); } diff --git a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj index 5260fae794..c526b4b922 100644 --- a/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj +++ b/backend/src/Squidex.Domain.Apps.Entities/Squidex.Domain.Apps.Entities.csproj @@ -19,7 +19,7 @@ - + @@ -29,7 +29,7 @@ - + diff --git a/backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs b/backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs index 63c4e4b2c6..869c10aebc 100644 --- a/backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs +++ b/backend/src/Squidex/Areas/Api/Config/OpenApi/OpenApiServices.cs @@ -15,6 +15,7 @@ using Squidex.Areas.Api.Controllers.Rules.Models; using Squidex.Domain.Apps.Core.Assets; using Squidex.Domain.Apps.Core.Contents; +using Squidex.Domain.Apps.Core.Schemas; using Squidex.Infrastructure; using Squidex.Infrastructure.Json.Objects; using Squidex.Infrastructure.Queries; @@ -115,10 +116,20 @@ private static void ConfigureSchemaSettings(JsonSchemaGeneratorSettings settings CreateAnyMap(), CreateAnyMap(), - CreateAnyMap>() + CreateAnyMap>(), + + new PrimitiveTypeMapper(typeof(FieldNames), schema => + { + schema.Type = JsonObjectType.Array; + + schema.Item = new JsonSchema + { + Type = JsonObjectType.String + }; + }), }; - settings.SchemaType = SchemaType.OpenApi3; + settings.SchemaType = NJsonSchema.SchemaType.OpenApi3; settings.FlattenInheritanceHierarchy = flatten; } diff --git a/backend/src/Squidex/Pipeline/Squid/SquidMiddleware.cs b/backend/src/Squidex/Pipeline/Squid/SquidMiddleware.cs index ab183323f9..2c71e4fffa 100644 --- a/backend/src/Squidex/Pipeline/Squid/SquidMiddleware.cs +++ b/backend/src/Squidex/Pipeline/Squid/SquidMiddleware.cs @@ -5,8 +5,8 @@ // All rights reserved. Licensed under the MIT license. // ========================================================================== -using Squidex.Infrastructure; using System.Text; +using Squidex.Infrastructure; namespace Squidex.Pipeline.Squid; diff --git a/backend/src/Squidex/Squidex.csproj b/backend/src/Squidex/Squidex.csproj index baaddf0058..a62bed67bc 100644 --- a/backend/src/Squidex/Squidex.csproj +++ b/backend/src/Squidex/Squidex.csproj @@ -56,12 +56,10 @@ - - - + - + @@ -76,11 +74,10 @@ - + - diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleTypeProviderTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleTypeProviderTests.cs new file mode 100644 index 0000000000..420a6638b9 --- /dev/null +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/HandleRules/RuleTypeProviderTests.cs @@ -0,0 +1,203 @@ +// ========================================================================== +// Squidex Headless CMS +// ========================================================================== +// Copyright (c) Squidex UG (haftungsbeschraenkt) +// All rights reserved. Licensed under the MIT license. +// ========================================================================== + +using System.ComponentModel.DataAnnotations; +using FluentAssertions; +using Squidex.Domain.Apps.Core.HandleRules; +using Squidex.Domain.Apps.Core.Rules; +using Squidex.Infrastructure.Validation; +using Xunit; + +namespace Squidex.Domain.Apps.Core.Operations.HandleRules; + +public class RuleTypeProviderTests +{ + private readonly RuleTypeProvider sut = new RuleTypeProvider(); + + private abstract class MyRuleActionHandler : RuleActionHandler + { + protected MyRuleActionHandler(RuleEventFormatter formatter) + : base(formatter) + { + } + } + + public enum ActionEnum + { + Yes, + No + } + + [RuleAction( + Title = "Action", + IconImage = "", + IconColor = "#1e5470", + Display = "Action display", + Description = "Action description.", + ReadMore = "https://www.readmore.com/")] + public sealed record MyRuleAction : RuleAction + { + [LocalizedRequired] + [Display(Name = "Url Name", Description = "Url Description")] + [Editor(RuleFieldEditor.Url)] + [Formattable] + public Uri Url { get; set; } + + [Editor(RuleFieldEditor.Javascript)] + public string Script { get; set; } + + [Editor(RuleFieldEditor.Text)] + public string Text { get; set; } + + [Editor(RuleFieldEditor.TextArea)] + public string TextMultiline { get; set; } + + [Editor(RuleFieldEditor.Password)] + public string Password { get; set; } + + [Editor(RuleFieldEditor.Text)] + public ActionEnum Enum { get; set; } + + [Editor(RuleFieldEditor.Text)] + public ActionEnum? EnumOptional { get; set; } + + [Editor(RuleFieldEditor.Text)] + public bool Boolean { get; set; } + + [Editor(RuleFieldEditor.Text)] + public bool? BooleanOptional { get; set; } + + [Editor(RuleFieldEditor.Text)] + public int Number { get; set; } + + [Editor(RuleFieldEditor.Text)] + public int? NumberOptional { get; set; } + } + + [Fact] + public void Should_create_definition() + { + var expected = new RuleActionDefinition + { + Type = typeof(MyRuleAction), + Title = "Action", + IconImage = "", + IconColor = "#1e5470", + Display = "Action display", + Description = "Action description.", + ReadMore = "https://www.readmore.com/" + }; + + expected.Properties.Add(new RuleActionProperty + { + Name = "url", + Display = "Url Name", + Description = "Url Description", + Editor = RuleFieldEditor.Url, + IsFormattable = true, + IsRequired = true + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "script", + Display = "Script", + Description = null, + Editor = RuleFieldEditor.Javascript, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "text", + Display = "Text", + Description = null, + Editor = RuleFieldEditor.Text, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "textMultiline", + Display = "TextMultiline", + Description = null, + Editor = RuleFieldEditor.TextArea, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "password", + Display = "Password", + Description = null, + Editor = RuleFieldEditor.Password, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "enum", + Display = "Enum", + Description = null, + Editor = RuleFieldEditor.Dropdown, + IsRequired = false, + Options = new[] { "Yes", "No" } + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "enumOptional", + Display = "EnumOptional", + Description = null, + Editor = RuleFieldEditor.Dropdown, + IsRequired = false, + Options = new[] { "Yes", "No" } + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "boolean", + Display = "Boolean", + Description = null, + Editor = RuleFieldEditor.Checkbox, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "booleanOptional", + Display = "BooleanOptional", + Description = null, + Editor = RuleFieldEditor.Checkbox, + IsRequired = false + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "number", + Display = "Number", + Description = null, + Editor = RuleFieldEditor.Number, + IsRequired = true + }); + + expected.Properties.Add(new RuleActionProperty + { + Name = "numberOptional", + Display = "NumberOptional", + Description = null, + Editor = RuleFieldEditor.Number, + IsRequired = false + }); + + sut.Add(); + + var currentDefinition = sut.Actions.Values.First(); + + currentDefinition.Should().BeEquivalentTo(expected); + } +} diff --git a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj index e3a87f103a..cb17c20938 100644 --- a/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj +++ b/backend/tools/TestSuite/TestSuite.Shared/TestSuite.Shared.csproj @@ -16,8 +16,8 @@ - - + + diff --git a/frontend/src/app/shared/components/assets/asset-dialog.component.html b/frontend/src/app/shared/components/assets/asset-dialog.component.html index 2041f397a2..74fd092cc4 100644 --- a/frontend/src/app/shared/components/assets/asset-dialog.component.html +++ b/frontend/src/app/shared/components/assets/asset-dialog.component.html @@ -136,7 +136,7 @@
-
+ diff --git a/frontend/src/app/shared/components/assets/asset-dialog.component.scss b/frontend/src/app/shared/components/assets/asset-dialog.component.scss index b3619be868..850aeb393f 100644 --- a/frontend/src/app/shared/components/assets/asset-dialog.component.scss +++ b/frontend/src/app/shared/components/assets/asset-dialog.component.scss @@ -33,6 +33,14 @@ visibility: hidden; } +.metadata-row { + margin-bottom: .5rem; + + &:last-child { + margin: 0; + } +} + .path { min-height: 2.5rem; }