From 6151a3dfdb1df881a0b2f235dcdb6852e93fe791 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 20 Dec 2024 12:33:46 +0100 Subject: [PATCH 01/23] [Add] MultiModelEditor --- COMET.Web.Common/Utilities/CopyCreator.cs | 96 +++++ .../Utilities/CopyElementDefinitionCreator.cs | 185 ++++++++++ COMET.Web.Common/Utilities/ThingCreator.cs | 334 ++++++++++++++++++ .../Components/OpenModelViewModel.cs | 5 + COMET.Web.Common/wwwroot/css/styles.css | 11 +- .../ElementDefinitionTree.razor | 92 +++++ .../ElementDefinitionTree.razor.cs | 256 ++++++++++++++ .../ElementDefinitionTree.razor.css | 5 + .../MultiModelEditor/MultiModelEditor.razor | 96 +++++ .../MultiModelEditor.razor.cs | 244 +++++++++++++ .../MultiModelEditor.razor.css | 3 + .../Extensions/ServiceCollectionExtensions.cs | 3 + COMETwebapp/Model/Applications.cs | 11 + COMETwebapp/Utilities/WebAppConstantValues.cs | 5 + .../ElementDefinitionTreeViewModel.cs | 254 +++++++++++++ .../IElementDefinitionTreeViewModel.cs | 71 ++++ .../IMultiModelEditorViewModel.cs | 110 ++++++ .../MultiModelEditorViewModel.cs | 330 +++++++++++++++++ .../Rows/ElementBaseTreeRowViewModel.cs | 93 +++++ .../ElementDefinitionTreeTreeRowViewModel.cs | 103 ++++++ .../Rows/ElementUsageTreeTreeRowViewModel.cs | 60 ++++ 21 files changed, 2366 insertions(+), 1 deletion(-) create mode 100644 COMET.Web.Common/Utilities/CopyCreator.cs create mode 100644 COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs create mode 100644 COMET.Web.Common/Utilities/ThingCreator.cs create mode 100644 COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor create mode 100644 COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs create mode 100644 COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.css create mode 100644 COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor create mode 100644 COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs create mode 100644 COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.css create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs create mode 100644 COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs diff --git a/COMET.Web.Common/Utilities/CopyCreator.cs b/COMET.Web.Common/Utilities/CopyCreator.cs new file mode 100644 index 00000000..bba23e52 --- /dev/null +++ b/COMET.Web.Common/Utilities/CopyCreator.cs @@ -0,0 +1,96 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// 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 COMET.Web.Common.Utilities +{ + using System.Linq; + using System.Threading.Tasks; + + using CDP4Common.CommonData; + using CDP4Common.EngineeringModelData; + + using CDP4Dal; + using CDP4Dal.Operations; + using CDP4Dal.Permission; + + /// + /// The class responsible for copy operations + /// + public class CopyCreator + { + /// + /// The in which the copy is performed + /// + private readonly ISession session; + + /// + /// Initializes a new instance of the class + /// + /// The associated + public CopyCreator(ISession session) + { + this.session = session; + } + + /// + /// Perform the copy operation of an + /// + /// The to copy + /// The target container + public async Task Copy(ElementDefinition elementDefinition, Iteration targetIteration) + { + // copy the payload to this iteration + var copyOperationHelper = new CopyPermissionHelper(this.session, false); + var copyPermissionResult = await copyOperationHelper.ComputeCopyPermissionAsync(elementDefinition, targetIteration); + + if (copyPermissionResult.ErrorList.Any()) + { + await this.WriteCopyOperation(elementDefinition, targetIteration, OperationKind.CopyKeepValuesChangeOwner); + } + else if (copyPermissionResult.CopyableThings.Any()) + { + await this.WriteCopyOperation(elementDefinition, targetIteration, OperationKind.CopyKeepValuesChangeOwner); + } + } + + /// + /// Create and write the copy operation + /// + /// The to copy + /// The target container + /// The + private async Task WriteCopyOperation(Thing thingToCopy, Thing targetContainer, OperationKind operationKind) + { + var clone = thingToCopy.Clone(false); + var containerClone = targetContainer.Clone(false); + + var transactionContext = TransactionContextResolver.ResolveContext(targetContainer); + var transaction = new ThingTransaction(transactionContext, containerClone); + transaction.Copy(clone, containerClone, operationKind); + + await this.session.Write(transaction.FinalizeTransaction()); + } + } +} \ No newline at end of file diff --git a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs new file mode 100644 index 00000000..a30fd89d --- /dev/null +++ b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs @@ -0,0 +1,185 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// 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 COMET.Web.Common.Utilities +{ + using System; + using System.Collections.Generic; + using System.Threading.Tasks; + + using CDP4Common.EngineeringModelData; + + using CDP4Dal; + using CDP4Dal.Operations; + + /// + /// The class responsible for copying Element Definition + /// + public class CopyElementDefinitionCreator + { + /// + /// The string added to the element definition name + /// + private const string CopyAffix = " - Copy"; + + /// + /// The in which the copy is performed + /// + private readonly ISession session; + + /// + /// The original-clone map + /// + private readonly Dictionary groupMap = new Dictionary(); + + /// + /// The original-clone map + /// + private readonly Dictionary valueSetMap = new Dictionary(); + + /// + /// Initializes a new instance of the class + /// + /// The associated + public CopyElementDefinitionCreator(ISession session) + { + this.session = session; + } + + /// + /// Perform the copy operation of an + /// + /// The to copy + /// Do we need to copy the ElementUsages also? + public async Task Copy(ElementDefinition elementDefinition, bool areUsagesCopied) + { + var iterationClone = (Iteration)elementDefinition.Container.Clone(false); + var transactionContext = TransactionContextResolver.ResolveContext(iterationClone); + var transaction = new ThingTransaction(transactionContext, iterationClone); + + var clone = elementDefinition.Clone(true); + clone.Iid = Guid.NewGuid(); + clone.Name += CopyAffix; + + if (!areUsagesCopied) + { + clone.ContainedElement.Clear(); + } + + this.ResolveReferences(elementDefinition, clone); + iterationClone.Element.Add(clone); + + transaction.CopyDeep(clone); + await this.session.Write(transaction.FinalizeTransaction()); + } + + /// + /// Resolve the references of the copy + /// + /// The original + /// The clone + private void ResolveReferences(ElementDefinition original, ElementDefinition deepClone) + { + // Order of the item in a list is should be kept when cloning + // register mapping between original and copy + for (var i = 0; i < original.ParameterGroup.Count; i++) + { + this.groupMap.Add(original.ParameterGroup[i], deepClone.ParameterGroup[i]); + } + + for (var i = 0; i < deepClone.Parameter.Count; i++) + { + var originalParameter = original.Parameter[i]; + var cloneParameter = deepClone.Parameter[i]; + + for (var j = 0; j < originalParameter.ValueSet.Count; j++) + { + this.valueSetMap.Add(originalParameter.ValueSet[j], cloneParameter.ValueSet[j]); + } + } + + for (var i = 0; i < deepClone.ContainedElement.Count; i++) + { + var originalUsage = original.ContainedElement[i]; + var cloneUsage = deepClone.ContainedElement[i]; + + for (var j = 0; j < originalUsage.ParameterOverride.Count; j++) + { + var originalOverride = originalUsage.ParameterOverride[j]; + var cloneOverride = cloneUsage.ParameterOverride[j]; + + for (var k = 0; k < originalOverride.ValueSet.Count; k++) + { + this.valueSetMap.Add(originalOverride.ValueSet[k], cloneOverride.ValueSet[k]); + } + } + } + + // Resolve references + foreach (var group in this.groupMap.Values) + { + if (group.ContainingGroup != null) + { + // use the mapped group + group.ContainingGroup = this.groupMap[group.ContainingGroup]; + } + } + + // fix the group of the cloned parameters + foreach (var parameter in deepClone.Parameter) + { + if (parameter.Group != null) + { + parameter.Group = this.groupMap[parameter.Group]; + } + + foreach (var parameterSubscription in parameter.ParameterSubscription) + { + foreach (var parameterSubscriptionValueSet in parameterSubscription.ValueSet) + { + parameterSubscriptionValueSet.SubscribedValueSet = + this.valueSetMap[parameterSubscriptionValueSet.SubscribedValueSet]; + } + } + } + + // fix the references of the subscription value set + foreach (var elementUsage in deepClone.ContainedElement) + { + foreach (var parameterOverride in elementUsage.ParameterOverride) + { + foreach (var parameterSubscription in parameterOverride.ParameterSubscription) + { + foreach (var parameterSubscriptionValueSet in parameterSubscription.ValueSet) + { + parameterSubscriptionValueSet.SubscribedValueSet = + this.valueSetMap[parameterSubscriptionValueSet.SubscribedValueSet]; + } + } + } + } + } + } +} \ No newline at end of file diff --git a/COMET.Web.Common/Utilities/ThingCreator.cs b/COMET.Web.Common/Utilities/ThingCreator.cs new file mode 100644 index 00000000..95f1f814 --- /dev/null +++ b/COMET.Web.Common/Utilities/ThingCreator.cs @@ -0,0 +1,334 @@ +// ---------------// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2023-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// 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 COMET.Web.Common.Utilities +{ + using System; + using System.Linq; + using System.Threading.Tasks; + + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using CDP4Dal; + using CDP4Dal.Operations; + + /// + /// The purpose of the is to encapsulate create logic for different things + /// + public class ThingCreator + { + /// + /// Create a new + /// + /// + /// The container of the that is to be created. + /// + /// + /// The that the is to be grouped in. + /// + /// + /// The that the new references + /// + /// + /// The that the references in case the is a + /// + /// + /// The that is the owner of the that is to be created. + /// + /// + /// The in which the current is to be added + /// + public async Task CreateParameter(ElementDefinition elementDefinition, ParameterGroup group, ParameterType parameterType, MeasurementScale measurementScale, DomainOfExpertise owner, ISession session) + { + if (elementDefinition == null) + { + throw new ArgumentNullException(nameof(elementDefinition), "The container ElementDefinition may not be null"); + } + + if (parameterType == null) + { + throw new ArgumentNullException(nameof(parameterType), "The ParameterType may not be null"); + } + + if (owner == null) + { + throw new ArgumentNullException(nameof(owner), "The owner DomainOfExpertise may not be null"); + } + + if (session == null) + { + throw new ArgumentNullException(nameof(session), "The session may not be null"); + } + + var parameter = new Parameter(Guid.NewGuid(), null, null) + { + Owner = owner, + ParameterType = parameterType, + Scale = measurementScale, + Group = group + }; + + var clone = elementDefinition.Clone(false); + clone.Parameter.Add(parameter); + + var transactionContext = TransactionContextResolver.ResolveContext(elementDefinition); + var transaction = new ThingTransaction(transactionContext, clone); + transaction.Create(parameter); + + try + { + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Create a new + /// + /// + /// The container of the that is to be created. + /// + /// + /// The that the new references. + /// + /// + /// The in which the new is to be added + /// + public async Task CreateUserRuleVerification(RuleVerificationList ruleVerificationList, Rule rule, ISession session) + { + if (ruleVerificationList == null) + { + throw new ArgumentNullException(nameof(ruleVerificationList), "The ruleVerificationList must not be null"); + } + + if (rule == null) + { + throw new ArgumentNullException(nameof(rule), "The rule must not be null"); + } + + if (session == null) + { + throw new ArgumentNullException(nameof(session), "The session may not be null"); + } + + var userRuleVerification = new UserRuleVerification(Guid.NewGuid(), null, null) + { + Rule = rule, + IsActive = false, + Status = RuleVerificationStatusKind.NONE + }; + + var clone = ruleVerificationList.Clone(false); + clone.RuleVerification.Add(userRuleVerification); + + var transactionContext = TransactionContextResolver.ResolveContext(ruleVerificationList); + var transaction = new ThingTransaction(transactionContext, clone); + transaction.Create(userRuleVerification); + + try + { + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Create a new + /// + /// + /// The container of the that is to be created. + /// + /// + /// The name for the + /// + /// + /// The in which the new is to be added + /// + public async Task CreateBuiltInRuleVerification(RuleVerificationList ruleVerificationList, string name, ISession session) + { + if (ruleVerificationList == null) + { + throw new ArgumentNullException(nameof(ruleVerificationList), "The ruleVerificationList must not be null"); + } + + if (string.IsNullOrEmpty(name)) + { + throw new ArgumentException("The name may not be null or empty"); + } + + if (session == null) + { + throw new ArgumentNullException(nameof(session), "The session may not be null"); + } + + var builtInRuleVerification = new BuiltInRuleVerification(Guid.NewGuid(), null, null) + { + Name = name, + IsActive = false, + Status = RuleVerificationStatusKind.NONE + }; + + var clone = ruleVerificationList.Clone(false); + clone.RuleVerification.Add(builtInRuleVerification); + + var transactionContext = TransactionContextResolver.ResolveContext(ruleVerificationList); + var transaction = new ThingTransaction(transactionContext, clone); + transaction.Create(builtInRuleVerification); + + try + { + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Create a new + /// + /// + /// The container of the that is to be created. + /// + /// + /// The referenced of the that is to be created. + /// + /// + /// The that is the owner of the that is to be created. + /// + /// + /// The in which the current is to be added + /// + public async Task CreateElementUsage(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) + { + if (container == null) + { + throw new ArgumentNullException(nameof(container), "The container must not be null"); + } + + if (referencedDefinition == null) + { + throw new ArgumentNullException(nameof(referencedDefinition), "The referencedDefinition must not be null"); + } + + if (owner == null) + { + throw new ArgumentNullException(nameof(owner), "The owner must not be null"); + } + + if (session == null) + { + throw new ArgumentNullException(nameof(session), "The session may not be null"); + } + + var clone = container.Clone(false); + var usage = new ElementUsage + { + Name = referencedDefinition.Name, + ShortName = referencedDefinition.ShortName, + Owner = owner, + ElementDefinition = referencedDefinition + }; + + clone.ContainedElement.Add(usage); + + var transactionContext = TransactionContextResolver.ResolveContext(container); + var transaction = new ThingTransaction(transactionContext, clone); + transaction.Create(usage); + + try + { + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); + } + catch (Exception ex) + { + throw; + } + } + + /// + /// Method for creating a for requirement verification between a and a . + /// + /// The for which the will be created + /// The for which the will be created + /// The that acts as the source of the + /// The that acts as the target of the + /// An awaitable + public async Task CreateBinaryRelationshipForRequirementVerification(ISession session, Iteration iteration, ParameterOrOverrideBase parameter, RelationalExpression relationalExpression) + { + session.OpenIterations.TryGetValue(iteration, out var tuple); + + var binaryRelationship = new BinaryRelationship(Guid.NewGuid(), null, null) { Owner = tuple?.Item1 }; + + var transaction = new ThingTransaction(TransactionContextResolver.ResolveContext(relationalExpression)); + + binaryRelationship.Container = iteration; + binaryRelationship.Source = parameter; + binaryRelationship.Target = relationalExpression; + + var iterationClone = iteration.Clone(false); + iterationClone.Relationship.Add(binaryRelationship); + transaction.CreateOrUpdate(iterationClone); + transaction.Create(binaryRelationship); + + try + { + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); + } + catch (Exception ex) + { + } + } + + /// + /// Checks if creating a for requirement verification is allowed for these two objects + /// + /// The + /// The + /// True if creation is allowed + public bool IsCreateBinaryRelationshipForRequirementVerificationAllowed(ParameterOrOverrideBase parameter, RelationalExpression relationalExpression) + { + return (parameter.ParameterType.Iid == relationalExpression.ParameterType.Iid) && + (!(parameter.ParameterType is QuantityKind) || (parameter.Scale == relationalExpression.Scale)) && + !relationalExpression.QueryRelationships + .Any( + x => x is BinaryRelationship relationship + && (relationship.Source.Iid == parameter.Iid)); + } + } +} diff --git a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs index 170f5ddb..d256e177 100644 --- a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs +++ b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs @@ -222,6 +222,11 @@ public virtual async Task> OpenSession() return Result.Fail(["The selected iteration and the domain of expertise should not be null"]); } + if (this.sessionService.OpenIterations.Items.Any(x => x.IterationSetup.Iid == this.SelectedIterationSetup.IterationSetupId)) + { + return Result.Fail(["The selected iteration is already openened"]); + } + this.IsOpeningSession = true; var result = await this.sessionService.ReadIteration(this.SelectedEngineeringModel.IterationSetup diff --git a/COMET.Web.Common/wwwroot/css/styles.css b/COMET.Web.Common/wwwroot/css/styles.css index 83c84789..c82e3775 100644 --- a/COMET.Web.Common/wwwroot/css/styles.css +++ b/COMET.Web.Common/wwwroot/css/styles.css @@ -606,4 +606,13 @@ left: 5px; /* Position of the icon */ color: #757575; /* Icon color */ font-size: 16px; /* Icon size */ - } \ No newline at end of file + } + +.treeview-drag-over { + background-color: burlywood; +} + +.treeview-item-drag-over { + background-color: burlywood; + box-shadow: 0px 0px 2px 10px burlywood; +} \ No newline at end of file diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor new file mode 100644 index 00000000..9327cfdd --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -0,0 +1,92 @@ +@using COMET.Web.Common.Model +@using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +@using CDP4JsonSerializer.JsonConverter +@inject IJSRuntime JSRuntime + +
+@if (this.IsModelSelectionEnabled) +{ + + +} +else +{ +
@this.ViewModel.Description
+} +
+
+ Drop here to create new element... +
+ + + @{ + var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; + var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); + var isTopElement = dataItem is ElementDefinitionTreeTreeRowViewModel { IsTopElement: true }; + + var style = isTopElement ? "font-weight-bold" : string.Empty; + } + + @if (draggable) + { +
+ @dataItem.ElementName +
+ } + else + { +
+ @dataItem.ElementName +
+ } +
+ + + + +
diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs new file mode 100644 index 00000000..f700d27f --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -0,0 +1,256 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Components.MultiModelEditor +{ + using CDP4Common.EngineeringModelData; + + using COMETwebapp.ViewModels.Components.MultiModelEditor; + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using DevExpress.Blazor; + + using Microsoft.AspNetCore.Components; + + /// + /// Support class for the component + /// + public partial class ElementDefinitionTree + { + /// + /// The injected + /// + [Inject] + public IElementDefinitionTreeViewModel ViewModel { get; set; } + + /// + /// The Iteration + /// + [Parameter] + public Iteration Iteration { get; set; } + + /// + /// Gets or sets a value indicating that another model can be selected for this TreeView or not + /// + [Parameter] + public bool IsModelSelectionEnabled { get; set; } + + /// + /// Fires after node selection has been changed for a specific item. + /// + [Parameter] + public EventCallback SelectionChanged { get; set; } + + /// + /// Fires after node dragging has been started for a specific item. + /// + [Parameter] + public EventCallback<(ElementDefinitionTree, ElementBaseTreeRowViewModel)> OnDragStart { get; set; } = new(); + + /// + /// Fires after node dragging has been ended for a specific item. + /// + [Parameter] + public EventCallback<(ElementDefinitionTree, ElementBaseTreeRowViewModel)> OnDragEnd { get; set; } = new(); + + /// + /// Fires after node drop has been executed on a specific item. + /// + [Parameter] + public EventCallback<(ElementDefinitionTree, ElementBaseTreeRowViewModel)> OnDrop { get; set; } = new(); + + /// + /// Fires after node drag-over has been started for a specific item. + /// + [Parameter] + public EventCallback<(ElementDefinitionTree, object)> OnDragEnter { get; set; } = new(); + + /// + /// Fires after node drag-over has been ended for a specific item. + /// + [Parameter] + public EventCallback<(ElementDefinitionTree, object)> OnDragLeave { get; set; } = new(); + + /// + /// Fires when calculation of is necessary + /// + [Parameter] + public EventCallback OnCalculateDropIsAllowed { get; set; } = new(); + + /// + /// Is evaluated when calculation if a node is draggable is necessary + /// + [Parameter] + public Func AllowNodeDrag { get; set; } = (x, y) => true; + + /// + /// Gets or sets a value indicating that dragging a node is allowed for this + /// + [Parameter] + public bool AllowDrag { get; set; } + + /// + /// Gets or sets a value indicating that dropping a node is allowed for this + /// + [Parameter] + public bool AllowDrop { get; set; } + + /// + /// Holds a reference to the object where the dragged node is dragged over + /// + private object dragOverNode; + + /// + /// Gets or sets a value indicating that dropping a node is allowed for this + /// + public bool AllowNodeDrop { get; set; } + + /// + /// A collection of + /// + public IEnumerable Rows { get; set; } + + /// + /// Gets or sets the + /// + public DxTreeView TreeView { get; set; } + + /// + /// Method invoked after each time the component has been rendered. Note that the component does + /// not automatically re-render after the completion of any returned , because + /// that would cause an infinite render loop. + /// + /// + /// Set to true if this is the first time has been invoked + /// on this component instance; otherwise false. + /// + /// A representing any asynchronous operation. + /// + /// The and lifecycle methods + /// are useful for performing interop, or interacting with values received from @ref. + /// Use the parameter to ensure that initialization work is only performed + /// once. + /// + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (this.ViewModel.Iteration != this.Iteration) + { + this.Iteration = this.ViewModel.Iteration; + } + } + + /// + /// Method invoked when the component has received parameters from its parent in + /// the render tree, and the incoming values have been assigned to properties. + /// + protected override void OnParametersSet() + { + base.OnParametersSet(); + + this.ViewModel.Iteration = this.Iteration; + } + + /// + /// Clears the selected node(s) in this + /// + public void ClearSelection() + { + this.TreeView.ClearSelection(); + } + + /// + /// Is executed when dragging of a node has started + /// + /// The node where dragging has been started for + /// an awaitable + private async Task DragStart(ElementBaseTreeRowViewModel node) + { + await this.OnDragStart.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + Console.WriteLine("DragStart"); + } + + /// + /// Is executed when dragging of a node has ended + /// + /// The node where dragging has been ended for + /// an awaitable + private async Task DragEnd(ElementBaseTreeRowViewModel node) + { + await this.OnDragEnd.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + Console.WriteLine("DragEnd"); + } + + /// + /// Is executed when a node has been dropped onto another node + /// + /// The node where the dragged node has been dropped onto + /// an awaitable + private async Task Drop(ElementBaseTreeRowViewModel node) + { + this.dragOverNode = null; + + if (this.AllowDrop) + { + await this.OnDrop.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + Console.WriteLine("Drop"); + } + } + + /// + /// Is executed when a dragged node hover over another droppable node + /// + /// The node where the dragged node has been hovered over + /// an awaitable + private async Task DragEnter(object node) + { + if (this.AllowDrop) + { + this.dragOverNode = node; + await this.OnDragEnter.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + + Console.WriteLine("DragEnter"); + } + } + + /// + /// Is executed when a dragged node is not hovered over another droppable node anymore + /// + /// The node where the dragged node had been hovered over + /// an awaitable + private async Task DragLeave(object node) + { + if (this.AllowDrop) + { + this.dragOverNode = null; + await this.OnDragLeave.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + Console.WriteLine("DragLeave"); + } + } + } +} diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.css b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.css new file mode 100644 index 00000000..bf169aed --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.css @@ -0,0 +1,5 @@ +.sticky-scrollable-column { + position: sticky; + top: 0px; + overflow: hidden; +} diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor new file mode 100644 index 00000000..9f6f1d0f --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -0,0 +1,96 @@ + + +@using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +@inherits SingleIterationApplicationBase; + + + +
+
+
+

Source Model

+ + +
+
+

Target Model

+ + +
+
+ +
+
+ @if (this.ViewModel.SelectedElementDefinition is not null) + { + + } + + +
+
+
+ +
+
+
+ + + + + + + + + + + +
diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs new file mode 100644 index 00000000..32c4a3c4 --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs @@ -0,0 +1,244 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Components.MultiModelEditor +{ + using CDP4Common.EngineeringModelData; + + using COMET.Web.Common.Components.Applications; + using COMET.Web.Common.Extensions; + + using COMETwebapp.ViewModels.Components.ModelEditor.Rows; + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using DevExpress.Blazor; + + using ReactiveUI; + + /// + /// Support class for the component + /// + public partial class MultiModelEditor + { + /// + /// Holds a reference to the data of the node where another node is dragged over + /// + private (ElementDefinitionTree, object) DragOverObject; + + /// + /// Holds a reference to the data of the node that is currently dragged + /// + private (ElementDefinitionTree, ElementBaseTreeRowViewModel) DragObject; + + /// + /// The validation messages to display + /// + private string ErrorMessage { get; set; } + + /// + /// Gets or sets the source tree component + /// + public ElementDefinitionTree SourceTree { get; set; } + + /// + /// Gets or sets the target tree component + /// + public ElementDefinitionTree TargetTree { get; set; } + + /// + /// Handles the post-assignement flow of the property + /// + protected override void OnViewModelAssigned() + { + base.OnViewModelAssigned(); + + this.Disposables.Add(this.WhenAnyValue(x => x.ViewModel.IsOnCreationMode).SubscribeAsync(_ => this.InvokeAsync(this.StateHasChanged))); + this.Disposables.Add(this.WhenAnyValue(x => x.ViewModel.IsOnAddingParameterMode).SubscribeAsync(_ => this.InvokeAsync(this.StateHasChanged))); + } + + /// + /// Initializes values of the component and of the ViewModel based on parameters provided from the url + /// + /// A for parameters + protected override void InitializeValues(Dictionary parameters) + { + } + + /// + /// Method executed when a is selected + /// + /// The + private void OnElementSelected(ElementBaseTreeRowViewModel args) + { + this.ViewModel.SelectElement(args?.ElementBase); + } + + /// + /// Is executed when dragging has been started for a specific node () in a specific + /// + /// A that contains the specific and the specific node () + /// an awaitable + private Task OnDragStart((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + { + this.DragObject = nodeData; + return Task.CompletedTask; + } + + /// + /// Is executed when dragging has been ended for a specific node () in a specific + /// + /// A that contains the specific and the specific node () + /// an awaitable + private Task OnDragEnd((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + { + this.DragObject = (null, null); + return Task.CompletedTask; + } + + /// + /// Is executed when a dragged node () has been dropped onto another element in a specific + /// + /// A that contains the specific and the specific node () + /// an awaitable + private async Task OnDrop((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + { + this.ErrorMessage = string.Empty; + + if (this.DragObject.Item2 is not ElementDefinitionTreeTreeRowViewModel elementDefinitionTreeRowViewModel) + { + return; + } + + try + { + if (nodeData.Item2 == null) + { + // Drop in the same model + await this.ViewModel.CopyAndAddNewElement(elementDefinitionTreeRowViewModel.ElementBase); + } + else + { + await this.ViewModel.AddNewElementUsage(elementDefinitionTreeRowViewModel.ElementBase, nodeData.Item2.ElementBase); + } + } + catch (Exception ex) + { + this.ErrorMessage = ex.Message; + } + finally + { + this.DragOverObject = (null, null); + this.DragObject = (null, null); + + this.StateHasChanged(); + } + } + + /// + /// Is executed when a dragged node () hovers over a specific element () in a specific + /// + /// A that contains the specific and the specific element () + /// an awaitable + private Task OnDragEnter((ElementDefinitionTree, object) elementData) + { + this.DragOverObject = elementData; + return Task.CompletedTask; + } + + /// + /// Is executed when a dragged node () leaves a previously hovered over specific element () in a specific + /// + /// A that contains the specific and the specific element () + /// an awaitable + private Task OnDragLeave((ElementDefinitionTree, object) elementData) + { + this.DragOverObject = (null, null); + return Task.CompletedTask; + } + + /// + /// Sets the AllowNodeDrop property for a specific node in a , based on the calculated data for and + /// + /// The to calculate this for + /// an awaitable + private Task SetDropIsAllowed(ElementDefinitionTree elementDefinitionTree) + { + elementDefinitionTree.AllowNodeDrop = this.CalculateDropIsAllowed(elementDefinitionTree); + return Task.CompletedTask; + } + + /// + /// Calculates is dropping of a dragged node () is allowed onto the node where it hovers over () + /// + /// The where to calculate for + /// A value indicating is dropping is actually allowed + private bool CalculateDropIsAllowed(ElementDefinitionTree elementDefinitionTree) + { + var dragOverObject = this.DragOverObject; + var dragObject = this.DragObject; + + if (elementDefinitionTree.AllowDrop) + { + if (dragObject != (null, null)) + { + if (dragOverObject == dragObject) + { + return false; + } + + if (dragOverObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragOverVm) + { + if (dragObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragVm) + { + if (dragOverVm.ElementBase == dragVm.ElementBase) + { + return false; + } + + if (dragOverVm.ElementBase.GetContainerOfType() == dragVm.ElementBase.GetContainerOfType()) + { + return true; + } + + return false; + } + + return false; + } + + if (dragOverObject.Item1 == elementDefinitionTree) + { + return true; + } + + return false; + } + + return false; + } + + return false; + } + } +} diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.css b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.css new file mode 100644 index 00000000..bc45ff21 --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.css @@ -0,0 +1,3 @@ +.sticky-scrollable-column { + max-height: 80vh; +} diff --git a/COMETwebapp/Extensions/ServiceCollectionExtensions.cs b/COMETwebapp/Extensions/ServiceCollectionExtensions.cs index b9127ddd..99cc9f37 100644 --- a/COMETwebapp/Extensions/ServiceCollectionExtensions.cs +++ b/COMETwebapp/Extensions/ServiceCollectionExtensions.cs @@ -57,6 +57,7 @@ namespace COMETwebapp.Extensions using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileHandler; using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FileRevisionHandler; using COMETwebapp.ViewModels.Components.EngineeringModel.FileStore.FolderHandler; + using COMETwebapp.ViewModels.Components.MultiModelEditor; using COMETwebapp.ViewModels.Components.ParameterEditor.BatchParameterEditor; using COMETwebapp.ViewModels.Components.ReferenceData; using COMETwebapp.ViewModels.Pages; @@ -104,6 +105,8 @@ public static void RegisterViewModels(this IServiceCollection serviceCollection) serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); + serviceCollection.AddTransient(); + serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); serviceCollection.AddTransient(); diff --git a/COMETwebapp/Model/Applications.cs b/COMETwebapp/Model/Applications.cs index b17029b7..5f1a2aed 100644 --- a/COMETwebapp/Model/Applications.cs +++ b/COMETwebapp/Model/Applications.cs @@ -30,6 +30,7 @@ namespace COMETwebapp.Model using COMETwebapp.Components.EngineeringModel; using COMETwebapp.Components.ModelDashboard; using COMETwebapp.Components.ModelEditor; + using COMETwebapp.Components.MultiModelEditor; using COMETwebapp.Components.ParameterEditor; using COMETwebapp.Components.ReferenceData; using COMETwebapp.Components.SiteDirectory; @@ -103,6 +104,16 @@ private static List InitializesApplications() ComponentType = typeof(ElementDefinitionTable) }, + new TabbedApplication + { + Name = "Multi Model Editor", + Color = "#76fd98", + IconType = typeof(FeatherBox), + Description = "Populate multiple models", + Url = WebAppConstantValues.MultiModelEditorPage, + ComponentType = typeof(MultiModelEditor) + }, + new TabbedApplication { Name = "Parameter Editor", diff --git a/COMETwebapp/Utilities/WebAppConstantValues.cs b/COMETwebapp/Utilities/WebAppConstantValues.cs index cf450af4..2f36c412 100644 --- a/COMETwebapp/Utilities/WebAppConstantValues.cs +++ b/COMETwebapp/Utilities/WebAppConstantValues.cs @@ -126,6 +126,11 @@ public static class WebAppConstantValues /// public const string ModelEditorPage = "ModelEditor"; + /// + /// The page name of the Model Editor + /// + public const string MultiModelEditorPage = "MultiModelEditor"; + /// /// The page nastringthe Book Editor /// diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs new file mode 100644 index 00000000..ac849a57 --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -0,0 +1,254 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor +{ + using System.Collections.ObjectModel; + + using CDP4Common.CommonData; + using CDP4Common.EngineeringModelData; + + using CDP4Dal; + using CDP4Dal.Events; + + using COMET.Web.Common.Model; + using COMET.Web.Common.Services.SessionManagement; + using COMET.Web.Common.ViewModels.Components.Applications; + + using COMETwebapp.Components.ModelEditor; + using COMETwebapp.Components.MultiModelEditor; + using COMETwebapp.ViewModels.Components.ModelEditor; + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using DynamicData; + + using ReactiveUI; + + /// + /// ViewModel for the + /// + public class ElementDefinitionTreeViewModel : ApplicationBaseViewModel, IElementDefinitionTreeViewModel + { + /// + /// The + /// + private readonly ISessionService sessionService; + + /// + /// Backing field for + /// + private Iteration iteration; + + /// + /// Gets the Description of the selected model and iteration + /// + public string Description => this.selectedIterationData?.IterationName ?? "Please select a model"; + + /// + /// Backing field for the property + /// + private IterationData selectedIterationData; + + /// + /// Creates a new instance of + /// + /// the + /// The + public ElementDefinitionTreeViewModel(ISessionService sessionService, ICDPMessageBus messageBus) : base(sessionService, messageBus) + { + this.sessionService = sessionService; + this.RegisterViewModelWithReusableRows(this); + this.Iterations.Add(null); + + this.Disposables.Add(this.WhenAnyValue(x => x.SelectedIterationData).Subscribe(x => this.Iteration = this.sessionService.OpenIterations.Items.SingleOrDefault(y => y.IterationSetup.Iid == x?.IterationSetupId))); + + this.Disposables.Add(this.WhenAnyValue(x => x.Iteration).Subscribe(x => + { + this.Rows.Clear(); + + if (x != null) + { + this.AddRows(x.Element); + this.SelectedIterationData = this.Iterations.SingleOrDefault(y => y?.IterationSetupId == x.IterationSetup.Iid); + } + })); + + this.Disposables.Add(this.sessionService.OpenIterations.Connect().Subscribe(this.RefreshIterations)); + + this.InitializeSubscriptions([typeof(ElementBase)]); + } + + /// + /// The from which to build the tree + /// + public Iteration Iteration + { + get => this.iteration; + set => this.RaiseAndSetIfChanged(ref this.iteration, value); + } + + /// + /// The from which to build the tree + /// + public IterationData SelectedIterationData + { + get => this.selectedIterationData; + set => this.RaiseAndSetIfChanged(ref this.selectedIterationData, value); + } + + /// + /// Gets or a collection of selectable s + /// + public ObservableCollection Iterations { get; } = new(); + + /// + /// All of the iteration + /// + public List Elements { get; set; } = []; + + /// + /// Gets the collection of the for target model + /// + public ObservableCollection Rows { get; set; } = []; + + /// + /// Represents the selected ElementDefinitionRowViewModel + /// + public ElementDefinition SelectedElementDefinition { get; set; } + + /// + /// Add rows related to that has been added + /// + /// A collection of added + public void AddRows(IEnumerable addedThings) + { + var listOfAddedElementBases = addedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList(); + this.Rows.AddRange(listOfAddedElementBases.Select(e => new ElementDefinitionTreeTreeRowViewModel(e))); + } + + /// + /// Updates rows related to that have been updated + /// + /// A collection of updated + public void UpdateRows(IEnumerable updatedThings) + { + foreach (var element in updatedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList()) + { + var row = this.Rows.FirstOrDefault(x => x.ElementBase.Iid == element.Iid); + row?.UpdateProperties(new ElementDefinitionTreeTreeRowViewModel(element)); + } + } + + /// + /// Remove rows related to a that has been deleted + /// + /// A collection of deleted + public void RemoveRows(IEnumerable deletedThings) + { + foreach (var elementId in deletedThings.OfType().Select(x => x.Iid)) + { + var row = this.Rows.FirstOrDefault(x => x.ElementBase.Iid == elementId); + + if (row != null) + { + this.Rows.Remove(row); + } + } + } + + /// + /// Handles the message received + /// + /// A + protected override async Task OnEndUpdate() + { + await this.OnSessionRefreshed(); + } + + /// + /// Handles the refresh of the current + /// + /// A + protected override async Task OnSessionRefreshed() + { + if (this.AddedThings.Count == 0 && this.DeletedThings.Count == 0 && this.UpdatedThings.Count == 0) + { + return; + } + + this.IsLoading = true; + await Task.Delay(1); + + this.UpdateInnerComponents(); + this.ClearRecordedChanges(); + this.IsLoading = false; + } + + /// + /// Refreshes the property + /// + /// The containing all the necessary changes + private void RefreshIterations(IChangeSet changeSet) + { + foreach (var change in changeSet) + { + switch (change.Reason) + { + case ListChangeReason.AddRange: + foreach (var changeItem in change.Range) + { + var newChangeIterationData = new IterationData(changeItem.IterationSetup, true); + + if (!this.Iterations.Contains(newChangeIterationData)) + { + this.Iterations.Add(newChangeIterationData); + } + } + + break; + + case ListChangeReason.Add: + var newIterationData = new IterationData(change.Item.Current.IterationSetup, true); + + if (!this.Iterations.Contains(newIterationData)) + { + this.Iterations.Add(newIterationData); + } + + break; + + case ListChangeReason.Remove: + var currentItem = this.Iterations.FirstOrDefault(x => x?.IterationSetupId == change.Item.Current.IterationSetup.Iid); + + if (currentItem != null) + { + this.Iterations.Remove(currentItem); + } + + break; + } + } + } + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs new file mode 100644 index 00000000..dd2fea60 --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs @@ -0,0 +1,71 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor +{ + using System.Collections.ObjectModel; + + using CDP4Common.EngineeringModelData; + + using COMET.Web.Common.Model; + using COMET.Web.Common.ViewModels.Components.Applications; + + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + /// + /// Interface for the + /// + public interface IElementDefinitionTreeViewModel : IHaveReusableRows + { + /// + /// Gets the collection of the + /// + ObservableCollection Rows { get; set; } + + /// + /// The from which to build the tree + /// + Iteration Iteration { get; set; } + + /// + /// Represents the selected ElementDefinitionRowViewModel + /// + ElementDefinition SelectedElementDefinition { get; set; } + + /// + /// Gets the Description of the selected model and iteration + /// + string Description { get; } + + /// + /// Gets or a collection of selectable s + /// + ObservableCollection Iterations { get; } + + /// + /// The from which to build the tree + /// + IterationData SelectedIterationData { get; set; } + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs new file mode 100644 index 00000000..4fb2ae07 --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs @@ -0,0 +1,110 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor +{ + using CDP4Common.EngineeringModelData; + + using COMET.Web.Common.ViewModels.Components.Applications; + + using COMETwebapp.Components.ModelEditor; + using COMETwebapp.ViewModels.Components.ModelEditor; + using COMETwebapp.ViewModels.Components.ModelEditor.AddParameterViewModel; + using COMETwebapp.ViewModels.Components.SystemRepresentation; + + /// + /// Interface for the + /// + public interface IMultiModelEditorViewModel : ISingleIterationApplicationBaseViewModel + { + /// + /// Gets the target /> + /// + Iteration TargetIteration { get; } + + /// + /// Gets the source + /// + Iteration SourceIteration { get; } + + /// + /// Value indicating the user is currently creating a new + /// + bool IsOnCreationMode { get; set; } + + /// + /// Represents the selected ElementDefinitionRowViewModel + /// + ElementDefinition SelectedElementDefinition { get; set; } + + /// + /// The + /// + IElementDefinitionDetailsViewModel ElementDefinitionDetailsViewModel { get; } + + /// + /// Gets the + /// + IElementDefinitionCreationViewModel ElementDefinitionCreationViewModel { get; set; } + + /// + /// Gets the + /// + IAddParameterViewModel AddParameterViewModel { get; set; } + + /// + /// Value indicating the user is currently adding a new to a + /// + bool IsOnAddingParameterMode { get; set; } + + /// + /// Opens the popup + /// + void OpenCreateElementDefinitionCreationPopup(); + + /// + /// Set the selected + /// + /// The selected + void SelectElement(ElementBase selectedElementBase); + + /// + /// Opens the popup + /// + void OpenAddParameterPopup(); + + /// + /// Add a new based on an existing + /// + /// The + Task CopyAndAddNewElement(ElementBase elementBase); + + /// + /// Add a new based on an existing + /// + /// The to be added as + /// The where to add the new to + Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase); + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs new file mode 100644 index 00000000..08f58b4f --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs @@ -0,0 +1,330 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor +{ + using CDP4Common.CommonData; + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using CDP4Dal; + using CDP4Dal.Events; + + using COMET.Web.Common.Services.SessionManagement; + using COMET.Web.Common.Utilities; + using COMET.Web.Common.ViewModels.Components.Applications; + + using COMETwebapp.Components.ModelEditor; + using COMETwebapp.ViewModels.Components.ModelEditor; + using COMETwebapp.ViewModels.Components.ModelEditor.AddParameterViewModel; + using COMETwebapp.ViewModels.Components.SystemRepresentation; + using COMETwebapp.ViewModels.Components.SystemRepresentation.Rows; + + using Microsoft.AspNetCore.Components; + + using ReactiveUI; + + /// + /// ViewModel for the + /// + public class MultiModelEditorViewModel : SingleIterationApplicationBaseViewModel, IMultiModelEditorViewModel + { + /// + /// The + /// + private readonly ISessionService sessionService; + + /// + /// Backing field for + /// + private bool isOnAddingParameterMode; + + /// + /// Backing field for + /// + private bool isOnCreationMode; + + /// + /// Creates a new instance of + /// + /// the + /// The + public MultiModelEditorViewModel(ISessionService sessionService, ICDPMessageBus messageBus) : base(sessionService, messageBus) + { + this.sessionService = sessionService; + var eventCallbackFactory = new EventCallbackFactory(); + + this.ElementDefinitionCreationViewModel = new ElementDefinitionCreationViewModel(sessionService, messageBus) + { + OnValidSubmit = eventCallbackFactory.Create(this, this.AddingElementDefinition) + }; + + this.AddParameterViewModel = new ModelEditor.AddParameterViewModel.AddParameterViewModel(sessionService, messageBus) + { + OnParameterAdded = eventCallbackFactory.Create(this, () => this.IsOnAddingParameterMode = false) + }; + + this.InitializeSubscriptions([typeof(ElementBase)]); + + this.TargetIteration = this.CurrentThing; + } + + /// + /// Represents the selected ElementDefinitionRowViewModel + /// + public ElementDefinition SelectedElementDefinition { get; set; } + + /// + /// The + /// + public IElementDefinitionDetailsViewModel ElementDefinitionDetailsViewModel { get; } = new ElementDefinitionDetailsViewModel(); + + /// + /// Gets the + /// + public IElementDefinitionCreationViewModel ElementDefinitionCreationViewModel { get; set; } + + /// + /// Gets the + /// + public IAddParameterViewModel AddParameterViewModel { get; set; } + + /// + /// Gets target + /// + public Iteration TargetIteration { get; set; } + + /// + /// Gets source + /// + public Iteration SourceIteration { get; set; } + + /// + /// Value indicating the user is currently creating a new + /// + public bool IsOnCreationMode + { + get => this.isOnCreationMode; + set => this.RaiseAndSetIfChanged(ref this.isOnCreationMode, value); + } + + /// + /// Value indicating the user is currently adding a new to a + /// + public bool IsOnAddingParameterMode + { + get => this.isOnAddingParameterMode; + set => this.RaiseAndSetIfChanged(ref this.isOnAddingParameterMode, value); + } + + /// + /// Set the selected + /// + /// The selected + public void SelectElement(ElementBase selectedElementBase) + { + // It is preferable to have a selection based on the Iid of the Thing + this.ElementDefinitionDetailsViewModel.SelectedSystemNode = selectedElementBase; + + this.SelectedElementDefinition = selectedElementBase switch + { + ElementDefinition definition => definition, + ElementUsage usage => usage.ElementDefinition, + _ => null + }; + + this.ElementDefinitionDetailsViewModel.Rows = this.SelectedElementDefinition?.Parameter.Select(x => new ElementDefinitionDetailsRowViewModel(x)).ToList(); + this.AddParameterViewModel.SetSelectedElementDefinition(this.SelectedElementDefinition); + } + + /// + /// Opens the popup + /// + public void OpenCreateElementDefinitionCreationPopup() + { + this.ElementDefinitionCreationViewModel.ElementDefinition = new ElementDefinition(); + this.ElementDefinitionCreationViewModel.SelectedCategories = new List(); + this.IsOnCreationMode = true; + } + + /// + /// Opens the popup + /// + public void OpenAddParameterPopup() + { + this.AddParameterViewModel.ResetValues(); + this.IsOnAddingParameterMode = true; + } + + /// + /// Add a new based on an existing + /// + /// The + public async Task CopyAndAddNewElement(ElementBase elementBase) + { + this.IsLoading = true; + + if (elementBase.GetContainerOfType() == this.CurrentThing) + { + var copyCreator = new CopyElementDefinitionCreator(this.sessionService.Session); + + try + { + await copyCreator.Copy((ElementDefinition)elementBase, true); + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + throw; + } + finally + { + this.IsLoading = false; + } + } + else + { + var copyCreator = new CopyCreator(this.sessionService.Session); + + try + { + await copyCreator.Copy((ElementDefinition)elementBase, this.CurrentThing); + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + throw; + } + finally + { + this.IsLoading = false; + } + } + } + + /// + /// Add a new based on an existing + /// + /// The to be added as + /// The where to add the new to + public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase) + { + if (fromElementBase.GetContainerOfType() == this.CurrentThing && toElementBase.GetContainerOfType() == this.CurrentThing) + { + this.IsLoading = true; + + var thingCreator = new ThingCreator(); + + try + { + await thingCreator.CreateElementUsage((ElementDefinition)toElementBase, (ElementDefinition)fromElementBase, this.sessionService.Session.OpenIterations.First(x => x.Key == this.CurrentThing).Value.Item1, this.sessionService.Session); + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + throw; + } + finally + { + this.IsLoading = false; + } + } + } + + /// + /// Tries to create a new + /// + /// A + public async Task AddingElementDefinition() + { + var thingsToCreate = new List(); + + if (this.ElementDefinitionCreationViewModel.SelectedCategories.Any()) + { + this.ElementDefinitionCreationViewModel.ElementDefinition.Category = this.ElementDefinitionCreationViewModel.SelectedCategories.ToList(); + } + + this.ElementDefinitionCreationViewModel.ElementDefinition.Container = this.CurrentThing; + thingsToCreate.Add(this.ElementDefinitionCreationViewModel.ElementDefinition); + var clonedIteration = this.CurrentThing.Clone(false); + + if (this.ElementDefinitionCreationViewModel.IsTopElement) + { + clonedIteration.TopElement = this.ElementDefinitionCreationViewModel.ElementDefinition; + } + + clonedIteration.Element.Add(this.ElementDefinitionCreationViewModel.ElementDefinition); + thingsToCreate.Add(clonedIteration); + + try + { + await this.sessionService.CreateOrUpdateThings(clonedIteration, thingsToCreate); + this.IsOnCreationMode = false; + } + catch (Exception exception) + { + Console.WriteLine(exception.Message); + throw; + } + } + + /// + /// Handles the message received + /// + /// A + protected override async Task OnEndUpdate() + { + await this.OnSessionRefreshed(); + } + + /// + /// Handles the refresh of the current + /// + /// A + protected override Task OnSessionRefreshed() + { + this.SelectElement(this.SelectedElementDefinition); + return Task.CompletedTask; + } + + /// + /// Update this view model properties + /// + /// A + protected override async Task OnThingChanged() + { + await base.OnThingChanged(); + + if (this.CurrentThing == null) + { + return; + } + + this.AddParameterViewModel.InitializeViewModel(this.CurrentThing); + this.ElementDefinitionCreationViewModel.InitializeViewModel(this.CurrentThing); + + this.IsLoading = false; + } + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs new file mode 100644 index 00000000..97ff46b8 --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs @@ -0,0 +1,93 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +{ + using CDP4Common.EngineeringModelData; + + using COMETwebapp.ViewModels.Components.ModelEditor.Rows; + + using ReactiveUI; + + /// + /// Row View Model for + /// + public abstract class ElementBaseTreeRowViewModel : ReactiveObject + { + /// + /// Backing field for + /// + private ElementBase elementBase; + + /// + /// Backing field for + /// + private string elementName; + + /// + /// Initializes a new instance of the class. + /// the + /// + protected ElementBaseTreeRowViewModel(ElementBase elementBase) + { + this.ElementBase = elementBase; + this.ElementName = elementBase.Name; + } + + /// + /// Initializes a new instance of the class. + /// + protected ElementBaseTreeRowViewModel() + { + } + + /// + /// The name of the + /// + public string ElementName + { + get => this.elementName; + set => this.RaiseAndSetIfChanged(ref this.elementName, value); + } + + /// + /// The + /// + public ElementBase ElementBase + { + get => this.elementBase; + set => this.RaiseAndSetIfChanged(ref this.elementBase, value); + } + + /// + /// Update this row view model properties + /// + /// The to use for updating + public void UpdateProperties(ElementBaseTreeRowViewModel elementBaseTreeRow) + { + this.ElementBase = elementBaseTreeRow.elementBase; + this.ElementName = elementBaseTreeRow.elementName; + } + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs new file mode 100644 index 00000000..6d0d62bf --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs @@ -0,0 +1,103 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +{ + using System.Collections.ObjectModel; + + using CDP4Common.EngineeringModelData; + + using COMETwebapp.ViewModels.Components.ModelEditor.Rows; + + using DynamicData; + + using ReactiveUI; + + /// + /// Row View Model for + /// + public class ElementDefinitionTreeTreeRowViewModel : ElementBaseTreeRowViewModel + { + /// + /// Backing field for + /// + private bool isTopElement; + + /// + /// Gets or the collection of + /// + public ObservableCollection Rows { get; } = new(); + + /// + /// Initializes a new instance of the class. + /// the + /// + public ElementDefinitionTreeTreeRowViewModel(ElementDefinition elementBase) : base(elementBase) + { + this.IsTopElement = elementBase == elementBase.GetContainerOfType().TopElement; + + var elementUsages = elementBase?.ContainedElement; + + if (elementUsages?.Any() ?? false) + { + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeTreeRowViewModel(x))); + } + } + + /// + /// Initializes a new instance of the class. + /// + public ElementDefinitionTreeTreeRowViewModel() + { + } + + /// + /// The value to check if the element base is the top element + /// + public bool IsTopElement + { + get => this.isTopElement; + set => this.RaiseAndSetIfChanged(ref this.isTopElement, value); + } + + /// + /// Update this row view model properties + /// + /// The to use for updating + public void UpdateProperties(ElementDefinitionTreeTreeRowViewModel elementDefinitionTreeRow) + { + base.UpdateProperties(elementDefinitionTreeRow); + this.IsTopElement = elementDefinitionTreeRow.isTopElement; + + this.Rows.Clear(); + + var elementUsages = (elementDefinitionTreeRow.ElementBase as ElementDefinition)?.ContainedElement; + + if (elementUsages != null) + { + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeTreeRowViewModel(x))); + } + } + } +} diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs new file mode 100644 index 00000000..bb489957 --- /dev/null +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs @@ -0,0 +1,60 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +{ + using CDP4Common.EngineeringModelData; + + using COMETwebapp.ViewModels.Components.ModelEditor.Rows; + + /// + /// Row View Model for + /// + public class ElementUsageTreeTreeRowViewModel : ElementBaseTreeRowViewModel + { + /// + /// Initializes a new instance of the class. + /// the + /// + public ElementUsageTreeTreeRowViewModel(ElementUsage elementUsage) : base(elementUsage) + { + } + + /// + /// Initializes a new instance of the class. + /// + public ElementUsageTreeTreeRowViewModel() + { + } + + /// + /// Update this row view model properties + /// + /// The to use for updating + public void UpdateProperties(ElementUsageTreeTreeRowViewModel elementUsageTreeRow) + { + base.UpdateProperties(elementUsageTreeRow); + } + } +} From 6b2f7f1300877daefeb52a558ce16a830feb4e0f Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 20 Dec 2024 14:56:53 +0100 Subject: [PATCH 02/23] Bugfixes, make copy two-way and show pills --- .../ElementDefinitionTree.razor | 32 ++++++++++---- .../MultiModelEditor/MultiModelEditor.razor | 42 ++++++++++--------- .../MultiModelEditor.razor.cs | 6 +-- .../Rows/ElementBaseTreeRowViewModel.cs | 21 +++++++++- 4 files changed, 68 insertions(+), 33 deletions(-) diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 9327cfdd..9e7765e9 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -1,8 +1,4 @@ -@using COMET.Web.Common.Model -@using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows -@using CDP4JsonSerializer.JsonConverter -@inject IJSRuntime JSRuntime - +@using COMET.Web.Common.Model +@using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows +@using CDP4JsonSerializer.JsonConverter +@inject IJSRuntime JSRuntime +
@if (this.IsModelSelectionEnabled) { @@ -60,7 +61,6 @@ else var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); var isTopElement = dataItem is ElementDefinitionTreeTreeRowViewModel { IsTopElement: true }; - var style = isTopElement ? "font-weight-bold" : string.Empty; } @@ -75,13 +75,29 @@ else @ondragenter="@(async () => await this.DragEnter(dataItem))" @ondragleave="@(async () => await this.DragLeave(dataItem))" @ondrop="@(async () => await this.Drop(dataItem))"> - @dataItem.ElementName + @dataItem.ElementName + + + @foreach (var category in @dataItem.ElementBase.GetAllCategories()) + { + + + + }
} else {
- @dataItem.ElementName + @dataItem.ElementName + + + @foreach (var category in @dataItem.ElementBase.GetAllCategories()) + { + + + + }
} diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index 9f6f1d0f..e42d29d9 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -20,6 +20,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. -------------------------------------------------------------------------------> +@using COMETwebapp.Components.ModelEditor @using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows @inherits SingleIterationApplicationBase; @@ -31,35 +32,36 @@

Source Model

+ IsModelSelectionEnabled="true">

Target Model

+ @ref="this.TargetTree" + Iteration="@this.ViewModel.CurrentThing" + SelectionChanged="model => { if (model != null) this.SourceTree.ClearSelection(); this.OnElementSelected(model);}" + AllowDrag="true" + AllowDrop="true" + OnCalculateDropIsAllowed="@(async x => await this.SetDropIsAllowed(x))" + OnDragEnter="@(async x => await this.OnDragEnter(x))" + OnDragLeave="@(async x => await this.OnDragLeave(x))" + OnDragStart="@(async x => await this.OnDragStart(x))" + OnDragEnd="@(async x => await this.OnDragEnd(x))" + OnDrop="@(async x => await this.OnDrop(x))" + AllowNodeDrag="(tree, model) => model is ElementDefinitionTreeTreeRowViewModel" + IsModelSelectionEnabled="false">
diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs index 32c4a3c4..b81926a7 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs @@ -88,10 +88,10 @@ protected override void InitializeValues(Dictionary parameters) /// /// Method executed when a is selected /// - /// The - private void OnElementSelected(ElementBaseTreeRowViewModel args) + /// The + private void OnElementSelected(ElementBaseTreeRowViewModel elementRowViewModel) { - this.ViewModel.SelectElement(args?.ElementBase); + this.ViewModel.SelectElement(elementRowViewModel?.ElementBase); } /// diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs index 97ff46b8..56f7ab3d 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs @@ -25,6 +25,7 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows { using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; using COMETwebapp.ViewModels.Components.ModelEditor.Rows; @@ -45,14 +46,20 @@ public abstract class ElementBaseTreeRowViewModel : ReactiveObject /// private string elementName; + /// + /// Backing field for + /// + private string ownerShortName; + /// /// Initializes a new instance of the class. - /// the + /// the /// protected ElementBaseTreeRowViewModel(ElementBase elementBase) { this.ElementBase = elementBase; this.ElementName = elementBase.Name; + this.OwnerShortName = elementBase.Owner.ShortName; } /// @@ -62,6 +69,15 @@ protected ElementBaseTreeRowViewModel() { } + /// + /// The shortname of the owning + /// + public string OwnerShortName + { + get => this.ownerShortName; + set => this.RaiseAndSetIfChanged(ref this.ownerShortName, value); + } + /// /// The name of the /// @@ -72,7 +88,7 @@ public string ElementName } /// - /// The + /// The /// public ElementBase ElementBase { @@ -88,6 +104,7 @@ public void UpdateProperties(ElementBaseTreeRowViewModel elementBaseTreeRow) { this.ElementBase = elementBaseTreeRow.elementBase; this.ElementName = elementBaseTreeRow.elementName; + this.OwnerShortName = elementBaseTreeRow.OwnerShortName; } } } From a649e9e7a933bc0606a2f926372eea486e91365f Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 20 Dec 2024 14:59:09 +0100 Subject: [PATCH 03/23] Review Comments --- COMET.Web.Common/Utilities/CopyCreator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/COMET.Web.Common/Utilities/CopyCreator.cs b/COMET.Web.Common/Utilities/CopyCreator.cs index bba23e52..839d632d 100644 --- a/COMET.Web.Common/Utilities/CopyCreator.cs +++ b/COMET.Web.Common/Utilities/CopyCreator.cs @@ -93,4 +93,4 @@ private async Task WriteCopyOperation(Thing thingToCopy, Thing targetContainer, await this.session.Write(transaction.FinalizeTransaction()); } } -} \ No newline at end of file +} From bd46ec259479db0b59db5f7c72092757b29c140c Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 20 Dec 2024 16:53:34 +0100 Subject: [PATCH 04/23] [FIX] - Copy problem for two-way drag-drop source <=> target --- .../ElementDefinitionTree.razor.cs | 8 ++++---- .../MultiModelEditor/MultiModelEditor.razor | 4 ++-- .../MultiModelEditor/MultiModelEditor.razor.cs | 2 +- .../MultiModelEditor/IMultiModelEditorViewModel.cs | 6 ++++-- .../MultiModelEditor/MultiModelEditorViewModel.cs | 14 ++++++++------ 5 files changed, 19 insertions(+), 15 deletions(-) diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index f700d27f..8d38a07f 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -48,7 +48,7 @@ public partial class ElementDefinitionTree /// The Iteration /// [Parameter] - public Iteration Iteration { get; set; } + public Iteration InitialIteration { get; set; } /// /// Gets or sets a value indicating that another model can be selected for this TreeView or not @@ -154,9 +154,9 @@ public partial class ElementDefinitionTree /// protected override async Task OnAfterRenderAsync(bool firstRender) { - if (this.ViewModel.Iteration != this.Iteration) + if (this.ViewModel.Iteration != this.InitialIteration) { - this.Iteration = this.ViewModel.Iteration; + this.InitialIteration = this.ViewModel.Iteration; } } @@ -168,7 +168,7 @@ protected override void OnParametersSet() { base.OnParametersSet(); - this.ViewModel.Iteration = this.Iteration; + this.ViewModel.Iteration ??= this.InitialIteration; } /// diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index e42d29d9..ad61947a 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -32,7 +32,7 @@

Source Model

Target Model /// Add a new based on an existing ///
- /// The - Task CopyAndAddNewElement(ElementBase elementBase); + /// The to copy the node to + /// The to copy + Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase); /// /// Add a new based on an existing diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs index 08f58b4f..9d05f059 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs @@ -36,6 +36,7 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor using COMET.Web.Common.ViewModels.Components.Applications; using COMETwebapp.Components.ModelEditor; + using COMETwebapp.Components.MultiModelEditor; using COMETwebapp.ViewModels.Components.ModelEditor; using COMETwebapp.ViewModels.Components.ModelEditor.AddParameterViewModel; using COMETwebapp.ViewModels.Components.SystemRepresentation; @@ -180,12 +181,13 @@ public void OpenAddParameterPopup() /// /// Add a new based on an existing /// - /// The - public async Task CopyAndAddNewElement(ElementBase elementBase) + /// The to copy the node to + /// The to copy + public async Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase) { this.IsLoading = true; - if (elementBase.GetContainerOfType() == this.CurrentThing) + if (elementBase.GetContainerOfType() == elementDefinitionTree.ViewModel.Iteration) { var copyCreator = new CopyElementDefinitionCreator(this.sessionService.Session); @@ -209,7 +211,7 @@ public async Task CopyAndAddNewElement(ElementBase elementBase) try { - await copyCreator.Copy((ElementDefinition)elementBase, this.CurrentThing); + await copyCreator.Copy((ElementDefinition)elementBase, elementDefinitionTree.ViewModel.Iteration); } catch (Exception exception) { @@ -230,7 +232,7 @@ public async Task CopyAndAddNewElement(ElementBase elementBase) /// The where to add the new to public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase) { - if (fromElementBase.GetContainerOfType() == this.CurrentThing && toElementBase.GetContainerOfType() == this.CurrentThing) + if (fromElementBase.GetContainerOfType() == toElementBase.GetContainerOfType()) { this.IsLoading = true; @@ -238,7 +240,7 @@ public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase to try { - await thingCreator.CreateElementUsage((ElementDefinition)toElementBase, (ElementDefinition)fromElementBase, this.sessionService.Session.OpenIterations.First(x => x.Key == this.CurrentThing).Value.Item1, this.sessionService.Session); + await thingCreator.CreateElementUsage((ElementDefinition)toElementBase, (ElementDefinition)fromElementBase, this.sessionService.Session.OpenIterations.First(x => x.Key == toElementBase.GetContainerOfType()).Value.Item1, this.sessionService.Session); } catch (Exception exception) { From 8c79b35a92fd1b3c5c005187f4ee0e1cdf7a2836 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 6 Jan 2025 13:32:02 +0100 Subject: [PATCH 05/23] Review comments and fix unit tests --- .../Utilities/CopyElementDefinitionCreator.cs | 2 +- .../Model/ApplicationsTestFixture.cs | 2 +- .../Common/OpenTabViewModelTestFixture.cs | 44 +++++++++++++++---- .../ElementDefinitionTree.razor.cs | 16 ++++--- COMETwebapp/Utilities/WebAppConstantValues.cs | 2 +- .../Common/OpenTab/OpenTabViewModel.cs | 2 +- .../MultiModelEditorViewModel.cs | 17 ++++--- .../Rows/ElementBaseTreeRowViewModel.cs | 2 +- 8 files changed, 63 insertions(+), 24 deletions(-) diff --git a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs index a30fd89d..f8b01040 100644 --- a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs +++ b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs @@ -182,4 +182,4 @@ private void ResolveReferences(ElementDefinition original, ElementDefinition dee } } } -} \ No newline at end of file +} diff --git a/COMETwebapp.Tests/Model/ApplicationsTestFixture.cs b/COMETwebapp.Tests/Model/ApplicationsTestFixture.cs index ec6a201f..7bea67cc 100644 --- a/COMETwebapp.Tests/Model/ApplicationsTestFixture.cs +++ b/COMETwebapp.Tests/Model/ApplicationsTestFixture.cs @@ -36,7 +36,7 @@ public void VerifyApplications() { var applications = Applications.ExistingApplications; - Assert.That(applications, Has.Count.EqualTo(13)); + Assert.That(applications, Has.Count.EqualTo(14)); foreach (var application in applications) { diff --git a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs index 27974a0c..7e00f4ea 100644 --- a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs @@ -53,6 +53,7 @@ public class OpenTabViewModelTestFixture private Mock sessionService; private Mock configurationService; private Mock tabsViewModel; + private SourceList alreadyOpenIterations; [SetUp] public void Setup() @@ -62,24 +63,25 @@ public void Setup() this.cacheService = new Mock(); this.tabsViewModel = new Mock(); - var id = Guid.NewGuid(); + var alreadyOpenIterationSetup = new IterationSetup { Iid = Guid.NewGuid() }; - var iterationToAdd = new Iteration + var alreadyOpenIteration = new Iteration { Container = new EngineeringModel { EngineeringModelSetup = new EngineeringModelSetup { - IterationSetup = { new IterationSetup { Iid = id } } + IterationSetup = { alreadyOpenIterationSetup } } }, - Iid = id + Iid = Guid.NewGuid(), + IterationSetup = alreadyOpenIterationSetup }; - var openIterations = new SourceList(); - openIterations.Add(iterationToAdd); + this.alreadyOpenIterations = new SourceList(); + this.alreadyOpenIterations.Add(alreadyOpenIteration); - this.sessionService.Setup(x => x.OpenIterations).Returns(openIterations); + this.sessionService.Setup(x => x.OpenIterations).Returns(this.alreadyOpenIterations); this.sessionService.Setup(x => x.OpenEngineeringModels).Returns([]); this.sessionService.Setup(x => x.ReadEngineeringModels(It.IsAny>())).Returns(Task.FromResult(new Result())); this.sessionService.Setup(x => x.ReadIteration(It.IsAny(), It.IsAny())).Returns(Task.FromResult(new Result())); @@ -105,11 +107,35 @@ public async Task VerifyOpenIterationAndModel() this.sessionService.Verify(x => x.ReadIteration(It.IsAny(), It.IsAny()), Times.Never); }); + var toBeOpenedIterationSetup = new IterationSetup { Iid = Guid.NewGuid() }; + + var toBeOpenedIteration = new Iteration + { + Container = new EngineeringModel + { + EngineeringModelSetup = new EngineeringModelSetup + { + IterationSetup = { toBeOpenedIterationSetup } + } + }, + Iid = Guid.NewGuid(), + IterationSetup = toBeOpenedIterationSetup + }; + var engineeringModelBodyApplication = Applications.ExistingApplications.OfType().First(x => x.Url == WebAppConstantValues.EngineeringModelPage); this.viewModel.SelectedApplication = engineeringModelBodyApplication; - this.viewModel.SelectedEngineeringModel = ((EngineeringModel)this.sessionService.Object.OpenIterations.Items.First().Container).EngineeringModelSetup; - this.viewModel.SelectedIterationSetup = new IterationData(this.viewModel.SelectedEngineeringModel.IterationSetup[0]); + this.viewModel.SelectedEngineeringModel = ((EngineeringModel)toBeOpenedIteration.Container).EngineeringModelSetup; + this.viewModel.SelectedIterationSetup = new IterationData(toBeOpenedIterationSetup); this.viewModel.SelectedDomainOfExpertise = new DomainOfExpertise(); + + //Make sure that toBeOpenedIteration is added to sessionService.OpenIterations when new Iteration is open + this.sessionService.Setup(x => x.ReadIteration(toBeOpenedIterationSetup, It.IsAny())) + .Returns(Task.FromResult(new Result())) + .Callback(() => + { + this.alreadyOpenIterations.Add(toBeOpenedIteration); + }); + await this.viewModel.OpenTab(panel); Assert.Multiple(() => diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index 8d38a07f..4fafebb6 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -44,6 +44,12 @@ public partial class ElementDefinitionTree [Inject] public IElementDefinitionTreeViewModel ViewModel { get; set; } + /// + /// Gets or sets the injected + /// + [Inject] + public ILogger Logger { get; set; } + /// /// The Iteration /// @@ -188,7 +194,7 @@ private async Task DragStart(ElementBaseTreeRowViewModel node) { await this.OnDragStart.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); - Console.WriteLine("DragStart"); + this.Logger.LogDebug("DragStart"); } /// @@ -200,7 +206,7 @@ private async Task DragEnd(ElementBaseTreeRowViewModel node) { await this.OnDragEnd.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); - Console.WriteLine("DragEnd"); + this.Logger.LogDebug("DragEnd"); } /// @@ -216,7 +222,7 @@ private async Task Drop(ElementBaseTreeRowViewModel node) { await this.OnDrop.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); - Console.WriteLine("Drop"); + this.Logger.LogDebug("Drop"); } } @@ -233,7 +239,7 @@ private async Task DragEnter(object node) await this.OnDragEnter.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); - Console.WriteLine("DragEnter"); + this.Logger.LogDebug("DragEnter"); } } @@ -249,7 +255,7 @@ private async Task DragLeave(object node) this.dragOverNode = null; await this.OnDragLeave.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); - Console.WriteLine("DragLeave"); + this.Logger.LogDebug("DragLeave"); } } } diff --git a/COMETwebapp/Utilities/WebAppConstantValues.cs b/COMETwebapp/Utilities/WebAppConstantValues.cs index 2f36c412..d9307bd0 100644 --- a/COMETwebapp/Utilities/WebAppConstantValues.cs +++ b/COMETwebapp/Utilities/WebAppConstantValues.cs @@ -77,7 +77,7 @@ public static class WebAppConstantValues public const string ModelDashboardPage = "ModelDashboard"; /// - /// The page name of the Engieering Model data + /// The page name of the Engineering Model data /// public const string EngineeringModelPage = "EngineeringModel"; diff --git a/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs b/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs index 236f0916..32882ac6 100644 --- a/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs +++ b/COMETwebapp/ViewModels/Components/Common/OpenTab/OpenTabViewModel.cs @@ -66,7 +66,7 @@ public class OpenTabViewModel : OpenModelViewModel, IOpenTabViewModel /// The /// The /// The - /// + /// The public OpenTabViewModel(ISessionService sessionService, IConfigurationService configurationService, ITabsViewModel tabsViewModel, ICacheService cacheService) : base(sessionService, configurationService, cacheService) { this.sessionService = sessionService; diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs index 9d05f059..3ccd732b 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs @@ -56,6 +56,11 @@ public class MultiModelEditorViewModel : SingleIterationApplicationBaseViewModel /// private readonly ISessionService sessionService; + /// + /// The injected + /// + private readonly ILogger logger; + /// /// Backing field for /// @@ -71,9 +76,11 @@ public class MultiModelEditorViewModel : SingleIterationApplicationBaseViewModel /// /// the /// The - public MultiModelEditorViewModel(ISessionService sessionService, ICDPMessageBus messageBus) : base(sessionService, messageBus) + /// The injected + public MultiModelEditorViewModel(ISessionService sessionService, ICDPMessageBus messageBus, ILogger logger) : base(sessionService, messageBus) { this.sessionService = sessionService; + this.logger = logger; var eventCallbackFactory = new EventCallbackFactory(); this.ElementDefinitionCreationViewModel = new ElementDefinitionCreationViewModel(sessionService, messageBus) @@ -197,7 +204,7 @@ public async Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTr } catch (Exception exception) { - Console.WriteLine(exception.Message); + this.logger.LogError(exception, string.Empty); throw; } finally @@ -215,7 +222,7 @@ public async Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTr } catch (Exception exception) { - Console.WriteLine(exception.Message); + this.logger.LogError(exception, string.Empty); throw; } finally @@ -244,7 +251,7 @@ public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase to } catch (Exception exception) { - Console.WriteLine(exception.Message); + this.logger.LogError(exception, string.Empty); throw; } finally @@ -286,7 +293,7 @@ public async Task AddingElementDefinition() } catch (Exception exception) { - Console.WriteLine(exception.Message); + this.logger.LogError(exception, string.Empty); throw; } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs index 56f7ab3d..1bbfba65 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs @@ -73,7 +73,7 @@ protected ElementBaseTreeRowViewModel() /// The shortname of the owning /// public string OwnerShortName - { + { get => this.ownerShortName; set => this.RaiseAndSetIfChanged(ref this.ownerShortName, value); } From a0a11706b0c25a4ab81db56f350a6c74fe84b78d Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 6 Jan 2025 14:49:37 +0100 Subject: [PATCH 06/23] Add SonarLint settings --- .sonarlint/COMETwebapp.json | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .sonarlint/COMETwebapp.json diff --git a/.sonarlint/COMETwebapp.json b/.sonarlint/COMETwebapp.json new file mode 100644 index 00000000..6951b41d --- /dev/null +++ b/.sonarlint/COMETwebapp.json @@ -0,0 +1,4 @@ +{ + "SonarCloudOrganization": "stariongroup", + "ProjectKey": "STARIONGROUP_COMET-WEB-Community-Edition" +} \ No newline at end of file From 14990c001973948602ad2aaf64138d0af6bf8048 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 6 Jan 2025 16:48:52 +0100 Subject: [PATCH 07/23] SonarQube fixes --- .../Components/CardView/CardView.razor | 2 +- .../Components/CardView/CardView.razor.cs | 2 +- COMET.Web.Common/Utilities/CopyCreator.cs | 12 +- .../Utilities/CopyElementDefinitionCreator.cs | 170 +++++++++--- COMET.Web.Common/Utilities/ThingCreator.cs | 262 ++---------------- .../Common/OpenTabViewModelTestFixture.cs | 3 +- .../ElementDefinitionTree.razor | 16 +- .../ElementDefinitionTree.razor.cs | 12 +- .../MultiModelEditor/MultiModelEditor.razor | 14 +- .../MultiModelEditor.razor.cs | 2 +- .../ElementDefinitionCreationViewModel.cs | 2 + .../IMultiModelEditorViewModel.cs | 2 +- .../MultiModelEditorViewModel.cs | 96 +++---- 13 files changed, 224 insertions(+), 371 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 63e8ad35..6c63e8a2 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -41,7 +41,7 @@

- +
/// The request to perform filtering of the items list /// an waitable - private ValueTask> LoadItems(ItemsProviderRequest request) + private ValueTask> LoadItemsAsync(ItemsProviderRequest request) { // Filter items based on the SearchTerm var filteredItems = !this.AllowSearch || string.IsNullOrWhiteSpace(this.SearchTerm) diff --git a/COMET.Web.Common/Utilities/CopyCreator.cs b/COMET.Web.Common/Utilities/CopyCreator.cs index 839d632d..36701615 100644 --- a/COMET.Web.Common/Utilities/CopyCreator.cs +++ b/COMET.Web.Common/Utilities/CopyCreator.cs @@ -59,19 +59,15 @@ public CopyCreator(ISession session) ///
/// The to copy /// The target container - public async Task Copy(ElementDefinition elementDefinition, Iteration targetIteration) + public async Task CopyAsync(ElementDefinition elementDefinition, Iteration targetIteration) { // copy the payload to this iteration var copyOperationHelper = new CopyPermissionHelper(this.session, false); var copyPermissionResult = await copyOperationHelper.ComputeCopyPermissionAsync(elementDefinition, targetIteration); - if (copyPermissionResult.ErrorList.Any()) + if (copyPermissionResult.ErrorList.Any() || copyPermissionResult.CopyableThings.Any()) { - await this.WriteCopyOperation(elementDefinition, targetIteration, OperationKind.CopyKeepValuesChangeOwner); - } - else if (copyPermissionResult.CopyableThings.Any()) - { - await this.WriteCopyOperation(elementDefinition, targetIteration, OperationKind.CopyKeepValuesChangeOwner); + await this.WriteCopyOperationAsync(elementDefinition, targetIteration, OperationKind.CopyKeepValuesChangeOwner); } } @@ -81,7 +77,7 @@ public async Task Copy(ElementDefinition elementDefinition, Iteration targetIter /// The to copy /// The target container /// The - private async Task WriteCopyOperation(Thing thingToCopy, Thing targetContainer, OperationKind operationKind) + private async Task WriteCopyOperationAsync(Thing thingToCopy, Thing targetContainer, OperationKind operationKind) { var clone = thingToCopy.Clone(false); var containerClone = targetContainer.Clone(false); diff --git a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs index f8b01040..47edce39 100644 --- a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs +++ b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs @@ -73,7 +73,19 @@ public CopyElementDefinitionCreator(ISession session) /// /// The to copy /// Do we need to copy the ElementUsages also? - public async Task Copy(ElementDefinition elementDefinition, bool areUsagesCopied) + public Task CopyAsync(ElementDefinition elementDefinition, bool areUsagesCopied) + { + ArgumentNullException.ThrowIfNull(elementDefinition); + + return this.CopyImplAsync(elementDefinition, areUsagesCopied); + } + + /// + /// Perform the copy operation of an + /// + /// The to copy + /// Do we need to copy the ElementUsages also? + private async Task CopyImplAsync(ElementDefinition elementDefinition, bool areUsagesCopied) { var iterationClone = (Iteration)elementDefinition.Container.Clone(false); var transactionContext = TransactionContextResolver.ResolveContext(iterationClone); @@ -99,27 +111,107 @@ public async Task Copy(ElementDefinition elementDefinition, bool areUsagesCopied /// Resolve the references of the copy /// /// The original - /// The clone + /// The clone private void ResolveReferences(ElementDefinition original, ElementDefinition deepClone) { + this.groupMap.Clear(); + this.valueSetMap.Clear(); + // Order of the item in a list is should be kept when cloning - // register mapping between original and copy - for (var i = 0; i < original.ParameterGroup.Count; i++) + this.RegisterParameterGroupMap(original, deepClone); + this.RegisterParameterValueSets(original, deepClone); + this.RegisterParameterOverrideValueSets(original, deepClone); + + // Resolve references + this.ResolveGroupMappings(); + this.ResolveParameterMappings(deepClone); + this.ResolveParameterSubscriptionValueSets(deepClone); + this.ResolveContainedElementSubscriptions(deepClone); + } + + /// + /// Resolve s for ContainedElements of the cloned + /// + /// The deeply cloned + private void ResolveContainedElementSubscriptions(ElementDefinition deepClone) + { + // fix the references of the subscription value set + foreach (var elementUsage in deepClone.ContainedElement) { - this.groupMap.Add(original.ParameterGroup[i], deepClone.ParameterGroup[i]); + foreach (var parameterOverride in elementUsage.ParameterOverride) + { + this.TryResolveParameterSubscriptionValueSets(parameterOverride); + } } + } - for (var i = 0; i < deepClone.Parameter.Count; i++) + /// + /// Resolve the already registered mappings for the original s' s + /// + /// The deeply cloned + private void ResolveParameterMappings(ElementDefinition deepClone) + { + // fix the group of the cloned parameters + foreach (var parameter in deepClone.Parameter) { - var originalParameter = original.Parameter[i]; - var cloneParameter = deepClone.Parameter[i]; + if (parameter.Group != null) + { + parameter.Group = this.groupMap[parameter.Group]; + } + } + } - for (var j = 0; j < originalParameter.ValueSet.Count; j++) + /// + /// Resolve already registered mappings for the cloned + /// + /// The deeply cloned + private void ResolveParameterSubscriptionValueSets(ElementDefinition deepClone) + { + // fix the group of the cloned parameters + foreach (var parameter in deepClone.Parameter) + { + this.TryResolveParameterSubscriptionValueSets(parameter); + } + } + + /// + /// Resolve already registered mappings for a specific + /// + /// The used to search for s + private void TryResolveParameterSubscriptionValueSets(ParameterOrOverrideBase parameterOrOverride) + { + foreach (var parameterSubscription in parameterOrOverride.ParameterSubscription) + { + foreach (var parameterSubscriptionValueSet in parameterSubscription.ValueSet) { - this.valueSetMap.Add(originalParameter.ValueSet[j], cloneParameter.ValueSet[j]); + parameterSubscriptionValueSet.SubscribedValueSet = + this.valueSetMap[parameterSubscriptionValueSet.SubscribedValueSet]; } } + } + /// + /// Resolve the already registered mappings + /// + private void ResolveGroupMappings() + { + foreach (var group in this.groupMap.Values) + { + if (group.ContainingGroup != null) + { + // use the mapped group + group.ContainingGroup = this.groupMap[group.ContainingGroup]; + } + } + } + + /// + /// Register mapping between original and copy s' contained s + /// + /// The source + /// The deeply cloned + private void RegisterParameterOverrideValueSets(ElementDefinition original, ElementDefinition deepClone) + { for (var i = 0; i < deepClone.ContainedElement.Count; i++) { var originalUsage = original.ContainedElement[i]; @@ -136,49 +228,37 @@ private void ResolveReferences(ElementDefinition original, ElementDefinition dee } } } + } - // Resolve references - foreach (var group in this.groupMap.Values) - { - if (group.ContainingGroup != null) - { - // use the mapped group - group.ContainingGroup = this.groupMap[group.ContainingGroup]; - } - } - - // fix the group of the cloned parameters - foreach (var parameter in deepClone.Parameter) + /// + /// Register mapping between original and copy s + /// + /// The source + /// The deeply cloned + private void RegisterParameterValueSets(ElementDefinition original, ElementDefinition deepClone) + { + for (var i = 0; i < deepClone.Parameter.Count; i++) { - if (parameter.Group != null) - { - parameter.Group = this.groupMap[parameter.Group]; - } + var originalParameter = original.Parameter[i]; + var cloneParameter = deepClone.Parameter[i]; - foreach (var parameterSubscription in parameter.ParameterSubscription) + for (var j = 0; j < originalParameter.ValueSet.Count; j++) { - foreach (var parameterSubscriptionValueSet in parameterSubscription.ValueSet) - { - parameterSubscriptionValueSet.SubscribedValueSet = - this.valueSetMap[parameterSubscriptionValueSet.SubscribedValueSet]; - } + this.valueSetMap.Add(originalParameter.ValueSet[j], cloneParameter.ValueSet[j]); } } + } - // fix the references of the subscription value set - foreach (var elementUsage in deepClone.ContainedElement) + /// + /// Register mapping between original and copy s + /// + /// The source + /// The deeply cloned + private void RegisterParameterGroupMap(ElementDefinition original, ElementDefinition deepClone) + { + for (var i = 0; i < original.ParameterGroup.Count; i++) { - foreach (var parameterOverride in elementUsage.ParameterOverride) - { - foreach (var parameterSubscription in parameterOverride.ParameterSubscription) - { - foreach (var parameterSubscriptionValueSet in parameterSubscription.ValueSet) - { - parameterSubscriptionValueSet.SubscribedValueSet = - this.valueSetMap[parameterSubscriptionValueSet.SubscribedValueSet]; - } - } - } + this.groupMap.Add(original.ParameterGroup[i], deepClone.ParameterGroup[i]); } } } diff --git a/COMET.Web.Common/Utilities/ThingCreator.cs b/COMET.Web.Common/Utilities/ThingCreator.cs index 95f1f814..9a7aa29f 100644 --- a/COMET.Web.Common/Utilities/ThingCreator.cs +++ b/COMET.Web.Common/Utilities/ThingCreator.cs @@ -26,7 +26,6 @@ namespace COMET.Web.Common.Utilities { using System; - using System.Linq; using System.Threading.Tasks; using CDP4Common.EngineeringModelData; @@ -41,180 +40,28 @@ namespace COMET.Web.Common.Utilities public class ThingCreator { /// - /// Create a new + /// Create a new /// - /// - /// The container of the that is to be created. - /// - /// - /// The that the is to be grouped in. - /// - /// - /// The that the new references + /// + /// The container of the that is to be created. /// - /// - /// The that the references in case the is a + /// + /// The referenced of the that is to be created. /// /// - /// The that is the owner of the that is to be created. + /// The that is the owner of the that is to be created. /// /// /// The in which the current is to be added /// - public async Task CreateParameter(ElementDefinition elementDefinition, ParameterGroup group, ParameterType parameterType, MeasurementScale measurementScale, DomainOfExpertise owner, ISession session) - { - if (elementDefinition == null) - { - throw new ArgumentNullException(nameof(elementDefinition), "The container ElementDefinition may not be null"); - } - - if (parameterType == null) - { - throw new ArgumentNullException(nameof(parameterType), "The ParameterType may not be null"); - } - - if (owner == null) - { - throw new ArgumentNullException(nameof(owner), "The owner DomainOfExpertise may not be null"); - } - - if (session == null) - { - throw new ArgumentNullException(nameof(session), "The session may not be null"); - } - - var parameter = new Parameter(Guid.NewGuid(), null, null) - { - Owner = owner, - ParameterType = parameterType, - Scale = measurementScale, - Group = group - }; - - var clone = elementDefinition.Clone(false); - clone.Parameter.Add(parameter); - - var transactionContext = TransactionContextResolver.ResolveContext(elementDefinition); - var transaction = new ThingTransaction(transactionContext, clone); - transaction.Create(parameter); - - try - { - var operationContainer = transaction.FinalizeTransaction(); - await session.Write(operationContainer); - } - catch (Exception ex) - { - throw; - } - } - - /// - /// Create a new - /// - /// - /// The container of the that is to be created. - /// - /// - /// The that the new references. - /// - /// - /// The in which the new is to be added - /// - public async Task CreateUserRuleVerification(RuleVerificationList ruleVerificationList, Rule rule, ISession session) - { - if (ruleVerificationList == null) - { - throw new ArgumentNullException(nameof(ruleVerificationList), "The ruleVerificationList must not be null"); - } - - if (rule == null) - { - throw new ArgumentNullException(nameof(rule), "The rule must not be null"); - } - - if (session == null) - { - throw new ArgumentNullException(nameof(session), "The session may not be null"); - } - - var userRuleVerification = new UserRuleVerification(Guid.NewGuid(), null, null) - { - Rule = rule, - IsActive = false, - Status = RuleVerificationStatusKind.NONE - }; - - var clone = ruleVerificationList.Clone(false); - clone.RuleVerification.Add(userRuleVerification); - - var transactionContext = TransactionContextResolver.ResolveContext(ruleVerificationList); - var transaction = new ThingTransaction(transactionContext, clone); - transaction.Create(userRuleVerification); - - try - { - var operationContainer = transaction.FinalizeTransaction(); - await session.Write(operationContainer); - } - catch (Exception ex) - { - throw; - } - } - - /// - /// Create a new - /// - /// - /// The container of the that is to be created. - /// - /// - /// The name for the - /// - /// - /// The in which the new is to be added - /// - public async Task CreateBuiltInRuleVerification(RuleVerificationList ruleVerificationList, string name, ISession session) + public Task CreateElementUsageAsync(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) { - if (ruleVerificationList == null) - { - throw new ArgumentNullException(nameof(ruleVerificationList), "The ruleVerificationList must not be null"); - } - - if (string.IsNullOrEmpty(name)) - { - throw new ArgumentException("The name may not be null or empty"); - } - - if (session == null) - { - throw new ArgumentNullException(nameof(session), "The session may not be null"); - } + ArgumentNullException.ThrowIfNull(container); + ArgumentNullException.ThrowIfNull(referencedDefinition); + ArgumentNullException.ThrowIfNull(owner); + ArgumentNullException.ThrowIfNull(session); - var builtInRuleVerification = new BuiltInRuleVerification(Guid.NewGuid(), null, null) - { - Name = name, - IsActive = false, - Status = RuleVerificationStatusKind.NONE - }; - - var clone = ruleVerificationList.Clone(false); - clone.RuleVerification.Add(builtInRuleVerification); - - var transactionContext = TransactionContextResolver.ResolveContext(ruleVerificationList); - var transaction = new ThingTransaction(transactionContext, clone); - transaction.Create(builtInRuleVerification); - - try - { - var operationContainer = transaction.FinalizeTransaction(); - await session.Write(operationContainer); - } - catch (Exception ex) - { - throw; - } + return this.CreateElementUsageImplAsync(container, referencedDefinition, owner, session); } /// @@ -232,29 +79,10 @@ public async Task CreateBuiltInRuleVerification(RuleVerificationList ruleVerific /// /// The in which the current is to be added /// - public async Task CreateElementUsage(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) + private async Task CreateElementUsageImplAsync(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) { - if (container == null) - { - throw new ArgumentNullException(nameof(container), "The container must not be null"); - } - - if (referencedDefinition == null) - { - throw new ArgumentNullException(nameof(referencedDefinition), "The referencedDefinition must not be null"); - } - - if (owner == null) - { - throw new ArgumentNullException(nameof(owner), "The owner must not be null"); - } - - if (session == null) - { - throw new ArgumentNullException(nameof(session), "The session may not be null"); - } - var clone = container.Clone(false); + var usage = new ElementUsage { Name = referencedDefinition.Name, @@ -269,66 +97,8 @@ public async Task CreateElementUsage(ElementDefinition container, ElementDefinit var transaction = new ThingTransaction(transactionContext, clone); transaction.Create(usage); - try - { - var operationContainer = transaction.FinalizeTransaction(); - await session.Write(operationContainer); - } - catch (Exception ex) - { - throw; - } - } - - /// - /// Method for creating a for requirement verification between a and a . - /// - /// The for which the will be created - /// The for which the will be created - /// The that acts as the source of the - /// The that acts as the target of the - /// An awaitable - public async Task CreateBinaryRelationshipForRequirementVerification(ISession session, Iteration iteration, ParameterOrOverrideBase parameter, RelationalExpression relationalExpression) - { - session.OpenIterations.TryGetValue(iteration, out var tuple); - - var binaryRelationship = new BinaryRelationship(Guid.NewGuid(), null, null) { Owner = tuple?.Item1 }; - - var transaction = new ThingTransaction(TransactionContextResolver.ResolveContext(relationalExpression)); - - binaryRelationship.Container = iteration; - binaryRelationship.Source = parameter; - binaryRelationship.Target = relationalExpression; - - var iterationClone = iteration.Clone(false); - iterationClone.Relationship.Add(binaryRelationship); - transaction.CreateOrUpdate(iterationClone); - transaction.Create(binaryRelationship); - - try - { - var operationContainer = transaction.FinalizeTransaction(); - await session.Write(operationContainer); - } - catch (Exception ex) - { - } - } - - /// - /// Checks if creating a for requirement verification is allowed for these two objects - /// - /// The - /// The - /// True if creation is allowed - public bool IsCreateBinaryRelationshipForRequirementVerificationAllowed(ParameterOrOverrideBase parameter, RelationalExpression relationalExpression) - { - return (parameter.ParameterType.Iid == relationalExpression.ParameterType.Iid) && - (!(parameter.ParameterType is QuantityKind) || (parameter.Scale == relationalExpression.Scale)) && - !relationalExpression.QueryRelationships - .Any( - x => x is BinaryRelationship relationship - && (relationship.Source.Iid == parameter.Iid)); + var operationContainer = transaction.FinalizeTransaction(); + await session.Write(operationContainer); } } } diff --git a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs index 7e00f4ea..2b3c71d6 100644 --- a/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/Common/OpenTabViewModelTestFixture.cs @@ -92,7 +92,8 @@ public void Setup() [TearDown] public void Teardown() { - this.viewModel.Dispose(); + this.viewModel?.Dispose(); + this.alreadyOpenIterations?.Dispose(); } [Test] diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 9e7765e9..f3798286 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -44,9 +44,9 @@ else
Drop here to create new element...
@@ -69,12 +69,12 @@ else
+ @ondragenter="@(async () => await this.DragEnterAsync(dataItem))" + @ondragleave="@(async () => await this.DragLeaveAsync(dataItem))" + @ondrop="@(async () => await this.DropAsync(dataItem))"> @dataItem.ElementName diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index 4fafebb6..1672ab72 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -108,7 +108,7 @@ public partial class ElementDefinitionTree /// Is evaluated when calculation if a node is draggable is necessary ///
[Parameter] - public Func AllowNodeDrag { get; set; } = (x, y) => true; + public Func AllowNodeDrag { get; set; } = (_, _) => true; /// /// Gets or sets a value indicating that dragging a node is allowed for this @@ -190,7 +190,7 @@ public void ClearSelection() /// /// The node where dragging has been started for /// an awaitable - private async Task DragStart(ElementBaseTreeRowViewModel node) + private async Task DragStartAsync(ElementBaseTreeRowViewModel node) { await this.OnDragStart.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); @@ -202,7 +202,7 @@ private async Task DragStart(ElementBaseTreeRowViewModel node) /// /// The node where dragging has been ended for /// an awaitable - private async Task DragEnd(ElementBaseTreeRowViewModel node) + private async Task DragEndAsync(ElementBaseTreeRowViewModel node) { await this.OnDragEnd.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); @@ -214,7 +214,7 @@ private async Task DragEnd(ElementBaseTreeRowViewModel node) /// /// The node where the dragged node has been dropped onto /// an awaitable - private async Task Drop(ElementBaseTreeRowViewModel node) + private async Task DropAsync(ElementBaseTreeRowViewModel node) { this.dragOverNode = null; @@ -231,7 +231,7 @@ private async Task Drop(ElementBaseTreeRowViewModel node) /// /// The node where the dragged node has been hovered over /// an awaitable - private async Task DragEnter(object node) + private async Task DragEnterAsync(object node) { if (this.AllowDrop) { @@ -248,7 +248,7 @@ private async Task DragEnter(object node) /// /// The node where the dragged node had been hovered over /// an awaitable - private async Task DragLeave(object node) + private async Task DragLeaveAsync(object node) { if (this.AllowDrop) { diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index ad61947a..a3798da5 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -33,7 +33,12 @@ The public void InitializeViewModel(Iteration iteration) { + ArgumentNullException.ThrowIfNull(iteration); + this.DomainOfExpertiseSelectorViewModel.CurrentIteration = iteration; this.DomainOfExpertiseSelectorViewModel.AvailableDomainsOfExpertise = ((EngineeringModel)iteration.Container).EngineeringModelSetup.ActiveDomain.OrderBy(x => x.Name, StringComparer.InvariantCultureIgnoreCase); } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs index 5dcaeecd..576c722b 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs @@ -100,7 +100,7 @@ public interface IMultiModelEditorViewModel : ISingleIterationApplicationBaseVie /// /// The to copy the node to /// The to copy - Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase); + Task CopyAndAddNewElementAsync(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase); /// /// Add a new based on an existing diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs index 3ccd732b..b3608ded 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs @@ -56,11 +56,6 @@ public class MultiModelEditorViewModel : SingleIterationApplicationBaseViewModel /// private readonly ISessionService sessionService; - /// - /// The injected - /// - private readonly ILogger logger; - /// /// Backing field for /// @@ -76,19 +71,17 @@ public class MultiModelEditorViewModel : SingleIterationApplicationBaseViewModel /// /// the /// The - /// The injected - public MultiModelEditorViewModel(ISessionService sessionService, ICDPMessageBus messageBus, ILogger logger) : base(sessionService, messageBus) + public MultiModelEditorViewModel(ISessionService sessionService, ICDPMessageBus messageBus) : base(sessionService, messageBus) { this.sessionService = sessionService; - this.logger = logger; var eventCallbackFactory = new EventCallbackFactory(); this.ElementDefinitionCreationViewModel = new ElementDefinitionCreationViewModel(sessionService, messageBus) { - OnValidSubmit = eventCallbackFactory.Create(this, this.AddingElementDefinition) + OnValidSubmit = eventCallbackFactory.Create(this, this.AddingElementDefinitionAsync) }; - this.AddParameterViewModel = new ModelEditor.AddParameterViewModel.AddParameterViewModel(sessionService, messageBus) + this.AddParameterViewModel = new AddParameterViewModel(sessionService, messageBus) { OnParameterAdded = eventCallbackFactory.Create(this, () => this.IsOnAddingParameterMode = false) }; @@ -190,45 +183,40 @@ public void OpenAddParameterPopup() /// /// The to copy the node to /// The to copy - public async Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase) + public Task CopyAndAddNewElementAsync(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase) + { + ArgumentNullException.ThrowIfNull(elementDefinitionTree); + ArgumentNullException.ThrowIfNull(elementBase); + + return this.CopyAndAddNewElementImplAsync(elementDefinitionTree, elementBase); + } + + /// + /// Add a new based on an existing + /// + /// The to copy the node to + /// The to copy + private async Task CopyAndAddNewElementImplAsync(ElementDefinitionTree elementDefinitionTree, ElementBase elementBase) { this.IsLoading = true; - if (elementBase.GetContainerOfType() == elementDefinitionTree.ViewModel.Iteration) + try { - var copyCreator = new CopyElementDefinitionCreator(this.sessionService.Session); - - try + if (elementBase.GetContainerOfType() == elementDefinitionTree.ViewModel.Iteration) { - await copyCreator.Copy((ElementDefinition)elementBase, true); + var copyCreator = new CopyElementDefinitionCreator(this.sessionService.Session); + await copyCreator.CopyAsync((ElementDefinition)elementBase, true); } - catch (Exception exception) + else { - this.logger.LogError(exception, string.Empty); - throw; - } - finally - { - this.IsLoading = false; + var copyCreator = new CopyCreator(this.sessionService.Session); + await copyCreator.CopyAsync((ElementDefinition)elementBase, elementDefinitionTree.ViewModel.Iteration); } } - else - { - var copyCreator = new CopyCreator(this.sessionService.Session); + finally - try - { - await copyCreator.Copy((ElementDefinition)elementBase, elementDefinitionTree.ViewModel.Iteration); - } - catch (Exception exception) - { - this.logger.LogError(exception, string.Empty); - throw; - } - finally - { - this.IsLoading = false; - } + { + this.IsLoading = false; } } @@ -237,7 +225,20 @@ public async Task CopyAndAddNewElement(ElementDefinitionTree elementDefinitionTr /// /// The to be added as /// The where to add the new to - public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase) + public Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase) + { + ArgumentNullException.ThrowIfNull(fromElementBase); + ArgumentNullException.ThrowIfNull(toElementBase); + + return this.AddNewElementUsageImplAsync(fromElementBase, toElementBase); + } + + /// + /// Add a new based on an existing + /// + /// The to be added as + /// The where to add the new to + private async Task AddNewElementUsageImplAsync(ElementBase fromElementBase, ElementBase toElementBase) { if (fromElementBase.GetContainerOfType() == toElementBase.GetContainerOfType()) { @@ -247,12 +248,7 @@ public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase to try { - await thingCreator.CreateElementUsage((ElementDefinition)toElementBase, (ElementDefinition)fromElementBase, this.sessionService.Session.OpenIterations.First(x => x.Key == toElementBase.GetContainerOfType()).Value.Item1, this.sessionService.Session); - } - catch (Exception exception) - { - this.logger.LogError(exception, string.Empty); - throw; + await thingCreator.CreateElementUsageAsync((ElementDefinition)toElementBase, (ElementDefinition)fromElementBase, this.sessionService.Session.OpenIterations.First(x => x.Key == toElementBase.GetContainerOfType()).Value.Item1, this.sessionService.Session); } finally { @@ -265,7 +261,7 @@ public async Task AddNewElementUsage(ElementBase fromElementBase, ElementBase to /// Tries to create a new /// /// A - public async Task AddingElementDefinition() + public async Task AddingElementDefinitionAsync() { var thingsToCreate = new List(); @@ -289,12 +285,10 @@ public async Task AddingElementDefinition() try { await this.sessionService.CreateOrUpdateThings(clonedIteration, thingsToCreate); - this.IsOnCreationMode = false; } - catch (Exception exception) + finally { - this.logger.LogError(exception, string.Empty); - throw; + this.IsOnCreationMode = false; } } From fe557d0a0bf241f4b8f29ddb85b30f301c7c35d2 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 6 Jan 2025 17:16:11 +0100 Subject: [PATCH 08/23] More SQ comments --- .../ElementDefinitionTree.razor | 8 +- .../ElementDefinitionTree.razor.cs | 4 +- .../MultiModelEditor/MultiModelEditor.razor | 46 ++++++----- .../MultiModelEditor.razor.cs | 78 +++++++++---------- 4 files changed, 69 insertions(+), 67 deletions(-) diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index f3798286..43b9e4fa 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -78,10 +78,10 @@ else @dataItem.ElementName - @foreach (var category in @dataItem.ElementBase.GetAllCategories()) + @foreach (var categoryShortName in @dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) { - + } @@ -92,10 +92,10 @@ else @dataItem.ElementName - @foreach (var category in @dataItem.ElementBase.GetAllCategories()) + @foreach (var categoryShortName in @dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) { - + } diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index 1672ab72..d21d08ce 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -158,12 +158,14 @@ public partial class ElementDefinitionTree /// Use the parameter to ensure that initialization work is only performed /// once. /// - protected override async Task OnAfterRenderAsync(bool firstRender) + protected override Task OnAfterRenderAsync(bool firstRender) { if (this.ViewModel.Iteration != this.InitialIteration) { this.InitialIteration = this.ViewModel.Iteration; } + + return Task.CompletedTask; } /// diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index a3798da5..bd51a38f 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -33,21 +33,23 @@ { - this.TargetTree.ClearSelection(); - } - this.OnElementSelected(model);}" + if (model != null) + { + this.TargetTree.ClearSelection(); + } + this.OnElementSelected(model); + }" AllowDrag="true" AllowDrop="true" - OnCalculateDropIsAllowed="@(async x => await this.SetDropIsAllowed(x))" - OnDragEnter="@(async x => await this.OnDragEnter(x))" - OnDragLeave="@(async x => await this.OnDragLeave(x))" - OnDragStart="@(async x => await this.OnDragStart(x))" - OnDragEnd="@(async x => await this.OnDragEnd(x))" - OnDrop="@(async x => await this.OnDrop(x))" - AllowNodeDrag="(tree, model) => model is ElementDefinitionTreeTreeRowViewModel" + OnCalculateDropIsAllowed="@(async x => await this.SetDropIsAllowedAsync(x))" + OnDragEnter="@(async x => await this.OnDragEnterAsync(x))" + OnDragLeave="@(async x => await this.OnDragLeaveAsync())" + OnDragStart="@(async x => await this.OnDragStartAsync(x))" + OnDragEnd="@(async x => await this.OnDragEndAsync())" + OnDrop="@(async x => await this.OnDropAsync(x))" + AllowNodeDrag="(_, model) => model is ElementDefinitionTreeTreeRowViewModel" IsModelSelectionEnabled="true"> @@ -56,21 +58,23 @@ + { if (model != null) { this.SourceTree.ClearSelection(); } - this.OnElementSelected(model);}" + this.OnElementSelected(model); + }" AllowDrag="true" AllowDrop="true" - OnCalculateDropIsAllowed="@(async x => await this.SetDropIsAllowed(x))" - OnDragEnter="@(async x => await this.OnDragEnter(x))" - OnDragLeave="@(async x => await this.OnDragLeave(x))" - OnDragStart="@(async x => await this.OnDragStart(x))" - OnDragEnd="@(async x => await this.OnDragEnd(x))" - OnDrop="@(async x => await this.OnDrop(x))" - AllowNodeDrag="(tree, model) => model is ElementDefinitionTreeTreeRowViewModel" + OnCalculateDropIsAllowed="@(async x => await this.SetDropIsAllowedAsync(x))" + OnDragEnter="@(async x => await this.OnDragEnterAsync(x))" + OnDragLeave="@(async x => await this.OnDragLeaveAsync())" + OnDragStart="@(async x => await this.OnDragStartAsync(x))" + OnDragEnd="@(async x => await this.OnDragEndAsync())" + OnDrop="@(async x => await this.OnDropAsync(x))" + AllowNodeDrag="(_, model) => model is ElementDefinitionTreeTreeRowViewModel" IsModelSelectionEnabled="false"> diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs index 39be32a9..885ccd16 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs @@ -32,8 +32,6 @@ namespace COMETwebapp.Components.MultiModelEditor using COMETwebapp.ViewModels.Components.ModelEditor.Rows; using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; - using DevExpress.Blazor; - using ReactiveUI; /// @@ -99,7 +97,7 @@ private void OnElementSelected(ElementBaseTreeRowViewModel elementRowViewModel) /// /// A that contains the specific and the specific node () /// an awaitable - private Task OnDragStart((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + private Task OnDragStartAsync((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) { this.DragObject = nodeData; return Task.CompletedTask; @@ -108,9 +106,8 @@ private Task OnDragStart((ElementDefinitionTree, ElementBaseTreeRowViewModel) no /// /// Is executed when dragging has been ended for a specific node () in a specific /// - /// A that contains the specific and the specific node () /// an awaitable - private Task OnDragEnd((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + private Task OnDragEndAsync() { this.DragObject = (null, null); return Task.CompletedTask; @@ -121,7 +118,7 @@ private Task OnDragEnd((ElementDefinitionTree, ElementBaseTreeRowViewModel) node /// /// A that contains the specific and the specific node () /// an awaitable - private async Task OnDrop((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) + private async Task OnDropAsync((ElementDefinitionTree, ElementBaseTreeRowViewModel) nodeData) { this.ErrorMessage = string.Empty; @@ -160,7 +157,7 @@ private async Task OnDrop((ElementDefinitionTree, ElementBaseTreeRowViewModel) n /// /// A that contains the specific and the specific element () /// an awaitable - private Task OnDragEnter((ElementDefinitionTree, object) elementData) + private Task OnDragEnterAsync((ElementDefinitionTree, object) elementData) { this.DragOverObject = elementData; return Task.CompletedTask; @@ -169,9 +166,8 @@ private Task OnDragEnter((ElementDefinitionTree, object) elementData) /// /// Is executed when a dragged node () leaves a previously hovered over specific element () in a specific /// - /// A that contains the specific and the specific element () /// an awaitable - private Task OnDragLeave((ElementDefinitionTree, object) elementData) + private Task OnDragLeaveAsync() { this.DragOverObject = (null, null); return Task.CompletedTask; @@ -182,7 +178,7 @@ private Task OnDragLeave((ElementDefinitionTree, object) elementData) /// /// The to calculate this for /// an awaitable - private Task SetDropIsAllowed(ElementDefinitionTree elementDefinitionTree) + private Task SetDropIsAllowedAsync(ElementDefinitionTree elementDefinitionTree) { elementDefinitionTree.AllowNodeDrop = this.CalculateDropIsAllowed(elementDefinitionTree); return Task.CompletedTask; @@ -198,46 +194,46 @@ private bool CalculateDropIsAllowed(ElementDefinitionTree elementDefinitionTree) var dragOverObject = this.DragOverObject; var dragObject = this.DragObject; - if (elementDefinitionTree.AllowDrop) + if (!elementDefinitionTree.AllowDrop) + { + return false; + } + + if (dragObject == (null, null)) + { + return false; + } + + if (dragOverObject == dragObject) + { + return false; + } + + if (dragOverObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragOverVm) { - if (dragObject != (null, null)) + if (dragObject.Item2 is not ElementDefinitionTreeTreeRowViewModel dragVm) { - if (dragOverObject == dragObject) - { - return false; - } - - if (dragOverObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragOverVm) - { - if (dragObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragVm) - { - if (dragOverVm.ElementBase == dragVm.ElementBase) - { - return false; - } - - if (dragOverVm.ElementBase.GetContainerOfType() == dragVm.ElementBase.GetContainerOfType()) - { - return true; - } - - return false; - } - - return false; - } - - if (dragOverObject.Item1 == elementDefinitionTree) - { - return true; - } + return false; + } + if (dragOverVm.ElementBase == dragVm.ElementBase) + { return false; } + if (dragOverVm.ElementBase.GetContainerOfType() == dragVm.ElementBase.GetContainerOfType()) + { + return true; + } + return false; } + if (dragOverObject.Item1 == elementDefinitionTree) + { + return true; + } + return false; } } From 269dcdc9e474afdb3ad3c70e14a686456f59d271 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Mon, 6 Jan 2025 17:28:20 +0100 Subject: [PATCH 09/23] Final SQ Comments --- COMET.Web.Common/Utilities/ThingCreator.cs | 4 ++-- .../Components/MultiModelEditor/ElementDefinitionTree.razor | 4 ++-- .../MultiModelEditor/ElementDefinitionTreeViewModel.cs | 5 ++--- .../MultiModelEditor/IElementDefinitionTreeViewModel.cs | 2 +- 4 files changed, 7 insertions(+), 8 deletions(-) diff --git a/COMET.Web.Common/Utilities/ThingCreator.cs b/COMET.Web.Common/Utilities/ThingCreator.cs index 9a7aa29f..afecf074 100644 --- a/COMET.Web.Common/Utilities/ThingCreator.cs +++ b/COMET.Web.Common/Utilities/ThingCreator.cs @@ -61,7 +61,7 @@ public Task CreateElementUsageAsync(ElementDefinition container, ElementDefiniti ArgumentNullException.ThrowIfNull(owner); ArgumentNullException.ThrowIfNull(session); - return this.CreateElementUsageImplAsync(container, referencedDefinition, owner, session); + return CreateElementUsageImplAsync(container, referencedDefinition, owner, session); } /// @@ -79,7 +79,7 @@ public Task CreateElementUsageAsync(ElementDefinition container, ElementDefiniti /// /// The in which the current is to be added /// - private async Task CreateElementUsageImplAsync(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) + private static async Task CreateElementUsageImplAsync(ElementDefinition container, ElementDefinition referencedDefinition, DomainOfExpertise owner, ISession session) { var clone = container.Clone(false); diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 43b9e4fa..f96f5e69 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -78,7 +78,7 @@ else @dataItem.ElementName - @foreach (var categoryShortName in @dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) + @foreach (var categoryShortName in dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) { @@ -92,7 +92,7 @@ else @dataItem.ElementName - @foreach (var categoryShortName in @dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) + @foreach (var categoryShortName in dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) { diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index ac849a57..c996e566 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -36,7 +36,6 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor using COMET.Web.Common.Services.SessionManagement; using COMET.Web.Common.ViewModels.Components.Applications; - using COMETwebapp.Components.ModelEditor; using COMETwebapp.Components.MultiModelEditor; using COMETwebapp.ViewModels.Components.ModelEditor; using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; @@ -125,12 +124,12 @@ public IterationData SelectedIterationData /// /// All of the iteration /// - public List Elements { get; set; } = []; + public List Elements { get; private set; } = []; /// /// Gets the collection of the for target model /// - public ObservableCollection Rows { get; set; } = []; + public ObservableCollection Rows { get; private set; } = []; /// /// Represents the selected ElementDefinitionRowViewModel diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs index dd2fea60..b37a9e0a 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs @@ -41,7 +41,7 @@ public interface IElementDefinitionTreeViewModel : IHaveReusableRows /// /// Gets the collection of the /// - ObservableCollection Rows { get; set; } + ObservableCollection Rows { get; } /// /// The from which to build the tree From b7a41e31218500ff9f3f45355e6f4751cd2e4879 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 09:28:13 +0100 Subject: [PATCH 10/23] SQ --- .../ElementDefinitionTableViewModel.cs | 6 +++--- .../ElementDefinitionTreeViewModel.cs | 18 +++++++++--------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/COMETwebapp/ViewModels/Components/ModelEditor/ElementDefinitionTableViewModel.cs b/COMETwebapp/ViewModels/Components/ModelEditor/ElementDefinitionTableViewModel.cs index 06e9ab43..fb99f9e3 100644 --- a/COMETwebapp/ViewModels/Components/ModelEditor/ElementDefinitionTableViewModel.cs +++ b/COMETwebapp/ViewModels/Components/ModelEditor/ElementDefinitionTableViewModel.cs @@ -80,7 +80,7 @@ public ElementDefinitionTableViewModel(ISessionService sessionService, ICDPMessa this.ElementDefinitionCreationViewModel = new ElementDefinitionCreationViewModel(sessionService, messageBus) { - OnValidSubmit = eventCallbackFactory.Create(this, this.AddingElementDefinition) + OnValidSubmit = eventCallbackFactory.Create(this, this.AddingElementDefinitionAsync) }; this.AddParameterViewModel = new AddParameterViewModel.AddParameterViewModel(sessionService, messageBus) @@ -95,7 +95,7 @@ public ElementDefinitionTableViewModel(ISessionService sessionService, ICDPMessa /// /// All of the iteration /// - public List Elements { get; set; } = []; + public List Elements { get; } = []; /// /// The @@ -244,7 +244,7 @@ public void RemoveRows(IEnumerable deletedThings) /// Tries to create a new /// /// A - public async Task AddingElementDefinition() + public async Task AddingElementDefinitionAsync() { var thingsToCreate = new List(); diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index c996e566..b1968c8e 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -215,14 +215,15 @@ private void RefreshIterations(IChangeSet changeSet) switch (change.Reason) { case ListChangeReason.AddRange: - foreach (var changeItem in change.Range) - { - var newChangeIterationData = new IterationData(changeItem.IterationSetup, true); - if (!this.Iterations.Contains(newChangeIterationData)) - { - this.Iterations.Add(newChangeIterationData); - } + var toBeAdded = change.Range + .Select(x => new IterationData(x.IterationSetup, true)) + .Where(x => !this.Iterations.Contains(x)) + .ToArray(); + + if (toBeAdded.Length > 0) + { + this.Iterations.AddRange(toBeAdded); } break; @@ -238,9 +239,8 @@ private void RefreshIterations(IChangeSet changeSet) break; case ListChangeReason.Remove: - var currentItem = this.Iterations.FirstOrDefault(x => x?.IterationSetupId == change.Item.Current.IterationSetup.Iid); - if (currentItem != null) + if (this.Iterations.FirstOrDefault(x => x?.IterationSetupId == change.Item.Current.IterationSetup.Iid) is { } currentItem) { this.Iterations.Remove(currentItem); } From 74dd7242bc620ed8178dbd357a2221a3ebd2c8f1 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 09:46:52 +0100 Subject: [PATCH 11/23] SQ --- .../Components/MultiModelEditor/MultiModelEditor.razor.cs | 2 +- .../MultiModelEditor/IMultiModelEditorViewModel.cs | 2 +- .../MultiModelEditor/MultiModelEditorViewModel.cs | 2 +- .../MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs | 6 +++++- .../Rows/ElementDefinitionTreeTreeRowViewModel.cs | 6 +++++- 5 files changed, 13 insertions(+), 5 deletions(-) diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs index 885ccd16..eb30f6a3 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs @@ -136,7 +136,7 @@ private async Task OnDropAsync((ElementDefinitionTree, ElementBaseTreeRowViewMod } else { - await this.ViewModel.AddNewElementUsage(elementDefinitionTreeRowViewModel.ElementBase, nodeData.Item2.ElementBase); + await this.ViewModel.AddNewElementUsageAsync(elementDefinitionTreeRowViewModel.ElementBase, nodeData.Item2.ElementBase); } } catch (Exception ex) diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs index 576c722b..b795cbf9 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IMultiModelEditorViewModel.cs @@ -107,6 +107,6 @@ public interface IMultiModelEditorViewModel : ISingleIterationApplicationBaseVie /// /// The to be added as /// The where to add the new to - Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase); + Task AddNewElementUsageAsync(ElementBase fromElementBase, ElementBase toElementBase); } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs index b3608ded..fceec26f 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/MultiModelEditorViewModel.cs @@ -225,7 +225,7 @@ private async Task CopyAndAddNewElementImplAsync(ElementDefinitionTree elementDe /// /// The to be added as /// The where to add the new to - public Task AddNewElementUsage(ElementBase fromElementBase, ElementBase toElementBase) + public Task AddNewElementUsageAsync(ElementBase fromElementBase, ElementBase toElementBase) { ArgumentNullException.ThrowIfNull(fromElementBase); ArgumentNullException.ThrowIfNull(toElementBase); diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs index 1bbfba65..90b2c7bc 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs @@ -1,5 +1,5 @@ // -------------------------------------------------------------------------------------------------------------------- -// +// // Copyright (c) 2024 Starion Group S.A. // // Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua @@ -57,6 +57,8 @@ public abstract class ElementBaseTreeRowViewModel : ReactiveObject /// protected ElementBaseTreeRowViewModel(ElementBase elementBase) { + ArgumentNullException.ThrowIfNull(elementBase); + this.ElementBase = elementBase; this.ElementName = elementBase.Name; this.OwnerShortName = elementBase.Owner.ShortName; @@ -102,6 +104,8 @@ public ElementBase ElementBase /// The to use for updating public void UpdateProperties(ElementBaseTreeRowViewModel elementBaseTreeRow) { + ArgumentNullException.ThrowIfNull(elementBaseTreeRow); + this.ElementBase = elementBaseTreeRow.elementBase; this.ElementName = elementBaseTreeRow.elementName; this.OwnerShortName = elementBaseTreeRow.OwnerShortName; diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs index 6d0d62bf..d320b04d 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs @@ -55,9 +55,11 @@ public class ElementDefinitionTreeTreeRowViewModel : ElementBaseTreeRowViewModel /// public ElementDefinitionTreeTreeRowViewModel(ElementDefinition elementBase) : base(elementBase) { + ArgumentNullException.ThrowIfNull(elementBase); + this.IsTopElement = elementBase == elementBase.GetContainerOfType().TopElement; - var elementUsages = elementBase?.ContainedElement; + var elementUsages = elementBase.ContainedElement; if (elementUsages?.Any() ?? false) { @@ -87,6 +89,8 @@ public bool IsTopElement /// The to use for updating public void UpdateProperties(ElementDefinitionTreeTreeRowViewModel elementDefinitionTreeRow) { + ArgumentNullException.ThrowIfNull(elementDefinitionTreeRow); + base.UpdateProperties(elementDefinitionTreeRow); this.IsTopElement = elementDefinitionTreeRow.isTopElement; From b32b53859d35a73a87c01e918edb2ea13bc7b6c7 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 11:02:48 +0100 Subject: [PATCH 12/23] Add coverage --- .../Components/OpenModelTestFixture.cs | 92 +++++++++++++------ .../Components/CardView/CardView.razor.cs | 6 +- .../Utilities/CopyElementDefinitionCreator.cs | 4 +- .../HaveObjectChangedTracking.cs | 6 +- .../Components/OpenModelViewModel.cs | 2 +- .../ElementDefinitionTreeViewModel.cs | 2 +- .../ElementDefinitionTreeTreeRowViewModel.cs | 2 +- 7 files changed, 76 insertions(+), 38 deletions(-) diff --git a/COMET.Web.Common.Tests/Components/OpenModelTestFixture.cs b/COMET.Web.Common.Tests/Components/OpenModelTestFixture.cs index 4dd98989..8d9e92e5 100644 --- a/COMET.Web.Common.Tests/Components/OpenModelTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/OpenModelTestFixture.cs @@ -59,33 +59,13 @@ public class OpenModelTestFixture private Mock sessionService; private Mock configurationService; private Mock cacheService; + private List engineeringModels; [SetUp] public void Setup() { - this.context = new TestContext(); - this.sessionService = new Mock(); - this.configurationService = new Mock(); - this.cacheService = new Mock(); - this.viewModel = new OpenModelViewModel(this.sessionService.Object, this.configurationService.Object, this.cacheService.Object); - this.context.ConfigureDevExpressBlazor(); - this.context.Services.AddSingleton(this.viewModel); - - var stringTableService = new Mock(); - this.context.Services.AddSingleton(stringTableService.Object); - } - - [TearDown] - public void Teardown() - { - this.context.CleanContext(); - } - - [Test] - public async Task VerifyOpenModel() - { - var engineeringModels = new List - { + this.engineeringModels = + [ new() { Iid = Guid.NewGuid(), @@ -99,6 +79,7 @@ public async Task VerifyOpenModel() } } }, + new() { Iid = Guid.NewGuid(), @@ -109,7 +90,7 @@ public async Task VerifyOpenModel() { Iid = Guid.NewGuid(), IterationNumber = 1, - FrozenOn = DateTime.Now - TimeSpan.FromDays(1) + FrozenOn = DateTimeOffset.Now.AddDays(-1).DateTime }, new IterationSetup { @@ -118,10 +99,31 @@ public async Task VerifyOpenModel() } } } - }; + ]; + + this.context = new TestContext(); + this.sessionService = new Mock(); + this.configurationService = new Mock(); + this.cacheService = new Mock(); + this.viewModel = new OpenModelViewModel(this.sessionService.Object, this.configurationService.Object, this.cacheService.Object); + this.context.ConfigureDevExpressBlazor(); + this.context.Services.AddSingleton(this.viewModel); + + var stringTableService = new Mock(); + this.context.Services.AddSingleton(stringTableService.Object); + } + [TearDown] + public void Teardown() + { + this.context.CleanContext(); + } + + [Test] + public async Task VerifyOpenModel() + { this.sessionService.Setup(x => x.OpenIterations).Returns(new SourceList()); - this.sessionService.Setup(x => x.GetParticipantModels()).Returns(engineeringModels); + this.sessionService.Setup(x => x.GetParticipantModels()).Returns(this.engineeringModels); var renderer = this.context.RenderComponent(); var layoutItems = renderer.FindComponents(); @@ -158,13 +160,49 @@ public async Task VerifyOpenModel() this.viewModel.SelectedEngineeringModel = null; + var result = await this.viewModel.OpenSession(); + Assert.Multiple(() => { Assert.That(this.viewModel.SelectedDomainOfExpertise, Is.Null); Assert.That(this.viewModel.SelectedIterationSetup, Is.Null); Assert.That(this.viewModel.AvailableIterationSetups, Is.Empty); Assert.That(this.viewModel.AvailablesDomainOfExpertises, Is.Empty); - Assert.That(async () => await this.viewModel.OpenSession(), Throws.Nothing); + Assert.That(result.IsFailed, Is.True); + Assert.That(result.Errors[0].Message, Is.EqualTo("The selected iteration and the domain of expertise should not be null")); + }); + } + + [Test] + public async Task VerifyOpenModelFails() + { + this.sessionService.Setup(x => x.OpenIterations).Returns(new SourceList()); + this.sessionService.Setup(x => x.GetParticipantModels()).Returns(this.engineeringModels); + this.context.RenderComponent(); + + this.sessionService.Setup(x => x.GetModelDomains(It.IsAny())) + .Returns(new List { new() { Name = "Thermodynamic" } }); + + this.viewModel.SelectedEngineeringModel = this.viewModel.AvailableEngineeringModelSetups.First(); + this.viewModel.SelectedDomainOfExpertise = this.viewModel.AvailablesDomainOfExpertises.First(); + this.viewModel.SelectedIterationSetup = this.viewModel.AvailableIterationSetups.First(); + + var iteration = new Iteration(Guid.NewGuid(), null, null) + { + IterationSetup = this.engineeringModels.SelectMany(x => x.IterationSetup).Single(x => x.Iid == this.viewModel.SelectedIterationSetup.IterationSetupId) + }; + + var openIterations = new SourceList(); + openIterations.Add(iteration); + + this.sessionService.Setup(x => x.OpenIterations).Returns(openIterations); + + var result = await this.viewModel.OpenSession(); + + Assert.Multiple(() => + { + Assert.That(result.IsFailed, Is.True); + Assert.That(result.Errors[0].Message, Is.EqualTo("The selected iteration is already openened")); }); } } diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index f7bd1656..7856e92b 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -41,7 +41,7 @@ public partial class CardView : DisposableComponent /// Gets or sets the item template for the list. /// [Parameter] - public RenderFragment? ItemTemplate { get; set; } + public RenderFragment ItemTemplate { get; set; } /// /// Gets or sets the list of items of type T to use @@ -90,12 +90,12 @@ public partial class CardView : DisposableComponent /// /// A reference to the component for loading items /// - private Virtualize? virtualize; // Reference to the Virtualize component + private Virtualize virtualize; // Reference to the Virtualize component /// /// The FastMember to use to perform actions on instances of type T /// - private TypeAccessor typeAccessor = TypeAccessor.Create(typeof(T)); + private readonly TypeAccessor typeAccessor = TypeAccessor.Create(typeof(T)); /// /// The selected Card in the CardView diff --git a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs index 47edce39..6a55286b 100644 --- a/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs +++ b/COMET.Web.Common/Utilities/CopyElementDefinitionCreator.cs @@ -52,12 +52,12 @@ public class CopyElementDefinitionCreator /// /// The original-clone map /// - private readonly Dictionary groupMap = new Dictionary(); + private readonly Dictionary groupMap = []; /// /// The original-clone map /// - private readonly Dictionary valueSetMap = new Dictionary(); + private readonly Dictionary valueSetMap = []; /// /// Initializes a new instance of the class diff --git a/COMET.Web.Common/Utilities/HaveObjectChangedTracking/HaveObjectChangedTracking.cs b/COMET.Web.Common/Utilities/HaveObjectChangedTracking/HaveObjectChangedTracking.cs index 65a4d6b2..27d028c8 100644 --- a/COMET.Web.Common/Utilities/HaveObjectChangedTracking/HaveObjectChangedTracking.cs +++ b/COMET.Web.Common/Utilities/HaveObjectChangedTracking/HaveObjectChangedTracking.cs @@ -42,17 +42,17 @@ public abstract class HaveObjectChangedTracking : DisposableObject, IHaveObjectC /// /// A collection of added s /// - protected readonly List AddedThings = new(); + protected readonly List AddedThings = []; /// /// A collection of deleted s /// - protected readonly List DeletedThings = new(); + protected readonly List DeletedThings = []; /// /// A collection of updated s /// - protected readonly List UpdatedThings = new(); + protected readonly List UpdatedThings = []; /// /// Initializes a new instance of diff --git a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs index d256e177..1cf9e711 100644 --- a/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs +++ b/COMET.Web.Common/ViewModels/Components/OpenModelViewModel.cs @@ -285,7 +285,7 @@ private void ComputeAvailableCollections() return; } - var currentModelIteration = this.SelectedEngineeringModel.IterationSetup.FirstOrDefault(x => x == this.sessionService.OpenIterations.Items.FirstOrDefault(i => i.Iid == x.IterationIid)?.IterationSetup); + var currentModelIteration = this.SelectedEngineeringModel.IterationSetup.Find(x => x == this.sessionService.OpenIterations.Items.FirstOrDefault(i => i.Iid == x.IterationIid)?.IterationSetup); this.SelectedIterationSetup = new IterationData(currentModelIteration); } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index b1968c8e..ba548bfc 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -119,7 +119,7 @@ public IterationData SelectedIterationData /// /// Gets or a collection of selectable s /// - public ObservableCollection Iterations { get; } = new(); + public ObservableCollection Iterations { get; } = []; /// /// All of the iteration diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs index d320b04d..b184fbd1 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs @@ -47,7 +47,7 @@ public class ElementDefinitionTreeTreeRowViewModel : ElementBaseTreeRowViewModel /// /// Gets or the collection of /// - public ObservableCollection Rows { get; } = new(); + public ObservableCollection Rows { get; } = []; /// /// Initializes a new instance of the class. From f51f6d4b7a37acf9c76fd67f10e5bf7f84f63b68 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 12:00:25 +0100 Subject: [PATCH 13/23] Add Creator tests --- ...CopyElementDefinitionCreatorTestFixture.cs | 161 ++++++++++++++++++ .../Utilities/ThingCreatorTestFixture.cs | 122 +++++++++++++ 2 files changed, 283 insertions(+) create mode 100644 COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs create mode 100644 COMET.Web.Common.Tests/Utilities/ThingCreatorTestFixture.cs diff --git a/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs b/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs new file mode 100644 index 00000000..997c736a --- /dev/null +++ b/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs @@ -0,0 +1,161 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2015-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, Nabil Abbar +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// 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 COMET.Web.Common.Tests.Utilities +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + + using CDP4Common.CommonData; + using CDP4Common.EngineeringModelData; + using CDP4Common.Types; + + using CDP4Dal; + using CDP4Dal.Operations; + + using COMET.Web.Common.Utilities; + + using Moq; + + using NUnit.Framework; + + /// + /// Suite of tests for the class + /// + [TestFixture] + public class CopyElementDefinitionCreatorTestFixture + { + private readonly Uri uri = new("http://test.com"); + private Mock session; + private Assembler asembler; + private ConcurrentDictionary> cache; + private CDPMessageBus messageBus; + + private EngineeringModel model; + private Iteration iteration; + private ElementDefinition elementDef1; + private ElementDefinition elementDef2; + private Parameter parameter1; + private ParameterValueSet valueSet1; + private Parameter parameter2; + private ParameterValueSet valueSet2; + private ParameterOverride parameterOverride; + private ParameterOverrideValueSet overrideValueset; + private ElementUsage usage; + + private ParameterSubscription sub1; + private ParameterSubscriptionValueSet subValueset1; + private ParameterSubscription sub2; + private ParameterSubscriptionValueSet subValueset2; + + [SetUp] + public void SetUp() + { + this.messageBus = new CDPMessageBus(); + this.session = new Mock(); + this.asembler = new Assembler(this.uri, this.messageBus); + this.cache = this.asembler.Cache; + + this.session.Setup(x => x.Assembler).Returns(this.asembler); + this.session.Setup(x => x.CDPMessageBus).Returns(this.messageBus); + + this.model = new EngineeringModel(Guid.NewGuid(), this.cache, this.uri); + this.iteration = new Iteration(Guid.NewGuid(), this.cache, this.uri); + + this.elementDef1 = new ElementDefinition(Guid.NewGuid(), this.cache, this.uri); + this.elementDef2 = new ElementDefinition(Guid.NewGuid(), this.cache, this.uri); + + this.parameter1 = new Parameter(Guid.NewGuid(), this.cache, this.uri); + this.valueSet1 = new ParameterValueSet(Guid.NewGuid(), this.cache, this.uri); + this.parameter2 = new Parameter(Guid.NewGuid(), this.cache, this.uri); + this.valueSet2 = new ParameterValueSet(Guid.NewGuid(), this.cache, this.uri); + this.usage = new ElementUsage(Guid.NewGuid(), this.cache, this.uri); + + this.parameterOverride = new ParameterOverride(Guid.NewGuid(), this.cache, this.uri); + this.parameterOverride.Parameter = this.parameter2; + this.overrideValueset = new ParameterOverrideValueSet(Guid.NewGuid(), this.cache, this.uri); + this.overrideValueset.ParameterValueSet = this.valueSet2; + + this.model.Iteration.Add(this.iteration); + this.iteration.Element.Add(this.elementDef1); + this.iteration.Element.Add(this.elementDef2); + + this.elementDef1.Parameter.Add(this.parameter1); + this.parameter1.ValueSet.Add(this.valueSet1); + + this.usage.ElementDefinition = this.elementDef2; + this.usage.ParameterOverride.Add(this.parameterOverride); + this.parameterOverride.ValueSet.Add(this.overrideValueset); + + this.elementDef1.ContainedElement.Add(this.usage); + + this.sub1 = new ParameterSubscription(Guid.NewGuid(), this.cache, this.uri); + this.sub2 = new ParameterSubscription(Guid.NewGuid(), this.cache, this.uri); + this.subValueset1 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri); + this.subValueset1.SubscribedValueSet = this.valueSet1; + this.subValueset2 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri); + this.subValueset2.SubscribedValueSet = this.overrideValueset; + + this.sub1.ValueSet.Add(this.subValueset1); + this.sub2.ValueSet.Add(this.subValueset2); + + this.parameter1.ParameterSubscription.Add(this.sub1); + this.parameterOverride.ParameterSubscription.Add(this.sub2); + + this.cache.TryAdd(new CacheKey(this.model.Iid, null), new Lazy(() => this.model)); + this.cache.TryAdd(new CacheKey(this.iteration.Iid, null), new Lazy(() => this.iteration)); + this.cache.TryAdd(new CacheKey(this.elementDef1.Iid, this.iteration.Iid), new Lazy(() => this.elementDef1)); + this.cache.TryAdd(new CacheKey(this.elementDef2.Iid, this.iteration.Iid), new Lazy(() => this.elementDef2)); + this.cache.TryAdd(new CacheKey(this.usage.Iid, this.iteration.Iid), new Lazy(() => this.usage)); + this.cache.TryAdd(new CacheKey(this.parameter1.Iid, this.iteration.Iid), new Lazy(() => this.parameter1)); + this.cache.TryAdd(new CacheKey(this.parameter2.Iid, this.iteration.Iid), new Lazy(() => this.parameter2)); + this.cache.TryAdd(new CacheKey(this.valueSet1.Iid, this.iteration.Iid), new Lazy(() => this.valueSet1)); + this.cache.TryAdd(new CacheKey(this.valueSet2.Iid, this.iteration.Iid), new Lazy(() => this.valueSet2)); + this.cache.TryAdd(new CacheKey(this.parameterOverride.Iid, this.iteration.Iid), new Lazy(() => this.parameterOverride)); + this.cache.TryAdd(new CacheKey(this.overrideValueset.Iid, this.iteration.Iid), new Lazy(() => this.overrideValueset)); + this.cache.TryAdd(new CacheKey(this.sub1.Iid, this.iteration.Iid), new Lazy(() => this.sub1)); + this.cache.TryAdd(new CacheKey(this.sub2.Iid, this.iteration.Iid), new Lazy(() => this.sub2)); + this.cache.TryAdd(new CacheKey(this.subValueset1.Iid, this.iteration.Iid), new Lazy(() => this.subValueset1)); + this.cache.TryAdd(new CacheKey(this.subValueset2.Iid, this.iteration.Iid), new Lazy(() => this.subValueset2)); + } + + [Test] + public async Task VerifyThatCopyWithUsageWorks() + { + var copy = new CopyElementDefinitionCreator(this.session.Object); + await copy.CopyAsync(this.elementDef1, true); + this.session.Verify(x => x.Write(It.Is(c => c.Operations.Count(op => op.OperationKind == OperationKind.Create) == 10))); + } + + [Test] + public async Task VerifyThatCopyWithoutUsageWorks() + { + var copy = new CopyElementDefinitionCreator(this.session.Object); + await copy.CopyAsync(this.elementDef1, false); + this.session.Verify(x => x.Write(It.Is(c => c.Operations.Count(op => op.OperationKind == OperationKind.Create) == 5))); + } + } +} diff --git a/COMET.Web.Common.Tests/Utilities/ThingCreatorTestFixture.cs b/COMET.Web.Common.Tests/Utilities/ThingCreatorTestFixture.cs new file mode 100644 index 00000000..8076c1fa --- /dev/null +++ b/COMET.Web.Common.Tests/Utilities/ThingCreatorTestFixture.cs @@ -0,0 +1,122 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2015-2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, Nabil Abbar +// +// This file is part of CDP4-COMET WEB Community Edition +// The CDP4-COMET WEB Community Edition is the Starion Web Application implementation of ECSS-E-TM-10-25 +// Annex A and Annex C. +// +// 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 COMET.Web.Common.Tests.Utilities +{ + using System.Collections.Concurrent; + + using CDP4Common.CommonData; + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + using CDP4Common.Types; + + using CDP4Dal; + using CDP4Dal.Operations; + + using COMET.Web.Common.Utilities; + + using Moq; + + using NUnit.Framework; + + /// + /// Suite of tests for the class + /// + [TestFixture] + public class ThingCreatorTestFixture + { + private Mock session; + private Mock sessionThatThrowsException; + private ThingCreator thingCreator; + private ConcurrentDictionary> cache; + + [SetUp] + public void SetUp() + { + this.session = new Mock(); + this.cache = new ConcurrentDictionary>(); + this.sessionThatThrowsException = new Mock(); + this.sessionThatThrowsException.Setup(x => x.Write(It.IsAny())).Throws(); + + this.thingCreator = new ThingCreator(); + } + + [Test] + public void VerifyThatArgumentNullExceptionsAreThrowOnCreateElementUsage() + { + var domainOfExpertise = new DomainOfExpertise(Guid.NewGuid(), this.cache, null); + var engineeringModel = new EngineeringModel(Guid.NewGuid(), this.cache, null); + var iteration = new Iteration(Guid.NewGuid(), this.cache, null); + engineeringModel.Iteration.Add(iteration); + + var elementDefinitionA = new ElementDefinition(Guid.NewGuid(), this.cache, null); + var elementDefinitionB = new ElementDefinition(Guid.NewGuid(), this.cache, null); + + iteration.Element.Add(elementDefinitionA); + iteration.Element.Add(elementDefinitionB); + + Assert.ThrowsAsync(async () => await this.thingCreator.CreateElementUsageAsync(null, elementDefinitionB, domainOfExpertise, this.session.Object)); + Assert.ThrowsAsync(async () => await this.thingCreator.CreateElementUsageAsync(elementDefinitionA, null, domainOfExpertise, this.session.Object)); + Assert.ThrowsAsync(async () => await this.thingCreator.CreateElementUsageAsync(elementDefinitionA, elementDefinitionB, null, this.session.Object)); + Assert.ThrowsAsync(async () => await this.thingCreator.CreateElementUsageAsync(elementDefinitionA, elementDefinitionB, domainOfExpertise, null)); + } + + [Test] + public async Task VerifyThatCreateElementUsageExecutesWrite() + { + var domainOfExpertise = new DomainOfExpertise(Guid.NewGuid(), this.cache, null); + var engineeringModel = new EngineeringModel(Guid.NewGuid(), this.cache, null); + var iteration = new Iteration(Guid.NewGuid(), this.cache, null); + engineeringModel.Iteration.Add(iteration); + + var elementDefinitionA = new ElementDefinition(Guid.NewGuid(), this.cache, null); + var elementDefinitionB = new ElementDefinition(Guid.NewGuid(), this.cache, null); + + iteration.Element.Add(elementDefinitionA); + iteration.Element.Add(elementDefinitionB); + + await this.thingCreator.CreateElementUsageAsync(elementDefinitionA, elementDefinitionB, domainOfExpertise, this.session.Object); + + this.session.Verify(x => x.Write(It.IsAny())); + } + + [Test] + public void VerifyThatExceptionIsThrownWhenCreateElementUsageFails() + { + var domainOfExpertise = new DomainOfExpertise(Guid.NewGuid(), this.cache, null); + var engineeringModel = new EngineeringModel(Guid.NewGuid(), this.cache, null); + var iteration = new Iteration(Guid.NewGuid(), this.cache, null); + engineeringModel.Iteration.Add(iteration); + + var elementDefinitionA = new ElementDefinition(Guid.NewGuid(), this.cache, null); + var elementDefinitionB = new ElementDefinition(Guid.NewGuid(), this.cache, null); + + iteration.Element.Add(elementDefinitionA); + iteration.Element.Add(elementDefinitionB); + + Assert.ThrowsAsync(async () => await this.thingCreator.CreateElementUsageAsync(elementDefinitionA, elementDefinitionB, domainOfExpertise, this.sessionThatThrowsException.Object)); + } + } +} From 871f2da296d3980af06c7f69cfaa4ad22599186c Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 12:08:23 +0100 Subject: [PATCH 14/23] SQ --- ...mpoundComponentSelectedEventTestFixture.cs | 4 ++-- ...CopyElementDefinitionCreatorTestFixture.cs | 23 ++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/COMET.Web.Common.Tests/Utilities/CompoundComponentSelectedEventTestFixture.cs b/COMET.Web.Common.Tests/Utilities/CompoundComponentSelectedEventTestFixture.cs index 84dc7ce7..e93134f6 100644 --- a/COMET.Web.Common.Tests/Utilities/CompoundComponentSelectedEventTestFixture.cs +++ b/COMET.Web.Common.Tests/Utilities/CompoundComponentSelectedEventTestFixture.cs @@ -48,14 +48,14 @@ public class CompoundComponentSelectedEventTestFixture [SetUp] public void SetUp() { - var parameterType = new CompoundParameterType() + var parameterType = new CompoundParameterType { Iid = Guid.NewGuid(), }; var compoundValues = new List { "1", "0", "3" }; - var parameterValueSet = new ParameterValueSet() + var parameterValueSet = new ParameterValueSet { Iid = Guid.NewGuid(), ValueSwitch = ParameterSwitchKind.MANUAL, diff --git a/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs b/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs index 997c736a..7d5ba6e0 100644 --- a/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs +++ b/COMET.Web.Common.Tests/Utilities/CopyElementDefinitionCreatorTestFixture.cs @@ -94,10 +94,11 @@ public void SetUp() this.valueSet2 = new ParameterValueSet(Guid.NewGuid(), this.cache, this.uri); this.usage = new ElementUsage(Guid.NewGuid(), this.cache, this.uri); - this.parameterOverride = new ParameterOverride(Guid.NewGuid(), this.cache, this.uri); - this.parameterOverride.Parameter = this.parameter2; - this.overrideValueset = new ParameterOverrideValueSet(Guid.NewGuid(), this.cache, this.uri); - this.overrideValueset.ParameterValueSet = this.valueSet2; + this.parameterOverride = new ParameterOverride(Guid.NewGuid(), this.cache, this.uri) + { Parameter = this.parameter2 }; + + this.overrideValueset = new ParameterOverrideValueSet(Guid.NewGuid(), this.cache, this.uri) + { ParameterValueSet = this.valueSet2 }; this.model.Iteration.Add(this.iteration); this.iteration.Element.Add(this.elementDef1); @@ -114,10 +115,16 @@ public void SetUp() this.sub1 = new ParameterSubscription(Guid.NewGuid(), this.cache, this.uri); this.sub2 = new ParameterSubscription(Guid.NewGuid(), this.cache, this.uri); - this.subValueset1 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri); - this.subValueset1.SubscribedValueSet = this.valueSet1; - this.subValueset2 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri); - this.subValueset2.SubscribedValueSet = this.overrideValueset; + + this.subValueset1 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri) + { + SubscribedValueSet = this.valueSet1 + }; + + this.subValueset2 = new ParameterSubscriptionValueSet(Guid.NewGuid(), this.cache, this.uri) + { + SubscribedValueSet = this.overrideValueset + }; this.sub1.ValueSet.Add(this.subValueset1); this.sub2.ValueSet.Add(this.subValueset2); From e7505554519e69d4437ae4e154ea3a5da432eb37 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 13:09:06 +0100 Subject: [PATCH 15/23] Coverage exclusion --- COMET.Web.Common/Utilities/CopyCreator.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/COMET.Web.Common/Utilities/CopyCreator.cs b/COMET.Web.Common/Utilities/CopyCreator.cs index 36701615..90733892 100644 --- a/COMET.Web.Common/Utilities/CopyCreator.cs +++ b/COMET.Web.Common/Utilities/CopyCreator.cs @@ -25,6 +25,7 @@ namespace COMET.Web.Common.Utilities { + using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Threading.Tasks; @@ -38,6 +39,7 @@ namespace COMET.Web.Common.Utilities /// /// The class responsible for copy operations /// + [ExcludeFromCodeCoverage(Justification = "No coverage for now, as test code would be related to CopyPermissionHelper methods for most part")] public class CopyCreator { /// From a4a0858afffee3249c8d6cb1c812dfbac9bc75fc Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 13:27:50 +0100 Subject: [PATCH 16/23] Add ElementBaseTreeRowViewModel tests --- .../ElementBaseTreeRowViewModelTestFixture.cs | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs new file mode 100644 index 00000000..0b95250f --- /dev/null +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs @@ -0,0 +1,124 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Tests.ViewModels.Components.ModelEditor +{ + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using NUnit.Framework; + + [TestFixture] + public class ElementBaseTreeRowViewModelTestFixture + { + private ElementDefinition elementBase; + private string elementName; + private DomainOfExpertise owner; + private string ownerShortName; + + [SetUp] + public void Setup() + { + this.elementName = "TestElement"; + this.ownerShortName = "TestOwner"; + + this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + { + ShortName = this.ownerShortName + }; + + this.elementBase = new ElementDefinition(Guid.NewGuid(), null, null) + { + Name = this.elementName, + Owner = this.owner + }; + } + + [TearDown] + public void TearDown() + { + } + + [Test] + public void VerifyCreation() + { + var testVM = new ElementbaseTreeViewModelTest(this.elementBase); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementBase)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + }); + } + + [Test] + public void VerifyCreationNullElement() + { + Assert.That(() => new ElementbaseTreeViewModelTest(null), Throws.ArgumentNullException); + } + + [Test] + public void VerifyCreationAndUpdateProperties() + { + var testVM = new ElementbaseTreeViewModelTest(); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.Null); + Assert.That(testVM.ElementName, Is.Null); + Assert.That(testVM.OwnerShortName, Is.Null); + }); + + testVM.UpdateProperties(new ElementbaseTreeViewModelTest(this.elementBase)); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementBase)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + }); + } + + [Test] + public void VerifyUpdatePropertiesNullElement() + { + Assert.That(() => new ElementbaseTreeViewModelTest(this.elementBase).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementbaseTreeViewModelTest().UpdateProperties(null), Throws.ArgumentNullException); + } + } + + public class ElementbaseTreeViewModelTest : ElementBaseTreeRowViewModel + { + public ElementbaseTreeViewModelTest(ElementBase elementBase) : base(elementBase) + { + } + + public ElementbaseTreeViewModelTest() + { + } + } +} From 933c578153c56c79aef545965bef39cbd4cbb0c6 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 14:05:45 +0100 Subject: [PATCH 17/23] Add xxTreeRowViewModel tests --- .../ElementBaseTreeRowViewModelTestFixture.cs | 20 +-- ...ntDefinitionTreeRowViewModelTestFixture.cs | 149 ++++++++++++++++++ ...ElementUsageTreeRowViewModelTestFixture.cs | 114 ++++++++++++++ .../ElementDefinitionTree.razor | 2 +- .../ElementDefinitionTree.razor.cs | 4 +- .../MultiModelEditor/MultiModelEditor.razor | 4 +- .../MultiModelEditor.razor.cs | 6 +- .../ElementDefinitionTreeViewModel.cs | 8 +- .../IElementDefinitionTreeViewModel.cs | 4 +- ...s => ElementDefinitionTreeRowViewModel.cs} | 20 +-- ...del.cs => ElementUsageTreeRowViewModel.cs} | 12 +- 11 files changed, 303 insertions(+), 40 deletions(-) create mode 100644 COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs create mode 100644 COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs rename COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/{ElementDefinitionTreeTreeRowViewModel.cs => ElementDefinitionTreeRowViewModel.cs} (85%) rename COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/{ElementUsageTreeTreeRowViewModel.cs => ElementUsageTreeRowViewModel.cs} (84%) diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs index 0b95250f..f4ec754a 100644 --- a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs @@ -22,7 +22,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace COMETwebapp.Tests.ViewModels.Components.ModelEditor +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor { using CDP4Common.EngineeringModelData; using CDP4Common.SiteDirectoryData; @@ -65,7 +65,7 @@ public void TearDown() [Test] public void VerifyCreation() { - var testVM = new ElementbaseTreeViewModelTest(this.elementBase); + var testVM = new ElementBaseTreeRowViewModelTest(this.elementBase); Assert.Multiple(() => { @@ -78,13 +78,13 @@ public void VerifyCreation() [Test] public void VerifyCreationNullElement() { - Assert.That(() => new ElementbaseTreeViewModelTest(null), Throws.ArgumentNullException); + Assert.That(() => new ElementBaseTreeRowViewModelTest(null), Throws.ArgumentNullException); } [Test] public void VerifyCreationAndUpdateProperties() { - var testVM = new ElementbaseTreeViewModelTest(); + var testVM = new ElementBaseTreeRowViewModelTest(); Assert.Multiple(() => { @@ -93,7 +93,7 @@ public void VerifyCreationAndUpdateProperties() Assert.That(testVM.OwnerShortName, Is.Null); }); - testVM.UpdateProperties(new ElementbaseTreeViewModelTest(this.elementBase)); + testVM.UpdateProperties(new ElementBaseTreeRowViewModelTest(this.elementBase)); Assert.Multiple(() => { @@ -106,18 +106,18 @@ public void VerifyCreationAndUpdateProperties() [Test] public void VerifyUpdatePropertiesNullElement() { - Assert.That(() => new ElementbaseTreeViewModelTest(this.elementBase).UpdateProperties(null), Throws.ArgumentNullException); - Assert.That(() => new ElementbaseTreeViewModelTest().UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementBaseTreeRowViewModelTest(this.elementBase).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementBaseTreeRowViewModelTest().UpdateProperties(null), Throws.ArgumentNullException); } } - public class ElementbaseTreeViewModelTest : ElementBaseTreeRowViewModel + public class ElementBaseTreeRowViewModelTest : ElementBaseTreeRowViewModel { - public ElementbaseTreeViewModelTest(ElementBase elementBase) : base(elementBase) + public ElementBaseTreeRowViewModelTest(ElementBase elementBase) : base(elementBase) { } - public ElementbaseTreeViewModelTest() + public ElementBaseTreeRowViewModelTest() { } } diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs new file mode 100644 index 00000000..48cfeea0 --- /dev/null +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs @@ -0,0 +1,149 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +{ + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using NUnit.Framework; + + [TestFixture] + public class ElementDefinitionTreeRowViewModelTestFixture + { + private ElementDefinition elementDefinition; + private ElementUsage elementUsage; + private string elementDefinitionName; + private DomainOfExpertise owner; + private string ownerShortName; + private string elementUsageName; + private Iteration iteration; + + [SetUp] + public void Setup() + { + this.elementDefinitionName = "TestElementDefinition"; + this.elementUsageName = "TestElementUsage"; + this.ownerShortName = "TestOwner"; + + this.iteration = new Iteration(); + + this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + { + ShortName = this.ownerShortName + }; + + this.elementUsage = new ElementUsage(Guid.NewGuid(), null, null) + { + Name = this.elementUsageName, + Owner = this.owner + }; + + this.elementDefinition = new ElementDefinition(Guid.NewGuid(), null, null) + { + Name = this.elementDefinitionName, + Owner = this.owner, + ContainedElement = { this.elementUsage } + }; + + this.iteration.Element.Add(this.elementDefinition); + } + + [TearDown] + public void TearDown() + { + } + + [Test] + public void VerifyCreation() + { + var testVM = new ElementDefinitionTreeRowViewModel(this.elementDefinition); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.IsTopElement, Is.False); + Assert.That(testVM.Rows.Count, Is.EqualTo(1)); + }); + } + + [Test] + public void VerifyCreationNullElement() + { + Assert.That(() => new ElementDefinitionTreeRowViewModel(null), Throws.ArgumentNullException); + } + + [Test] + public void VerifyCreationAndUpdateProperties() + { + var testVM = new ElementDefinitionTreeRowViewModel(); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.Null); + Assert.That(testVM.ElementName, Is.Null); + Assert.That(testVM.OwnerShortName, Is.Null); + Assert.That(testVM.IsTopElement, Is.False); + Assert.That(testVM.Rows.Count, Is.EqualTo(0)); + }); + + this.iteration.TopElement = this.elementDefinition; + + testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(this.elementDefinition)); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.IsTopElement, Is.True); + Assert.That(testVM.Rows.Count, Is.EqualTo(1)); + }); + + this.elementDefinition.ContainedElement.Clear(); + + testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(this.elementDefinition)); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.IsTopElement, Is.True); + Assert.That(testVM.Rows.Count, Is.EqualTo(0)); + }); + } + + [Test] + public void VerifyUpdatePropertiesNullElement() + { + Assert.That(() => new ElementDefinitionTreeRowViewModel(this.elementDefinition).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementDefinitionTreeRowViewModel().UpdateProperties(null), Throws.ArgumentNullException); + } + } +} diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs new file mode 100644 index 00000000..6bf3c5f0 --- /dev/null +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs @@ -0,0 +1,114 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +{ + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using NUnit.Framework; + + [TestFixture] + public class ElementUsageTreeRowViewModelTestFixture + { + private ElementUsage elementUsage; + private string elementName; + private DomainOfExpertise owner; + private string ownerShortName; + + [SetUp] + public void Setup() + { + this.elementName = "TestElement"; + this.ownerShortName = "TestOwner"; + + this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + { + ShortName = this.ownerShortName + }; + + this.elementUsage = new ElementUsage(Guid.NewGuid(), null, null) + { + Name = this.elementName, + Owner = this.owner + }; + } + + [TearDown] + public void TearDown() + { + } + + [Test] + public void VerifyCreation() + { + var testVM = new ElementUsageTreeRowViewModel(this.elementUsage); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementUsage)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + }); + } + + [Test] + public void VerifyCreationNullElement() + { + Assert.That(() => new ElementUsageTreeRowViewModel(null), Throws.ArgumentNullException); + } + + [Test] + public void VerifyCreationAndUpdateProperties() + { + var testVM = new ElementUsageTreeRowViewModel(); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.Null); + Assert.That(testVM.ElementName, Is.Null); + Assert.That(testVM.OwnerShortName, Is.Null); + }); + + testVM.UpdateProperties(new ElementUsageTreeRowViewModel(this.elementUsage)); + + Assert.Multiple(() => + { + Assert.That(testVM.ElementBase, Is.EqualTo(this.elementUsage)); + Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + }); + } + + [Test] + public void VerifyUpdatePropertiesNullElement() + { + Assert.That(() => new ElementUsageTreeRowViewModel(this.elementUsage).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementUsageTreeRowViewModel().UpdateProperties(null), Throws.ArgumentNullException); + } + } + +} diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index f96f5e69..40e394bc 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -60,7 +60,7 @@ else @{ var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); - var isTopElement = dataItem is ElementDefinitionTreeTreeRowViewModel { IsTopElement: true }; + var isTopElement = dataItem is ElementDefinitionTreeRowViewModel { IsTopElement: true }; var style = isTopElement ? "font-weight-bold" : string.Empty; } diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index d21d08ce..0de0c78b 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -133,9 +133,9 @@ public partial class ElementDefinitionTree public bool AllowNodeDrop { get; set; } /// - /// A collection of + /// A collection of /// - public IEnumerable Rows { get; set; } + public IEnumerable Rows { get; set; } /// /// Gets or sets the diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index bd51a38f..4ad3b32d 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -49,7 +49,7 @@ OnDragStart="@(async x => await this.OnDragStartAsync(x))" OnDragEnd="@(async x => await this.OnDragEndAsync())" OnDrop="@(async x => await this.OnDropAsync(x))" - AllowNodeDrag="(_, model) => model is ElementDefinitionTreeTreeRowViewModel" + AllowNodeDrag="(_, model) => model is ElementDefinitionTreeRowViewModel" IsModelSelectionEnabled="true"> @@ -74,7 +74,7 @@ OnDragStart="@(async x => await this.OnDragStartAsync(x))" OnDragEnd="@(async x => await this.OnDragEndAsync())" OnDrop="@(async x => await this.OnDropAsync(x))" - AllowNodeDrag="(_, model) => model is ElementDefinitionTreeTreeRowViewModel" + AllowNodeDrag="(_, model) => model is ElementDefinitionTreeRowViewModel" IsModelSelectionEnabled="false"> diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs index eb30f6a3..6ae73077 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor.cs @@ -122,7 +122,7 @@ private async Task OnDropAsync((ElementDefinitionTree, ElementBaseTreeRowViewMod { this.ErrorMessage = string.Empty; - if (this.DragObject.Item2 is not ElementDefinitionTreeTreeRowViewModel elementDefinitionTreeRowViewModel) + if (this.DragObject.Item2 is not ElementDefinitionTreeRowViewModel elementDefinitionTreeRowViewModel) { return; } @@ -209,9 +209,9 @@ private bool CalculateDropIsAllowed(ElementDefinitionTree elementDefinitionTree) return false; } - if (dragOverObject.Item2 is ElementDefinitionTreeTreeRowViewModel dragOverVm) + if (dragOverObject.Item2 is ElementDefinitionTreeRowViewModel dragOverVm) { - if (dragObject.Item2 is not ElementDefinitionTreeTreeRowViewModel dragVm) + if (dragObject.Item2 is not ElementDefinitionTreeRowViewModel dragVm) { return false; } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index ba548bfc..22787c24 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -127,9 +127,9 @@ public IterationData SelectedIterationData public List Elements { get; private set; } = []; /// - /// Gets the collection of the for target model + /// Gets the collection of the for target model /// - public ObservableCollection Rows { get; private set; } = []; + public ObservableCollection Rows { get; private set; } = []; /// /// Represents the selected ElementDefinitionRowViewModel @@ -143,7 +143,7 @@ public IterationData SelectedIterationData public void AddRows(IEnumerable addedThings) { var listOfAddedElementBases = addedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList(); - this.Rows.AddRange(listOfAddedElementBases.Select(e => new ElementDefinitionTreeTreeRowViewModel(e))); + this.Rows.AddRange(listOfAddedElementBases.Select(e => new ElementDefinitionTreeRowViewModel(e))); } /// @@ -155,7 +155,7 @@ public void UpdateRows(IEnumerable updatedThings) foreach (var element in updatedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList()) { var row = this.Rows.FirstOrDefault(x => x.ElementBase.Iid == element.Iid); - row?.UpdateProperties(new ElementDefinitionTreeTreeRowViewModel(element)); + row?.UpdateProperties(new ElementDefinitionTreeRowViewModel(element)); } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs index b37a9e0a..0bbb0cd2 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs @@ -39,9 +39,9 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor public interface IElementDefinitionTreeViewModel : IHaveReusableRows { /// - /// Gets the collection of the + /// Gets the collection of the /// - ObservableCollection Rows { get; } + ObservableCollection Rows { get; } /// /// The from which to build the tree diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs similarity index 85% rename from COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs rename to COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs index b184fbd1..56254f01 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs @@ -37,7 +37,7 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows /// /// Row View Model for /// - public class ElementDefinitionTreeTreeRowViewModel : ElementBaseTreeRowViewModel + public class ElementDefinitionTreeRowViewModel : ElementBaseTreeRowViewModel { /// /// Backing field for @@ -45,15 +45,15 @@ public class ElementDefinitionTreeTreeRowViewModel : ElementBaseTreeRowViewModel private bool isTopElement; /// - /// Gets or the collection of + /// Gets or the collection of /// - public ObservableCollection Rows { get; } = []; + public ObservableCollection Rows { get; } = []; /// /// Initializes a new instance of the class. /// the /// - public ElementDefinitionTreeTreeRowViewModel(ElementDefinition elementBase) : base(elementBase) + public ElementDefinitionTreeRowViewModel(ElementDefinition elementBase) : base(elementBase) { ArgumentNullException.ThrowIfNull(elementBase); @@ -63,14 +63,14 @@ public ElementDefinitionTreeTreeRowViewModel(ElementDefinition elementBase) : ba if (elementUsages?.Any() ?? false) { - this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeTreeRowViewModel(x))); + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x))); } } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public ElementDefinitionTreeTreeRowViewModel() + public ElementDefinitionTreeRowViewModel() { } @@ -86,8 +86,8 @@ public bool IsTopElement /// /// Update this row view model properties /// - /// The to use for updating - public void UpdateProperties(ElementDefinitionTreeTreeRowViewModel elementDefinitionTreeRow) + /// The to use for updating + public void UpdateProperties(ElementDefinitionTreeRowViewModel elementDefinitionTreeRow) { ArgumentNullException.ThrowIfNull(elementDefinitionTreeRow); @@ -100,7 +100,7 @@ public void UpdateProperties(ElementDefinitionTreeTreeRowViewModel elementDefini if (elementUsages != null) { - this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeTreeRowViewModel(x))); + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x))); } } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeRowViewModel.cs similarity index 84% rename from COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs rename to COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeRowViewModel.cs index bb489957..8a773d6d 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementUsageTreeRowViewModel.cs @@ -31,28 +31,28 @@ namespace COMETwebapp.ViewModels.Components.MultiModelEditor.Rows /// /// Row View Model for /// - public class ElementUsageTreeTreeRowViewModel : ElementBaseTreeRowViewModel + public class ElementUsageTreeRowViewModel : ElementBaseTreeRowViewModel { /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// the /// - public ElementUsageTreeTreeRowViewModel(ElementUsage elementUsage) : base(elementUsage) + public ElementUsageTreeRowViewModel(ElementUsage elementUsage) : base(elementUsage) { } /// /// Initializes a new instance of the class. /// - public ElementUsageTreeTreeRowViewModel() + public ElementUsageTreeRowViewModel() { } /// /// Update this row view model properties /// - /// The to use for updating - public void UpdateProperties(ElementUsageTreeTreeRowViewModel elementUsageTreeRow) + /// The to use for updating + public void UpdateProperties(ElementUsageTreeRowViewModel elementUsageTreeRow) { base.UpdateProperties(elementUsageTreeRow); } From 9fcdadc17c8be28cb7b92c5e18e42eb29d58c0ff Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Tue, 7 Jan 2025 17:04:10 +0100 Subject: [PATCH 18/23] Add ElementDefinitionTree unit tests --- ...ementDefinitionTreeViewModelTestFixture.cs | 247 ++++++++++++++++++ .../ElementBaseTreeRowViewModelTestFixture.cs | 34 +-- ...ntDefinitionTreeRowViewModelTestFixture.cs | 60 ++--- ...ElementUsageTreeRowViewModelTestFixture.cs | 34 +-- COMETwebapp.sln.DotSettings | 1 + .../ElementDefinitionTreeViewModel.cs | 16 +- 6 files changed, 318 insertions(+), 74 deletions(-) create mode 100644 COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs rename COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/{ => Rows}/ElementBaseTreeRowViewModelTestFixture.cs (77%) rename COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/{ => Rows}/ElementDefinitionTreeRowViewModelTestFixture.cs (67%) rename COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/{ => Rows}/ElementUsageTreeRowViewModelTestFixture.cs (74%) diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs new file mode 100644 index 00000000..1e7f71d4 --- /dev/null +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs @@ -0,0 +1,247 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +{ + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using CDP4Dal; + using CDP4Dal.Events; + + using CDP4Web.Enumerations; + + using COMET.Web.Common.Model; + using COMET.Web.Common.Services.SessionManagement; + + using COMETwebapp.ViewModels.Components.MultiModelEditor; + + using DynamicData; + + using Moq; + + using NUnit.Framework; + + [TestFixture] + public class ElementDefinitionTreeViewModelTestFixture + { + private ElementDefinitionTreeViewModel viewModel; + private DomainOfExpertise domain; + private Mock sessionService; + private Iteration iteration; + private CDPMessageBus messageBus; + private ElementDefinition topElement; + private SourceList iterations; + + [SetUp] + public void Setup() + { + this.messageBus = new CDPMessageBus(); + this.sessionService = new Mock(); + var session = new Mock(); + this.sessionService.Setup(x => x.Session).Returns(session.Object); + + this.domain = new DomainOfExpertise + { + Iid = Guid.NewGuid(), + ShortName = "SYS" + }; + + session.Setup(x => x.ActivePerson).Returns(new Person + { + DefaultDomain = this.domain + }); + + var siteDirectory = new SiteDirectory { Domain = { this.domain } }; + session.Setup(x => x.RetrieveSiteDirectory()).Returns(siteDirectory); + this.sessionService.Setup(x => x.GetSiteDirectory()).Returns(siteDirectory); + + this.topElement = new ElementDefinition + { + Iid = Guid.NewGuid(), + Name = "Container", + Owner = this.domain + }; + + var elementDefinition = new ElementDefinition + { + Iid = Guid.NewGuid(), + Name = "Box", + Owner = this.domain + }; + + var usage1 = new ElementUsage + { + Name = "Box1", + Iid = Guid.NewGuid(), + ElementDefinition = elementDefinition, + Owner = this.domain + }; + + this.topElement.ContainedElement.Add(usage1); + + this.iteration = new Iteration + { + Iid = Guid.NewGuid(), + Element = { this.topElement, elementDefinition }, + TopElement = this.topElement, + Container = new EngineeringModel + { + EngineeringModelSetup = new EngineeringModelSetup() + }, + IterationSetup = new IterationSetup + { + IterationNumber = 1, + Container = new EngineeringModelSetup { Name = "ModelName", ShortName = "ModelShortName" } + } + }; + + this.iterations = new SourceList(); + this.iterations.Add(this.iteration); + + this.sessionService.Setup(x => x.OpenIterations).Returns(this.iterations); + + this.viewModel = new ElementDefinitionTreeViewModel(this.sessionService.Object, this.messageBus); + } + + [TearDown] + public void TearDown() + { + this.messageBus.ClearSubscriptions(); + this.viewModel.Dispose(); + } + + [Test] + public void VerifySelectIteration() + { + Assert.Multiple(() => + { + Assert.That(this.viewModel.Description, Is.EqualTo("Please select a model")); + Assert.That(this.viewModel.Iteration, Is.Null); + Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); + Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); + Assert.That(this.viewModel.SelectedIterationData, Is.Null); + Assert.That(this.viewModel.Rows.Count, Is.EqualTo(0)); + }); + + this.viewModel.Iteration = this.iteration; + var expectedIterationData = new IterationData(this.iteration.IterationSetup, true); + + Assert.Multiple(() => + { + Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); + Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); + Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); + Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); + Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); + Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); + }); + } + + [Test] + public void VerifyUpdatedElement() + { + this.viewModel.Iteration = this.iteration; + var expectedIterationData = new IterationData(this.iteration.IterationSetup, true); + + Assert.Multiple(() => + { + Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); + Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); + Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); + Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); + Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); + Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); + }); + + Assert.That(this.viewModel.GetUpdatedThings(), Is.Empty); + + this.topElement.Name = "Changed"; + this.messageBus.SendObjectChangeEvent(this.topElement, EventKind.Updated); + + Assert.That(this.viewModel.GetUpdatedThings(), Has.Count.EqualTo(1)); + + this.messageBus.SendMessage(SessionServiceEvent.SessionRefreshed, this.sessionService.Object.Session); + + Assert.That(this.viewModel.Rows.Single(x => x.IsTopElement).ElementName, Is.EqualTo(this.topElement.Name)); + } + + [Test] + public void VerifyRemoveAndAddElement() + { + this.viewModel.Iteration = this.iteration; + var expectedIterationData = new IterationData(this.iteration.IterationSetup, true); + + Assert.Multiple(() => + { + Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); + Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); + Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); + Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); + Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); + Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); + }); + + this.messageBus.SendMessage(SessionServiceEvent.SessionRefreshed, this.sessionService.Object.Session); + this.messageBus.SendMessage(new SessionEvent(null, SessionStatus.EndUpdate)); + + Assert.That(this.viewModel.GetDeletedThings(), Is.Empty); + Assert.That(this.viewModel.GetAddedThings(), Is.Empty); + Assert.That(this.viewModel.GetUpdatedThings(), Is.Empty); + + this.messageBus.SendObjectChangeEvent(this.topElement, EventKind.Removed); + + Assert.That(this.viewModel.GetDeletedThings(), Has.Count.EqualTo(1)); + Assert.That(this.viewModel.Rows, Has.Count.EqualTo(2)); + + this.messageBus.SendMessage(SessionServiceEvent.SessionRefreshed, this.sessionService.Object.Session); + + Assert.That(this.viewModel.Rows, Has.Count.EqualTo(1)); + + this.messageBus.SendObjectChangeEvent(this.topElement, EventKind.Added); + + Assert.That(this.viewModel.GetAddedThings(), Has.Count.EqualTo(1)); + Assert.That(this.viewModel.Rows, Has.Count.EqualTo(1)); + + this.messageBus.SendMessage(SessionServiceEvent.SessionRefreshed, this.sessionService.Object.Session); + + Assert.That(this.viewModel.Rows, Has.Count.EqualTo(2)); + + Assert.That(this.viewModel.Rows.Single(x => x.IsTopElement).ElementName, Is.EqualTo(this.topElement.Name)); + } + + [Test] + public void VerifyIterationRefresh() + { + Assert.That(this.viewModel.Iterations, Has.Count.EqualTo(2)); + + this.iterations.Remove(this.iteration); + Assert.That(this.viewModel.Iterations, Has.Count.EqualTo(1)); + + this.iterations.Add(this.iteration); + + Assert.That(this.viewModel.Iterations, Has.Count.EqualTo(2)); + } + } +} diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementBaseTreeRowViewModelTestFixture.cs similarity index 77% rename from COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs rename to COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementBaseTreeRowViewModelTestFixture.cs index f4ec754a..1e903603 100644 --- a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementBaseTreeRowViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementBaseTreeRowViewModelTestFixture.cs @@ -22,7 +22,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor.Rows { using CDP4Common.EngineeringModelData; using CDP4Common.SiteDirectoryData; @@ -42,18 +42,18 @@ public class ElementBaseTreeRowViewModelTestFixture [SetUp] public void Setup() { - this.elementName = "TestElement"; - this.ownerShortName = "TestOwner"; + elementName = "TestElement"; + ownerShortName = "TestOwner"; - this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + owner = new DomainOfExpertise(Guid.NewGuid(), null, null) { - ShortName = this.ownerShortName + ShortName = ownerShortName }; - this.elementBase = new ElementDefinition(Guid.NewGuid(), null, null) + elementBase = new ElementDefinition(Guid.NewGuid(), null, null) { - Name = this.elementName, - Owner = this.owner + Name = elementName, + Owner = owner }; } @@ -65,13 +65,13 @@ public void TearDown() [Test] public void VerifyCreation() { - var testVM = new ElementBaseTreeRowViewModelTest(this.elementBase); + var testVM = new ElementBaseTreeRowViewModelTest(elementBase); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementBase)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementBase)); + Assert.That(testVM.ElementName, Is.EqualTo(elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); }); } @@ -93,20 +93,20 @@ public void VerifyCreationAndUpdateProperties() Assert.That(testVM.OwnerShortName, Is.Null); }); - testVM.UpdateProperties(new ElementBaseTreeRowViewModelTest(this.elementBase)); + testVM.UpdateProperties(new ElementBaseTreeRowViewModelTest(elementBase)); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementBase)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementBase)); + Assert.That(testVM.ElementName, Is.EqualTo(elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); }); } [Test] public void VerifyUpdatePropertiesNullElement() { - Assert.That(() => new ElementBaseTreeRowViewModelTest(this.elementBase).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementBaseTreeRowViewModelTest(elementBase).UpdateProperties(null), Throws.ArgumentNullException); Assert.That(() => new ElementBaseTreeRowViewModelTest().UpdateProperties(null), Throws.ArgumentNullException); } } diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementDefinitionTreeRowViewModelTestFixture.cs similarity index 67% rename from COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs rename to COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementDefinitionTreeRowViewModelTestFixture.cs index 48cfeea0..8e4ceafa 100644 --- a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeRowViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementDefinitionTreeRowViewModelTestFixture.cs @@ -22,7 +22,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor.Rows { using CDP4Common.EngineeringModelData; using CDP4Common.SiteDirectoryData; @@ -45,31 +45,31 @@ public class ElementDefinitionTreeRowViewModelTestFixture [SetUp] public void Setup() { - this.elementDefinitionName = "TestElementDefinition"; - this.elementUsageName = "TestElementUsage"; - this.ownerShortName = "TestOwner"; + elementDefinitionName = "TestElementDefinition"; + elementUsageName = "TestElementUsage"; + ownerShortName = "TestOwner"; - this.iteration = new Iteration(); + iteration = new Iteration(); - this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + owner = new DomainOfExpertise(Guid.NewGuid(), null, null) { - ShortName = this.ownerShortName + ShortName = ownerShortName }; - this.elementUsage = new ElementUsage(Guid.NewGuid(), null, null) + elementUsage = new ElementUsage(Guid.NewGuid(), null, null) { - Name = this.elementUsageName, - Owner = this.owner + Name = elementUsageName, + Owner = owner }; - this.elementDefinition = new ElementDefinition(Guid.NewGuid(), null, null) + elementDefinition = new ElementDefinition(Guid.NewGuid(), null, null) { - Name = this.elementDefinitionName, - Owner = this.owner, - ContainedElement = { this.elementUsage } + Name = elementDefinitionName, + Owner = owner, + ContainedElement = { elementUsage } }; - this.iteration.Element.Add(this.elementDefinition); + iteration.Element.Add(elementDefinition); } [TearDown] @@ -80,13 +80,13 @@ public void TearDown() [Test] public void VerifyCreation() { - var testVM = new ElementDefinitionTreeRowViewModel(this.elementDefinition); + var testVM = new ElementDefinitionTreeRowViewModel(elementDefinition); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); Assert.That(testVM.IsTopElement, Is.False); Assert.That(testVM.Rows.Count, Is.EqualTo(1)); }); @@ -112,28 +112,28 @@ public void VerifyCreationAndUpdateProperties() Assert.That(testVM.Rows.Count, Is.EqualTo(0)); }); - this.iteration.TopElement = this.elementDefinition; + iteration.TopElement = elementDefinition; - testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(this.elementDefinition)); + testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(elementDefinition)); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); Assert.That(testVM.IsTopElement, Is.True); Assert.That(testVM.Rows.Count, Is.EqualTo(1)); }); - this.elementDefinition.ContainedElement.Clear(); + elementDefinition.ContainedElement.Clear(); - testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(this.elementDefinition)); + testVM.UpdateProperties(new ElementDefinitionTreeRowViewModel(elementDefinition)); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementDefinition)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementDefinitionName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementDefinition)); + Assert.That(testVM.ElementName, Is.EqualTo(elementDefinitionName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); Assert.That(testVM.IsTopElement, Is.True); Assert.That(testVM.Rows.Count, Is.EqualTo(0)); }); @@ -142,7 +142,7 @@ public void VerifyCreationAndUpdateProperties() [Test] public void VerifyUpdatePropertiesNullElement() { - Assert.That(() => new ElementDefinitionTreeRowViewModel(this.elementDefinition).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementDefinitionTreeRowViewModel(elementDefinition).UpdateProperties(null), Throws.ArgumentNullException); Assert.That(() => new ElementDefinitionTreeRowViewModel().UpdateProperties(null), Throws.ArgumentNullException); } } diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementUsageTreeRowViewModelTestFixture.cs similarity index 74% rename from COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs rename to COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementUsageTreeRowViewModelTestFixture.cs index 6bf3c5f0..1bc7c9a2 100644 --- a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementUsageTreeRowViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/Rows/ElementUsageTreeRowViewModelTestFixture.cs @@ -22,7 +22,7 @@ // // -------------------------------------------------------------------------------------------------------------------- -namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor +namespace COMETwebapp.Tests.ViewModels.Components.MultiModeleditor.Rows { using CDP4Common.EngineeringModelData; using CDP4Common.SiteDirectoryData; @@ -42,18 +42,18 @@ public class ElementUsageTreeRowViewModelTestFixture [SetUp] public void Setup() { - this.elementName = "TestElement"; - this.ownerShortName = "TestOwner"; + elementName = "TestElement"; + ownerShortName = "TestOwner"; - this.owner = new DomainOfExpertise(Guid.NewGuid(), null, null) + owner = new DomainOfExpertise(Guid.NewGuid(), null, null) { - ShortName = this.ownerShortName + ShortName = ownerShortName }; - this.elementUsage = new ElementUsage(Guid.NewGuid(), null, null) + elementUsage = new ElementUsage(Guid.NewGuid(), null, null) { - Name = this.elementName, - Owner = this.owner + Name = elementName, + Owner = owner }; } @@ -65,13 +65,13 @@ public void TearDown() [Test] public void VerifyCreation() { - var testVM = new ElementUsageTreeRowViewModel(this.elementUsage); + var testVM = new ElementUsageTreeRowViewModel(elementUsage); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementUsage)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementUsage)); + Assert.That(testVM.ElementName, Is.EqualTo(elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); }); } @@ -93,20 +93,20 @@ public void VerifyCreationAndUpdateProperties() Assert.That(testVM.OwnerShortName, Is.Null); }); - testVM.UpdateProperties(new ElementUsageTreeRowViewModel(this.elementUsage)); + testVM.UpdateProperties(new ElementUsageTreeRowViewModel(elementUsage)); Assert.Multiple(() => { - Assert.That(testVM.ElementBase, Is.EqualTo(this.elementUsage)); - Assert.That(testVM.ElementName, Is.EqualTo(this.elementName)); - Assert.That(testVM.OwnerShortName, Is.EqualTo(this.ownerShortName)); + Assert.That(testVM.ElementBase, Is.EqualTo(elementUsage)); + Assert.That(testVM.ElementName, Is.EqualTo(elementName)); + Assert.That(testVM.OwnerShortName, Is.EqualTo(ownerShortName)); }); } [Test] public void VerifyUpdatePropertiesNullElement() { - Assert.That(() => new ElementUsageTreeRowViewModel(this.elementUsage).UpdateProperties(null), Throws.ArgumentNullException); + Assert.That(() => new ElementUsageTreeRowViewModel(elementUsage).UpdateProperties(null), Throws.ArgumentNullException); Assert.That(() => new ElementUsageTreeRowViewModel().UpdateProperties(null), Throws.ArgumentNullException); } } diff --git a/COMETwebapp.sln.DotSettings b/COMETwebapp.sln.DotSettings index 1ccd90dc..b33ead4a 100644 --- a/COMETwebapp.sln.DotSettings +++ b/COMETwebapp.sln.DotSettings @@ -280,6 +280,7 @@ True True True + True True True True diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index 22787c24..1c535259 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -121,11 +121,6 @@ public IterationData SelectedIterationData /// public ObservableCollection Iterations { get; } = []; - /// - /// All of the iteration - /// - public List Elements { get; private set; } = []; - /// /// Gets the collection of the for target model /// @@ -180,28 +175,29 @@ public void RemoveRows(IEnumerable deletedThings) /// Handles the message received /// /// A - protected override async Task OnEndUpdate() + protected override Task OnEndUpdate() { - await this.OnSessionRefreshed(); + return this.OnSessionRefreshed(); } /// /// Handles the refresh of the current /// /// A - protected override async Task OnSessionRefreshed() + protected override Task OnSessionRefreshed() { if (this.AddedThings.Count == 0 && this.DeletedThings.Count == 0 && this.UpdatedThings.Count == 0) { - return; + return Task.CompletedTask; } this.IsLoading = true; - await Task.Delay(1); this.UpdateInnerComponents(); this.ClearRecordedChanges(); this.IsLoading = false; + + return Task.CompletedTask; } /// From ee5cfb382b46070e70dfa12bb6d9993d53fff646 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Wed, 8 Jan 2025 17:02:41 +0100 Subject: [PATCH 19/23] Refcatoring and first version ElementDefinitionTree tests --- .../ElementDefinitionTreeTestFixture.cs | 255 ++++++++++++++++++ ...ementDefinitionTreeViewModelTestFixture.cs | 4 - .../ElementDefinitionTree.razor | 49 ++-- .../ElementDefinitionTree.razor.cs | 28 +- .../ElementDefinitionTreeItem.razor | 34 +++ .../ElementDefinitionTreeItem.razor.cs | 54 ++++ .../MultiModelEditor/MultiModelEditor.razor | 8 +- .../ElementDefinitionTreeViewModel.cs | 5 - .../IElementDefinitionTreeViewModel.cs | 5 - 9 files changed, 383 insertions(+), 59 deletions(-) create mode 100644 COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs create mode 100644 COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor create mode 100644 COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor.cs diff --git a/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs b/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs new file mode 100644 index 00000000..2179be21 --- /dev/null +++ b/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs @@ -0,0 +1,255 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Tests.Components.MultiModelEditor +{ + using Bunit; + using Bunit.Rendering; + + using CDP4Common.EngineeringModelData; + using CDP4Common.SiteDirectoryData; + + using COMET.Web.Common.Model; + using COMET.Web.Common.Model.Configuration; + using COMET.Web.Common.Services.ConfigurationService; + using COMET.Web.Common.Services.SessionManagement; + using COMET.Web.Common.Test.Helpers; + + using COMETwebapp.Components.MultiModelEditor; + using COMETwebapp.ViewModels.Components.MultiModelEditor; + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using DevExpress.Blazor; + + using Microsoft.Extensions.DependencyInjection; + + using Moq; + + using NUnit.Framework; + + using TestContext = Bunit.TestContext; + + [TestFixture] + public class ElementDefinitionTreeTestFixture + { + private TestContext context; + private IRenderedComponent renderedComponent; + private ElementDefinitionTree tree; + private Mock elementDefinitionTreeViewModel; + + [SetUp] + public void SetUp() + { + this.context = new TestContext(); + this.context.ConfigureDevExpressBlazor(); + + var configuration = new Mock(); + configuration.Setup(x => x.ServerConfiguration).Returns(new ServerConfiguration()); + var elementDefinition = new ElementDefinition(); + + var row = new ElementDefinitionTreeRowViewModel + { + ElementName = "Test1", + ElementBase = elementDefinition, + OwnerShortName = "Owner", + IsTopElement = true + }; + + this.elementDefinitionTreeViewModel = new Mock(); + this.elementDefinitionTreeViewModel.Setup(x => x.Rows).Returns([row]); + + this.context.Services.AddSingleton(configuration.Object); + this.context.Services.AddSingleton(this.elementDefinitionTreeViewModel.Object); + this.context.Services.AddSingleton(); + + this.renderedComponent = this.context.RenderComponent(); + this.tree = this.renderedComponent.Instance; + } + + [TearDown] + public void TearDown() + { + this.context.CleanContext(); + } + + [Test] + public void VerifyComponent() + { + Assert.Multiple(() => + { + Assert.That(this.renderedComponent, Is.Not.Null); + Assert.That(this.tree, Is.Not.Null); + Assert.That(this.tree.ViewModel, Is.Not.Null); + }); + } + + [Test] + public void VerifyElementSelection() + { + ElementBaseTreeRowViewModel selectedModel = null; + + this.renderedComponent = this.context.RenderComponent(parameters => + { + parameters + .Add(p => p.SelectionChanged, model => selectedModel = model); + }); + + var treeView = this.renderedComponent.FindComponent(); + + Assert.Multiple(() => + { + Assert.That(treeView.Instance.GetSelectedNodeInfo(), Is.Null); + Assert.That(selectedModel, Is.Null); + }); + + var firstSourceRow = treeView.Find(".dxbl-treeview-item-container"); + firstSourceRow.Click(); + + Assert.Multiple(() => + { + Assert.That(treeView.Instance.GetSelectedNodeInfo(), Is.Not.Null); + Assert.That(selectedModel, Is.Not.Null); + }); + } + + [Test] + public void VerifyIsModelSelectionEnabled() + { + Assert.That(() => this.renderedComponent.FindComponent>(), Throws.TypeOf()); + + this.renderedComponent = this.context.RenderComponent(parameters => + { + parameters + .Add(p => p.IsModelSelectionEnabled, true); + }); + + Assert.That(this.renderedComponent.FindComponent>(), Is.Not.Null); + } + + [Test] + public void VerifyInitialIteration() + { + var iteration = new Iteration + { + Iid = Guid.NewGuid(), + IterationSetup = new IterationSetup + { + IterationNumber = 1, + Container = new EngineeringModelSetup + { + Iid = Guid.NewGuid(), + Name = "ModelName", + ShortName = "ModelShortName" + } + } + }; + + this.elementDefinitionTreeViewModel.VerifySet(x => x.Iteration = iteration, Times.Never); + + this.renderedComponent = this.context.RenderComponent(parameters => + { + parameters + .Add(p => p.InitialIteration, iteration); + }); + + this.elementDefinitionTreeViewModel.VerifySet(x => x.Iteration = iteration, Times.Once); + } + + [Test] + public void VerifyInitialIterationChangeFromViewModel() + { + var iteration1 = new Iteration + { + Iid = Guid.NewGuid(), + IterationSetup = new IterationSetup + { + IterationNumber = 1, + Container = new EngineeringModelSetup + { + Iid = Guid.NewGuid(), + Name = "ModelName", + ShortName = "ModelShortName" + } + } + }; + + var iteration2 = new Iteration + { + Iid = Guid.NewGuid(), + IterationSetup = new IterationSetup + { + IterationNumber = 2, + Container = iteration1.IterationSetup.Container + } + }; + + this.elementDefinitionTreeViewModel.VerifySet(x => x.Iteration = iteration1, Times.Never); + + this.renderedComponent = this.context.RenderComponent(parameters => + { + parameters + .Add(p => p.InitialIteration, iteration1); + }); + + this.elementDefinitionTreeViewModel.VerifySet(x => x.Iteration = iteration1, Times.Once); + this.elementDefinitionTreeViewModel.Setup(x => x.Iteration).Returns(iteration2); + + this.renderedComponent.Render(); + + Assert.That(this.renderedComponent.Instance.InitialIteration, Is.EqualTo(iteration2)); + } + + [Test] + public void VerifyDragIsNotAllowed() + { + var firstItem = this.renderedComponent.Find(".dxbl-text").FirstElementChild; + + Assert.Multiple(() => + { + Assert.That(firstItem, Is.Not.Null); + Assert.That(firstItem.InnerHtml, Contains.Substring("Test1")); + Assert.That(firstItem.Attributes.Length, Is.EqualTo(1)); + }); + } + + [Test] + public void VerifyDragIsAllowed() + { + this.renderedComponent = this.context.RenderComponent(parameters => + { + parameters + .Add(p => p.AllowDrag, true); + }); + + var firstItem = this.renderedComponent.Find(".dxbl-text").FirstElementChild; + + Assert.Multiple(() => + { + Assert.That(firstItem, Is.Not.Null); + Assert.That(firstItem.InnerHtml, Contains.Substring("Test1")); + Assert.That(firstItem.Attributes.Length, Is.EqualTo(9)); + }); + } + } +} diff --git a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs index 1e7f71d4..c2b47041 100644 --- a/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs +++ b/COMETwebapp.Tests/ViewModels/Components/MultiModeleditor/ElementDefinitionTreeViewModelTestFixture.cs @@ -140,7 +140,6 @@ public void VerifySelectIteration() Assert.That(this.viewModel.Description, Is.EqualTo("Please select a model")); Assert.That(this.viewModel.Iteration, Is.Null); Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); - Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); Assert.That(this.viewModel.SelectedIterationData, Is.Null); Assert.That(this.viewModel.Rows.Count, Is.EqualTo(0)); }); @@ -153,7 +152,6 @@ public void VerifySelectIteration() Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); - Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); }); @@ -170,7 +168,6 @@ public void VerifyUpdatedElement() Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); - Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); }); @@ -198,7 +195,6 @@ public void VerifyRemoveAndAddElement() Assert.That(this.viewModel.Description, Is.EqualTo(expectedIterationData.IterationName)); Assert.That(this.viewModel.Iteration, Is.EqualTo(this.iteration)); Assert.That(this.viewModel.Iterations.Count, Is.EqualTo(2)); - Assert.That(this.viewModel.SelectedElementDefinition, Is.Null); Assert.That(this.viewModel.SelectedIterationData, Is.EqualTo(expectedIterationData)); Assert.That(this.viewModel.Rows.Count, Is.EqualTo(2)); }); diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 40e394bc..232e4d88 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -21,7 +21,6 @@ -------------------------------------------------------------------------------> @using COMET.Web.Common.Model @using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows -@using CDP4JsonSerializer.JsonConverter @inject IJSRuntime JSRuntime
@@ -52,7 +51,7 @@ else
@@ -61,45 +60,29 @@ else var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); var isTopElement = dataItem is ElementDefinitionTreeRowViewModel { IsTopElement: true }; - var style = isTopElement ? "font-weight-bold" : string.Empty; + var cssClass = isTopElement ? "font-weight-bold" : string.Empty; } @if (draggable) { -
- @dataItem.ElementName - - - @foreach (var categoryShortName in dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) - { - - - - } -
+ } else { -
- @dataItem.ElementName - - - @foreach (var categoryShortName in dataItem.ElementBase.GetAllCategories().Select(x => x.ShortName)) - { - - - - } -
+ } + diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index 0de0c78b..72bc27ff 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -128,14 +128,14 @@ public partial class ElementDefinitionTree private object dragOverNode; /// - /// Gets or sets a value indicating that dropping a node is allowed for this + /// Holds a reference to dragover count due to nested child elements of the dragOverNode /// - public bool AllowNodeDrop { get; set; } + private int dragOverCounter; /// - /// A collection of + /// Gets or sets a value indicating that dropping a node is allowed for this /// - public IEnumerable Rows { get; set; } + public bool AllowNodeDrop { get; set; } /// /// Gets or sets the @@ -237,7 +237,14 @@ private async Task DragEnterAsync(object node) { if (this.AllowDrop) { + if (this.dragOverNode != node) + { + this.dragOverCounter = 0; + } + this.dragOverNode = node; + this.dragOverCounter++; + await this.OnDragEnter.InvokeAsync((this, node)); await this.OnCalculateDropIsAllowed.InvokeAsync(this); @@ -254,10 +261,15 @@ private async Task DragLeaveAsync(object node) { if (this.AllowDrop) { - this.dragOverNode = null; - await this.OnDragLeave.InvokeAsync((this, node)); - await this.OnCalculateDropIsAllowed.InvokeAsync(this); - this.Logger.LogDebug("DragLeave"); + this.dragOverCounter--; + + if (this.dragOverCounter <= 0) + { + this.dragOverNode = null; + await this.OnDragLeave.InvokeAsync((this, node)); + await this.OnCalculateDropIsAllowed.InvokeAsync(this); + this.Logger.LogDebug("DragLeave"); + } } } } diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor new file mode 100644 index 00000000..0da81199 --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor @@ -0,0 +1,34 @@ + + +
+ @this.ElementBaseTreeRowViewModel.ElementName + + + + @foreach (var categoryShortName in this.ElementBaseTreeRowViewModel.ElementBase.GetAllCategories().Select(x => x.ShortName)) + { + + + + } +
diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor.cs new file mode 100644 index 00000000..fb3485f6 --- /dev/null +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor.cs @@ -0,0 +1,54 @@ +// -------------------------------------------------------------------------------------------------------------------- +// +// Copyright (c) 2024 Starion Group S.A. +// +// Authors: Sam Gerené, Alex Vorobiev, Alexander van Delft, Jaime Bernar, Théate Antoine, João Rua +// +// This file is part of COMET WEB Community Edition +// The COMET WEB Community Edition is the Starion Group Web Application implementation of ECSS-E-TM-10-25 Annex A and Annex C. +// +// The COMET WEB Community Edition is free software; you can redistribute it and/or +// modify it under the terms of the GNU Affero General Public +// License as published by the Free Software Foundation; either +// version 3 of the License, or (at your option) any later version. +// +// The COMET WEB Community Edition is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +// Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . +// +// -------------------------------------------------------------------------------------------------------------------- + +namespace COMETwebapp.Components.MultiModelEditor +{ + using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows; + + using Microsoft.AspNetCore.Components; + + /// + /// Support class for the component + /// + public partial class ElementDefinitionTreeItem + { + /// + /// The css class string for the item + /// + [Parameter] + public string CssClass { get; set; } = string.Empty; + + /// + /// The to show in the item + /// + [Parameter] + public ElementBaseTreeRowViewModel ElementBaseTreeRowViewModel { get; set; } + + /// + /// Handle unmatched values, like "draggable" html attribute, so no error is thrown + /// + [Parameter(CaptureUnmatchedValues = true)] + public Dictionary AdditionalAttributes { get; set; } + } +} diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index 4ad3b32d..b2d90f09 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -99,14 +99,14 @@ - + - + - + - + diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index 1c535259..f8b00b05 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -126,11 +126,6 @@ public IterationData SelectedIterationData ///
public ObservableCollection Rows { get; private set; } = []; - /// - /// Represents the selected ElementDefinitionRowViewModel - /// - public ElementDefinition SelectedElementDefinition { get; set; } - /// /// Add rows related to that has been added /// diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs index 0bbb0cd2..720aa34d 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/IElementDefinitionTreeViewModel.cs @@ -48,11 +48,6 @@ public interface IElementDefinitionTreeViewModel : IHaveReusableRows ///
Iteration Iteration { get; set; } - /// - /// Represents the selected ElementDefinitionRowViewModel - /// - ElementDefinition SelectedElementDefinition { get; set; } - /// /// Gets the Description of the selected model and iteration /// From a67b0dd7a8b674a2f379bf7682015b2021d83da0 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Thu, 9 Jan 2025 14:43:10 +0100 Subject: [PATCH 20/23] [FIX] sizing issues --- .../Components/CardView/CardView.razor | 2 +- COMET.Web.Common/wwwroot/css/styles.css | 6 +- .../ElementDefinitionTree.razor | 84 ++++++++++--------- .../MultiModelEditor/MultiModelEditor.razor | 42 +++++----- 4 files changed, 69 insertions(+), 65 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 6c63e8a2..4ef7adc2 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -39,7 +39,7 @@

-
+
diff --git a/COMET.Web.Common/wwwroot/css/styles.css b/COMET.Web.Common/wwwroot/css/styles.css index c82e3775..f097231f 100644 --- a/COMET.Web.Common/wwwroot/css/styles.css +++ b/COMET.Web.Common/wwwroot/css/styles.css @@ -613,6 +613,6 @@ } .treeview-item-drag-over { - background-color: burlywood; - box-shadow: 0px 0px 2px 10px burlywood; -} \ No newline at end of file + background-color: burlywood !important; + box-shadow: 0px 0px 2px 10px burlywood !important; +} diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 232e4d88..28e64623 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -23,7 +23,7 @@ @using COMETwebapp.ViewModels.Components.MultiModelEditor.Rows @inject IJSRuntime JSRuntime -
+
@if (this.IsModelSelectionEnabled) { + style="height:25px;color:cadetblue;@(this.AllowDrop ? "" : "visibility:hidden;")"> Drop here to create new element...
- - - @{ - var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; - var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); - var isTopElement = dataItem is ElementDefinitionTreeRowViewModel { IsTopElement: true }; - var cssClass = isTopElement ? "font-weight-bold" : string.Empty; - } +
+ + + @{ + var dataItem = (ElementBaseTreeRowViewModel)context.DataItem; + var draggable = this.AllowDrag && this.AllowNodeDrag.Invoke(this, dataItem); + var isTopElement = dataItem is ElementDefinitionTreeRowViewModel { IsTopElement: true }; + var cssClass = isTopElement ? "font-weight-bold" : string.Empty; + } - @if (draggable) - { - - } - else - { - - } + @if (draggable) + { + + } + else + { + + } - - - - - - + + + + + + +
diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index b2d90f09..c804807a 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -26,9 +26,9 @@ -
-
-
+
+
+

Source Model

-
+

Target Model

-
- -
-
- @if (this.ViewModel.SelectedElementDefinition is not null) - { - - } + +
+
+ @if (this.ViewModel.SelectedElementDefinition is not null) + { + + } - + +
-
-
- -
- +
+ +
+ +
From 4bd4696e9c0499ba0129e2090063e4f97710acbc Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Thu, 9 Jan 2025 16:04:28 +0100 Subject: [PATCH 21/23] [FIX] Alignment --- .../Components/CardView/CardView.razor | 2 +- .../ElementDefinitionTreeItem.razor | 18 ++++++++++-------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index 4ef7adc2..a4b5edae 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -39,7 +39,7 @@

-
+
diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor index 0da81199..3599d225 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor @@ -20,15 +20,17 @@ // along with this program. If not, see http://www.gnu.org/licenses/. -------------------------------------------------------------------------------> -
+
@this.ElementBaseTreeRowViewModel.ElementName - - - - @foreach (var categoryShortName in this.ElementBaseTreeRowViewModel.ElementBase.GetAllCategories().Select(x => x.ShortName)) - { + - + - } + @foreach (var categoryShortName in this.ElementBaseTreeRowViewModel.ElementBase.GetAllCategories().Select(x => x.ShortName)) + { + + + + } +
From 150ca18680f0b07534a9b6139a956750fa3cabf7 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Thu, 9 Jan 2025 17:19:51 +0100 Subject: [PATCH 22/23] [FIX] Unit tests --- .../MultiModelEditor/ElementDefinitionTreeTestFixture.cs | 4 ++-- COMETwebapp/appsettings.Development.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs b/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs index 2179be21..b4d51a10 100644 --- a/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs +++ b/COMETwebapp.Tests/Components/MultiModelEditor/ElementDefinitionTreeTestFixture.cs @@ -229,7 +229,7 @@ public void VerifyDragIsNotAllowed() { Assert.That(firstItem, Is.Not.Null); Assert.That(firstItem.InnerHtml, Contains.Substring("Test1")); - Assert.That(firstItem.Attributes.Length, Is.EqualTo(1)); + Assert.That(firstItem.Attributes.Length, Is.EqualTo(2)); }); } @@ -248,7 +248,7 @@ public void VerifyDragIsAllowed() { Assert.That(firstItem, Is.Not.Null); Assert.That(firstItem.InnerHtml, Contains.Substring("Test1")); - Assert.That(firstItem.Attributes.Length, Is.EqualTo(9)); + Assert.That(firstItem.Attributes.Length, Is.EqualTo(10)); }); } } diff --git a/COMETwebapp/appsettings.Development.json b/COMETwebapp/appsettings.Development.json index a7200e21..7db92f2e 100644 --- a/COMETwebapp/appsettings.Development.json +++ b/COMETwebapp/appsettings.Development.json @@ -1,6 +1,6 @@ { "ServerConfiguration": { - "ServerAddress": "http://localhost:5000/" + "ServerAddress": "" }, "DetailedErrors": true } From 47bc7b4990e18e0f2182ea19d48fffe9b3235bb5 Mon Sep 17 00:00:00 2001 From: Alexander van Delft Date: Fri, 10 Jan 2025 15:54:43 +0100 Subject: [PATCH 23/23] [Add] Search in Treeview --- .../CardView/CardViewTestFixture.cs | 13 ++-- .../Components/CardView/CardField.cs | 4 +- .../Components/CardView/CardView.razor | 2 +- .../Components/CardView/CardView.razor.cs | 6 ++ COMET.Web.Common/wwwroot/css/styles.css | 8 +++ .../ModelEditor/DetailsPanelEditor.razor | 7 +-- .../ElementDefinitionTree.razor | 62 +++++++++++-------- .../ElementDefinitionTree.razor.cs | 21 +++++++ .../ElementDefinitionTreeItem.razor | 11 ++-- .../MultiModelEditor/MultiModelEditor.razor | 12 ++-- .../ElementDefinitionTreeViewModel.cs | 42 ++++++++++++- .../Rows/ElementBaseTreeRowViewModel.cs | 26 ++++++++ .../Rows/ElementDefinitionTreeRowViewModel.cs | 4 +- 13 files changed, 165 insertions(+), 53 deletions(-) diff --git a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs index 3ba04fe4..8cb9cc45 100644 --- a/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs +++ b/COMET.Web.Common.Tests/Components/CardView/CardViewTestFixture.cs @@ -45,8 +45,8 @@ public class CardViewTestFixture private TestClass testClass2 = new (); private TestClass testClass3 = new (); private TestClass[] testClasses; - private string[] searchFields = new[] { "Id", "Name" }; - private string[] sortFields = new[] { string.Empty, "Id", "Name" }; + private readonly string[] searchFields = ["Id", "Name"]; + private readonly string[] sortFields = [string.Empty, "Id", "Name"]; private static RenderFragment NormalTemplate() { @@ -110,7 +110,8 @@ public void VerifyComponent() parameters .Add(p => p.Items, this.testClasses) .Add(p => p.ItemSize, 150) - .Add(p => p.ItemTemplate, NormalTemplate()); + .Add(p => p.ItemTemplate, NormalTemplate()) + .Add(p => p.ScrollableAreaCssClass, "scrollable-area"); }); var cardView = component; @@ -122,6 +123,7 @@ public void VerifyComponent() Assert.That(cardView.Instance.ItemSize, Is.EqualTo(150)); Assert.That(cardView.Instance.SearchFields, Is.EquivalentTo(this.searchFields)); Assert.That(cardView.Instance.SortFields, Is.EquivalentTo(this.sortFields)); + Assert.That(cardView.Instance.ScrollableAreaCssClass, Is.EqualTo("scrollable-area")); }); var textBoxParentComponent = component.Find("#search-textbox"); @@ -140,6 +142,10 @@ public void VerifyComponent() Assert.That(comboBoxParentComponent.Attributes.Single(x => x.Name == "style").Value.Contains("visibility:block"), Is.True); }); + var scrollableAreaComponent = component.Find(".scrollable-area"); + + Assert.That(scrollableAreaComponent, Is.Not.Null); + var cardFields = component.FindComponents>(); Assert.Multiple(() => @@ -430,7 +436,6 @@ public async Task VerifySortComponent() cardFields = component.FindComponents>(); - //var sortedTestClasses = this.testClasses.OrderBy(x => x.Id.ToString()).Select(x => x.Name).ToList(); var sortedTestClasses = this.testClasses.OrderBy(x => x.Id).Select(x => x.Name).ToList(); var sortedCarFields = cardFields.Where(x => x.Markup.StartsWith("Name-")).Select(x => x.Markup).ToList(); diff --git a/COMET.Web.Common/Components/CardView/CardField.cs b/COMET.Web.Common/Components/CardView/CardField.cs index 68ff33ac..07970d72 100644 --- a/COMET.Web.Common/Components/CardView/CardField.cs +++ b/COMET.Web.Common/Components/CardView/CardField.cs @@ -95,7 +95,7 @@ static CardField() protected override void OnInitialized() { base.OnInitialized(); - this.CardView.InitializeCardField(this); + this.CardView?.InitializeCardField(this); } /// @@ -105,7 +105,7 @@ protected override void OnInitialized() protected override void BuildRenderTree(RenderTreeBuilder builder) { base.BuildRenderTree(builder); - var value = typeAccessor[this.Context, this.FieldName].ToString(); + var value = string.IsNullOrWhiteSpace(this.FieldName) ? this.Context.ToString(): typeAccessor[this.Context, this.FieldName].ToString(); if (this.AllowSearch && !string.IsNullOrWhiteSpace(value) && !string.IsNullOrWhiteSpace(this.SearchTerm)) { diff --git a/COMET.Web.Common/Components/CardView/CardView.razor b/COMET.Web.Common/Components/CardView/CardView.razor index a4b5edae..a4294e6f 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor +++ b/COMET.Web.Common/Components/CardView/CardView.razor @@ -39,7 +39,7 @@

-
+
diff --git a/COMET.Web.Common/Components/CardView/CardView.razor.cs b/COMET.Web.Common/Components/CardView/CardView.razor.cs index 7856e92b..290daac8 100644 --- a/COMET.Web.Common/Components/CardView/CardView.razor.cs +++ b/COMET.Web.Common/Components/CardView/CardView.razor.cs @@ -61,6 +61,12 @@ public partial class CardView : DisposableComponent [Parameter] public float MinWidth { get; set; } = 250; + /// + /// CssClass of the ScrollableArea + /// + [Parameter] + public string ScrollableAreaCssClass { get; set; } = string.Empty; + /// /// Gets or sets a collection of propertynames of type T to perform search on /// diff --git a/COMET.Web.Common/wwwroot/css/styles.css b/COMET.Web.Common/wwwroot/css/styles.css index f097231f..8986761a 100644 --- a/COMET.Web.Common/wwwroot/css/styles.css +++ b/COMET.Web.Common/wwwroot/css/styles.css @@ -616,3 +616,11 @@ background-color: burlywood !important; box-shadow: 0px 0px 2px 10px burlywood !important; } + +.treeview-scrollarea { + height: calc(90vh - 185px); +} + +.cardview-detailspanel-scrollarea { + height: calc(90vh - 145px); +} diff --git a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor index 86228ede..97565b56 100644 --- a/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor +++ b/COMETwebapp/Components/ModelEditor/DetailsPanelEditor.razor @@ -19,16 +19,11 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -------------------------------------------------------------------------------> -@using CDP4Common.SiteDirectoryData; -@using COMET.Web.Common.Extensions; -@using COMETwebapp.Model -@using CDP4Common.EngineeringModelData -@using COMETwebapp.ViewModels.Components.SystemRepresentation.Rows; @inherits DisposableComponent @if (this.ViewModel.SelectedSystemNode != null) { - +
diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor index 28e64623..584beed9 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor @@ -40,23 +40,34 @@ else
@this.ViewModel.Description
}
+

+ + + + +
+ + +
+

+ style="padding:2px;width:100%;border:dotted 1px;border-radius: 5px;height:25px;color:cadetblue;@(this.AllowDrop ? "" : "visibility:hidden;")"> Drop here to create new element...
-
+
@{ @@ -66,30 +77,31 @@ else var cssClass = isTopElement ? "font-weight-bold" : string.Empty; } - @if (draggable) - { - - } - else - { - - } + + @if (draggable) + { + + } + else + { + + } + - - + +
diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs index 72bc27ff..29d6f988 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTree.razor.cs @@ -122,6 +122,12 @@ public partial class ElementDefinitionTree [Parameter] public bool AllowDrop { get; set; } + /// + /// CssClass of the ScrollableArea + /// + [Parameter] + public string ScrollableAreaCssClass { get; set; } = string.Empty; + /// /// Holds a reference to the object where the dragged node is dragged over /// @@ -142,6 +148,21 @@ public partial class ElementDefinitionTree ///
public DxTreeView TreeView { get; set; } + /// + /// Gets or sets a value indication that searching is allowed + /// + public bool AllowSearch { get; set; } = true; + + /// + /// Gets or sets a collection of propertynames of type T to perform search on + /// + public HashSet SearchFields { get; private set; } = []; + + /// + /// Gets or sets the term where to search/filter items on + /// + public string SearchTerm { get; set; } = string.Empty; + /// /// Method invoked after each time the component has been rendered. Note that the component does /// not automatically re-render after the completion of any returned , because diff --git a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor index 3599d225..550922a0 100644 --- a/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor +++ b/COMETwebapp/Components/MultiModelEditor/ElementDefinitionTreeItem.razor @@ -19,17 +19,20 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see http://www.gnu.org/licenses/. -------------------------------------------------------------------------------> -
- @this.ElementBaseTreeRowViewModel.ElementName + + + - + @foreach (var categoryShortName in this.ElementBaseTreeRowViewModel.ElementBase.GetAllCategories().Select(x => x.ShortName)) { - + } diff --git a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor index c804807a..c2625db6 100644 --- a/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor +++ b/COMETwebapp/Components/MultiModelEditor/MultiModelEditor.razor @@ -29,9 +29,9 @@
-

Source Model

-

Target Model

-
- -
+
diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs index f8b00b05..2aaf4ce6 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/ElementDefinitionTreeViewModel.cs @@ -88,7 +88,7 @@ public ElementDefinitionTreeViewModel(ISessionService sessionService, ICDPMessag if (x != null) { - this.AddRows(x.Element); + this.AddRows(x.Element.OrderBy(x => x.Name)); this.SelectedIterationData = this.Iterations.SingleOrDefault(y => y?.IterationSetupId == x.IterationSetup.Iid); } })); @@ -134,6 +134,11 @@ public void AddRows(IEnumerable addedThings) { var listOfAddedElementBases = addedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList(); this.Rows.AddRange(listOfAddedElementBases.Select(e => new ElementDefinitionTreeRowViewModel(e))); + + if (listOfAddedElementBases.Count > 0) + { + this.SortRows(); + } } /// @@ -142,10 +147,22 @@ public void AddRows(IEnumerable addedThings) /// A collection of updated public void UpdateRows(IEnumerable updatedThings) { + var sortCollection = false; + foreach (var element in updatedThings.OfType().Where(x => this.Iteration?.Element.Contains(x) ?? false).ToList()) { var row = this.Rows.FirstOrDefault(x => x.ElementBase.Iid == element.Iid); - row?.UpdateProperties(new ElementDefinitionTreeRowViewModel(element)); + + if (row != null) + { + sortCollection = true; + row.UpdateProperties(new ElementDefinitionTreeRowViewModel(element)); + } + } + + if (sortCollection) + { + this.SortRows(); } } @@ -155,15 +172,36 @@ public void UpdateRows(IEnumerable updatedThings) /// A collection of deleted public void RemoveRows(IEnumerable deletedThings) { + var sortCollection = false; + foreach (var elementId in deletedThings.OfType().Select(x => x.Iid)) { var row = this.Rows.FirstOrDefault(x => x.ElementBase.Iid == elementId); if (row != null) { + sortCollection = true; this.Rows.Remove(row); } } + + if (sortCollection) + { + this.SortRows(); + } + } + + /// + /// Sorts the rows collection + /// + public void SortRows() + { + var sortableList = this.Rows.OrderBy(x => x.ElementName).ToList(); + + for (int i = 0; i < sortableList.Count; i++) + { + this.Rows.Move(this.Rows.IndexOf(sortableList[i]), i); + } } /// diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs index 90b2c7bc..a46a4081 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementBaseTreeRowViewModel.cs @@ -51,6 +51,11 @@ public abstract class ElementBaseTreeRowViewModel : ReactiveObject /// private string ownerShortName; + /// + /// Backing field for + /// + private string searchString; + /// /// Initializes a new instance of the class. /// the @@ -62,6 +67,7 @@ protected ElementBaseTreeRowViewModel(ElementBase elementBase) this.ElementBase = elementBase; this.ElementName = elementBase.Name; this.OwnerShortName = elementBase.Owner.ShortName; + this.SetSearchString(); } /// @@ -71,6 +77,15 @@ protected ElementBaseTreeRowViewModel() { } + /// + /// The string that can be used to search for + /// + public string SearchString + { + get => this.searchString; + set => this.RaiseAndSetIfChanged(ref this.searchString, value); + } + /// /// The shortname of the owning /// @@ -98,6 +113,16 @@ public ElementBase ElementBase set => this.RaiseAndSetIfChanged(ref this.elementBase, value); } + /// + /// Calculates and sets the value of the SearchString property + /// + private void SetSearchString() + { + var value = $"{this.ElementName}```{this.OwnerShortName}```{string.Join("```", this.ElementBase?.GetAllCategories().Select(x => x.ShortName) ?? [""])}"; + + this.SearchString = value; + } + /// /// Update this row view model properties /// @@ -109,6 +134,7 @@ public void UpdateProperties(ElementBaseTreeRowViewModel elementBaseTreeRow) this.ElementBase = elementBaseTreeRow.elementBase; this.ElementName = elementBaseTreeRow.elementName; this.OwnerShortName = elementBaseTreeRow.OwnerShortName; + this.SetSearchString(); } } } diff --git a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs index 56254f01..eaec6562 100644 --- a/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs +++ b/COMETwebapp/ViewModels/Components/MultiModelEditor/Rows/ElementDefinitionTreeRowViewModel.cs @@ -63,7 +63,7 @@ public ElementDefinitionTreeRowViewModel(ElementDefinition elementBase) : base(e if (elementUsages?.Any() ?? false) { - this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x))); + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x)).OrderBy(x => x.ElementName)); } } @@ -100,7 +100,7 @@ public void UpdateProperties(ElementDefinitionTreeRowViewModel elementDefinition if (elementUsages != null) { - this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x))); + this.Rows.AddRange(elementUsages.Select(x => new ElementUsageTreeRowViewModel(x)).OrderBy(x => x.ElementName)); } } }