From c971f9c187388e9f9542cc63c4dd36dd2174ae5e Mon Sep 17 00:00:00 2001 From: Sebastian Date: Mon, 13 Feb 2023 18:00:16 +0100 Subject: [PATCH] More autocompletion. --- .../FieldDescriptions.Designer.cs | 9 +++ .../FieldDescriptions.resx | 3 + .../GenerateFilters/FilterExtensions.cs | 5 +- .../GenerateFilters/FilterVisitor.cs | 1 + .../Scripting/Internal/JintExtensions.cs | 6 +- .../Scripting/ScriptingCompleter.cs | 73 ++++++++++++------- .../Queries/FilterField.cs | 3 +- .../Queries/FilterSchema.cs | 2 + .../Controllers/Schemas/SchemasController.cs | 19 ++++- .../Scripting/ScriptingCompleterTests.cs | 19 ++++- .../shared/preview-button.component.ts | 4 +- .../schema-preview-urls-form.component.html | 2 +- .../schema-preview-urls-form.component.ts | 14 +++- .../forms/editors/code-editor.component.scss | 4 + .../shared/services/schemas.service.spec.ts | 18 +++++ .../app/shared/services/schemas.service.ts | 6 ++ frontend/src/app/theme/_bootstrap.scss | 11 +-- 17 files changed, 150 insertions(+), 49 deletions(-) diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs index 12b84afa7..5504b9fcc 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.Designer.cs @@ -60,6 +60,15 @@ namespace Squidex.Domain.Apps.Core { } } + /// + /// Looks up a localized string similar to The access token of the current user.. + /// + public static string AccessToken { + get { + return ResourceManager.GetString("AccessToken", resourceCulture); + } + } + /// /// Looks up a localized string similar to The user or client that triggered the event or command.. /// diff --git a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx index 784f19edb..102999c0b 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx +++ b/backend/src/Squidex.Domain.Apps.Core.Model/FieldDescriptions.resx @@ -117,6 +117,9 @@ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + The access token of the current user. + The user or client that triggered the event or command. diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterExtensions.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterExtensions.cs index b2f4e0a80..860b89254 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterExtensions.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterExtensions.cs @@ -39,7 +39,8 @@ public static class FilterExtensions foreach (var partitionKey in partitioning.AllKeys) { - var partitionDescription = FieldPartitionDescription(field, partitioning.GetName(partitionKey) ?? partitionKey); + var partitionName = partitioning.GetName(partitionKey) ?? partitionKey; + var partitionDescription = BuildPartitionDescription(field, partitionName); var partitionField = new FilterField( fieldSchema, @@ -69,7 +70,7 @@ public static class FilterExtensions return dataSchema; } - private static string FieldPartitionDescription(RootField field, string partition) + private static string BuildPartitionDescription(RootField field, string partition) { var name = field.DisplayName(); diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterVisitor.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterVisitor.cs index f647869bf..da0366af4 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterVisitor.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/GenerateFilters/FilterVisitor.cs @@ -98,6 +98,7 @@ internal sealed class FilterVisitor : IFieldVisitor ExtendAsync(this ScriptExecutionContext context, + internal static ScriptExecutionContext ExtendAsync(this ScriptExecutionContext context, IEnumerable extensions) { foreach (var extension in extensions) @@ -46,7 +46,7 @@ public static class JintExtensions return context; } - internal static ScriptExecutionContext Extend(this ScriptExecutionContext context, + internal static ScriptExecutionContext Extend(this ScriptExecutionContext context, IEnumerable extensions) { foreach (var extension in extensions) @@ -57,7 +57,7 @@ public static class JintExtensions return context; } - internal static ScriptExecutionContext Extend(this ScriptExecutionContext context, + internal static ScriptExecutionContext Extend(this ScriptExecutionContext context, ScriptVars vars, ScriptOptions options) { diff --git a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs index 8119badb4..a462a967e 100644 --- a/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs +++ b/backend/src/Squidex.Domain.Apps.Core.Operations/Scripting/ScriptingCompleter.cs @@ -80,6 +80,13 @@ public sealed class ScriptingCompleter return new Process(descriptors, dataSchema.Flatten()).FieldRule(); } + public IReadOnlyList PreviewUrl(FilterSchema dataSchema) + { + Guard.NotNull(dataSchema); + + return new Process(descriptors, dataSchema.Flatten()).PreviewUrl(); + } + public IReadOnlyList AssetScript() { return new Process(descriptors).AssetScript(); @@ -157,9 +164,7 @@ public sealed class ScriptingCompleter public IReadOnlyList ContentScript() { - var scope = ScriptScope.ContentScript | ScriptScope.Transform | ScriptScope.Async; - - AddHelpers(scope); + AddHelpers(ScriptScope.ContentScript | ScriptScope.Transform | ScriptScope.Async); AddObject("ctx", FieldDescriptions.Context, () => { @@ -171,9 +176,7 @@ public sealed class ScriptingCompleter public IReadOnlyList ContentTrigger() { - var scope = ScriptScope.ContentTrigger | ScriptScope.Async; - - AddHelpers(scope); + AddHelpers(ScriptScope.ContentTrigger | ScriptScope.Async); AddObject("event", FieldDescriptions.Event, () => { @@ -207,6 +210,25 @@ public sealed class ScriptingCompleter return Build(); } + public IReadOnlyList PreviewUrl() + { + AddString("id", + FieldDescriptions.EntityId); + + AddNumber("version", + FieldDescriptions.EntityVersion); + + AddString("accessToken", + FieldDescriptions.AccessToken); + + AddObject("data", FieldDescriptions.ContentData, () => + { + AddData(); + }); + + return Build(); + } + public IReadOnlyList FieldRule() { AddObject("user", FieldDescriptions.User, () => @@ -381,39 +403,38 @@ public sealed class ScriptingCompleter } foreach (var field in dataSchema.Fields) + { + JsonType type = ConvertType(field); + + Add(type, field.Path, field.Description, field.Schema.AllowedValues); + } + + static JsonType ConvertType(FilterField field) { switch (field.Schema.Type) { case FilterSchemaType.Any: - AddAny(field.Path, field.Description); - break; + return JsonType.Any; case FilterSchemaType.Boolean: - AddBoolean(field.Path, field.Description); - break; + return JsonType.Boolean; case FilterSchemaType.DateTime: - AddString(field.Path, field.Description); - break; + return JsonType.String; case FilterSchemaType.GeoObject: - AddObject(field.Path, field.Description); - break; + return JsonType.Object; case FilterSchemaType.Guid: - AddString(field.Path, field.Description); - break; + return JsonType.String; case FilterSchemaType.Number: - AddNumber(field.Path, field.Description); - break; + return JsonType.Number; case FilterSchemaType.Object: - AddObject(field.Path, field.Description); - break; + return JsonType.Object; case FilterSchemaType.ObjectArray: - AddArray(field.Path, field.Description); - break; + return JsonType.Array; case FilterSchemaType.String: - AddString(field.Path, field.Description); - break; + return JsonType.String; case FilterSchemaType.StringArray: - AddArray(field.Path, field.Description); - break; + return JsonType.Array; + default: + return JsonType.String; } } } diff --git a/backend/src/Squidex.Infrastructure/Queries/FilterField.cs b/backend/src/Squidex.Infrastructure/Queries/FilterField.cs index a49bd4bec..d8768d79d 100644 --- a/backend/src/Squidex.Infrastructure/Queries/FilterField.cs +++ b/backend/src/Squidex.Infrastructure/Queries/FilterField.cs @@ -9,5 +9,4 @@ namespace Squidex.Infrastructure.Queries; -public sealed record FilterField(FilterSchema Schema, string Path, string? Description = null, - bool IsNullable = false); +public sealed record FilterField(FilterSchema Schema, string Path, string? Description = null, bool IsNullable = false); diff --git a/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs b/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs index 965b54d80..effd19dac 100644 --- a/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs +++ b/backend/src/Squidex.Infrastructure/Queries/FilterSchema.cs @@ -25,6 +25,8 @@ public sealed record FilterSchema(FilterSchemaType Type) public ReadonlyList? Fields { get; init; } + public string[]? AllowedValues { get; init; } + public object? Extra { get; init; } public FilterSchema Flatten(int maxDepth = 7, Predicate? predicate = null) diff --git a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs index d639f3f3c..7396df0bf 100644 --- a/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs +++ b/backend/src/Squidex/Areas/Api/Controllers/Schemas/SchemasController.cs @@ -313,7 +313,7 @@ public sealed class SchemasController : ApiController [ApiPermissionOrAnonymous] [ApiCosts(1)] [ApiExplorerSettings(IgnoreApi = true)] - public async Task GetScriptCompletion(string app, string schema, + public async Task GetContentScriptsCompletion(string app, string schema, [FromServices] ScriptingCompleter completer) { var completion = completer.ContentScript(await BuildModel()); @@ -326,7 +326,7 @@ public sealed class SchemasController : ApiController [ApiPermissionOrAnonymous] [ApiCosts(1)] [ApiExplorerSettings(IgnoreApi = true)] - public async Task GetScriptTriggerCompletion(string app, string schema, + public async Task GetContentTriggersCompletion(string app, string schema, [FromServices] ScriptingCompleter completer) { var completion = completer.ContentTrigger(await BuildModel()); @@ -339,7 +339,7 @@ public sealed class SchemasController : ApiController [ApiPermissionOrAnonymous] [ApiCosts(1)] [ApiExplorerSettings(IgnoreApi = true)] - public async Task GetScriptFieldRulesCompletion(string app, string schema, + public async Task GetFieldRulesCompletion(string app, string schema, [FromServices] ScriptingCompleter completer) { var completion = completer.FieldRule(await BuildModel()); @@ -347,6 +347,19 @@ public sealed class SchemasController : ApiController return Ok(completion); } + [HttpGet] + [Route("apps/{app}/schemas/{schema}/completion/prview-urls")] + [ApiPermissionOrAnonymous] + [ApiCosts(1)] + [ApiExplorerSettings(IgnoreApi = true)] + public async Task GetPreviewUrlsCompletion(string app, string schema, + [FromServices] ScriptingCompleter completer) + { + var completion = completer.PreviewUrl(await BuildModel()); + + return Ok(completion); + } + [HttpGet] [Route("apps/{app}/schemas/{schema}/filters")] [ApiPermissionOrAnonymous] diff --git a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs index 44f860738..2e39ec23d 100644 --- a/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs +++ b/backend/tests/Squidex.Domain.Apps.Core.Tests/Operations/Scripting/ScriptingCompleterTests.cs @@ -190,7 +190,24 @@ public class ScriptingCompleterTests "user.displayName", "user.email", "user.id", - "user.role", + "user.role" + }); + } + + [Fact] + public void Should_describe_prview_url() + { + var actual = sut.PreviewUrl(dataSchema); + + AssertCompletion(actual, + new[] + { + "accessToken", + "data", + "data['my-field']", + "data['my-field'].iv", + "id", + "version" }); } diff --git a/frontend/src/app/features/content/shared/preview-button.component.ts b/frontend/src/app/features/content/shared/preview-button.component.ts index 2568b0273..7f80234f9 100644 --- a/frontend/src/app/features/content/shared/preview-button.component.ts +++ b/frontend/src/app/features/content/shared/preview-button.component.ts @@ -75,9 +75,7 @@ export class PreviewButtonComponent extends StatefulComponent implements } private navigateTo(name: string) { - const vars = { ...this.content }; - - vars['accessToken'] = this.authService.user?.accessToken; + const vars = { ...this.content, ...this.authService.user || {} }; const url = interpolate(this.schema.previewUrls[name], vars, 'iv'); diff --git a/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html b/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html index af868dd64..1d0cccf51 100644 --- a/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html +++ b/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.html @@ -22,7 +22,7 @@
- +
diff --git a/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts b/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts index a503a6e6d..bd22988ba 100644 --- a/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts +++ b/frontend/src/app/features/schemas/pages/schema/preview/schema-preview-urls-form.component.ts @@ -5,27 +5,35 @@ * Copyright (c) Squidex UG (haftungsbeschränkt). All rights reserved. */ -import { Component, Input } from '@angular/core'; -import { ConfigurePreviewUrlsForm, SchemaDto, SchemasState } from '@app/shared'; +import { Component, Input, OnInit } from '@angular/core'; +import { EMPTY, Observable, shareReplay } from 'rxjs'; +import { ConfigurePreviewUrlsForm, SchemaCompletions, SchemaDto, SchemasService, SchemasState } from '@app/shared'; @Component({ selector: 'sqx-schema-preview-urls-form', styleUrls: ['./schema-preview-urls-form.component.scss'], templateUrl: './schema-preview-urls-form.component.html', }) -export class SchemaPreviewUrlsFormComponent { +export class SchemaPreviewUrlsFormComponent implements OnInit { @Input() public schema!: SchemaDto; public editForm = new ConfigurePreviewUrlsForm(); + public fieldCompletions: Observable = EMPTY; + public isEditable = false; constructor( private readonly schemasState: SchemasState, + private readonly schemasService: SchemasService, ) { } + public ngOnInit() { + this.fieldCompletions = this.schemasService.getFieldRulesCompletion(this.schemasState.appName, this.schema.name).pipe(shareReplay(1)); + } + public ngOnChanges() { this.isEditable = this.schema.canUpdateUrls; diff --git a/frontend/src/app/framework/angular/forms/editors/code-editor.component.scss b/frontend/src/app/framework/angular/forms/editors/code-editor.component.scss index 2f40df19f..df485e537 100644 --- a/frontend/src/app/framework/angular/forms/editors/code-editor.component.scss +++ b/frontend/src/app/framework/angular/forms/editors/code-editor.component.scss @@ -33,6 +33,10 @@ background: $color-border !important; } + .ace_bracket { + display: none; + } + .ace_scroller { box-shadow: none !important; } diff --git a/frontend/src/app/shared/services/schemas.service.spec.ts b/frontend/src/app/shared/services/schemas.service.spec.ts index 16ad0bb0b..98eecc9bb 100644 --- a/frontend/src/app/shared/services/schemas.service.spec.ts +++ b/frontend/src/app/shared/services/schemas.service.spec.ts @@ -629,6 +629,24 @@ describe('SchemasService', () => { expect(completions!).toEqual([]); })); + it('should make get request to get preview urls completions', + inject([SchemasService, HttpTestingController], (schemasService: SchemasService, httpMock: HttpTestingController) => { + let completions: SchemaCompletions; + + schemasService.getPreviewUrlsCompletion('my-app', 'my-schema').subscribe(result => { + completions = result; + }); + + const req = httpMock.expectOne('http://service/p/api/apps/my-app/schemas/my-schema/completion/preview-urls'); + + expect(req.request.method).toEqual('GET'); + expect(req.request.headers.get('If-Match')).toBeNull(); + + req.flush([]); + + expect(completions!).toEqual([]); + })); + function schemaPropertiesResponse(id: number, suffix = '') { const key = `${id}${suffix}`; diff --git a/frontend/src/app/shared/services/schemas.service.ts b/frontend/src/app/shared/services/schemas.service.ts index 049f972cf..e4917a7bf 100644 --- a/frontend/src/app/shared/services/schemas.service.ts +++ b/frontend/src/app/shared/services/schemas.service.ts @@ -764,6 +764,12 @@ export class SchemasService { return this.http.get(url); } + public getPreviewUrlsCompletion(appName: string, schemaName: string): Observable { + const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/completion/preview-urls`); + + return this.http.get(url); + } + public getFilters(appName: string, schemaName: string): Observable { const url = this.apiUrl.buildUrl(`api/apps/${appName}/schemas/${schemaName}/filters`); diff --git a/frontend/src/app/theme/_bootstrap.scss b/frontend/src/app/theme/_bootstrap.scss index 3e4d37134..a86ab2776 100644 --- a/frontend/src/app/theme/_bootstrap.scss +++ b/frontend/src/app/theme/_bootstrap.scss @@ -394,11 +394,10 @@ a { } &.active { - & { - background: none; - border-color: $color-theme-brand; - color: $color-theme-brand; - } + background: none; + border-color: $color-theme-brand; + border-radius: $border-radius; + color: $color-theme-brand; i { color: $color-theme-brand; @@ -415,6 +414,8 @@ a { &:hover { color: $color-theme-brand; + border-color: $color-border; + border-radius: $border-radius; i { color: $color-theme-brand;