Skip to content

Commit c4398ee

Browse files
adaussyAxelRICHARD
authored andcommitted
[2194] Properly report feedback messages while moving elements in GV
Bug: #2194 Signed-off-by: Arthur Daussy <arthur.daussy@obeo.fr>
1 parent 1b03721 commit c4398ee

13 files changed

Lines changed: 902 additions & 22 deletions

File tree

CHANGELOG.adoc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
=== Improvements
1818

1919
- https://github.com/eclipse-syson/syson/issues/2198[#2198] [diagrams] Improve diagram-to-diagram drag and drop to support dropping multiple graphical nodes at once, leveraging Sirius Web's `droppedNodes` and `droppedElements` variables.
20+
- https://github.com/eclipse-syson/syson/issues/2194[#2194] [diagrams] Properly report feedback messages to user when using _ISysMLMoveElementService_.
2021

2122
=== New features
2223

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.syson.application.controllers.diagrams.general.view;
14+
15+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
16+
import static org.eclipse.sirius.components.diagrams.tests.DiagramEventPayloadConsumer.assertRefreshedDiagramThat;
17+
18+
import com.jayway.jsonpath.Configuration;
19+
import com.jayway.jsonpath.DocumentContext;
20+
import com.jayway.jsonpath.JsonPath;
21+
import com.jayway.jsonpath.TypeRef;
22+
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
23+
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
24+
25+
import java.time.Duration;
26+
import java.util.List;
27+
import java.util.UUID;
28+
import java.util.concurrent.atomic.AtomicReference;
29+
import java.util.function.Consumer;
30+
31+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramEventInput;
32+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DiagramRefreshedEventPayload;
33+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DropNodesInput;
34+
import org.eclipse.sirius.components.diagrams.Diagram;
35+
import org.eclipse.sirius.components.diagrams.events.ReconnectEdgeKind;
36+
import org.eclipse.sirius.components.diagrams.layoutdata.Position;
37+
import org.eclipse.sirius.components.graphql.tests.api.GraphQLResult;
38+
import org.eclipse.sirius.components.representations.Message;
39+
import org.eclipse.sirius.components.representations.MessageLevel;
40+
import org.eclipse.sirius.web.tests.services.api.IGivenInitialServerState;
41+
import org.eclipse.syson.AbstractIntegrationTests;
42+
import org.eclipse.syson.GivenSysONServer;
43+
import org.eclipse.syson.application.controllers.diagrams.checkers.CheckDiagramElementCount;
44+
import org.eclipse.syson.application.controllers.diagrams.testers.DropNodesWithMessageMutationRunner;
45+
import org.eclipse.syson.application.controllers.diagrams.testers.EdgeReconnectionTester;
46+
import org.eclipse.syson.application.data.GVWithReadOnlyNodesProjectData;
47+
import org.eclipse.syson.services.diagrams.DiagramComparator;
48+
import org.eclipse.syson.services.diagrams.api.IGivenDiagramSubscription;
49+
import org.junit.jupiter.api.BeforeEach;
50+
import org.junit.jupiter.api.DisplayName;
51+
import org.junit.jupiter.api.Test;
52+
import org.springframework.beans.factory.annotation.Autowired;
53+
import org.springframework.boot.test.context.SpringBootTest;
54+
import org.springframework.transaction.annotation.Transactional;
55+
56+
import reactor.core.publisher.Flux;
57+
import reactor.test.StepVerifier;
58+
59+
/**
60+
* Tests the feedback messages returned after forbidden move or edge reconnection resulting in a move operation.
61+
*
62+
* @author Arthur Daussy
63+
*/
64+
@Transactional
65+
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
66+
public class GVForbiddenMoveTests extends AbstractIntegrationTests {
67+
68+
@Autowired
69+
private IGivenInitialServerState givenInitialServerState;
70+
71+
@Autowired
72+
private IGivenDiagramSubscription givenDiagramSubscription;
73+
74+
@Autowired
75+
private EdgeReconnectionTester edgeReconnectionTester;
76+
77+
@Autowired
78+
private DropNodesWithMessageMutationRunner dropRunner;
79+
80+
@Autowired
81+
private DiagramComparator diagramComparator;
82+
83+
@BeforeEach
84+
public void setUp() {
85+
this.givenInitialServerState.initialize();
86+
}
87+
88+
@DisplayName("GIVEN composite edge, WHEN when reconnecting to a read only source, THEN no new edge is created and a feedback message is reported")
89+
@GivenSysONServer({ GVWithReadOnlyNodesProjectData.SCRIPT_PATH })
90+
@Test
91+
public void reconnectCompositeEdgeSourceToReadOnlyObject() {
92+
var flux = this.givenSubscriptionToDiagram(GVWithReadOnlyNodesProjectData.EDITING_CONTEXT_ID);
93+
94+
AtomicReference<Diagram> diagram = new AtomicReference<>();
95+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);
96+
97+
Runnable reconnectEdgeRunnable = this.buildReconnectRunnable(
98+
GVWithReadOnlyNodesProjectData.GraphicalIds.PART_ID,
99+
ReconnectEdgeKind.SOURCE,
100+
diagram,
101+
List.of(new Message("Unable to move part2 in Part: Unable to move a Element to a read only Element", MessageLevel.WARNING)));
102+
103+
Consumer<Object> newEdgeConsumer = assertRefreshedDiagramThat(newDiagram -> {
104+
new CheckDiagramElementCount(this.diagramComparator)
105+
.hasNewEdgeCount(0)
106+
.check(diagram.get(), newDiagram, true);
107+
});
108+
109+
StepVerifier.create(flux)
110+
.consumeNextWith(initialDiagramContentConsumer)
111+
.then(reconnectEdgeRunnable)
112+
.consumeNextWith(newEdgeConsumer)
113+
.thenCancel()
114+
.verify(Duration.ofSeconds(10));
115+
116+
}
117+
118+
@DisplayName("GIVEN a part and a read only package, WHEN when dropping the part on the read only package, THEN the drop should be prevented and a feedback message should be sent")
119+
@GivenSysONServer({ GVWithReadOnlyNodesProjectData.SCRIPT_PATH })
120+
@Test
121+
public void moveElementToReadOnlyElement() {
122+
var flux = this.givenSubscriptionToDiagram(GVWithReadOnlyNodesProjectData.EDITING_CONTEXT_ID);
123+
124+
AtomicReference<Diagram> diagram = new AtomicReference<>();
125+
Consumer<Object> initialDiagramContentConsumer = assertRefreshedDiagramThat(diagram::set);
126+
127+
Runnable dropPartOnPartsRunnable = () -> {
128+
GraphQLResult response = this.dropRunner.run(new DropNodesInput(UUID.randomUUID(),
129+
GVWithReadOnlyNodesProjectData.EDITING_CONTEXT_ID,
130+
GVWithReadOnlyNodesProjectData.GraphicalIds.DIAGRAM_ID,
131+
List.of(GVWithReadOnlyNodesProjectData.GraphicalIds.PART1_ID),
132+
GVWithReadOnlyNodesProjectData.GraphicalIds.PARTS_PKG_ID,
133+
List.of(new Position(0, 0))));
134+
135+
assertThat(this.readDropNodesMessages(response.data()))
136+
.isEqualTo(List.of(new Message("Unable to move part1 in Parts: Unable to move a Element to a read only Element", MessageLevel.WARNING)));
137+
};
138+
Consumer<Object> newEdgeConsumer = assertRefreshedDiagramThat(newDiagram -> {
139+
new CheckDiagramElementCount(this.diagramComparator)
140+
.hasNewEdgeCount(0)
141+
.hasNewNodeCount(0)
142+
.check(diagram.get(), newDiagram, true);
143+
});
144+
145+
StepVerifier.create(flux)
146+
.consumeNextWith(initialDiagramContentConsumer)
147+
.then(dropPartOnPartsRunnable)
148+
.consumeNextWith(newEdgeConsumer)
149+
.thenCancel()
150+
.verify(Duration.ofSeconds(10));
151+
152+
}
153+
154+
private List<Message> readDropNodesMessages(String responseData) {
155+
Configuration conf = Configuration.builder()
156+
.jsonProvider(new JacksonJsonProvider())
157+
.mappingProvider(new JacksonMappingProvider())
158+
.build();
159+
DocumentContext ctx = JsonPath.using(conf).parse(responseData);
160+
return ctx.read("$.data.dropNodes.messages", new TypeRef<List<Message>>() {
161+
});
162+
}
163+
164+
private Runnable buildReconnectRunnable(String newTarget, ReconnectEdgeKind reconnectionKind, AtomicReference<Diagram> diagram, List<Message> expectedMessages) {
165+
return () ->
166+
assertThat(
167+
this.edgeReconnectionTester.reconnectEdge(GVWithReadOnlyNodesProjectData.EDITING_CONTEXT_ID,
168+
diagram,
169+
GVWithReadOnlyNodesProjectData.GraphicalIds.NESTED_USAGE_EDGE,
170+
newTarget,
171+
reconnectionKind)).isEqualTo(expectedMessages);
172+
}
173+
174+
private Flux<DiagramRefreshedEventPayload> givenSubscriptionToDiagram(String editingContextId) {
175+
var diagramEventInput = new DiagramEventInput(UUID.randomUUID(), editingContextId, GVWithReadOnlyNodesProjectData.GraphicalIds.DIAGRAM_ID);
176+
return this.givenDiagramSubscription.subscribe(diagramEventInput);
177+
}
178+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.syson.application.controllers.diagrams.testers;
14+
15+
import java.util.Objects;
16+
17+
import org.eclipse.sirius.components.collaborative.diagrams.dto.DropNodesInput;
18+
import org.eclipse.sirius.components.graphql.tests.api.GraphQLResult;
19+
import org.eclipse.sirius.components.graphql.tests.api.IGraphQLRequestor;
20+
import org.eclipse.sirius.components.graphql.tests.api.IMutationRunner;
21+
import org.springframework.stereotype.Service;
22+
/**
23+
* Same as {@link org.eclipse.sirius.components.diagrams.tests.graphql.DropNodesMutationRunner} but with feedback messages.
24+
*
25+
* @author Arthur Daussy
26+
*/
27+
@Service
28+
public class DropNodesWithMessageMutationRunner implements IMutationRunner<DropNodesInput> {
29+
private static final String DROP_NODE_MUTATION = """
30+
mutation dropNodes($input: DropNodesInput!) {
31+
dropNodes(input: $input) {
32+
__typename
33+
... on SuccessPayload {
34+
messages {
35+
body
36+
level
37+
}
38+
}
39+
... on ErrorPayload {
40+
message
41+
messages {
42+
body
43+
level
44+
}
45+
}
46+
}
47+
}
48+
""";
49+
50+
private final IGraphQLRequestor graphQLRequestor;
51+
52+
public DropNodesWithMessageMutationRunner(IGraphQLRequestor graphQLRequestor) {
53+
this.graphQLRequestor = Objects.requireNonNull(graphQLRequestor);
54+
}
55+
56+
@Override
57+
public GraphQLResult run(DropNodesInput input) {
58+
return this.graphQLRequestor.execute(DROP_NODE_MUTATION, input);
59+
}
60+
}

backend/application/syson-application/src/test/java/org/eclipse/syson/application/controllers/diagrams/testers/EdgeReconnectionTester.java

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,14 @@
1414

1515
import static org.assertj.core.api.Assertions.assertThat;
1616

17+
import com.jayway.jsonpath.Configuration;
18+
import com.jayway.jsonpath.DocumentContext;
1719
import com.jayway.jsonpath.JsonPath;
20+
import com.jayway.jsonpath.TypeRef;
21+
import com.jayway.jsonpath.spi.json.JacksonJsonProvider;
22+
import com.jayway.jsonpath.spi.mapper.JacksonMappingProvider;
1823

24+
import java.util.List;
1925
import java.util.UUID;
2026
import java.util.concurrent.atomic.AtomicReference;
2127

@@ -24,6 +30,7 @@
2430
import org.eclipse.sirius.components.diagrams.Diagram;
2531
import org.eclipse.sirius.components.diagrams.events.ReconnectEdgeKind;
2632
import org.eclipse.sirius.components.diagrams.tests.graphql.ReconnectEdgeMutationRunner;
33+
import org.eclipse.sirius.components.representations.Message;
2734
import org.springframework.beans.factory.annotation.Autowired;
2835
import org.springframework.stereotype.Service;
2936

@@ -38,7 +45,7 @@ public class EdgeReconnectionTester {
3845
@Autowired
3946
private ReconnectEdgeMutationRunner reconnectEdgeMutationRunner;
4047

41-
public void reconnectEdge(String projectId, AtomicReference<Diagram> diagram, String edgeId, String newEdgeEnd, ReconnectEdgeKind reconnectEdgeKind) {
48+
public List<Message> reconnectEdge(String projectId, AtomicReference<Diagram> diagram, String edgeId, String newEdgeEnd, ReconnectEdgeKind reconnectEdgeKind) {
4249
var reconnectEdgeInput = new ReconnectEdgeInput(
4350
UUID.randomUUID(),
4451
projectId,
@@ -51,6 +58,16 @@ public void reconnectEdge(String projectId, AtomicReference<Diagram> diagram, St
5158
var createEdgeResult = this.reconnectEdgeMutationRunner.run(reconnectEdgeInput);
5259
var typename = JsonPath.read(createEdgeResult.data(), "$.data.reconnectEdge.__typename");
5360
assertThat(typename).isEqualTo(SuccessPayload.class.getSimpleName());
61+
62+
// Return feedback messages
63+
Configuration conf = Configuration.builder()
64+
.jsonProvider(new JacksonJsonProvider())
65+
.mappingProvider(new JacksonMappingProvider())
66+
.build();
67+
DocumentContext ctx = JsonPath.using(conf).parse(createEdgeResult.data());
68+
return ctx.read("$.data.reconnectEdge.messages", new TypeRef<List<Message>>() {
69+
});
70+
5471
}
5572

5673
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*******************************************************************************
2+
* Copyright (c) 2026 Obeo.
3+
* This program and the accompanying materials
4+
* are made available under the terms of the Eclipse Public License v2.0
5+
* which accompanies this distribution, and is available at
6+
* https://www.eclipse.org/legal/epl-2.0/
7+
*
8+
* SPDX-License-Identifier: EPL-2.0
9+
*
10+
* Contributors:
11+
* Obeo - initial API and implementation
12+
*******************************************************************************/
13+
package org.eclipse.syson.application.data;
14+
15+
/**
16+
* Project data for project "SysMLv2-WithReadOnlyNode".
17+
*
18+
* @author Arthur Daussy
19+
*/
20+
public class GVWithReadOnlyNodesProjectData {
21+
22+
public static final String SCRIPT_PATH = "/scripts/database-content/SysMLv2-WithReadOnlyNode.sql";
23+
24+
public static final String EDITING_CONTEXT_ID = "6e261505-a9ee-49df-8695-20c43a748d51";
25+
26+
/**
27+
* Ids of graphical elements.
28+
*/
29+
public static final class GraphicalIds {
30+
31+
public static final String DIAGRAM_ID = "032613cb-2bbb-41be-857a-2687a3901b91";
32+
33+
public static final String PART_ID = "d4c076f3-8158-39e5-849d-39ab4e6e9e1a";
34+
35+
public static final String PART1_ID = "7b744096-f831-3aa8-8728-c2f61bc52f73";
36+
37+
public static final String PARTS_PAR_ID = "34c0a2e2-6bf4-3e76-8cf6-8bbaea855a3a";
38+
39+
public static final String PARTS_PKG_ID = "592ab045-03de-3f13-9812-5541588848c8";
40+
41+
public static final String NESTED_USAGE_EDGE = "5755ae43-957a-3513-b64a-cc2776759a7b";
42+
43+
}
44+
}

0 commit comments

Comments
 (0)