Browse Source

Fixes2 (#1251)

* Support radio button in search.

* Backup fixes

* Fix tests
pull/1254/head
Sebastian Stehle 9 months ago
committed by GitHub
parent
commit
53ddb1594c
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 75
      backend/extensions/Squidex.Extensions/Actions/RuleEventMigrator.cs
  2. 15
      backend/extensions/Squidex.Extensions/Actions/RuleEventPlugin.cs
  3. 78
      backend/src/Migrations/Migrations/OldRuleEventMigrator.cs
  4. 14
      backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs
  5. 5
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandler.cs
  6. 1
      backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupReader.cs
  7. 13
      backend/src/Squidex.Domain.Apps.Entities/Backup/IEventMigrator.cs
  8. 12
      backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs
  9. 10
      backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs
  10. 1
      backend/src/Squidex.Infrastructure/Plugins/PluginLoaders.cs
  11. 16
      backend/src/Squidex.Infrastructure/Reflection/DelegateTypeProvider.cs
  12. 6
      backend/src/Squidex/Config/Domain/BackupsServices.cs
  13. 24
      backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs
  14. 45
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs
  15. 172
      backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEventMigratorTests.cs
  16. 7
      frontend/src/app/shared/components/search/search-form.component.ts

75
backend/extensions/Squidex.Extensions/Actions/RuleEventMigrator.cs

@ -0,0 +1,75 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Extensions.Actions.Script;
using Squidex.Flows;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Extensions.Actions;
public sealed class RuleEventMigrator(TypeRegistry typeRegistry, IJsonSerializer serializer) : IEventMigrator
{
private readonly string[] migratedEvents =
[
typeRegistry.GetName<IEvent>(typeof(RuleCreated)),
typeRegistry.GetName<IEvent>(typeof(RuleUpdated)),
];
public string? MigrateEvent(string type, string json)
{
if (!migratedEvents.Contains(type))
{
return null;
}
var parsed = serializer.Deserialize<JsonValue>(json);
var isChanged = false;
void Handle(JsonValue value)
{
if (value.Type == JsonValueType.Object)
{
var obj = value.AsObject;
foreach (var (key, nested) in obj)
{
if (key == "$type" && nested.Value is string flowStepType)
{
if (!typeRegistry.TryGetType<FlowStep>(flowStepType, out _))
{
isChanged = true;
obj["$type"] = typeRegistry.GetName<FlowStep, ScriptFlowStep>();
}
}
else
{
Handle(nested);
}
}
}
else if (value.Type == JsonValueType.Array)
{
foreach (var item in value.AsArray)
{
Handle(item);
}
}
}
Handle(parsed);
if (!isChanged)
{
return null;
}
return serializer.Serialize(parsed);
}
}

15
backend/extensions/Squidex.Extensions/Actions/RuleEventPlugin.cs

@ -0,0 +1,15 @@
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Infrastructure.Plugins;
namespace Squidex.Extensions.Actions;
public sealed class RuleEventPlugin : IPlugin
{
public void ConfigureServices(IServiceCollection services, IConfiguration config)
{
services.AddTransientAs<RuleEventMigrator>()
.As<IEventMigrator>();
}
}

78
backend/src/Migrations/Migrations/OldRuleEventMigrator.cs

@ -0,0 +1,78 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Migrations.OldEvents;
using Squidex.Domain.Apps.Core.Rules.Deprecated;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Json.Objects;
using Squidex.Infrastructure.Reflection;
namespace Migrations.Migrations;
public sealed class OldRuleEventMigrator(TypeRegistry typeRegistry, IJsonSerializer serializer) : IEventMigrator
{
#pragma warning disable CS0612 // Type or member is obsolete
#pragma warning disable CS0618 // Type or member is obsolete
private readonly string[] migratedEvents =
[
typeRegistry.GetName<IEvent>(typeof(RuleCreated)),
typeRegistry.GetName<IEvent>(typeof(RuleUpdated)),
];
public string? MigrateEvent(string type, string json)
{
if (!migratedEvents.Contains(type))
{
return null;
}
var parsed = serializer.Deserialize<JsonValue>(json);
var isChanged = false;
void Handle(JsonValue value)
{
if (value.Type == JsonValueType.Object)
{
var obj = value.AsObject;
foreach (var (key, nested) in obj)
{
if (key == "actionType" && nested.Value is string flowStepType)
{
if (!typeRegistry.TryGetType<RuleAction>(flowStepType, out _))
{
isChanged = true;
obj["actionType"] = "Webhook";
}
}
else
{
Handle(nested);
}
}
}
else if (value.Type == JsonValueType.Array)
{
foreach (var item in value.AsArray)
{
Handle(item);
}
}
}
Handle(parsed);
if (!isChanged)
{
return null;
}
return serializer.Serialize(parsed);
}
#pragma warning restore CS0612 // Type or member is obsolete
#pragma warning restore CS0618 // Type or member is obsolete
}

14
backend/src/Squidex.Domain.Apps.Entities/Backup/BackupReader.cs

@ -100,6 +100,7 @@ public sealed class BackupReader : DisposableObjectBase, IBackupReader
public async IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(
IEventStreamNames eventStreamNames,
IEventFormatter eventFormatter,
IEnumerable<IEventMigrator> eventMigrators,
[EnumeratorCancellation] CancellationToken ct = default)
{
Guard.NotNull(eventFormatter);
@ -118,6 +119,19 @@ public sealed class BackupReader : DisposableObjectBase, IBackupReader
{
var storedEvent = serializer.Deserialize<CompatibleStoredEvent>(stream).ToStoredEvent();
foreach (var migrator in eventMigrators)
{
var migrated = migrator.MigrateEvent(storedEvent.Data.Type, storedEvent.Data.Payload);
if (migrated != null)
{
storedEvent = storedEvent with
{
Data = storedEvent.Data with { Payload = migrated },
};
break;
}
}
var eventStream = storedEvent.StreamName;
var eventEnvelope = eventFormatter.Parse(storedEvent);

5
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupHandler.cs

@ -16,6 +16,11 @@ public interface IBackupHandler
int Order => 0;
public string ProcessEvent(string type, string json)
{
return json;
}
public Task<bool> RestoreEventAsync(Envelope<IEvent> @event, RestoreContext context,
CancellationToken ct)
{

1
backend/src/Squidex.Domain.Apps.Entities/Backup/IBackupReader.cs

@ -28,5 +28,6 @@ public interface IBackupReader : IDisposable
IAsyncEnumerable<(string Stream, Envelope<IEvent> Event)> ReadEventsAsync(
IEventStreamNames eventStreamNames,
IEventFormatter eventFormatter,
IEnumerable<IEventMigrator> eventMigrators,
CancellationToken ct = default);
}

13
backend/src/Squidex.Domain.Apps.Entities/Backup/IEventMigrator.cs

@ -0,0 +1,13 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Domain.Apps.Entities.Backup;
public interface IEventMigrator
{
string? MigrateEvent(string type, string json);
}

12
backend/src/Squidex.Domain.Apps.Entities/Backup/RestoreJob.cs

@ -31,6 +31,7 @@ public sealed class RestoreJob(
IEventFormatter eventFormatter,
IEventStore eventStore,
IEventStreamNames eventStreamNames,
IEnumerable<IEventMigrator> eventMigrators,
IUserResolver userResolver,
ILogger<RestoreJob> log)
: IJobRunner
@ -171,6 +172,10 @@ public sealed class RestoreJob(
log.LogError(ex, "Backup with job id {backupId} from URL '{url}' failed.", context.Job.Id, state.Url);
throw;
}
finally
{
state.Reader?.Dispose();
}
}
private async Task AssignContributorAsync(JobRunContext run, State state)
@ -275,11 +280,6 @@ public sealed class RestoreJob(
activity?.SetTag("totalCommits", commits.Count);
activity?.SetTag("totalEvents", commits.Sum(x => x.Events.Count));
if (commits.Any(x => x.StreamName.Contains("46b2fb05-3438-4b99-8c1d-bac8925a33dd")))
{
Debugger.Break();
}
await eventStore.AppendUnsafeAsync(commits, ct);
}
@ -293,7 +293,7 @@ public sealed class RestoreJob(
private async IAsyncEnumerable<(string Stream, long Offset, Envelope<IEvent> Event)> HandleEventsAsync(JobRunContext run, State state,
[EnumeratorCancellation] CancellationToken ct)
{
var @events = state.Reader.ReadEventsAsync(eventStreamNames, eventFormatter, ct);
var @events = state.Reader.ReadEventsAsync(eventStreamNames, eventFormatter, eventMigrators, ct);
await foreach (var (stream, @event) in events.WithCancellation(ct))
{

10
backend/src/Squidex.Infrastructure/Json/System/PolymorphicConverter.cs

@ -10,8 +10,6 @@ using System.Text.Json.Serialization;
using System.Text.Json.Serialization.Metadata;
using Squidex.Infrastructure.Reflection;
#pragma warning disable MA0084 // Local variables should not hide other symbols
namespace Squidex.Infrastructure.Json.System;
public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
@ -40,15 +38,15 @@ public sealed class PolymorphicConverter<T> : JsonConverter<T> where T : class
{
if (typeRegistry.TryGetConfig(baseType, out var config) && config.TryGetName(typeInfo.Type, out var typeName))
{
var discriminatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty;
var discriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), discriminatorName);
var typeDiscriminatorName = config.DiscriminatorProperty ?? Constants.DefaultDiscriminatorProperty;
var typeDiscriminatorField = typeInfo.CreateJsonPropertyInfo(typeof(string), typeDiscriminatorName);
discriminatorField.Get = x =>
typeDiscriminatorField.Get = x =>
{
return typeName;
};
typeInfo.Properties.Insert(0, discriminatorField);
typeInfo.Properties.Insert(0, typeDiscriminatorField);
}
baseType = baseType.BaseType;

1
backend/src/Squidex.Infrastructure/Plugins/PluginLoaders.cs

@ -23,7 +23,6 @@ public static class PluginLoaders
return PluginLoader.CreateFromAssemblyFile(candidate.FullName, config =>
{
config.PreferSharedTypes = true;
config.SharedAssemblies.AddRange(sharedAssemblies);
});
}

16
backend/src/Squidex.Infrastructure/Reflection/DelegateTypeProvider.cs

@ -0,0 +1,16 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
namespace Squidex.Infrastructure.Reflection;
public sealed class DelegateTypeProvider(Action<TypeRegistry> action) : ITypeProvider
{
public void Map(TypeRegistry typeRegistry)
{
action(typeRegistry);
}
}

6
backend/src/Squidex/Config/Domain/BackupsServices.cs

@ -5,12 +5,15 @@
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Migrations.Migrations;
using Squidex.Domain.Apps.Entities.Apps;
using Squidex.Domain.Apps.Entities.Assets;
using Squidex.Domain.Apps.Entities.Backup;
using Squidex.Domain.Apps.Entities.Contents;
using Squidex.Domain.Apps.Entities.Rules;
using Squidex.Domain.Apps.Entities.Schemas;
using Squidex.Flows;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Config.Domain;
@ -49,5 +52,8 @@ public static class BackupsServices
services.AddTransientAs<RestoreJob>()
.AsSelf();
services.AddTransientAs<OldRuleEventMigrator>()
.As<IEventMigrator>();
}
}

24
backend/tests/Squidex.Domain.Apps.Core.Tests/TestHelpers/TestUtils.cs

@ -73,12 +73,22 @@ public static class TestUtils
public static IJsonSerializer CreateSerializer(Action<JsonSerializerOptions>? configure = null)
{
var serializerSettings = DefaultOptions(configure);
return CreateSerializer(TypeRegistry, configure);
}
public static IJsonSerializer CreateSerializer(TypeRegistry typeRegistry, Action<JsonSerializerOptions>? configure = null)
{
var serializerSettings = DefaultOptions(typeRegistry, configure);
return new SystemJsonSerializer(serializerSettings);
}
public static JsonSerializerOptions DefaultOptions(Action<JsonSerializerOptions>? configure = null)
{
return DefaultOptions(TypeRegistry, configure);
}
public static JsonSerializerOptions DefaultOptions(TypeRegistry typeRegistry, Action<JsonSerializerOptions>? configure = null)
{
var options = new JsonSerializerOptions(JsonSerializerDefaults.Web);
@ -89,10 +99,10 @@ public static class TestUtils
options.Converters.Add(new GeoJsonConverterFactory());
options.Converters.Add(new HeaderValueConverter());
options.Converters.Add(new JsonValueConverter());
options.Converters.Add(new PolymorphicConverter<FieldProperties>(TypeRegistry));
options.Converters.Add(new PolymorphicConverter<FlowStep>(TypeRegistry));
options.Converters.Add(new PolymorphicConverter<IEvent>(TypeRegistry));
options.Converters.Add(new PolymorphicConverter<RuleTrigger>(TypeRegistry));
options.Converters.Add(new PolymorphicConverter<FieldProperties>(typeRegistry));
options.Converters.Add(new PolymorphicConverter<FlowStep>(typeRegistry));
options.Converters.Add(new PolymorphicConverter<IEvent>(typeRegistry));
options.Converters.Add(new PolymorphicConverter<RuleTrigger>(typeRegistry));
options.Converters.Add(new ReadonlyDictionaryConverterFactory());
options.Converters.Add(new ReadonlyListConverterFactory());
options.Converters.Add(new StringConverter<CompareOperator>());
@ -117,11 +127,11 @@ public static class TestUtils
options.Converters.Add(new JsonStringEnumConverter());
options.IncludeFields = true;
options.TypeInfoResolver = new DefaultJsonTypeInfoResolver()
.WithAddedModifier(PolymorphicConverter<None>.Modifier(TypeRegistry))
.WithAddedModifier(PolymorphicConverter<None>.Modifier(typeRegistry))
.WithAddedModifier(JsonIgnoreReadonlyProperties.Modifier<Entity>())
.WithAddedModifier(JsonRenameAttribute.Modifier);
#pragma warning disable CS0618 // Type or member is obsolete
options.Converters.Add(new PolymorphicConverter<RuleAction>(TypeRegistry));
options.Converters.Add(new PolymorphicConverter<RuleAction>(typeRegistry));
#pragma warning restore CS0618 // Type or member is obsolete
configure?.Invoke(options);

45
backend/tests/Squidex.Domain.Apps.Entities.Tests/Backup/BackupReaderWriterTests.cs

@ -20,6 +20,7 @@ public class BackupReaderWriterTests
{
private readonly IEventFormatter eventFormatter;
private readonly IEventStreamNames eventStreamNames = A.Fake<IEventStreamNames>();
private readonly IEventMigrator eventMigrator = A.Fake<IEventMigrator>();
private readonly IJsonSerializer serializer = TestUtils.DefaultSerializer;
private readonly TypeRegistry typeRegistry = new TypeRegistry();
@ -34,6 +35,9 @@ public class BackupReaderWriterTests
typeRegistry.Add<IEvent, MyEvent>("MyEvent");
eventFormatter = new DefaultEventFormatter(typeRegistry, serializer);
A.CallTo(() => eventMigrator.MigrateEvent(A<string>._, A<string>._))
.Returns(null);
}
[Fact]
@ -63,16 +67,15 @@ public class BackupReaderWriterTests
[Fact]
public async Task Should_return_true_if_file_exists()
{
var file = "File.json";
var value = Guid.NewGuid();
var fileName = "File.json";
var fileData = Guid.NewGuid();
await TestReaderWriterAsync(BackupVersion.V1, async writer =>
{
await WriteJsonGuidAsync(writer, file, value);
await WriteJsonGuidAsync(writer, fileName, fileData);
}, async reader =>
{
var hasFile = await reader.HasFileAsync(file);
var hasFile = await reader.HasFileAsync(fileName);
Assert.True(hasFile);
});
@ -81,16 +84,15 @@ public class BackupReaderWriterTests
[Fact]
public async Task Should_return_file_if_file_does_not_exist()
{
var file = "File.json";
var value = Guid.NewGuid();
var fileName = "File.json";
var fileData = Guid.NewGuid();
await TestReaderWriterAsync(BackupVersion.V1, async writer =>
{
await Task.Yield();
}, async reader =>
{
var hasFile = await reader.HasFileAsync(file);
var hasFile = await reader.HasFileAsync(fileName);
Assert.False(hasFile);
});
@ -99,36 +101,34 @@ public class BackupReaderWriterTests
[Fact]
public async Task Should_read_and_write_json_async()
{
var file = "File.json";
var value = Guid.NewGuid();
var fileName = "File.json";
var fileData = Guid.NewGuid();
await TestReaderWriterAsync(BackupVersion.V1, async writer =>
{
await WriteJsonGuidAsync(writer, file, value);
await WriteJsonGuidAsync(writer, fileName, fileData);
}, async reader =>
{
var read = await ReadJsonGuidAsync(reader, file);
var read = await ReadJsonGuidAsync(reader, fileName);
Assert.Equal(value, read);
Assert.Equal(fileData, read);
});
}
[Fact]
public async Task Should_read_and_write_blob_async()
{
var file = "File.json";
var value = Guid.NewGuid();
var fileName = "File.json";
var fileData = Guid.NewGuid();
await TestReaderWriterAsync(BackupVersion.V1, async writer =>
{
await WriteGuidAsync(writer, file, value);
await WriteGuidAsync(writer, fileName, fileData);
}, async reader =>
{
var read = await ReadGuidAsync(reader, file);
var read = await ReadGuidAsync(reader, fileName);
Assert.Equal(value, read);
Assert.Equal(fileData, read);
});
}
@ -178,7 +178,6 @@ public class BackupReaderWriterTests
for (var i = 0; i < 200; i++)
{
var @event = new MyEvent();
var envelope = Envelope.Create(@event);
envelope.Headers.Add("Id", @event.Id.ToString());
@ -211,7 +210,7 @@ public class BackupReaderWriterTests
{
var targetEvents = new List<(string Stream, Envelope<IEvent> Event)>();
await foreach (var @event in reader.ReadEventsAsync(eventStreamNames, eventFormatter))
await foreach (var @event in reader.ReadEventsAsync(eventStreamNames, eventFormatter, [eventMigrator]))
{
var index = int.Parse(@event.Event.Headers["Index"].ToString(), NumberStyles.Integer, CultureInfo.InvariantCulture);

172
backend/tests/Squidex.Domain.Apps.Entities.Tests/Rules/RuleEventMigratorTests.cs

@ -0,0 +1,172 @@
// ==========================================================================
// Squidex Headless CMS
// ==========================================================================
// Copyright (c) Squidex UG (haftungsbeschraenkt)
// All rights reserved. Licensed under the MIT license.
// ==========================================================================
using Squidex.Domain.Apps.Core.TestHelpers;
using Squidex.Domain.Apps.Events.Rules;
using Squidex.Extensions.Actions;
using Squidex.Extensions.Actions.Script;
using Squidex.Extensions.Actions.Webhook;
using Squidex.Flows;
using Squidex.Flows.Internal;
using Squidex.Infrastructure.EventSourcing;
using Squidex.Infrastructure.Json;
using Squidex.Infrastructure.Reflection;
namespace Squidex.Domain.Apps.Entities.Rules;
public class RuleEventMigratorTests
{
private readonly TypeRegistry typeRegistrySerializer = new TypeRegistry();
private readonly TypeRegistry typeRegistryMigrator = new TypeRegistry();
private readonly IJsonSerializer serializer;
private readonly RuleEventMigrator sut;
public RuleEventMigratorTests()
{
typeRegistryMigrator.Add<IEvent, RuleCreated>("RuleCreated");
typeRegistryMigrator.Add<IEvent, RuleUpdated>("RuleUpdated");
typeRegistryMigrator.Add<FlowStep, ScriptFlowStep>("Script");
typeRegistrySerializer.Add<FlowStep, ScriptFlowStep>("Script");
typeRegistrySerializer.Add<FlowStep, WebhookFlowStep>("Webhook");
// Create the serializer after the types have been registered.
serializer = TestUtils.CreateSerializer(typeRegistrySerializer);
sut = new RuleEventMigrator(typeRegistryMigrator, serializer);
}
[Fact]
public void Should_not_migrate_rule_created_event_if_flow_step_is_known()
{
typeRegistryMigrator.Add<FlowStep, WebhookFlowStep>("Webhook");
var @event = new RuleCreated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new WebhookFlowStep(),
},
},
},
};
var json = serializer.Serialize(@event, true);
var result = sut.MigrateEvent(typeRegistryMigrator.GetName<IEvent>(@event.GetType()), json);
Assert.Null(result);
}
[Fact]
public void Should_migrate_rule_created_event()
{
var @event = new RuleCreated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new WebhookFlowStep(),
},
},
},
};
var json = serializer.Serialize(@event, true);
var resultJson = sut.MigrateEvent(typeRegistryMigrator.GetName<IEvent>(@event.GetType()), json);
var resultEvent = serializer.Deserialize<RuleCreated>(resultJson!);
resultEvent.Should().BeEquivalentTo(
new RuleCreated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new WebhookFlowStep(),
},
},
},
},
options => options.RespectingDeclaredTypes());
}
[Fact]
public void Should_not_migrate_rule_updated_event_if_flow_step_is_known()
{
typeRegistryMigrator.Add<FlowStep, WebhookFlowStep>("Webhook");
var @event = new RuleUpdated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new WebhookFlowStep(),
},
},
},
};
var json = serializer.Serialize(@event, true);
var result = sut.MigrateEvent(typeRegistryMigrator.GetName<IEvent>(@event.GetType()), json);
Assert.Null(result);
}
[Fact]
public void Should_migrate_rule_updated_event()
{
var @event = new RuleUpdated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new WebhookFlowStep(),
},
},
},
};
var json = serializer.Serialize(@event, true);
var resultJson = sut.MigrateEvent(typeRegistryMigrator.GetName<IEvent>(@event.GetType()), json);
var resultEvent = serializer.Deserialize<RuleCreated>(resultJson!);
resultEvent.Should().BeEquivalentTo(
new RuleUpdated
{
Flow = new FlowDefinition
{
Steps = new Dictionary<Guid, FlowStepDefinition>
{
[Guid.Empty] = new FlowStepDefinition
{
Step = new ScriptFlowStep(),
},
},
},
},
options => options.RespectingDeclaredTypes());
}
}

7
frontend/src/app/shared/components/search/search-form.component.ts

@ -8,9 +8,9 @@
import { AsyncPipe } from '@angular/common';
import { booleanAttribute, ChangeDetectionStrategy, Component, EventEmitter, Input, Output, Type } from '@angular/core';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import { BooleanValue, BootstrapClasses, EMPTY_FILTER_MODEL, FieldComponent, FilterField, Input as FilterInput, FilterModel, FilterOptions, NumberValue, StringValue } from 'ngx-inline-filter';
import { BooleanValue, BootstrapClasses, EMPTY_FILTER_MODEL, FieldComponent, FilterField, Input as FilterInput, FilterModel, FilterOptions, NumberValue, SelectValue, StringValue } from 'ngx-inline-filter';
import { Observable } from 'rxjs';
import { ControlErrorsComponent, DateTimeEditorComponent, DropdownComponent, FocusOnInitDirective, HighlightPipe, LocalizerService, ModalDialogComponent, ModalDirective, ShortcutComponent, TooltipDirective, TourStepDirective, TranslatePipe } from '@app/framework';
import { ControlErrorsComponent, DateTimeEditorComponent, DropdownComponent, FocusOnInitDirective, HighlightPipe, LocalizerService, ModalDialogComponent, ModalDirective, ShortcutComponent, TooltipDirective, TourStepDirective, TranslatePipe, Types } from '@app/framework';
import { AppLanguageDto, ContributorsState, DialogModel, Queries, Query, QueryModel, sanitize, SaveQueryForm, TypedSimpleChanges } from '@app/shared/internal';
import { UserDtoPicture } from '../pipes';
import { ReferenceInputComponent } from '../references/reference-input.component';
@ -170,6 +170,9 @@ export class SearchFormComponent {
args = { editor: 'Status', statuses: model.statuses };
} else if (type === 'String' && extra?.editor === 'User') {
args = { editor: 'User' };
} else if (type === 'String' && Types.isArrayOfString(extra?.options)) {
args = (extra.options as string[]).map(value => ({ value, label: value }));
component = SelectValue;
} else if (type === 'String' && !extra) {
component = StringValue;
} else if (type === 'StringArray' && extra?.schemaIds) {

Loading…
Cancel
Save