diff --git a/.gitignore b/.gitignore index 940794e6..a52a7183 100644 --- a/.gitignore +++ b/.gitignore @@ -46,7 +46,6 @@ dlldata.c project.lock.json project.fragment.lock.json artifacts/ -**/Properties/launchSettings.json *_i.c *_p.c diff --git a/src/SeqCli/Cli/Features/OutputFormatFeature.cs b/src/SeqCli/Cli/Features/OutputFormatFeature.cs index 8ae28432..f7ee857d 100644 --- a/src/SeqCli/Cli/Features/OutputFormatFeature.cs +++ b/src/SeqCli/Cli/Features/OutputFormatFeature.cs @@ -134,7 +134,7 @@ public void WriteEntity(Entity entity) else { var dyn = (dynamic) jo; - Console.WriteLine($"{entity.Id} {dyn.Title ?? dyn.Name ?? dyn.Username}"); + Console.WriteLine($"{entity.Id} {dyn.Title ?? dyn.Name ?? dyn.Username ?? dyn.Expression}"); } } diff --git a/src/SeqCli/Templates/Import/GenericEntity.cs b/src/SeqCli/Templates/Import/GenericEntity.cs index d86a28f0..48838362 100644 --- a/src/SeqCli/Templates/Import/GenericEntity.cs +++ b/src/SeqCli/Templates/Import/GenericEntity.cs @@ -21,4 +21,5 @@ class GenericEntity : Entity { public string? Title { get; set; } public string? Name { get; set; } + public string? Expression { get; set; } } \ No newline at end of file diff --git a/src/SeqCli/Templates/Import/TemplateSetImporter.cs b/src/SeqCli/Templates/Import/TemplateSetImporter.cs index 91b76add..fe2c3ed4 100644 --- a/src/SeqCli/Templates/Import/TemplateSetImporter.cs +++ b/src/SeqCli/Templates/Import/TemplateSetImporter.cs @@ -75,21 +75,40 @@ static class TemplateSetImporter var resourceGroupLink = template.ResourceGroup + "Resources"; var link = apiRoot.Links.Single(l => resourceGroupLink.Equals(l.Key, StringComparison.OrdinalIgnoreCase)); var resourceGroup = await connection.Client.GetAsync(apiRoot, link.Key); + + // ExpressionIndexes with mapped ids or identical expressions are assumed to be equivalent. + var immutableTarget = template.ResourceGroup != "ExpressionIndexes"; if (state.TryGetCreatedEntityId(template.Name, out var existingId) && await CheckEntityExistenceAsync(connection, resourceGroup, existingId)) { asObject["Id"] = existingId; - await UpdateEntityAsync(connection, resourceGroup, asObject, existingId); - Log.Information("Updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + if (immutableTarget) + { + Log.Information("No work required for existing immutable entity {EntityId} from {TemplateName}", existingId, template.Name); + } + else + { + await UpdateEntityAsync(connection, resourceGroup, asObject, existingId); + Log.Information("Updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + } } else if (merge && !state.TryGetCreatedEntityId(template.Name, out _) && await TryFindMergeTargetAsync(connection, resourceGroup, asObject) is { } mergedId) { asObject["Id"] = mergedId; - await UpdateEntityAsync(connection, resourceGroup, asObject, mergedId); - state.AddOrUpdateCreatedEntityId(template.Name, mergedId); - Log.Information("Merged and updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + + if (immutableTarget) + { + Log.Information("Adding merge entry for existing immutable entity {EntityId} from {TemplateName}", existingId, template.Name); + state.AddOrUpdateCreatedEntityId(template.Name, mergedId); + } + else + { + await UpdateEntityAsync(connection, resourceGroup, asObject, mergedId); + state.AddOrUpdateCreatedEntityId(template.Name, mergedId); + Log.Information("Merged and updated existing entity {EntityId} from {TemplateName}", existingId, template.Name); + } } else { @@ -104,20 +123,24 @@ await TryFindMergeTargetAsync(connection, resourceGroup, asObject) is { } merged static async Task TryFindMergeTargetAsync(SeqConnection connection, ResourceGroup resourceGroup, IDictionary entity) { if (!entity.TryGetValue("Title", out var nameOrTitleValue) && - !entity.TryGetValue("Name", out nameOrTitleValue) || + !entity.TryGetValue("Name", out nameOrTitleValue) && + !entity.TryGetValue("Expression", out nameOrTitleValue)|| nameOrTitleValue is not string nameOrTitle) { return null; } - // O(Ntemplates*Nentities) - easy target for optimization with some caching. - var candidates = await connection.Client.GetAsync>(resourceGroup, "Items", - new Dictionary + var parameters = resourceGroup.Links["Items"].Template.Contains("shared") + ? new Dictionary { ["shared"] = true - }); + } + : null; + + // O(Ntemplates*Nentities) - easy target for optimization with some caching. + var candidates = await connection.Client.GetAsync>(resourceGroup, "Items", parameters); - return candidates.FirstOrDefault(e => e.Title == nameOrTitle || e.Name == nameOrTitle)?.Id; + return candidates.FirstOrDefault(e => e.Title == nameOrTitle || e.Name == nameOrTitle || e.Expression == nameOrTitle)?.Id; } static async Task CreateEntityAsync(SeqConnection connection, ResourceGroup resourceGroup, object entity) diff --git a/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs index 84b82f03..f87608b8 100644 --- a/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs +++ b/test/SeqCli.EndToEnd/Indexes/IndexesTestCase.cs @@ -17,10 +17,10 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm Assert.Equal(0, exit); var expressionIndex = (await connection.ExpressionIndexes.ListAsync()).Single(e => e.Expression == expr); - var signal = (await connection.Signals.ListAsync()).First(s => !s.IsIndexSuppressed); + var signal = (await connection.Signals.ListAsync(shared: true)).First(s => !s.IsIndexSuppressed); var indexForSignal = (await connection.Indexes.ListAsync()).First(i => i.IndexedEntityId == signal.Id); - exit = runner.Exec("index list"); + exit = runner.Exec("index list --json"); Assert.Equal(0, exit); Assert.Contains(expressionIndex.Id, runner.LastRunProcess!.Output); Assert.Contains(signal.Id, runner.LastRunProcess!.Output); diff --git a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs index 1286d37c..1b5dc311 100644 --- a/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs +++ b/test/SeqCli.EndToEnd/Templates/TemplateExportImportTestCase.cs @@ -43,7 +43,7 @@ public async Task ExecuteAsync(SeqConnection connection, ILogger logger, CliComm await File.WriteAllTextAsync(exportedFilename, content); - exit = runner.Exec("template import", $"-i \"{_testDataFolder.Path}\""); + exit = runner.Exec("template import", $"-i \"{_testDataFolder.Path}\" --merge"); Assert.Equal(0, exit); var created = Assert.Single(await connection.Signals.ListAsync(shared: true), s => s.Title == newTitle);