diff --git a/EA-ModelKit.Tests/Converters/BooleanToVisibilityConverterTestFixture.cs b/EA-ModelKit.Tests/Converters/BooleanToVisibilityConverterTestFixture.cs new file mode 100644 index 0000000..12224d6 --- /dev/null +++ b/EA-ModelKit.Tests/Converters/BooleanToVisibilityConverterTestFixture.cs @@ -0,0 +1,48 @@ +// ----------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Nederland B.V. +// +// Authors: Alex Vorobiev, Antoine Théate, Sam Gerené, Anh-Toan Bui Long +// +// This file is part of ESA SysML plugin for Enterprise Architect +// European Space Agency Community License – v2.4 Permissive (Type 3) +// See LICENSE file for details +// +// +// ----------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Converters +{ + using System.Globalization; + using System.Windows; + + using EAModelKit.Converters; + + using NUnit.Framework; + + [TestFixture] + public class BooleanToVisibilityConverterTestFixture + { + private BooleanToVisibilityConverter converter; + + [SetUp] + public void Setup() + { + this.converter = new BooleanToVisibilityConverter(); + } + + [Test] + public void VerifyConvert() + { + Assert.Multiple(() => + { + Assert.That(this.converter.Convert(null, typeof(Visibility), "", CultureInfo.InvariantCulture), Is.EqualTo(Visibility.Collapsed)); + Assert.That(this.converter.Convert(true, typeof(Visibility), "", CultureInfo.InvariantCulture), Is.EqualTo(Visibility.Visible)); + Assert.That(this.converter.Convert(false, typeof(Visibility), "", CultureInfo.InvariantCulture), Is.EqualTo(Visibility.Collapsed)); + Assert.That(this.converter.Convert(false, typeof(Visibility), "Invert", CultureInfo.InvariantCulture), Is.EqualTo(Visibility.Visible)); + Assert.That(this.converter.Convert(true, typeof(Visibility), "Invert", CultureInfo.InvariantCulture), Is.EqualTo(Visibility.Collapsed)); + Assert.That(() => this.converter.ConvertBack(Visibility.Collapsed, typeof(bool), "", CultureInfo.InvariantCulture), Throws.Exception); + }); + } + } +} diff --git a/EA-ModelKit.Tests/Converters/StringCollectionConverterTestFixture.cs b/EA-ModelKit.Tests/Converters/StringCollectionConverterTestFixture.cs new file mode 100644 index 0000000..5b52bc0 --- /dev/null +++ b/EA-ModelKit.Tests/Converters/StringCollectionConverterTestFixture.cs @@ -0,0 +1,45 @@ +// ----------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Nederland B.V. +// +// Authors: Alex Vorobiev, Antoine Théate, Sam Gerené, Anh-Toan Bui Long +// +// This file is part of ESA SysML plugin for Enterprise Architect +// European Space Agency Community License – v2.4 Permissive (Type 3) +// See LICENSE file for details +// +// +// ----------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Converters +{ + using System.Globalization; + + using EAModelKit.Converters; + + using NUnit.Framework; + + [TestFixture] + public class StringCollectionConverterTestFixture + { + private StringCollectionConverter converter; + + [SetUp] + public void Setup() + { + this.converter = new StringCollectionConverter(); + } + + [Test] + public void VerifyConvert() + { + Assert.That(this.converter.Convert(null, typeof(IEnumerable), null, CultureInfo.InvariantCulture), Is.EquivalentTo(new List())); + } + + [Test] + public void VerifyConvertBack() + { + Assert.That(this.converter.ConvertBack(null, typeof(IEnumerable), null, CultureInfo.InvariantCulture), Is.Empty); + } + } +} diff --git a/EA-ModelKit.Tests/EA-ModelKit.Tests.csproj b/EA-ModelKit.Tests/EA-ModelKit.Tests.csproj index 60c981d..0c07b84 100644 --- a/EA-ModelKit.Tests/EA-ModelKit.Tests.csproj +++ b/EA-ModelKit.Tests/EA-ModelKit.Tests.csproj @@ -30,6 +30,7 @@ ..\lib\Interop.EA.dll + @@ -42,6 +43,9 @@ Always + + Always + - \ No newline at end of file + diff --git a/EA-ModelKit.Tests/Helpers/TestSlimElement.cs b/EA-ModelKit.Tests/Helpers/TestSlimElement.cs new file mode 100644 index 0000000..dd1c9de --- /dev/null +++ b/EA-ModelKit.Tests/Helpers/TestSlimElement.cs @@ -0,0 +1,58 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Helpers +{ + using EA; + + using EAModelKit.Model.Slims; + + using Moq; + + /// + /// is a that is used for test purpose + /// + internal class TestSlimElement: SlimElement + { + /// + /// Initializes a new instance of + /// + /// The associated + /// The associated collection of + public TestSlimElement(Element element, IReadOnlyList taggedValues) : base(element, taggedValues) + { + } + + public TestSlimElement(string kind, string name, string alias, string notes, IReadOnlyList taggedValues): + this(CreateElement(kind, name, alias, notes), taggedValues) + { + } + + private static Element CreateElement(string kind, string name, string alias, string notes) + { + var element = new Mock(); + element.Setup(x => x.Name).Returns(name); + element.Setup(x => x.Alias).Returns(alias); + element.Setup(x => x.Notes).Returns(notes); + element.Setup(x => x.Stereotype).Returns(kind); + return element.Object; + } + } +} diff --git a/EA-ModelKit.Tests/Resources/CacheService/TaggedValues.xml b/EA-ModelKit.Tests/Resources/CacheService/TaggedValues.xml new file mode 100644 index 0000000..0a8ece5 --- /dev/null +++ b/EA-ModelKit.Tests/Resources/CacheService/TaggedValues.xml @@ -0,0 +1,27 @@ + + + + + + 20 + "ABC" + "" + + + 20 + "CDF" + "15" + + + 26 + "CDF" + "16" + + + 10 + "CDF" + "17" + + + + diff --git a/EA-ModelKit.Tests/Services/Cache/CacheServiceTestFixture.cs b/EA-ModelKit.Tests/Services/Cache/CacheServiceTestFixture.cs new file mode 100644 index 0000000..6e782d3 --- /dev/null +++ b/EA-ModelKit.Tests/Services/Cache/CacheServiceTestFixture.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Services.Cache +{ + using EA; + + using EAModelKit.Services.Cache; + + using Moq; + + using NUnit.Framework; + + using File = System.IO.File; + + [TestFixture] + public class CacheServiceTestFixture + { + private CacheService cacheService; + private Mock repository; + + [SetUp] + public void Setup() + { + this.cacheService = new CacheService(); + this.repository = new Mock(); + this.cacheService.Initialize(this.repository.Object); + + this.repository.Setup(x => x.SQLQuery(It.Is(i => i.Contains("t_objectproperties")))) + .Returns(QueryResourceContent("TaggedValues.xml")); + } + + [Test] + public void VerifyGetTaggedValues() + { + Assert.Multiple(() => + { + Assert.That(this.cacheService.GetTaggedValues(10).Count, Is.EqualTo(1)); + Assert.That(this.cacheService.GetTaggedValues(20).Count, Is.EqualTo(2)); + Assert.That(this.cacheService.GetTaggedValues(26).Count, Is.EqualTo(1)); + Assert.That(this.cacheService.GetTaggedValues(27).Count, Is.EqualTo(0)); + Assert.That(this.cacheService.GetTaggedValues([10,20,26]).Count, Is.EqualTo(4)); + }); + } + + private static string QueryResourceContent(string fileName) + { + var path = Path.Combine(Directory.GetCurrentDirectory(), "Resources", "CacheService", fileName); + return File.ReadAllText(path); + } + } +} diff --git a/EA-ModelKit.Tests/Services/Dispatcher/DispatcherServiceTestFixture.cs b/EA-ModelKit.Tests/Services/Dispatcher/DispatcherServiceTestFixture.cs index c813c8e..f95e772 100644 --- a/EA-ModelKit.Tests/Services/Dispatcher/DispatcherServiceTestFixture.cs +++ b/EA-ModelKit.Tests/Services/Dispatcher/DispatcherServiceTestFixture.cs @@ -20,11 +20,17 @@ namespace EAModelKit.Tests.Services.Dispatcher { + using Autofac; + using EA; + using EAModelKit.Services.Cache; using EAModelKit.Services.Dispatcher; using EAModelKit.Services.Logger; using EAModelKit.Services.Selection; + using EAModelKit.Services.ViewBuilder; + using EAModelKit.ViewModels.Exporter; + using EAModelKit.Views.Export; using Microsoft.Extensions.Logging; @@ -32,6 +38,8 @@ namespace EAModelKit.Tests.Services.Dispatcher using NUnit.Framework; + using App = EAModelKit.App; + [TestFixture] public class DispatcherServiceTestFixture { @@ -39,15 +47,19 @@ public class DispatcherServiceTestFixture private Mock selectionService; private Mock loggerService; private Mock repository; - + private Mock viewBuilderService; + private Mock cacheService; + [SetUp] public void Setup() { this.selectionService = new Mock(); this.loggerService = new Mock(); this.repository = new Mock(); - - this.dispatcher = new DispatcherService(this.loggerService.Object, this.selectionService.Object); + this.viewBuilderService = new Mock(); + this.cacheService = new Mock(); + + this.dispatcher = new DispatcherService(this.loggerService.Object, this.selectionService.Object, this.viewBuilderService.Object, this.cacheService.Object); } [Test] @@ -72,6 +84,11 @@ public void VerifyDisconnect() [Test] public void VerifyOnGenericExport() { + this.selectionService.Setup(x => x.QuerySelectedElements(this.repository.Object)).Returns([]); + this.dispatcher.OnGenericExport(this.repository.Object); + + this.loggerService.Verify(x => x.Log(LogLevel.Warning, It.IsAny()), Times.Once); + var selectedElements = new List { CreateNewElement("Requirement"), @@ -83,16 +100,63 @@ public void VerifyOnGenericExport() CreateNewElement("Block") }; + var exporterViewModel = new Mock(); + var container = new ContainerBuilder(); + container.RegisterInstance(exporterViewModel.Object); + App.BuildContainer(container); this.selectionService.Setup(x => x.QuerySelectedElements(this.repository.Object)).Returns(selectedElements); this.dispatcher.OnGenericExport(this.repository.Object); - + Assert.Multiple(() => { - this.loggerService.Verify(x => x.Log(LogLevel.Debug, "Found {0} Elements With Stereotype {1}", 4, "Requirement"), Times.Once); - this.loggerService.Verify(x => x.Log(LogLevel.Debug, "Found {0} Elements With Stereotype {1}", 3, "Block"), Times.Once); + exporterViewModel.Verify(x => x.InitializeViewModel(selectedElements), Times.Once); + this.viewBuilderService.Verify(x => x.ShowDxDialog(exporterViewModel.Object), Times.Once); }); } + [Test] + public void VerifyResetServices() + { + this.dispatcher.OnFileOpen(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Once); + + this.dispatcher.OnFileNew(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(2)); + + this.dispatcher.OnPostNewPackage(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(3)); + + this.dispatcher.OnPostNewElement(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(4)); + + this.dispatcher.OnPostNewConnector(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(5)); + + this.dispatcher.OnPostNewAttribute(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(6)); + + this.dispatcher.OnPreDeleteElement(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(7)); + + this.dispatcher.OnPreDeleteAttribute(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(8)); + + this.dispatcher.OnPreDeleteConnector(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(9)); + + this.dispatcher.OnPreDeletePackage(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(10)); + + this.dispatcher.OnPostNewDiagram(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(11)); + + this.dispatcher.OnPreDeleteDiagram(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(12)); + + this.dispatcher.OnNotifyContextItemModified(this.repository.Object); + this.cacheService.Verify(x => x.Initialize(this.repository.Object), Times.Exactly(13)); + } + private static Element CreateNewElement(string stereotypeName) { var element = new Mock(); diff --git a/EA-ModelKit.Tests/Services/Exporter/GenericExporterServiceTestFixture.cs b/EA-ModelKit.Tests/Services/Exporter/GenericExporterServiceTestFixture.cs new file mode 100644 index 0000000..47829da --- /dev/null +++ b/EA-ModelKit.Tests/Services/Exporter/GenericExporterServiceTestFixture.cs @@ -0,0 +1,90 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Services.Exporter +{ + using EA; + + using EAModelKit.Model.Export; + using EAModelKit.Model.Slims; + using EAModelKit.Services.Exporter; + using EAModelKit.Services.Logger; + using EAModelKit.Services.Writer; + + using Microsoft.Extensions.Logging; + + using Moq; + + using NUnit.Framework; + + using Task = Task; + + [TestFixture] + public class GenericExporterServiceTestFixture + { + private GenericExporterService exporterService; + private Mock loggerService; + private Mock excelWriter; + + [SetUp] + public void Setup() + { + this.loggerService = new Mock(); + this.excelWriter = new Mock(); + this.exporterService = new GenericExporterService(this.loggerService.Object, this.excelWriter.Object); + } + + [Test] + public async Task VerifyExportElements() + { + var slimTaggedValue = new SlimTaggedValue + { + ContainerId = 1, + Name = "abc", + Value = "15" + }; + + var element = new Mock(); + element.Setup(x => x.Name).Returns("abc"); + element.Setup(x => x.Stereotype).Returns("Function"); + element.Setup(x => x.ElementID).Returns(slimTaggedValue.ContainerId); + + var slimTaggedValues = new List { slimTaggedValue }; + var slimElement = new SlimElement(element.Object, slimTaggedValues); + + var genericConfigurations = new List + { + new([slimElement], [slimTaggedValue.Name]) + }; + + this.excelWriter.Setup(x => x.WriteAsync(It.IsAny>>(), It.IsAny())) + .Returns(Task.CompletedTask); + + await this.exporterService.ExportElementsAsync("abcpath", genericConfigurations); + + this.loggerService.Verify(x => x.Log(LogLevel.Information, It.IsAny(), It.IsAny()), Times.Exactly(2)); + + this.excelWriter.Setup(x => x.WriteAsync(It.IsAny>>(), It.IsAny())) + .ThrowsAsync(new InvalidOperationException()); + + Assert.That(() => this.exporterService.ExportElementsAsync("abcpath", genericConfigurations), Throws.InvalidOperationException); + } + } +} diff --git a/EA-ModelKit.Tests/Services/Writer/ExcelWriterTestFixture.cs b/EA-ModelKit.Tests/Services/Writer/ExcelWriterTestFixture.cs new file mode 100644 index 0000000..06ae26a --- /dev/null +++ b/EA-ModelKit.Tests/Services/Writer/ExcelWriterTestFixture.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.Services.Writer +{ + using EAModelKit.Model.Export; + using EAModelKit.Services.Writer; + using EAModelKit.Tests.Helpers; + + using NUnit.Framework; + + [TestFixture] + public class ExcelWriterTestFixture + { + private ExcelWriter excelWriter; + private string exportFilePath; + + [SetUp] + public void Setup() + { + this.excelWriter = new ExcelWriter(); + this.exportFilePath = Path.Combine(Path.GetDirectoryName(Path.GetDirectoryName(TestContext.CurrentContext.TestDirectory))!, "ExportTestToExcel.xlsx"); + } + + [TearDown] + public void Teardown() + { + if (File.Exists(this.exportFilePath)) + { + File.Delete(this.exportFilePath); + } + } + + [Test] + public void VerifyWrite() + { + const string kind = "TestElement"; + var element = new ExportableElement(new TestSlimElement(kind, "name", "alias", "a note", []), []); + + var content = new Dictionary> + { + {kind, [element]} + }; + + Assert.That(() => this.excelWriter.WriteAsync(content, this.exportFilePath), Throws.Nothing); + } + } +} diff --git a/EA-ModelKit.Tests/ViewModels/Exporter/GenericExporterViewModelTestFixture.cs b/EA-ModelKit.Tests/ViewModels/Exporter/GenericExporterViewModelTestFixture.cs new file mode 100644 index 0000000..02a8865 --- /dev/null +++ b/EA-ModelKit.Tests/ViewModels/Exporter/GenericExporterViewModelTestFixture.cs @@ -0,0 +1,230 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Tests.ViewModels.Exporter +{ + using DevExpress.Mvvm.Native; + + using EA; + + using EAModelKit.Behaviors; + using EAModelKit.Model.Export; + using EAModelKit.Model.Slims; + using EAModelKit.Services.Cache; + using EAModelKit.Services.Exporter; + using EAModelKit.Services.Logger; + using EAModelKit.Services.ViewBuilder; + using EAModelKit.ViewModels.Exporter; + + using Moq; + + using NUnit.Framework; + + using Task = System.Threading.Tasks.Task; + + [TestFixture] + public class GenericExporterViewModelTestFixture + { + private GenericExporterViewModel exporterViewModel; + private Mock loggerService; + private Mock cacheService; + private Mock builderService; + private Mock exporterService; + private List elements; + private Mock closeWindowBehavior; + + [SetUp] + public void Setup() + { + this.loggerService = new Mock(); + this.cacheService = new Mock(); + this.builderService = new Mock(); + this.exporterService = new Mock(); + + this.exporterViewModel = new GenericExporterViewModel(this.loggerService.Object, this.cacheService.Object, this.builderService.Object, this.exporterService.Object); + this.closeWindowBehavior = new Mock(); + this.exporterViewModel.CloseWindowBehavior = this.closeWindowBehavior.Object; + + this.elements = + [ + CreateNewElement(4, "Requirement", ""), + CreateNewElement(8, "Block", "Function"), + CreateNewElement(16, "Block", "Product"), + CreateNewElement(32, "Block", "Function"), + ]; + + var taggedValues = new List + { + new () + { + ContainerId = 4, + Name = "ABC" + }, + new () + { + ContainerId = 4, + Name = "DEF" + }, + new () + { + ContainerId = 8, + Name = "123" + }, + new () + { + ContainerId = 8, + Name = "456" + }, + new () + { + ContainerId = 32, + Name = "123" + } + }; + + this.cacheService.Setup(x => x.GetTaggedValues(It.IsAny())) + .Returns(taggedValues); + } + + [Test] + public void VerifyViewModelProperties() + { + Assert.Multiple(() => + { + Assert.That(this.exporterViewModel.CanProceed, Is.False); + Assert.That(this.exporterViewModel.SelectedFilePath, Is.Null); + Assert.That(this.exporterViewModel.ExportSetups.Items, Is.Empty); + Assert.That(this.exporterViewModel.OutputFileCommand, Is.Null); + Assert.That(this.exporterViewModel.ExportCommand, Is.Null); + }); + } + + [Test] + public void VerifyInitializeViewModel() + { + Assert.Multiple(() => + { + Assert.That(() => this.exporterViewModel.InitializeViewModel(null), Throws.ArgumentNullException); + Assert.That(() => this.exporterViewModel.InitializeViewModel([]), Throws.ArgumentException); + }); + + this.exporterViewModel.InitializeViewModel(this.elements); + + Assert.Multiple(() => + { + this.cacheService.Verify(x => x.GetTaggedValues(It.IsAny()), Times.Once); + Assert.That(this.exporterViewModel.ExportSetups.Items, Is.Not.Empty); + Assert.That(this.exporterViewModel.ExportSetups, Has.Count.EqualTo(3)); + Assert.That(this.exporterViewModel.OutputFileCommand, Is.Not.Null); + Assert.That(this.exporterViewModel.ExportCommand, Is.Not.Null); + Assert.That(this.exporterViewModel.ExportSetups.Items.All(x => x.ShouldBeExported), Is.True); + + Assert.That(this.exporterViewModel.ExportSetups.Items.Single(x => x.ElementKind == "Requirement").AvailableTaggedValuesForExport + .Count(), Is.EqualTo(2)); + + Assert.That(this.exporterViewModel.ExportSetups.Items.Single(x => x.ElementKind == "Function").AvailableTaggedValuesForExport + .Count(), Is.EqualTo(2)); + + Assert.That(this.exporterViewModel.ExportSetups.Items.Single(x => x.ElementKind == "Product").HaveAnyTaggedValues, + Is.False); + + Assert.That(this.exporterViewModel.ExportSetups.Items.All(x => x.AvailableTaggedValuesForExport.Count() + == x.SelectedTaggedValuesForExport.Count()), Is.True); + }); + } + + [Test] + public void VerifyOutputFileCommand() + { + this.exporterViewModel.InitializeViewModel(this.elements); + + this.builderService.Setup(x => x.GetSaveFileDialog(It.IsAny(), It.IsAny(), It.IsAny(),0)) + .Returns(string.Empty); + + this.exporterViewModel.OutputFileCommand.Execute().Subscribe(); + Assert.That(this.exporterViewModel.SelectedFilePath, Is.Empty); + + const string selectedPath = @"a\path\to\file.xlsx"; + + this.builderService.Setup(x => x.GetSaveFileDialog(It.IsAny(), It.IsAny(), It.IsAny(),0)) + .Returns(selectedPath); + + this.exporterViewModel.OutputFileCommand.Execute().Subscribe(); + Assert.That(this.exporterViewModel.SelectedFilePath, Is.EqualTo(selectedPath)); + } + + [Test] + public void VerifyCanProceedComputation() + { + this.exporterViewModel.InitializeViewModel(this.elements); + + Assert.That(this.exporterViewModel.CanProceed, Is.False); + + this.builderService.Setup(x => x.GetSaveFileDialog(It.IsAny(), It.IsAny(), It.IsAny(),0)) + .Returns("abc"); + + this.exporterViewModel.OutputFileCommand.Execute().Subscribe(); + Assert.That(this.exporterViewModel.CanProceed, Is.True); + this.exporterViewModel.ExportSetups.Items.ForEach(x => x.ShouldBeExported = false); + + Assert.That(this.exporterViewModel.CanProceed, Is.False); + this.exporterViewModel.ExportSetups.Items.First().ShouldBeExported = true; + Assert.That(this.exporterViewModel.CanProceed, Is.True); + } + + [Test] + public void VerifyExportCommand() + { + this.exporterViewModel.InitializeViewModel(this.elements); + + this.builderService.Setup(x => x.GetSaveFileDialog(It.IsAny(), It.IsAny(), It.IsAny(),0)) + .Returns("abc"); + + this.exporterService.Setup(x => x.ExportElementsAsync(It.IsAny(), It.IsAny>())) + .Returns(Task.CompletedTask); + + this.exporterViewModel.OutputFileCommand.Execute().Subscribe(); + this.exporterViewModel.ExportCommand.Execute().Subscribe(); + + this.closeWindowBehavior.Verify(x => x.Close(), Times.Once); + + this.exporterService.Setup(x => x.ExportElementsAsync(It.IsAny(), It.IsAny>())) + .ThrowsAsync(new InvalidOperationException()); + + this.exporterViewModel.ExportCommand.Execute().Subscribe(); + + Assert.Multiple(() => + { + this.closeWindowBehavior.Verify(x => x.Close(), Times.Once); + this.loggerService.Verify(x => x.LogException(It.IsAny(), It.IsAny()), Times.Once); + }); + } + + private static Element CreateNewElement(int id, string typeName, string stereotype) + { + var element = new Mock(); + element.Setup(x => x.ElementID).Returns(id); + element.Setup(x => x.Type).Returns(typeName); + element.Setup(x => x.Stereotype).Returns(stereotype); + + return element.Object; + } + } +} diff --git a/EA-ModelKit/App.cs b/EA-ModelKit/App.cs index 43d7792..64e6177 100644 --- a/EA-ModelKit/App.cs +++ b/EA-ModelKit/App.cs @@ -79,6 +79,11 @@ public class App /// private IVersionService versionService; + /// + /// Stores the location of the assembly, used to resolve other dependencies + /// + private static readonly string AssemblyLocation = Path.GetDirectoryName(typeof(App).Assembly.Location)!; + /// /// Initializes a new instance of the class. /// @@ -87,7 +92,7 @@ public App() Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture; Thread.CurrentThread.CurrentUICulture = CultureInfo.InvariantCulture; AppDomain.CurrentDomain.AssemblyResolve += CurrentDomainOnAssemblyResolve; - Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)!); + Directory.SetCurrentDirectory(AssemblyLocation); Log.Logger = new LoggerConfiguration() .MinimumLevel @@ -122,7 +127,7 @@ public App() /// Builds the /// /// An optional - public static void BuildContainer(ContainerBuilder containerBuilder = null) + public static void BuildContainer(ContainerBuilder containerBuilder) { containerBuilder ??= new ContainerBuilder(); Container = containerBuilder.Build(); @@ -171,24 +176,16 @@ public string EA_GetRibbonCategory(Repository _) /// The definition of the menu option public object EA_GetMenuItems(Repository repository, string location, string menuName) { - switch (location) + return location switch { - case "MainMenu": - switch (menuName) - { - case "": - return MenuHeaderName; - case MenuHeaderName: - return new[] - { - GenericExportEntry - }; - } - - break; - } - - return null; + "MainMenu" => menuName switch + { + "" => MenuHeaderName, + MenuHeaderName => new[] { GenericExportEntry }, + _ => null + }, + _ => null + }; } /// @@ -243,6 +240,167 @@ public void EA_GetMenuState(Repository repository, string location, string menuN isEnabled = repository.IsProjectOpen(); } + /// + /// The event occurs when the model being viewed by the Enterprise Architect user changes, for whatever reason (through + /// user interaction or Add-In activity). + /// + /// The + public void EA_FileOpen(Repository repository) + { + this.dispatcher.OnFileOpen(repository); + } + + /// + /// The event occurs when the model being viewed by the Enterprise Architect user changes, for whatever reason (through + /// user interaction or Add-In activity). + /// + /// The + public void EA_FileNew(Repository repository) + { + this.dispatcher.OnFileNew(repository); + } + + /// + /// This event occurs when a user drags a new Package from the Toolbox or Resources window onto a diagram, + /// or by selecting the New Package icon from the Project Browser. + /// + /// The + /// The + /// Return True if the Package has been updated during this notification. Return False otherwise. + public bool EA_OnPostNewPackage(Repository repository, EventProperties info) + { + this.dispatcher.OnPostNewPackage(repository); + return false; + } + + /// + /// This event occurs after a user has dragged a new element from the Toolbox or 'Resources' tab of the Browser window onto a diagram. + /// The notification is provided immediately after the element is added to the model. + /// + /// The + /// The + /// Return True if the element has been updated during this notification. Return False otherwise. + public bool EA_OnPostNewElement(Repository repository, EventProperties info) + { + this.dispatcher.OnPostNewElement(repository); + return false; + } + + /// + /// This event occurs after a user has dragged a new connector from the Toolbox or 'Resources' tab of the Browser window onto a diagram. + /// The notification is provided immediately after the connector is added to the model. + /// + /// The + /// The + /// Return True if the connector has been updated during this notification. Return False otherwise. + public bool EA_OnPostNewConnector(Repository repository, EventProperties info) + { + this.dispatcher.OnPostNewConnector(repository); + return false; + } + + /// + /// This event occurs when a user creates a new attribute on an element by either drag-and-dropping from the Browser window, using the 'Attributes' tab of the Features window, or using the in-place editor on the diagram. + /// The notification is provided immediately after the attribute is created. + /// + /// The + /// The + /// Return True if the attribute has been updated during this notification. Return False otherwise. + public bool EA_OnPostNewAttribute(Repository repository, EventProperties info) + { + this.dispatcher.OnPostNewAttribute(repository); + return false; + } + + /// + /// EA_OnPostNewDiagram notifies Add-Ins that a new diagram has been created. It enables Add-Ins to modify the diagram upon creation. + /// + /// The + /// The + /// Return True if the attribute has been updated during this notification. Return False otherwise. + public bool EA_OnPostNewDiagram(Repository repository, EventProperties info) + { + this.dispatcher.OnPostNewDiagram(repository); + return false; + } + + /// + /// This event occurs when a user deletes an element from the Browser window or on a diagram. + /// The notification is provided immediately before the element is deleted, so that the Add-In can disable deletion of the element. + /// + /// The + /// The + /// Return True to enable deletion of the element from the model. Return False to disable deletion of the element. + public bool EA_OnPreDeleteElement(Repository repository, EventProperties info) + { + this.dispatcher.OnPreDeleteElement(repository); + return true; + } + + /// + /// This event occurs when a user attempts to permanently delete an attribute from the Browser window. + /// The notification is provided immediately before the attribute is deleted, so that the Add-In can disable deletion of the attribute. + /// + /// The + /// The + /// Return True to enable deletion of the attribute from the model. Return False to disable deletion of the attribute. + public bool EA_OnPreDeleteAttribute(Repository repository, EventProperties info) + { + this.dispatcher.OnPreDeleteAttribute(repository); + return true; + } + + /// + /// This event occurs when a user attempts to permanently delete a connector on a diagram. + /// The notification is provided immediately before the connector is deleted, so that the Add-In can disable deletion of the connector. + /// + /// The + /// The + /// Return True to enable deletion of the connector from the model. Return False to disable deletion of the connector. + public bool EA_OnPreDeleteConnector(Repository repository, EventProperties info) + { + this.dispatcher.OnPreDeleteConnector(repository); + return true; + } + + /// + /// This event occurs when a user attempts to permanently delete a Package from the Browser window. + /// The notification is provided immediately before the Package is deleted, so that the Add-In can disable deletion of the Package. + /// + /// The + /// The + /// Return True to enable deletion of the Package from the model. Return False to disable deletion of the Package. + public bool EA_OnPreDeletePackage(Repository repository, EventProperties info) + { + this.dispatcher.OnPreDeletePackage(repository); + return true; + } + + /// + /// This event occurs when a user attempts to permanently delete a diagram from the Browser window. + /// The notification is provided immediately before the diagram is deleted, so that the Add-In can disable deletion of the diagram. + /// + /// The + /// The + /// Return True to enable deletion of the Package from the model. Return False to disable deletion of the Package. + public bool EA_OnPreDeleteDiagram(Repository repository, EventProperties info) + { + this.dispatcher.OnPreDeleteDiagram(repository); + return true; + } + + /// + /// This event occurs when a user has modified the context item. Add-Ins that require knowledge of when an item has been + /// modified can subscribe to this broadcast function. + /// + /// The + /// The guid of the Item + /// The of the item + public void EA_OnNotifyContextItemModified(Repository repository, string guid, ObjectType objectType) + { + this.dispatcher.OnNotifyContextItemModified(repository); + } + /// /// Resolves all required services for the current class /// @@ -264,14 +422,12 @@ private void ResolveRequiredServices() /// The assembly private static Assembly CurrentDomainOnAssemblyResolve(object sender, ResolveEventArgs args) { - var folderPath = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); - - if (string.IsNullOrEmpty(folderPath)) + if (string.IsNullOrEmpty(AssemblyLocation)) { return null; } - var assemblyPath = Path.Combine(folderPath, new AssemblyName(args.Name).Name + ".dll"); + var assemblyPath = Path.Combine(AssemblyLocation, new AssemblyName(args.Name).Name + ".dll"); return !File.Exists(assemblyPath) ? null : Assembly.LoadFile(assemblyPath); } diff --git a/EA-ModelKit/Behaviors/CloseWindowBehavior.cs b/EA-ModelKit/Behaviors/CloseWindowBehavior.cs new file mode 100644 index 0000000..d72ab35 --- /dev/null +++ b/EA-ModelKit/Behaviors/CloseWindowBehavior.cs @@ -0,0 +1,72 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Behaviors +{ + using System.Diagnostics.CodeAnalysis; + using System.Windows; + + using DevExpress.Mvvm.UI.Interactivity; + + using EAModelKit.Utilities.Dialogs; + + /// + /// Attachable behavior for a view that can be close from its view model + /// + [ExcludeFromCodeCoverage] + internal class CloseWindowBehavior: Behavior, ICloseWindowBehavior + { + /// + /// Closes the view + /// + public void Close() + { + this.AssociatedObject.Close(); + } + + /// + /// Occurs when this behavior attaches to a view + /// + protected override void OnAttached() + { + base.OnAttached(); + this.AssociatedObject.DataContextChanged += this.DataContextChanged; + } + + /// + /// Occurs when this behavior detaches from its associated view + /// + protected override void OnDetaching() + { + base.OnDetaching(); + this.AssociatedObject.DataContextChanged -= this.DataContextChanged; + } + + /// + /// Occurs when the data context of changes + /// + /// The sender + /// The + private void DataContextChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ((ICloseableWindowViewModel)this.AssociatedObject.DataContext).CloseWindowBehavior = this; + } + } +} diff --git a/EA-ModelKit/Behaviors/ICloseWindowBehavior.cs b/EA-ModelKit/Behaviors/ICloseWindowBehavior.cs new file mode 100644 index 0000000..d7ebd0a --- /dev/null +++ b/EA-ModelKit/Behaviors/ICloseWindowBehavior.cs @@ -0,0 +1,35 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Behaviors +{ + using DevExpress.Mvvm.UI.Interactivity; + + /// + /// The provides closing behavior for a Window + /// + internal interface ICloseWindowBehavior + { + /// + /// Closes the view + /// + void Close(); + } +} diff --git a/EA-ModelKit/Converters/BooleanToVisibilityConverter.cs b/EA-ModelKit/Converters/BooleanToVisibilityConverter.cs new file mode 100644 index 0000000..bf6e1e5 --- /dev/null +++ b/EA-ModelKit/Converters/BooleanToVisibilityConverter.cs @@ -0,0 +1,81 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Converters +{ + using System; + using System.Globalization; + using System.Windows; + using System.Windows.Data; + + /// + /// Converts booleans to . Null and false == collapsed, true == visible. + /// + public class BooleanToVisibilityConverter : IValueConverter + { + /// + /// Convert a bool to . + /// + /// The incoming type. + /// The target type. + /// The converter parameter. The value can be "Invert" to inverse the result + /// The supplied culture + /// if the value is true. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (value == null) + { + return Visibility.Collapsed; + } + + var visibility = (bool)value ? Visibility.Visible : Visibility.Collapsed; + + if (parameter is "Invert") + { + visibility = visibility == Visibility.Visible ? Visibility.Collapsed : Visibility.Visible; + } + + return visibility; + } + + /// + /// Does nothing. + /// + /// + /// The incoming collection. + /// + /// + /// The target type. + /// + /// + /// The parameter passed on to this conversion. + /// + /// + /// The culture information. + /// + /// + /// Throws always. + /// + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/EA-ModelKit/Converters/CollectionConverter.cs b/EA-ModelKit/Converters/CollectionConverter.cs new file mode 100644 index 0000000..05fc49e --- /dev/null +++ b/EA-ModelKit/Converters/CollectionConverter.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Converters +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Windows.Data; + + /// + /// Converts a of to and from an + /// + /// Any object + internal abstract class CollectionConverter : IValueConverter + { + /// Converts a value. + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return value == null ? [] : (IEnumerable)value; + } + + /// Converts a value. + /// The value that is produced by the binding target. + /// The type to convert to. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns , the valid null value is used. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + var selection = (IList)value; + + if (selection == null) + { + return Enumerable.Empty(); + } + + var items = selection.Cast().ToList(); + + return items; + } + } +} diff --git a/EA-ModelKit/Converters/StringCollectionConverter.cs b/EA-ModelKit/Converters/StringCollectionConverter.cs new file mode 100644 index 0000000..ce23f0d --- /dev/null +++ b/EA-ModelKit/Converters/StringCollectionConverter.cs @@ -0,0 +1,31 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Converters +{ + using System.Collections.Generic; + + /// + /// Converts a of string to and from an + /// + internal class StringCollectionConverter : CollectionConverter + { + } +} diff --git a/EA-ModelKit/EA-ModelKit.csproj b/EA-ModelKit/EA-ModelKit.csproj index d19c08e..c5df0ed 100644 --- a/EA-ModelKit/EA-ModelKit.csproj +++ b/EA-ModelKit/EA-ModelKit.csproj @@ -33,6 +33,7 @@ + diff --git a/EA-ModelKit/Extensions/ContainerBuilderExtensions.cs b/EA-ModelKit/Extensions/ContainerBuilderExtensions.cs index fc3f513..2785853 100644 --- a/EA-ModelKit/Extensions/ContainerBuilderExtensions.cs +++ b/EA-ModelKit/Extensions/ContainerBuilderExtensions.cs @@ -24,10 +24,15 @@ namespace EAModelKit.Extensions using AutofacSerilogIntegration; + using EAModelKit.Services.Cache; using EAModelKit.Services.Dispatcher; + using EAModelKit.Services.Exporter; using EAModelKit.Services.Logger; using EAModelKit.Services.Selection; using EAModelKit.Services.Version; + using EAModelKit.Services.ViewBuilder; + using EAModelKit.Services.Writer; + using EAModelKit.ViewModels.Exporter; using Microsoft.Extensions.Logging; @@ -52,6 +57,10 @@ public static void RegisterServices(this ContainerBuilder builder) builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As().SingleInstance(); + builder.RegisterType().As(); + builder.RegisterType().As(); } /// @@ -60,6 +69,7 @@ public static void RegisterServices(this ContainerBuilder builder) /// The public static void RegisterViewModels(this ContainerBuilder builder) { + builder.RegisterType().As(); } } } diff --git a/EA-ModelKit/Model/Export/ExportableElement.cs b/EA-ModelKit/Model/Export/ExportableElement.cs new file mode 100644 index 0000000..19a0bbe --- /dev/null +++ b/EA-ModelKit/Model/Export/ExportableElement.cs @@ -0,0 +1,67 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Model.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Net; + + using EA; + + using EAModelKit.Model.Slims; + + /// + /// Data class that provide data that have to be exported from an + /// + internal class ExportableElement: ExportableObject + { + /// + /// Defines the base headers for the + /// + private readonly string[] baseHeaders = ["Name", "Alias", "Notes"]; + + /// + /// Initializes a new instance of + /// + /// The that should be exported + /// All name of TaggeValue that have to be exported + public ExportableElement(SlimElement element, IReadOnlyList taggedValuesToExport) + { + this.KindName = element.ElementKind; + this.Headers = [..this.baseHeaders, ..taggedValuesToExport]; + + var values = new List {element.Name, element.Alias,WebUtility.HtmlDecode(element.Notes)}; + + for(var baseHeaderIndex = 0;baseHeaderIndex < this.baseHeaders.Length;baseHeaderIndex++) + { + this.ExportableValues[this.baseHeaders[baseHeaderIndex]] = values[baseHeaderIndex]; + } + + foreach (var taggedValueToExport in taggedValuesToExport) + { + this.ExportableValues[taggedValueToExport] = element.TaggedValues.TryGetValue(taggedValueToExport, out var existingValue) + ? string.Join(Environment.NewLine, existingValue.Select(x => x.Value)) + : string.Empty; + } + } + } +} diff --git a/EA-ModelKit/Model/Export/ExportableObject.cs b/EA-ModelKit/Model/Export/ExportableObject.cs new file mode 100644 index 0000000..c85942d --- /dev/null +++ b/EA-ModelKit/Model/Export/ExportableObject.cs @@ -0,0 +1,69 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Model.Export +{ + using System; + using System.Collections.Generic; + using System.Linq; + + /// + /// Based common class for any object that are exportable + /// + internal abstract class ExportableObject + { + /// + /// Gets all exportable values, associated to the header + /// + protected readonly Dictionary ExportableValues = []; + + /// + /// Gets the value at an header position + /// + /// The header position + /// The current value + public string this[string header] => this.TryGetValue(header); + + /// + /// Gets the headers to be used to export the + /// + public IReadOnlyList Headers { get; protected set; } + + /// + /// Gets the name of the current export kind + /// + public string KindName { get; protected set; } + + /// + /// Tries to get the value for the provided + /// + /// The name of the header + /// The associated value + private string TryGetValue(string header) + { + if (!this.Headers.Contains(header)) + { + throw new ArgumentOutOfRangeException(nameof(header), $"The provided header value [{header}] is not a valid header"); + } + + return this.ExportableValues.TryGetValue(header, out var value) ? value : string.Empty; + } + } +} diff --git a/EA-ModelKit/Model/Export/GenericExportConfiguration.cs b/EA-ModelKit/Model/Export/GenericExportConfiguration.cs new file mode 100644 index 0000000..b6cb65f --- /dev/null +++ b/EA-ModelKit/Model/Export/GenericExportConfiguration.cs @@ -0,0 +1,56 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Model.Export +{ + using System.Collections.Generic; + + using EA; + + using EAModelKit.Model.Slims; + + /// + /// The provides configuration details that should be used to export + /// + /// + internal class GenericExportConfiguration + { + /// + /// Initializes a new instance of the class. + /// + /// The collection of that should be exported + /// The collection of TaggedValue names that should be exported + public GenericExportConfiguration(IReadOnlyList exportableElements, IReadOnlyList exportableTaggedValues) + { + this.ExportableElements = exportableElements; + this.ExportableTaggedValues = exportableTaggedValues; + } + + /// + /// Gets the read-only collection of that have to be exported + /// + public IReadOnlyList ExportableElements { get; } + + /// + /// Gets the read-only collection of name of TaggedValues that should be exported + /// + public IReadOnlyList ExportableTaggedValues { get; } + } +} diff --git a/EA-ModelKit/Model/Slims/SlimElement.cs b/EA-ModelKit/Model/Slims/SlimElement.cs new file mode 100644 index 0000000..b873c74 --- /dev/null +++ b/EA-ModelKit/Model/Slims/SlimElement.cs @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Model.Slims +{ + using System.Collections.Generic; + using System.Linq; + + using EA; + + /// + /// Slim class for + /// + internal class SlimElement + { + /// + /// Initializes a new instance of + /// + /// The associated + /// The associated collection of + public SlimElement(Element element, IReadOnlyList taggedValues) + { + this.Name = element.Name; + this.Notes = element.Notes; + this.Alias = element.Alias; + this.ElementType = element.Type; + this.Stereotype = element.Stereotype; + this.ElementKind = string.IsNullOrEmpty(this.Stereotype) ? this.ElementType : this.Stereotype; + + this.TaggedValues = taggedValues.GroupBy(x => x.Name) + .ToDictionary(x => x.Key, IReadOnlyList (x) => x.ToList()); + } + + /// + /// Gets the type of the + /// + public string ElementType { get; } + + /// + /// Gets the applied stereotype to the associated + /// + public string Stereotype { get; } + + /// + /// Gets the kind of Element that is represented + /// + public string ElementKind { get; } + + /// + /// Gets the 's name + /// + public string Name { get; } + + /// + /// Gets the 's alias + /// + public string Alias { get; } + + /// + /// Gets the 's Notes + /// + public string Notes { get; } + + /// + /// Gets the associated dictionary of , grouped by TaggedValueName + /// + public IReadOnlyDictionary> TaggedValues { get; } + } +} diff --git a/EA-ModelKit/Model/Wrappers/PackageWrapper.cs b/EA-ModelKit/Model/Slims/SlimPackage.cs similarity index 80% rename from EA-ModelKit/Model/Wrappers/PackageWrapper.cs rename to EA-ModelKit/Model/Slims/SlimPackage.cs index 222af38..23645f0 100644 --- a/EA-ModelKit/Model/Wrappers/PackageWrapper.cs +++ b/EA-ModelKit/Model/Slims/SlimPackage.cs @@ -1,5 +1,5 @@ // ------------------------------------------------------------------------------------------------- -// +// // // Copyright (C) 2024 Starion Group S.A. // @@ -18,7 +18,7 @@ // // ----------------------------------------------------------------------------------------------- -namespace EAModelKit.Model.Wrappers +namespace EAModelKit.Model.Slims { using System; using System.Collections.Generic; @@ -27,22 +27,22 @@ namespace EAModelKit.Model.Wrappers using EA; /// - /// Wrapper class for a + /// Slim class for a /// - public class PackageWrapper + public class SlimPackage { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PackageWrapper() + public SlimPackage() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// The associated - public PackageWrapper(IDualPackage package) + public SlimPackage(IDualPackage package) { if (package == null) { @@ -78,17 +78,17 @@ public PackageWrapper(IDualPackage package) public int ContainerId { get; set; } /// - /// Gets the name of the + /// Gets the name of the /// public string PackageName { get; set; } /// - /// Queries nested id that are contained into a + /// Queries nested id that are contained into a /// - /// A collection of all available - /// The id of the container + /// A collection of all available + /// The id of the container /// A collection of all nested ids - public static IReadOnlyCollection QueryContainedPackagesId(IReadOnlyCollection packages, int containerId) + public static IReadOnlyCollection QueryContainedPackagesId(IReadOnlyCollection packages, int containerId) { var nestedPackageIds = new List(); diff --git a/EA-ModelKit/Model/Slims/SlimTaggedValue.cs b/EA-ModelKit/Model/Slims/SlimTaggedValue.cs new file mode 100644 index 0000000..8a40325 --- /dev/null +++ b/EA-ModelKit/Model/Slims/SlimTaggedValue.cs @@ -0,0 +1,45 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Model.Slims +{ + using EA; + + /// + /// Slim class for the + /// + public class SlimTaggedValue + { + /// + /// Gets or sets the ID of the 's container + /// + public int ContainerId { get; set; } + + /// + /// Gets or sets the 's name + /// + public string Name { get; set; } + + /// + /// Gets or sets the 's value + /// + public string Value { get; set; } + } +} diff --git a/EA-ModelKit/Services/Cache/CacheService.cs b/EA-ModelKit/Services/Cache/CacheService.cs new file mode 100644 index 0000000..19c47fb --- /dev/null +++ b/EA-ModelKit/Services/Cache/CacheService.cs @@ -0,0 +1,126 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Cache +{ + using System.Collections.Generic; + using System.Globalization; + using System.Linq; + using System.Xml.Linq; + + using EA; + + using EAModelKit.Model.Slims; + using EAModelKit.Utilities; + + /// + /// The provides caching functionalities within the plugin + /// + internal class CacheService : ICacheService + { + /// + /// The that should be use to perform queries + /// + private Repository currentRepository; + + /// + /// Flag that asserts if the service should reset before processing the next query + /// + private bool resetOnNextQuery = true; + + /// + /// Cache dictionary for all casted that an have + /// + private Dictionary> taggedValuesPerElement = []; + + /// + /// Initializes this service properties + /// + /// The + public void Initialize(Repository repository) + { + this.resetOnNextQuery = true; + this.currentRepository = repository; + } + + /// + /// Get all contained by an + /// + /// The ID of the container + /// All contained + public IReadOnlyList GetTaggedValues(int elementId) + { + this.VerifyNeedReset(); + return this.taggedValuesPerElement.TryGetValue(elementId, out var taggedValues) ? taggedValues : []; + } + + /// + /// Get all contained by many + /// + /// An array of id + /// A read-only collection of + public IReadOnlyList GetTaggedValues(int[] elementIds) + { + this.VerifyNeedReset(); + + var taggedValues = elementIds + .AsParallel() + .SelectMany(id => this.taggedValuesPerElement.TryGetValue(id, out var cachedTaggedValues) + ? cachedTaggedValues + : Enumerable.Empty()); + + return taggedValues.ToList(); + } + + /// + /// Verifies if cache should be reset or not + /// + private void VerifyNeedReset() + { + if (!this.resetOnNextQuery) + { + return; + } + + this.CacheSlimTaggedValues(); + this.resetOnNextQuery = false; + } + + /// + /// Caches all existing into + /// + private void CacheSlimTaggedValues() + { + const string sqlQuery = "SELECT Object_ID, Property, Value FROM t_objectproperties"; + + var queryResult = this.currentRepository.SQLQuery(sqlQuery); + var xElement = XElement.Parse(queryResult); + var xRows = xElement.Descendants("Row"); + + this.taggedValuesPerElement = xRows.Select(r => new SlimTaggedValue + { + ContainerId = int.Parse(r.Elements().First(XElementHelper.MatchElementByName("Object_ID")).Value, CultureInfo.InvariantCulture), + Name = r.Elements().First(XElementHelper.MatchElementByName("Property")).Value, + Value = r.Elements().First(XElementHelper.MatchElementByName("Value")).Value + }).GroupBy(s => s.ContainerId) + .ToDictionary(x => x.Key, IReadOnlyList (x) => x.ToList()); + } + } +} diff --git a/EA-ModelKit/Services/Cache/ICacheService.cs b/EA-ModelKit/Services/Cache/ICacheService.cs new file mode 100644 index 0000000..cd25241 --- /dev/null +++ b/EA-ModelKit/Services/Cache/ICacheService.cs @@ -0,0 +1,54 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Cache +{ + using System.Collections.Generic; + + using EA; + + using EAModelKit.Model.Slims; + + /// + /// The provides caching functionalities within the plugin + /// + internal interface ICacheService + { + /// + /// Initializes this service properties + /// + /// The + void Initialize(Repository repository); + + /// + /// Get all contained by many + /// + /// An array of id + /// A read-only collection of + IReadOnlyList GetTaggedValues(int[] elementIds); + + /// + /// Get all contained by an + /// + /// The ID of the container + /// All contained + IReadOnlyList GetTaggedValues(int elementId); + } +} diff --git a/EA-ModelKit/Services/Dispatcher/DispatcherService.cs b/EA-ModelKit/Services/Dispatcher/DispatcherService.cs index 9b627f4..b78cf36 100644 --- a/EA-ModelKit/Services/Dispatcher/DispatcherService.cs +++ b/EA-ModelKit/Services/Dispatcher/DispatcherService.cs @@ -20,20 +20,31 @@ namespace EAModelKit.Services.Dispatcher { - using System.Linq; + using Autofac; using EA; + using EAModelKit.Services.Cache; using EAModelKit.Services.Logger; using EAModelKit.Services.Selection; + using EAModelKit.Services.ViewBuilder; + using EAModelKit.ViewModels.Exporter; + using EAModelKit.Views.Export; using Microsoft.Extensions.Logging; + using App = App; + /// /// The provides EA events abstraction layer and available actions entry point /// internal class DispatcherService : IDispatcherService { + /// + /// Gets the injected + /// + private readonly ICacheService cacheService; + /// /// Gets the injected /// @@ -44,13 +55,22 @@ internal class DispatcherService : IDispatcherService /// private readonly ISelectionService selectionService; + /// + /// Gets the injected + /// + private readonly IViewBuilderService viewBuilderService; + /// Initializes a new instance of the class. /// The injected /// The injected - public DispatcherService(ILoggerService logger, ISelectionService selectionService) + /// The injected + /// The injected + public DispatcherService(ILoggerService logger, ISelectionService selectionService, IViewBuilderService viewBuilderService, ICacheService cacheService) { this.logger = logger; this.selectionService = selectionService; + this.viewBuilderService = viewBuilderService; + this.cacheService = cacheService; } /// @@ -78,12 +98,141 @@ public void Disconnect() public void OnGenericExport(Repository repository) { var selectedElements = this.selectionService.QuerySelectedElements(repository); - var elementsPerStereotype = selectedElements.GroupBy(x => x.Stereotype).ToDictionary(x => x.Key, x => x.ToList()); - foreach (var elements in elementsPerStereotype) + if (selectedElements.Count == 0) { - this.logger.Log(LogLevel.Debug, "Found {0} Elements With Stereotype {1}", elements.Value.Count, elements.Key); + this.logger.Log(LogLevel.Warning, "Cannot proceed with export feature since no Element is part of the selection"); + return; } + + var exporterViewModel = App.Container.Resolve(); + exporterViewModel.InitializeViewModel(selectedElements); + this.viewBuilderService.ShowDxDialog(exporterViewModel); + } + + /// + /// Handles the file opening event + /// + /// The + public void OnFileOpen(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handles the creation of a new file + /// + /// The + public void OnFileNew(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the post-creation of a + /// + /// The + public void OnPostNewPackage(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the post-creation of an + /// + /// The + public void OnPostNewElement(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the post-creation of a + /// + /// The + public void OnPostNewConnector(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the post-creation of an + /// + /// The + public void OnPostNewAttribute(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the pre-deletion of an + /// + /// The + public void OnPreDeleteElement(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the pre-deletion of an + /// + /// The + public void OnPreDeleteAttribute(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the pre-deletion of a + /// + /// The + public void OnPreDeleteConnector(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the pre-deletion of a + /// + /// The + public void OnPreDeletePackage(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the post-creation of a + /// + /// The + public void OnPostNewDiagram(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the pre-deletion of a + /// + /// The + public void OnPreDeleteDiagram(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Handle the modification of the current item + /// + /// The + public void OnNotifyContextItemModified(Repository repository) + { + this.ResetServices(repository); + } + + /// + /// Reset some services that requires to after a model update + /// + private void ResetServices(Repository repository) + { + this.cacheService.Initialize(repository); } } } diff --git a/EA-ModelKit/Services/Dispatcher/IDispatcherService.cs b/EA-ModelKit/Services/Dispatcher/IDispatcherService.cs index 30868ba..ffe2a0d 100644 --- a/EA-ModelKit/Services/Dispatcher/IDispatcherService.cs +++ b/EA-ModelKit/Services/Dispatcher/IDispatcherService.cs @@ -43,5 +43,83 @@ internal interface IDispatcherService /// /// The EA void OnGenericExport(Repository repository); + + /// + /// Handles the file opening event + /// + /// The + void OnFileOpen(Repository repository); + + /// + /// Handles the creation of a new file + /// + /// The + void OnFileNew(Repository repository); + + /// + /// Handle the post-creation of a + /// + /// The + void OnPostNewPackage(Repository repository); + + /// + /// Handle the post-creation of an + /// + /// The + void OnPostNewElement(Repository repository); + + /// + /// Handle the post-creation of a + /// + /// The + void OnPostNewConnector(Repository repository); + + /// + /// Handle the post-creation of an + /// + /// The + void OnPostNewAttribute(Repository repository); + + /// + /// Handle the pre-deletion of an + /// + /// The + void OnPreDeleteElement(Repository repository); + + /// + /// Handle the pre-deletion of an + /// + /// The + void OnPreDeleteAttribute(Repository repository); + + /// + /// Handle the pre-deletion of a + /// + /// The + void OnPreDeleteConnector(Repository repository); + + /// + /// Handle the pre-deletion of a + /// + /// The + void OnPreDeletePackage(Repository repository); + + /// + /// Handle the post-creation of a + /// + /// The + void OnPostNewDiagram(Repository repository); + + /// + /// Handle the pre-deletion of a + /// + /// The + void OnPreDeleteDiagram(Repository repository); + + /// + /// Handle the modification of the current item + /// + /// The + void OnNotifyContextItemModified(Repository repository); } } diff --git a/EA-ModelKit/Services/Exporter/GenericExporterService.cs b/EA-ModelKit/Services/Exporter/GenericExporterService.cs new file mode 100644 index 0000000..8036a7d --- /dev/null +++ b/EA-ModelKit/Services/Exporter/GenericExporterService.cs @@ -0,0 +1,86 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Exporter +{ + using System.Collections.Generic; + using System.Linq; + + using EA; + + using EAModelKit.Model.Export; + using EAModelKit.Services.Logger; + using EAModelKit.Services.Writer; + + using Microsoft.Extensions.Logging; + + using Task = System.Threading.Tasks.Task; + + /// + /// The provides export feature + /// + internal class GenericExporterService : IGenericExporterService + { + /// + /// Gets the injected + /// + private readonly ILoggerService logger; + + /// + /// Gets the injected + /// + private readonly IExcelWriter writer; + + /// + /// Initializes a new instance of + /// + /// The injected + /// The injected + public GenericExporterService(ILoggerService loggerService, IExcelWriter writer) + { + this.logger = loggerService; + this.writer = writer; + } + + /// + /// Exports data based on to a specific file + /// + /// The path to the output file to be used + /// The colleciton of that defines export configuration + public async Task ExportElementsAsync(string filePath, IReadOnlyList elementsConfigurations) + { + var exportableObjects = new Dictionary>(); + + foreach (var elementsConfiguration in elementsConfigurations) + { + exportableObjects[elementsConfiguration.ExportableElements[0].ElementKind] = elementsConfiguration.ExportableElements + .Select(x => new ExportableElement(x, elementsConfiguration.ExportableTaggedValues)) + .ToList(); + } + + this.logger.Log(LogLevel.Information, "Starting to export {0} kind of Elements, {1} Elements in total to file {2}", exportableObjects.Keys.Count, + exportableObjects.Values.Sum(x => x.Count), filePath); + + await this.writer.WriteAsync(exportableObjects, filePath); + + this.logger.Log(LogLevel.Information, "Export completed successfully"); + } + } +} diff --git a/EA-ModelKit/Services/Exporter/IGenericExporterService.cs b/EA-ModelKit/Services/Exporter/IGenericExporterService.cs new file mode 100644 index 0000000..92d8d42 --- /dev/null +++ b/EA-ModelKit/Services/Exporter/IGenericExporterService.cs @@ -0,0 +1,43 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Exporter +{ + using System.Collections.Generic; + + using EA; + + using EAModelKit.Model.Export; + + using Task = System.Threading.Tasks.Task; + + /// + /// The provides export feature + /// + internal interface IGenericExporterService + { + /// + /// Exports data based on to a specific file + /// + /// The path to the output file to be used + /// The colleciton of that defines export configuration + Task ExportElementsAsync(string filePath, IReadOnlyList elementsConfigurations); + } +} diff --git a/EA-ModelKit/Services/Selection/ISelectionService.cs b/EA-ModelKit/Services/Selection/ISelectionService.cs index 8ed5f19..925d9d4 100644 --- a/EA-ModelKit/Services/Selection/ISelectionService.cs +++ b/EA-ModelKit/Services/Selection/ISelectionService.cs @@ -34,6 +34,6 @@ internal interface ISelectionService /// /// The /// A read-only collection of selected s - public IReadOnlyCollection QuerySelectedElements(Repository repository); + public IReadOnlyList QuerySelectedElements(Repository repository); } } diff --git a/EA-ModelKit/Services/Selection/SelectionService.cs b/EA-ModelKit/Services/Selection/SelectionService.cs index ede5837..3b8c5e0 100644 --- a/EA-ModelKit/Services/Selection/SelectionService.cs +++ b/EA-ModelKit/Services/Selection/SelectionService.cs @@ -28,7 +28,8 @@ namespace EAModelKit.Services.Selection using EA; - using EAModelKit.Model.Wrappers; + using EAModelKit.Model.Slims; + using EAModelKit.Utilities; /// /// The provides information about Element that are currently selected or contained by a selected package, supporting nesting. @@ -40,7 +41,7 @@ internal class SelectionService : ISelectionService /// /// The /// A read-only collection of selected s - public IReadOnlyCollection QuerySelectedElements(Repository repository) + public IReadOnlyList QuerySelectedElements(Repository repository) { var selectedPackagesId = QueryCurrentlySelectedPackagesId(repository); @@ -54,7 +55,7 @@ public IReadOnlyCollection QuerySelectedElements(Repository repository) foreach (var selectedPackageId in selectedPackagesId) { - allSelectedPackages.AddRange(PackageWrapper.QueryContainedPackagesId(existingPackages, selectedPackageId)); + allSelectedPackages.AddRange(SlimPackage.QueryContainedPackagesId(existingPackages, selectedPackageId)); } var sqlQuery = $"SELECT Object_ID from t_object WHERE Package_ID in ({string.Join(",", allSelectedPackages)}) AND Object_Type != 'Package'"; @@ -63,7 +64,7 @@ public IReadOnlyCollection QuerySelectedElements(Repository repository) var xElement = XElement.Parse(sqlResponse); var xRows = xElement.Descendants("Row"); - var selectedElementsId = xRows.Select(r => int.Parse(r.Elements().First(MatchElementByName("Object_ID")).Value, + var selectedElementsId = xRows.Select(r => int.Parse(r.Elements().First(XElementHelper.MatchElementByName("Object_ID")).Value, CultureInfo.InvariantCulture)).Distinct().ToList(); return selectedElementsId.Count == 0 ? [] : repository.GetElementSet(string.Join(",", selectedElementsId), 0).OfType().ToList(); @@ -92,21 +93,11 @@ private static HashSet QueryCurrentlySelectedPackagesId(IDualRepository rep } /// - /// Function that verifies that an matches a name - /// - /// The name that have to match - /// A - private static Func MatchElementByName(string matchingName) - { - return x => string.Equals(x.Name.LocalName, matchingName, StringComparison.InvariantCultureIgnoreCase); - } - - /// - /// Queries all that exists inside the current EA project + /// Queries all that exists inside the current EA project /// /// The - /// A read-only collection of , for all Package that exists inside the current EA project - private static IReadOnlyCollection QueriesAllExistingPackages(IDualRepository repository) + /// A read-only collection of , for all Package that exists inside the current EA project + private static IReadOnlyCollection QueriesAllExistingPackages(IDualRepository repository) { const string sqlQuery = "SELECT package.Package_Id as PACKAGE_ID, package.Parent_Id as PARENT_ID from t_package package"; var sqlResponse = repository.SQLQuery(sqlQuery); @@ -115,10 +106,10 @@ private static IReadOnlyCollection QueriesAllExistingPackages(ID return [ - ..xRows.Select(r => new PackageWrapper + ..xRows.Select(r => new SlimPackage { - PackageId = int.Parse(r.Elements().First(MatchElementByName("PACKAGE_ID")).Value, CultureInfo.InvariantCulture), - ContainerId = int.Parse(r.Elements().First(MatchElementByName("PARENT_ID")).Value, CultureInfo.InvariantCulture) + PackageId = int.Parse(r.Elements().First(XElementHelper.MatchElementByName("PACKAGE_ID")).Value, CultureInfo.InvariantCulture), + ContainerId = int.Parse(r.Elements().First(XElementHelper.MatchElementByName("PARENT_ID")).Value, CultureInfo.InvariantCulture) }) ]; } diff --git a/EA-ModelKit/Services/Version/VersionService.cs b/EA-ModelKit/Services/Version/VersionService.cs index 1f548ae..d797198 100644 --- a/EA-ModelKit/Services/Version/VersionService.cs +++ b/EA-ModelKit/Services/Version/VersionService.cs @@ -33,7 +33,7 @@ internal class VersionService : IVersionService /// public VersionService() { - this.Version = Assembly.GetExecutingAssembly().GetName().Version; + this.Version = typeof(VersionService).Assembly.GetName().Version; } /// diff --git a/EA-ModelKit/Services/ViewBuilder/IViewBuilderService.cs b/EA-ModelKit/Services/ViewBuilder/IViewBuilderService.cs new file mode 100644 index 0000000..81cf32f --- /dev/null +++ b/EA-ModelKit/Services/ViewBuilder/IViewBuilderService.cs @@ -0,0 +1,50 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.ViewBuilder +{ + using DevExpress.Xpf.Core; + + /// + /// The provides build feature for views. + /// + internal interface IViewBuilderService + { + /// + /// Brings a to the user sight as a modal with it's associated view model of the provided type + /// + /// + /// Any + /// The View Model to associate with the view + /// The View Model instance + /// A value indicating the dialog result + bool? ShowDxDialog(TViewModel viewModel = null) where TView : ThemedWindow, new() where TViewModel : class; + + /// + /// Gets the location of the file to be saved + /// + /// the default filename + /// the extension of the file to create + /// the filter for the dialog + /// the index of the filter currently selected + /// the path of the file to create or null if the operation was cancelled. + string GetSaveFileDialog(string defaultFilename, string extension, string filter, int filterIndex); + } +} diff --git a/EA-ModelKit/Services/ViewBuilder/ViewBuilderService.cs b/EA-ModelKit/Services/ViewBuilder/ViewBuilderService.cs new file mode 100644 index 0000000..11400b3 --- /dev/null +++ b/EA-ModelKit/Services/ViewBuilder/ViewBuilderService.cs @@ -0,0 +1,91 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.ViewBuilder +{ + using System.Diagnostics.CodeAnalysis; + using System.Windows; + + using Autofac; + + using DevExpress.Xpf.Core; + + using Microsoft.Win32; + + /// + /// The provides build feature for views. + /// + [ExcludeFromCodeCoverage] + internal class ViewBuilderService : IViewBuilderService + { + /// + /// Brings a to the user sight as a modal with it's associated view model of the provided type + /// + /// + /// Any + /// The View Model to associate with the view + /// The View Model instance + /// A value indicating the dialog result + public bool? ShowDxDialog(TViewModel viewModel = default) where TView : ThemedWindow, new() where TViewModel : class + { + return BuildView(viewModel).ShowDialog(); + } + + /// + /// Gets the location of the file to be saved + /// + /// the default filename + /// the extension of the file to create + /// the filter for the dialog + /// the index of the filter currently selected + /// the path of the file to create or null if the operation was cancelled. + public string GetSaveFileDialog(string defaultFilename, string extension, string filter, int filterIndex) + { + var saveFileDialog = new SaveFileDialog + { + FileName = defaultFilename, + DefaultExt = extension, + Filter = filter + }; + + if (!string.IsNullOrEmpty(saveFileDialog.Filter)) + { + saveFileDialog.FilterIndex = filterIndex; + } + + var showDialog = saveFileDialog.ShowDialog(); + var result = showDialog != null && (bool)showDialog; + + return result ? saveFileDialog.FileName : null; + } + + /// + /// Builds up the view instance with it's associated view model of the provided type + /// + /// The view to show + /// The View Model to associate with the view + /// The View Model instance + private static TView BuildView(TViewModel viewModel) where TView : Window, new() where TViewModel : class + { + viewModel ??= App.Container.Resolve(); + return new TView { DataContext = viewModel }; + } + } +} diff --git a/EA-ModelKit/Services/Writer/ExcelWriter.cs b/EA-ModelKit/Services/Writer/ExcelWriter.cs new file mode 100644 index 0000000..c74b413 --- /dev/null +++ b/EA-ModelKit/Services/Writer/ExcelWriter.cs @@ -0,0 +1,121 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Writer +{ + using System.Collections.Generic; + using System.IO; + using System.Threading.Tasks; + + using ClosedXML.Excel; + + using EAModelKit.Model.Export; + + /// + /// The provides writting features to Excel format + /// + internal class ExcelWriter : IExcelWriter + { + /// + /// Writes the content of the dictionary into an Excel file, at the given location + /// + /// The Dictionary that contains all information that should be exported. + /// The export file path + /// A + public Task WriteAsync(IReadOnlyDictionary> exportableObjectsContent, string filePath) + { + return Task.Run(() => + { + using var workBook = new XLWorkbook(); + + foreach (var exportableObjects in exportableObjectsContent) + { + CreateExcelWorkSheet(workBook, exportableObjects.Key, exportableObjects.Value); + } + + using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write); + workBook.SaveAs(fileStream); + }); + } + + /// + /// + /// + /// + /// + private static void CreateExcelWorkSheet(IXLWorkbook workBook, string workSheetName, IReadOnlyList exportableObjects) + { + var headerContent = exportableObjects[0].Headers; + var workSheet = workBook.AddWorksheet(workSheetName.Replace("/", "")); + + WriteHeader(workSheet, headerContent); + WorkSheetContent(workSheet, headerContent, exportableObjects); + + const double minWidth = 20; + const double maxWidth = 70; + + foreach (var item in workSheet.ColumnsUsed()) + { + item.AdjustToContents(minWidth, maxWidth); + } + } + + /// + /// Writes the content of all into a + /// + /// The + /// The header content + /// The collection of that have to be written + private static void WorkSheetContent(IXLWorksheet workSheet, IReadOnlyList headerContent, IReadOnlyList exportableObjects) + { + var rowIndex = 2; + + foreach (var exportableObject in exportableObjects) + { + var row = workSheet.Row(rowIndex++); + row.Style.Alignment.WrapText = true; + + for (var columnIndex = 1; columnIndex <= headerContent.Count; columnIndex++) + { + var cell = row.Cell(columnIndex); + cell.Value = exportableObject[headerContent[columnIndex - 1]]; + } + } + } + + /// + /// Writes the header (first row) of a with the + /// + /// The + /// The content of the header + private static void WriteHeader(IXLWorksheet workSheet, IReadOnlyList headerContent) + { + var headerRow = workSheet.Row(1); + headerRow.Style.Font.Bold = true; + workSheet.SheetView.FreezeRows(1); + + for (var headerIndex = 1; headerIndex <= headerContent.Count; headerIndex++) + { + var cell = headerRow.Cell(headerIndex); + cell.Value = headerContent[headerIndex - 1]; + } + } + } +} diff --git a/EA-ModelKit/Services/Writer/IExcelWriter.cs b/EA-ModelKit/Services/Writer/IExcelWriter.cs new file mode 100644 index 0000000..2273a1e --- /dev/null +++ b/EA-ModelKit/Services/Writer/IExcelWriter.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Services.Writer +{ + using System.Collections.Generic; + using System.Threading.Tasks; + + using EAModelKit.Model.Export; + + /// + /// The provides writting features to Excel format + /// + internal interface IExcelWriter + { + /// + /// Writes the content of the dictionary into an Excel file, at the given location + /// + /// The Dictionary that contains all information that should be exported. + /// The export file path + /// A + Task WriteAsync(IReadOnlyDictionary> exportableObjectsContent, string filePath); + } +} diff --git a/EA-ModelKit/Utilities/Dialogs/BaseDialogViewModel.cs b/EA-ModelKit/Utilities/Dialogs/BaseDialogViewModel.cs new file mode 100644 index 0000000..c95d1cc --- /dev/null +++ b/EA-ModelKit/Utilities/Dialogs/BaseDialogViewModel.cs @@ -0,0 +1,88 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Utilities.Dialogs +{ + using System.Reactive; + + using EAModelKit.Behaviors; + using EAModelKit.Services.Logger; + + using ReactiveUI; + + /// + /// The provides base behavior and properties definition for any viewmodel linked to a dialog + /// + internal abstract class BaseDialogViewModel: DisposableReactiveObject, IBaseDialogViewModel + { + /// + /// Backing field for + /// + private bool isBusy; + + /// + /// Backing field for + /// + private bool isTopMost = true; + + /// + /// Gets the injected + /// + protected ILoggerService LoggerService { get; private set; } + + /// + /// Initialize a new instance of + /// + /// The + protected BaseDialogViewModel(ILoggerService loggerService) + { + this.LoggerService = loggerService; + this.CancelCommand = ReactiveCommand.Create(() => this.CloseWindowBehavior?.Close()); + } + + /// + /// Gets the that cancel the operation + /// + public ReactiveCommand CancelCommand { get; } + + /// + /// Gets or sets the + /// + public ICloseWindowBehavior CloseWindowBehavior { get; set; } + + /// + /// Asserts that the current viewModel is busy with a task + /// + public bool IsBusy + { + get => this.isBusy; + set => this.RaiseAndSetIfChanged(ref this.isBusy, value); + } + + /// + /// Asserts that the view should be the topmost or not + /// + public bool IsTopMost + { + get => this.isTopMost; + set => this.RaiseAndSetIfChanged(ref this.isTopMost, value); + } + } +} diff --git a/EA-ModelKit/Utilities/Dialogs/IBaseDialogViewModel.cs b/EA-ModelKit/Utilities/Dialogs/IBaseDialogViewModel.cs new file mode 100644 index 0000000..3f4ac07 --- /dev/null +++ b/EA-ModelKit/Utilities/Dialogs/IBaseDialogViewModel.cs @@ -0,0 +1,40 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Utilities.Dialogs +{ + using System; + + /// + /// The provides base behavior and properties definition for any viewmodel linked to a dialog + /// + internal interface IBaseDialogViewModel: IDisposable, ICloseableWindowViewModel + { + /// + /// Asserts that the current viewModel is busy with a task + /// + bool IsBusy { get; set; } + + /// + /// Asserts that the associated view should be the topmost or not + /// + bool IsTopMost { get; set; } + } +} diff --git a/EA-ModelKit/Utilities/Dialogs/ICloseableWindowViewModel.cs b/EA-ModelKit/Utilities/Dialogs/ICloseableWindowViewModel.cs new file mode 100644 index 0000000..bdf54e7 --- /dev/null +++ b/EA-ModelKit/Utilities/Dialogs/ICloseableWindowViewModel.cs @@ -0,0 +1,36 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Utilities.Dialogs +{ + using EAModelKit.Behaviors; + + /// + /// The provides interface definition for any ViewModel that requires + /// an attachable + /// + internal interface ICloseableWindowViewModel + { + /// + /// Gets or sets the behavior instance + /// + ICloseWindowBehavior CloseWindowBehavior { get; set; } + } +} diff --git a/EA-ModelKit/Utilities/DisposableReactiveObject.cs b/EA-ModelKit/Utilities/DisposableReactiveObject.cs new file mode 100644 index 0000000..da276e8 --- /dev/null +++ b/EA-ModelKit/Utilities/DisposableReactiveObject.cs @@ -0,0 +1,62 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Utilities +{ + using System; + using System.Collections.Generic; + + using ReactiveUI; + + /// + /// Utility class that implements the interface and that is a + /// + public abstract class DisposableReactiveObject : ReactiveObject, IDisposable + { + /// + /// A collection of + /// + protected readonly List Disposables = []; + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + this.Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// Value asserting if this component should dispose or not + protected virtual void Dispose(bool disposing) + { + if (!disposing) + { + return; + } + + this.Disposables.ForEach(x => x.Dispose()); + this.Disposables.Clear(); + } + } +} diff --git a/EA-ModelKit/Utilities/XElementHelper.cs b/EA-ModelKit/Utilities/XElementHelper.cs new file mode 100644 index 0000000..479c133 --- /dev/null +++ b/EA-ModelKit/Utilities/XElementHelper.cs @@ -0,0 +1,41 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.Utilities +{ + using System; + using System.Xml.Linq; + + /// + /// Helper class for query on + /// + internal static class XElementHelper + { + /// + /// Function that verifies that an matches a name + /// + /// The name that have to match + /// A + public static Func MatchElementByName(string matchingName) + { + return x => string.Equals(x.Name.LocalName, matchingName, StringComparison.InvariantCultureIgnoreCase); + } + } +} diff --git a/EA-ModelKit/ViewModels/Exporter/GenericExportSetupViewModel.cs b/EA-ModelKit/ViewModels/Exporter/GenericExportSetupViewModel.cs new file mode 100644 index 0000000..80cadc9 --- /dev/null +++ b/EA-ModelKit/ViewModels/Exporter/GenericExportSetupViewModel.cs @@ -0,0 +1,122 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.ViewModels.Exporter +{ + using System; + using System.Collections.Generic; + using System.Linq; + + using EA; + + using EAModelKit.Model.Slims; + + using ReactiveUI; + + /// + /// The provides setup functionalities for values that have to be exported for Element of the same kind + /// + internal class GenericExportSetupViewModel : ReactiveObject, IGenericExportSetupViewModel + { + /// + /// Backing field for + /// + private IEnumerable availableTaggedValuesForExport; + + /// + /// Backing field for + /// + private IEnumerable selectedTaggedValuesForExport; + + /// + /// Backing field for + /// + private bool shouldBeExported = true; + + /// + /// Initializes a new instance of the class. + /// + /// The read-only list of + public GenericExportSetupViewModel(IReadOnlyList elements) + { + if (elements == null) + { + throw new ArgumentNullException(nameof(elements)); + } + + if (elements.Count == 0) + { + throw new ArgumentException("The collection of elements cannot be empty", nameof(elements)); + } + + this.ElementKind = elements[0].ElementKind; + + this.AvailableTaggedValuesForExport = elements + .SelectMany(x => x.TaggedValues.Keys) + .Distinct() + .OrderBy(x => x); + + this.SelectedTaggedValuesForExport = [..this.AvailableTaggedValuesForExport]; + this.ExportableElements = elements; + } + + /// + /// Gets the collection of exportable tied to this setup + /// + public IReadOnlyList ExportableElements { get; } + + /// + /// Asserts that any TaggedValue are available for export + /// + public bool HaveAnyTaggedValues => this.AvailableTaggedValuesForExport.Any(); + + /// + /// Gets or sets the collection of selected TaggedValues name that are selected for export + /// + public IEnumerable SelectedTaggedValuesForExport + { + get => this.selectedTaggedValuesForExport; + set => this.RaiseAndSetIfChanged(ref this.selectedTaggedValuesForExport, value); + } + + /// + /// Gets the associated Element Kind + /// + public string ElementKind { get; } + + /// + /// Asserts that all for the current stereotype should be exported or not + /// + public bool ShouldBeExported + { + get => this.shouldBeExported; + set => this.RaiseAndSetIfChanged(ref this.shouldBeExported, value); + } + + /// + /// Gets or sets the collection of available TaggedValues name that could be exported + /// + public IEnumerable AvailableTaggedValuesForExport + { + get => this.availableTaggedValuesForExport; + set => this.RaiseAndSetIfChanged(ref this.availableTaggedValuesForExport, value); + } + } +} diff --git a/EA-ModelKit/ViewModels/Exporter/GenericExporterViewModel.cs b/EA-ModelKit/ViewModels/Exporter/GenericExporterViewModel.cs new file mode 100644 index 0000000..38c0bf2 --- /dev/null +++ b/EA-ModelKit/ViewModels/Exporter/GenericExporterViewModel.cs @@ -0,0 +1,217 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.ViewModels.Exporter +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reactive; + using System.Windows.Input; + + using DynamicData; + using DynamicData.Binding; + + using EA; + + using EAModelKit.Model.Export; + using EAModelKit.Model.Slims; + using EAModelKit.Services.Cache; + using EAModelKit.Services.Exporter; + using EAModelKit.Services.Logger; + using EAModelKit.Services.ViewBuilder; + using EAModelKit.Utilities.Dialogs; + + using ReactiveUI; + + using Task = System.Threading.Tasks.Task; + + /// + /// The is a ViewModel that provides user interaction to export data + /// + internal class GenericExporterViewModel : BaseDialogViewModel, IGenericExporterViewModel + { + /// + /// The injected + /// + private readonly ICacheService cacheService; + + /// + /// The injected + /// + private readonly IViewBuilderService viewBuilderService; + + /// + /// Backing field for + /// + private bool canProceed; + + /// + /// Backing field for + /// + private string selectedFilePath; + + /// + /// Gets the injected + /// + private readonly IGenericExporterService exporterService; + + /// + /// Initialize a new instance of + /// + /// The + /// The injected + /// The injected + /// The injected + public GenericExporterViewModel(ILoggerService loggerService, ICacheService cacheService, IViewBuilderService viewBuilderService, + IGenericExporterService exporterService) : base(loggerService) + { + this.cacheService = cacheService; + this.viewBuilderService = viewBuilderService; + this.exporterService = exporterService; + } + + /// + /// Gets the path to the file that should be use for export + /// + public string SelectedFilePath + { + get => this.selectedFilePath; + private set => this.RaiseAndSetIfChanged(ref this.selectedFilePath, value); + } + + /// + /// Asserts if the can be executed or not + /// + public bool CanProceed + { + get => this.canProceed; + set => this.RaiseAndSetIfChanged(ref this.canProceed, value); + } + + /// + /// Gets the of + /// + public SourceList ExportSetups { get; } = new(); + + /// + /// Gets the that allows the selection of the output file + /// + public ReactiveCommand OutputFileCommand { get; private set; } + + /// + /// Gets the that allows the export of data + /// + public ReactiveCommand ExportCommand { get; private set; } + + /// + /// Initialies properties of the ViewModel + /// + /// A collection of that have been selected for export + public void InitializeViewModel(IReadOnlyList elements) + { + if (elements == null) + { + throw new ArgumentNullException(nameof(elements)); + } + + if (elements.Count == 0) + { + throw new ArgumentException("The collection of Element to export can not be empty", nameof(elements)); + } + + var taggedValues = this.cacheService.GetTaggedValues([..elements.Select(x => x.ElementID)]) + .GroupBy(x => x.ContainerId) + .ToDictionary(x => x.Key, x => x.ToList()); + + var slimElements = elements.Select(x => new SlimElement(x, taggedValues.TryGetValue(x.ElementID, out var existingTaggedValues) + ? existingTaggedValues + : [])); + + this.ExportSetups.AddRange(slimElements.GroupBy(x => x.ElementKind) + .Select(e => new GenericExportSetupViewModel(e.ToList()))); + + this.InitializeObservablesAndCommands(); + } + + /// + /// Initializes and for this view model + /// + private void InitializeObservablesAndCommands() + { + this.OutputFileCommand = ReactiveCommand.Create(this.OnOutputFileSelect); + + this.Disposables.Add(this.ExportSetups.Connect().WhenPropertyChanged(x => x.ShouldBeExported) + .Subscribe(_ => this.ComputeCanProceed())); + + this.Disposables.Add(this.WhenPropertyChanged(x => x.SelectedFilePath).Subscribe(_ => this.ComputeCanProceed())); + this.ExportCommand = ReactiveCommand.CreateFromTask(this.OnExportAsync, this.WhenAnyValue(x => x.CanProceed)); + } + + /// + /// Proceed with the export functionalitis following the user-defined setup + /// + private async Task OnExportAsync() + { + this.IsBusy = true; + + try + { + var exportConfiguration = this.ExportSetups.Items.Where(x => x.ShouldBeExported) + .Select(x => new GenericExportConfiguration(x.ExportableElements, x.SelectedTaggedValuesForExport.ToList())); + + await this.exporterService.ExportElementsAsync(this.selectedFilePath, [..exportConfiguration]); + this.CloseWindowBehavior.Close(); + } + catch (Exception ex) + { + this.LoggerService.LogException(ex, "An error occured while exporting data."); + } + finally + { + this.IsBusy = false; + } + } + + /// + /// Computes the value of + /// + private void ComputeCanProceed() + { + if (string.IsNullOrEmpty(this.SelectedFilePath)) + { + this.CanProceed = false; + return; + } + + this.CanProceed = this.ExportSetups.Items.Any(x => x.ShouldBeExported); + } + + /// + /// Handles the behavior of the output file selection + /// + private void OnOutputFileSelect() + { + this.IsTopMost = false; + this.SelectedFilePath = this.viewBuilderService.GetSaveFileDialog("Export", ".xlsx", "Excel File|*.xlsx", 0); + this.IsTopMost = true; + } + } +} diff --git a/EA-ModelKit/ViewModels/Exporter/IGenericExportSetupViewModel.cs b/EA-ModelKit/ViewModels/Exporter/IGenericExportSetupViewModel.cs new file mode 100644 index 0000000..b7db725 --- /dev/null +++ b/EA-ModelKit/ViewModels/Exporter/IGenericExportSetupViewModel.cs @@ -0,0 +1,65 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.ViewModels.Exporter +{ + using System.Collections.Generic; + using System.ComponentModel; + + using EA; + + using EAModelKit.Model.Slims; + + /// + /// The provides setup functionalities for values that have to be exported for Element of the same kind + /// + internal interface IGenericExportSetupViewModel: INotifyPropertyChanged + { + /// + /// Gets or sets the collection of selected TaggedValues name that are selected for export + /// + IEnumerable SelectedTaggedValuesForExport { get; set; } + + /// + /// Gets the associated Element Kind + /// + string ElementKind { get; } + + /// + /// Asserts that all for the current stereotype should be exported or not + /// + bool ShouldBeExported { get; set; } + + /// + /// Gets or sets the collection of available TaggedValues name that could be exported + /// + IEnumerable AvailableTaggedValuesForExport { get; set; } + + /// + /// Asserts that any TaggedValue are available for export + /// + bool HaveAnyTaggedValues { get; } + + /// + /// Gets the collection of exportable tied to this setup + /// + IReadOnlyList ExportableElements { get; } + } +} diff --git a/EA-ModelKit/ViewModels/Exporter/IGenericExporterViewModel.cs b/EA-ModelKit/ViewModels/Exporter/IGenericExporterViewModel.cs new file mode 100644 index 0000000..708385e --- /dev/null +++ b/EA-ModelKit/ViewModels/Exporter/IGenericExporterViewModel.cs @@ -0,0 +1,70 @@ +// ------------------------------------------------------------------------------------------------- +// +// +// Copyright (C) 2024 Starion Group S.A. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +// +// ----------------------------------------------------------------------------------------------- + +namespace EAModelKit.ViewModels.Exporter +{ + using System.Collections.Generic; + using System.Reactive; + + using DynamicData; + + using EA; + + using EAModelKit.Utilities.Dialogs; + + using ReactiveUI; + + /// + /// The is a ViewModel that provides user interaction to export data + /// + internal interface IGenericExporterViewModel : IBaseDialogViewModel + { + /// + /// Gets the of + /// + SourceList ExportSetups { get; } + + /// + /// Gets the path to the file that should be use for export + /// + string SelectedFilePath { get; } + + /// + /// Gets the that allows the selection of the output file + /// + ReactiveCommand OutputFileCommand { get; } + + /// + /// Asserts if the can be executed or not + /// + bool CanProceed { get; set; } + + /// + /// Gets the that allows the export of data + /// + ReactiveCommand ExportCommand { get; } + + /// + /// Initialies properties of the ViewModel + /// + /// A collection of that have been selected for export + void InitializeViewModel(IReadOnlyList elements); + } +} diff --git a/EA-ModelKit/Views/Export/GenericExport.xaml b/EA-ModelKit/Views/Export/GenericExport.xaml new file mode 100644 index 0000000..8538a7a --- /dev/null +++ b/EA-ModelKit/Views/Export/GenericExport.xaml @@ -0,0 +1,119 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Selected Output File: + + +