Skip to content

Commit cc77a84

Browse files
committed
[2170] Handle multi-selection for Add existing element(s) tool
Bug: #2170 Signed-off-by: Axel RICHARD <axel.richard@obeo.fr>
1 parent 27cf103 commit cc77a84

7 files changed

Lines changed: 225 additions & 18 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ This feature can be de-activated by setting the property `org.eclipse.syson.test
7979
The cache holding standard libraries can be invalidated for a specific test method or test class by using the `@InvalidateStandardLibrariesCache` annotation, ensuring the editing contexts are loaded from scratch.
8080
- https://github.com/eclipse-syson/syson/issues/2154[#2154] [diagrams] Improve the _Duplicate Element_ diagram tool to support multi-selection in standard diagrams.
8181
- https://github.com/eclipse-syson/syson/issues/2160[#2160] [diagrams] Improve the _View As_ diagram tool on graphical nodes to support multi-selection in standard diagrams.
82+
- https://github.com/eclipse-syson/syson/issues/2170[#2170] [diagrams] Improve the _Add existing elements_ diagram tool on graphical nodes to support multi-selection in standard diagrams.
8283
- https://github.com/eclipse-syson/syson/issues/2148[#2148] [diagrams] Merge the two perform action creation tools into a single tool, leveraging the updated selection dialog.
8384
- https://github.com/eclipse-syson/syson/issues/2152[#2152] [diagrams] Leverage the latest selection dialog changes to allow creating a sub action with and without associating the sub action with another `ActionUsage`.
8485

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/general/view/GVAddExistingElementsTests.java

Lines changed: 130 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ public void addExistingElementsRecursiveOnDiagram() {
194194
// Objects.equals(imageStyle.getImageURL(), "images/start_action.svg")
195195
// && Objects.equals("start", n.getTargetObjectLabel()));
196196

197-
this.checkAction2(newDiagram);
197+
this.checkAction2(newDiagram, false);
198198

199199
this.checkRequirementUsage(newDiagram);
200200
});
@@ -207,6 +207,95 @@ public void addExistingElementsRecursiveOnDiagram() {
207207
.verify(Duration.ofSeconds(10));
208208
}
209209

210+
@DisplayName("GIVEN a General View diagram with multiple selected nodes, WHEN Add existing elements is invoked, THEN existing elements are added for each selected node")
211+
@GivenSysONServer({ GeneralViewAddExistingElementsTestProjectData.SCRIPT_PATH })
212+
@Test
213+
public void addExistingElementsOnMultiSelection() {
214+
var flux = this.givenSubscriptionToDiagram();
215+
216+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID,
217+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
218+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
219+
220+
AtomicReference<Diagram> diagram = new AtomicReference<>();
221+
AtomicReference<String> part1NodeId = new AtomicReference<>();
222+
AtomicReference<String> action1NodeId = new AtomicReference<>();
223+
224+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);
225+
226+
String addExistingElementsOnDiagramToolId = diagramDescriptionIdProvider.getDiagramCreationToolId("Add existing elements");
227+
Runnable addExistingElementsOnDiagram = () -> this.nodeCreationTester.invokeTool(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID, diagram, addExistingElementsOnDiagramToolId);
228+
229+
Consumer<Object> diagramWithTopNodesConsumer = assertRefreshedDiagramThat(newDiagram -> {
230+
diagram.set(newDiagram);
231+
part1NodeId.set(this.getNodeIdWithLabel(newDiagram, PART1));
232+
action1NodeId.set(this.getNodeIdWithLabel(newDiagram, ACTION1));
233+
});
234+
235+
String addExistingElementsGroupToolId = diagramDescriptionIdProvider.getGroupNodeToolId("Add existing elements");
236+
Runnable addExistingElementsOnMultiSelection = () -> this.nodeCreationTester.invokeTool(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID, diagram.get().getId(),
237+
List.of(part1NodeId.get(), action1NodeId.get()), addExistingElementsGroupToolId, List.of());
238+
239+
Consumer<Object> updatedDiagramConsumer = assertRefreshedDiagramThat(newDiagram -> {
240+
this.checkPart1(newDiagram);
241+
this.checkAction1(newDiagram, true);
242+
});
243+
244+
StepVerifier.create(flux)
245+
.consumeNextWith(initialDiagramContentConsumer)
246+
.then(addExistingElementsOnDiagram)
247+
.consumeNextWith(diagramWithTopNodesConsumer)
248+
.then(addExistingElementsOnMultiSelection)
249+
.consumeNextWith(updatedDiagramConsumer)
250+
.thenCancel()
251+
.verify(Duration.ofSeconds(10));
252+
}
253+
254+
@DisplayName("GIVEN a General View diagram with multiple selected nodes, WHEN Add existing elements recursive is invoked, THEN existing elements are added recursively for each selected node")
255+
@GivenSysONServer({ GeneralViewAddExistingElementsTestProjectData.SCRIPT_PATH })
256+
@Test
257+
public void addExistingElementsRecursiveOnMultiSelection() {
258+
var flux = this.givenSubscriptionToDiagram();
259+
260+
var diagramDescription = this.givenDiagramDescription.getDiagramDescription(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID,
261+
SysONRepresentationDescriptionIdentifiers.GENERAL_VIEW_DIAGRAM_DESCRIPTION_ID);
262+
var diagramDescriptionIdProvider = new DiagramDescriptionIdProvider(diagramDescription, this.diagramIdProvider);
263+
264+
AtomicReference<Diagram> diagram = new AtomicReference<>();
265+
AtomicReference<String> part1NodeId = new AtomicReference<>();
266+
AtomicReference<String> action1NodeId = new AtomicReference<>();
267+
268+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);
269+
270+
String addExistingElementsOnDiagramToolId = diagramDescriptionIdProvider.getDiagramCreationToolId("Add existing elements");
271+
Runnable addExistingElementsOnDiagram = () -> this.nodeCreationTester.invokeTool(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID, diagram, addExistingElementsOnDiagramToolId);
272+
273+
Consumer<Object> diagramWithTopNodesConsumer = assertRefreshedDiagramThat(newDiagram -> {
274+
diagram.set(newDiagram);
275+
part1NodeId.set(this.getNodeIdWithLabel(newDiagram, PART1));
276+
action1NodeId.set(this.getNodeIdWithLabel(newDiagram, ACTION1));
277+
});
278+
279+
String addExistingElementsRecursiveGroupToolId = diagramDescriptionIdProvider.getGroupNodeToolId("Add existing elements (recursive)");
280+
Runnable addExistingElementsRecursiveOnMultiSelection = () -> this.nodeCreationTester.invokeTool(GeneralViewAddExistingElementsTestProjectData.EDITING_CONTEXT_ID, diagram.get().getId(),
281+
List.of(part1NodeId.get(), action1NodeId.get()), addExistingElementsRecursiveGroupToolId, List.of());
282+
283+
Consumer<Object> updatedDiagramConsumer = assertRefreshedDiagramThat(newDiagram -> {
284+
this.checkPart1(newDiagram);
285+
this.checkAction1(newDiagram, true);
286+
this.checkAction2(newDiagram, true);
287+
});
288+
289+
StepVerifier.create(flux)
290+
.consumeNextWith(initialDiagramContentConsumer)
291+
.then(addExistingElementsOnDiagram)
292+
.consumeNextWith(diagramWithTopNodesConsumer)
293+
.then(addExistingElementsRecursiveOnMultiSelection)
294+
.consumeNextWith(updatedDiagramConsumer)
295+
.thenCancel()
296+
.verify(Duration.ofSeconds(10));
297+
}
298+
210299
@DisplayName("GIVEN an ActionUsage with its action flow compartment displayed and a nested ActionUsage in it, WHEN Delete from diagram the nested ActionUsage then use the Add existing element tool on the action flow compartment, THEN the nested ActionUsage should only be displayed in the action flow compartment")
211300
@GivenSysONServer({ GeneralViewAddExistingElementsActionFlowCompartmentTestProjectData.SCRIPT_PATH })
212301
@Test
@@ -272,8 +361,24 @@ private void checkPackageNode(Diagram newDiagram) {
272361
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), ATTRIBUTE_DEFINITION));
273362
}
274363

275-
private void checkAction2(Diagram newDiagram) {
276-
var action1ActionFlowCompartment = this.checkAction1(newDiagram);
364+
private void checkPart1(Diagram newDiagram) {
365+
var optPart1Node = newDiagram.getNodes().stream()
366+
.filter(n -> Objects.equals(n.getTargetObjectLabel(), PART1))
367+
.findFirst();
368+
assertThat(optPart1Node).isPresent();
369+
var part1PartsCompartment = this.getCompartment(optPart1Node.get(), "parts");
370+
assertThat(part1PartsCompartment)
371+
.as(PART1 + " should contain a parts compartment")
372+
.isPresent();
373+
assertThat(part1PartsCompartment.get().getChildNodes())
374+
.as(PART1 + " parts compartment should contain 1 child")
375+
.hasSize(1)
376+
.as(PART1 + " parts compartment should contain " + PART2)
377+
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), PART2));
378+
}
379+
380+
private void checkAction2(Diagram newDiagram, boolean expectStartNode) {
381+
var action1ActionFlowCompartment = this.checkAction1(newDiagram, expectStartNode);
277382
var optAction2Node = action1ActionFlowCompartment.getChildNodes().stream()
278383
.filter(n -> Objects.equals(n.getTargetObjectLabel(), ACTION2))
279384
.findFirst();
@@ -300,7 +405,7 @@ private void checkAction2(Diagram newDiagram) {
300405
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), ACTION3));
301406
}
302407

303-
private Node checkAction1(Diagram newDiagram) {
408+
private Node checkAction1(Diagram newDiagram, boolean expectStartNode) {
304409
var optAction1Node = newDiagram.getNodes().stream()
305410
.filter(n -> Objects.equals(n.getTargetObjectLabel(), ACTION1))
306411
.findFirst();
@@ -320,11 +425,21 @@ private Node checkAction1(Diagram newDiagram) {
320425
assertThat(action1ActionFlowCompartment)
321426
.as(ACTION1 + " should contain an action flow compartment")
322427
.isPresent();
323-
assertThat(action1ActionFlowCompartment.get().getChildNodes())
324-
.as(ACTION1 + " action flow compartment should contain 2 children")
325-
.hasSize(1) // @technical-debt should be 2 when start node will be synchronized
428+
var action1ActionFlowCompartmentChildNodes = action1ActionFlowCompartment.get().getChildNodes();
429+
var expectedChildNodeCount = 1;
430+
if (expectStartNode) {
431+
expectedChildNodeCount = 2;
432+
}
433+
assertThat(action1ActionFlowCompartmentChildNodes)
434+
.as(ACTION1 + " action flow compartment should contain " + expectedChildNodeCount + " child nodes")
435+
.hasSize(expectedChildNodeCount)
326436
.as(ACTION1 + " action flow compartment should contain " + ACTION2)
327437
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), ACTION2));
438+
if (expectStartNode) {
439+
assertThat(action1ActionFlowCompartmentChildNodes)
440+
.as(ACTION1 + " action flow compartment should contain a start node")
441+
.anyMatch(n -> Objects.equals(n.getTargetObjectLabel(), "start"));
442+
}
328443
return action1ActionFlowCompartment.get();
329444
}
330445

@@ -339,6 +454,14 @@ private void checkRequirementUsage(Diagram newDiagram) {
339454
.allMatch(node -> node.getModifiers().contains(ViewModifier.Hidden));
340455
}
341456

457+
private String getNodeIdWithLabel(Diagram diagram, String label) {
458+
return diagram.getNodes().stream()
459+
.filter(node -> Objects.equals(node.getTargetObjectLabel(), label))
460+
.map(Node::getId)
461+
.findFirst()
462+
.orElseThrow();
463+
}
464+
342465
private Optional<Node> getCompartment(Node node, String compartmentName) {
343466
return node.getChildNodes().stream()
344467
.filter(n -> Objects.equals(n.getInsideLabel().getText(), compartmentName))

backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/DiagramMutationExposeService.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,41 @@ public Element addToExposedElements(Element element, boolean recursive, IEditing
205205
return element;
206206
}
207207

208+
/**
209+
* Adds/displays existing elements from the given element on the matching selected graphical node.
210+
*
211+
* @param element
212+
* the given {@link Element}.
213+
* @param recursive
214+
* if the process should add elements recursively.
215+
* @param editingContext
216+
* the {@link IEditingContext} of the tool.
217+
* @param diagramContext
218+
* the {@link DiagramContext} of the tool.
219+
* @param selectedNodes
220+
* the selected graphical nodes matching the selected semantic elements.
221+
* @param convertedNodes
222+
* the map of all existing node descriptions in the DiagramDescription of this Diagram.
223+
* @return the given {@link Element}.
224+
*/
225+
public Element addToExposedElements(Element element, boolean recursive, IEditingContext editingContext, DiagramContext diagramContext, List<Node> selectedNodes,
226+
Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
227+
var selectedNode = this.findSelectedNode(element, selectedNodes);
228+
this.addToExposedElements(element, recursive, editingContext, diagramContext, selectedNode, convertedNodes);
229+
return element;
230+
}
231+
232+
private Node findSelectedNode(Element element, List<Node> selectedNodes) {
233+
if (selectedNodes == null) {
234+
return null;
235+
}
236+
var elementId = this.siriusWebCoreServices.identityService().getId(element);
237+
return selectedNodes.stream()
238+
.filter(selectedNode -> Objects.equals(selectedNode.getTargetObjectId(), elementId))
239+
.findFirst()
240+
.orElse(null);
241+
}
242+
208243
/**
209244
* Remove the given Element from the exposedElements reference of the {@link ViewUsage} that is the target of the
210245
* given {@link DiagramContext}. Also removes potential children that are sub-nodes of the given selectedNode

backend/services/syson-diagram-services/src/main/java/org/eclipse/syson/diagram/services/aql/DiagramMutationAQLService.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,14 @@ public Element addToExposedElements(Element element, boolean recursive, IEditing
8080
return this.diagramMutationExposeService.addToExposedElements(element, recursive, editingContext, diagramContext, selectedNode, convertedNodes);
8181
}
8282

83+
/**
84+
* {@link DiagramMutationExposeService#addToExposedElements(Element, boolean, IEditingContext, DiagramContext, List, Map)}.
85+
*/
86+
public Element addToExposedElements(Element element, boolean recursive, IEditingContext editingContext, DiagramContext diagramContext, List<Node> selectedNodes,
87+
Map<org.eclipse.sirius.components.view.diagram.NodeDescription, NodeDescription> convertedNodes) {
88+
return this.diagramMutationExposeService.addToExposedElements(element, recursive, editingContext, diagramContext, selectedNodes, convertedNodes);
89+
}
90+
8391
/**
8492
* {@link DiagramMutationElementService#createBindingConnectorAsUsage(Feature, Feature, Node, Node, IEditingContext, DiagramContext)}.
8593
*/

backend/views/syson-diagram-common-view/src/main/java/org/eclipse/syson/diagram/common/view/services/description/ToolDescriptionService.java

Lines changed: 37 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
import org.eclipse.syson.diagram.services.aql.DiagramMutationAQLService;
3838
import org.eclipse.syson.model.services.aql.ModelMutationAQLService;
3939
import org.eclipse.syson.services.UtilService;
40+
import org.eclipse.syson.sysml.Element;
4041
import org.eclipse.syson.sysml.Feature;
4142
import org.eclipse.syson.sysml.FeatureDirectionKind;
4243
import org.eclipse.syson.sysml.FeatureMembership;
@@ -171,6 +172,18 @@ public NodeToolSection relatedElementsNodeToolSection(boolean nested) {
171172
.build();
172173
}
173174

175+
/**
176+
* Create a {@link NodeToolSection} containing the {@code Add Existing Elements} tools for node multi-selection.
177+
*
178+
* @return The created {@link NodeToolSection}
179+
*/
180+
public NodeToolSection relatedElementsGroupToolSection() {
181+
return this.diagramBuilderHelper.newNodeToolSection()
182+
.name("Related Elements")
183+
.nodeTools(this.addExistingElementsGroupTool(false), this.addExistingElementsGroupTool(true))
184+
.build();
185+
}
186+
174187
/**
175188
* Create a {@link NodeTool} adding/displaying existing elements from {@code self} on the context diagram or diagram
176189
* element.
@@ -187,8 +200,9 @@ public NodeTool addExistingElementsTool(boolean recursive, boolean nested) {
187200

188201
var addToExposedElements = this.viewBuilderHelper.newChangeContext()
189202
.expression(
190-
ServiceMethod.of5(DiagramMutationAQLService::addToExposedElements).aqlSelf("" + recursive, IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE,
191-
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));
203+
ServiceMethod.of5(DiagramMutationAQLService.class, DiagramMutationAQLService::addToExposedElements, Element.class, boolean.class, IEditingContext.class, DiagramContext.class,
204+
Node.class, java.util.Map.class).aqlSelf("" + recursive, IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, Node.SELECTED_NODE,
205+
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE));
192206

193207
var changeContextViewUsageOwner = this.viewBuilderHelper.newChangeContext()
194208
.expression(ServiceMethod.of0(UtilService::getViewUsageOwner).aqlSelf())
@@ -212,6 +226,27 @@ public NodeTool addExistingElementsTool(boolean recursive, boolean nested) {
212226
.build();
213227
}
214228

229+
private NodeTool addExistingElementsGroupTool(boolean recursive) {
230+
String title = "Add existing elements";
231+
String iconURL = "/icons/AddExistingElements.svg";
232+
if (recursive) {
233+
title += " (recursive)";
234+
iconURL = "/icons/AddExistingElementsRecursive.svg";
235+
}
236+
237+
return this.diagramBuilderHelper.newNodeTool()
238+
.name(title)
239+
.iconURLsExpression(iconURL)
240+
.preconditionExpression("aql:selectedNodes->notEmpty() and selectedEdges->isEmpty() and self->forAll(e | e.oclIsKindOf(sysml::Element))")
241+
.body(this.viewBuilderHelper.newChangeContext()
242+
.expression(ServiceMethod.of5(DiagramMutationAQLService.class, DiagramMutationAQLService::addToExposedElements, Element.class, boolean.class, IEditingContext.class, DiagramContext.class,
243+
List.class, java.util.Map.class)
244+
.aqlSelf("" + recursive, IEditingContext.EDITING_CONTEXT, DiagramContext.DIAGRAM_CONTEXT, "selectedNodes",
245+
ViewDiagramDescriptionConverter.CONVERTED_NODES_VARIABLE))
246+
.build())
247+
.build();
248+
}
249+
215250
/**
216251
* Return a tool section used to store Requirements related tools.
217252
*

0 commit comments

Comments
 (0)